libzeep-7.3.2/0000775000175000017500000000000015150027072013016 5ustar maartenmaartenlibzeep-7.3.2/.clang-format0000664000175000017500000000106115150027072015367 0ustar maartenmaartenBasedOnStyle: LLVM UseTab: AlignWithSpaces IndentWidth: 4 TabWidth: 4 BreakBeforeBraces: Allman ColumnLimit: 0 NamespaceIndentation: Inner FixNamespaceComments: true AccessModifierOffset: -2 AllowShortCaseLabelsOnASingleLine: true IndentCaseLabels: true BreakConstructorInitializers: BeforeComma BraceWrapping: BeforeLambdaBody: false AlignAfterOpenBracket: DontAlign Cpp11BracedListStyle: false LambdaBodyIndentation: Signature AllowShortLambdasOnASingleLine: Inline EmptyLineBeforeAccessModifier: LogicalBlock IndentPPDirectives: AfterHash PPIndentWidth: 1 libzeep-7.3.2/.clang-tidy0000664000175000017500000000205615150027072015055 0ustar maartenmaartenChecks: > abseil-*, -abseil-string-find-str-contains, bugprone-*, -bugprone-easily-swappable-parameters, -bugprone-throwing-static-initialization, cert-*, -cert-int09-c, -cert-err58-cpp, clang-*, modernize*, -modernize-use-trailing-return-type, -modernize-avoid-c-arrays, -modernize-use-designated-initializers, performance-*, -performance-enum-size, hicpp-*, -hicpp-braces-around-statements, -hicpp-avoid-c-arrays, -hicpp-no-array-decay, -hicpp-special-member-functions, -hicpp-signed-bitwise, -hicpp-uppercase-literal-suffix, -hicpp-explicit-conversions, google-*, -google-readability-braces-around-statements, -google-explicit-constructor, -google-build-using-namespace, misc-*, -misc-use-internal-linkage, -misc-no-recursion, -misc-const-correctness, -misc-multiple-inheritance, -misc-include-cleaner, -misc-non-private-member-variables-in-classes HeaderFilterRegex: ".*" ExcludeHeaderFilterRegex: "date.h|date/date.h|date/tz.h" libzeep-7.3.2/.github/0000775000175000017500000000000015150027072014356 5ustar maartenmaartenlibzeep-7.3.2/.github/workflows/0000775000175000017500000000000015150027072016413 5ustar maartenmaartenlibzeep-7.3.2/.github/workflows/build-documentation.yml0000664000175000017500000000276215150027072023113 0ustar maartenmaarten# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml name: publish docs on: push: branches: [ "trunk" ] permissions: contents: read pages: write id-token: write concurrency: group: "pages" cancel-in-progress: false jobs: docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set reusable strings id: strings shell: bash run: | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - name: Install dependencies Ubuntu run: sudo apt-get update && sudo apt-get install cmake doxygen graphviz - uses: actions/setup-python@v4 with: python-version: '3.9' cache: 'pip' # caching pip dependencies - run: pip install -r docs/requirements.txt - name: Configure CMake run: cmake -S docs -B build - name: Run Sphinx run: cmake --build build --target Sphinx-libzeep - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: ${{ steps.strings.outputs.build-output-dir }}/sphinx deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: docs steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 libzeep-7.3.2/.github/workflows/cmake-multi-platform.yml0000664000175000017500000000615115150027072023173 0ustar maartenmaartenname: multi platform test on: push: branches: [ "trunk", "develop" ] pull_request: branches: [ "trunk" ] jobs: build: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] build_type: [Release] cpp_compiler: [g++, clang++, cl] boost_asio: [ON, OFF] include: - os: windows-latest cpp_compiler: cl - os: ubuntu-latest cpp_compiler: g++ - os: ubuntu-latest cpp_compiler: clang++ - os: macos-latest cpp_compiler: clang++ exclude: - os: windows-latest cpp_compiler: g++ - os: windows-latest cpp_compiler: clang++ - os: ubuntu-latest cpp_compiler: cl - os: macos-latest cpp_compiler: cl - os: macos-latest cpp_compiler: g++ steps: - uses: actions/checkout@v4 - name: Set reusable strings id: strings shell: bash run: echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - name: Install dependencies Ubuntu if: matrix.os == 'ubuntu-latest' run: > sudo apt-get update && sudo apt-get install libboost-dev mrc ninja-build catch2 libasio-dev - name: Install Boost Windows if: matrix.os == 'windows-latest' uses: MarkusJx/install-boost@v2 id: install-boost with: boost_version: 1.88.0 platform_version: 2025 toolset: msvc - name: Install dependencies macOS if: matrix.os == 'macos-latest' run: > brew install asio boost catch2 - name: Set up MSVC if: matrix.os == 'windows-latest' uses: ilammy/msvc-dev-cmd@v1 - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type env: BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} run: > cmake -B ${{ steps.strings.outputs.build-output-dir }} -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DZEEP_USE_BOOST_ASIO=${{ matrix.boost_asio }} -S ${{ github.workspace }} - name: Build # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} - name: Test working-directory: ${{ steps.strings.outputs.build-output-dir }} # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest --build-config ${{ matrix.build_type }} libzeep-7.3.2/.gitignore0000664000175000017500000000043015150027072015003 0ustar maartenmaartentest/xml/XML-Test-Suite/ tests/ .vs/ .vscode build/ Testing/Temporary CMakeSettings.json include/zeep/config.hpp **/.gdb_history include/zeep/http/asio.hpp lib-xml/src/revision.hpp docs/api docs/conf.py src/revision.hpp test/xml/XML-Test-Suite/ .cache/ modules/zeep/http/asio.hpp libzeep-7.3.2/CMakeLists.txt0000664000175000017500000001643015150027072015562 0ustar maartenmaarten# Copyright Maarten L. Hekkelman, 2023-2026 # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) cmake_minimum_required(VERSION 3.28) project(libzeep VERSION 7.3.2 LANGUAGES CXX) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) include(CMakePackageConfigHelpers) include(CPM) include(CTest) include(VersionString) option(ZEEP_BUILD_DOCUMENTATION "Build documentation" OFF) option(ZEEP_USE_BOOST_ASIO "Use the asio library from Boost instead of the non-boost version" ON) option(ZEEP_BUILD_EXAMPLES "Build the example applications" ON) # New policy for Boost, set it here if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.30) cmake_policy(SET CMP0167 NEW) endif() if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") elseif(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") endif() if(WIN32) # Windows specific stuff set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) if(${CMAKE_SYSTEM_VERSION} GREATER_EQUAL 10) # Windows 10 add_definitions(-D _WIN32_WINNT=0x0A00) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.3) # Windows 8.1 add_definitions(-D _WIN32_WINNT=0x0603) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.2) # Windows 8 add_definitions(-D _WIN32_WINNT=0x0602) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.1) # Windows 7 add_definitions(-D _WIN32_WINNT=0x0601) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.0) # Windows Vista add_definitions(-D _WIN32_WINNT=0x0600) else() # Windows XP (5.1) add_definitions(-D _WIN32_WINNT=0x0501) endif() add_definitions(-DNOMINMAX) endif() if(MSVC) # make msvc standards compliant... add_compile_options(/permissive-) endif() # Optionally use mrc to create resources if(WIN32 AND BUILD_SHARED_LIBS) message(STATUS "zeep: Not using resources when building shared libraries for Windows") else() find_package(mrc QUIET) if(MRC_FOUND) option(USE_RSRC "Use mrc to create resources" ON) if(USE_RSRC) message(STATUS "zeep: Using resources compiled with ${MRC_EXECUTABLE} version ${MRC_VERSION_STRING}") set(WEBAPP_USES_RESOURCES 1) endif() else() message(STATUS "zeep: Not using resources since mrc was not found") endif() endif() set(CMAKE_THREAD_PREFER_PTHREAD TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE) find_package(Threads REQUIRED) if(ZEEP_USE_BOOST_ASIO) find_package(Boost 1.74 REQUIRED COMPONENTS headers) message(STATUS "zeep: Using asio from boost") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/asio-boost.hpp.in ${CMAKE_CURRENT_SOURCE_DIR}/include/zeep/http/asio.hpp @ONLY) set(FIND_DEPENDENCY_BOOST "find_dependency(Boost REQUIRED)") else() find_path(ASIO_INCLUDE_DIR asio.hpp) if(ASIO_INCLUDE_DIR STREQUAL "ASIO_INCLUDE_DIR-NOTFOUND") CPMAddPackage( NAME asio URL https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-36-0.tar.gz) set(ASIO_INCLUDE_DIR ${asio_SOURCE_DIR}/asio/include) endif() message(STATUS "zeep: Using asio from ${ASIO_INCLUDE_DIR}") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/asio.hpp.in ${CMAKE_CURRENT_SOURCE_DIR}/include/zeep/http/asio.hpp @ONLY) endif() try_run(TEST_STD_CHRONO_ZONED_TIME_R TEST_STD_CHRONO_ZONED_TIME_C SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/test-chrono-zoned_time.cpp CXX_STANDARD 20) if("${TEST_STD_CHRONO_ZONED_TIME_R}" EQUAL "0") set(USE_STD_CHRONO_ZONED_TIME TRUE) else() set(USE_STD_CHRONO_ZONED_TIME FALSE) endif() if(NOT USE_STD_CHRONO_ZONED_TIME) message(NOTICE "zeep: Using Howard Hinnants date for from_stream") CPMFindPackage( NAME date GIT_REPOSITORY "https://github.com/HowardHinnant/date" VERSION 3.0.3 EXCLUDE_FROM_ALL YES OPTIONS "USE_SYSTEM_TZ_DB ON" "BUILD_TZ_LIB ON") add_compile_definitions(USE_DATE_H) endif() CPMFindPackage(NAME zeem GIT_REPOSITORY "https://github.com/mhekkel/zeem.git" VERSION 2.1.1 EXCLUDE_FROM_ALL YES) if(UNIX) find_file(HAVE_SYS_WAIT_H "sys/wait.h") if(HAVE_SYS_WAIT_H) set(HTTP_SERVER_HAS_PREFORK 1) endif() find_file(HAVE_UNISTD_H "unistd.h") if(HAVE_UNISTD_H) set(HTTP_HAS_UNIX_DAEMON 1) endif() endif() # Generate the revision.hpp file write_version_header("${CMAKE_CURRENT_SOURCE_DIR}/src" LIB_NAME "libzeep") # Library rules file(GLOB_RECURSE zeep_cpp src/*.cpp) file(GLOB_RECURSE zeep_hpp include/*.hpp) list(APPEND zeep_hpp include/zeep/config.hpp) # TODO: SOAP is not working at the moment, really need to fix that list(FILTER zeep_cpp EXCLUDE REGEX src/soap-controller.cpp) if(NOT USE_RSRC) list(FILTER zeep_cpp EXCLUDE REGEX src/controller-rsrc.cpp) endif() if(NOT HTTP_SERVER_HAS_PREFORK) list(FILTER zeep_hpp EXCLUDE REGEX zeep/http/preforked-server.hpp) list(FILTER zeep_cpp EXCLUDE REGEX src/preforked-server.cpp) endif() add_library(zeep ${zeep_cpp}) add_library(zeep::zeep ALIAS zeep) target_sources(zeep PUBLIC FILE_SET zeep_headers TYPE HEADERS BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include FILES ${zeep_hpp}) set_target_properties(zeep PROPERTIES POSITION_INDEPENDENT_CODE ON) target_compile_features(zeep PUBLIC cxx_std_20) target_include_directories(zeep PUBLIC "$") target_link_libraries(zeep PUBLIC Threads::Threads zeem::zeem) if(NOT USE_STD_CHRONO_ZONED_TIME) target_link_libraries(zeep PUBLIC date::date-tz) endif() if(ZEEP_USE_BOOST_ASIO) target_link_libraries(zeep PUBLIC Boost::headers) endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") target_link_options(zeep PRIVATE -undefined dynamic_lookup) endif() # Install rules install(TARGETS zeep EXPORT zeep-targets FILE_SET zeep_headers DESTINATION include) install(EXPORT zeep-targets NAMESPACE zeep:: DESTINATION lib/cmake/zeep) configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/zeep-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/zeep/zeep-config.cmake INSTALL_DESTINATION lib/cmake/zeep) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/zeep/zeep-config-version.cmake" COMPATIBILITY AnyNewerVersion) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zeep/zeep-config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/zeep/zeep-config-version.cmake" DESTINATION lib/cmake/zeep COMPONENT Devel) set(zeep_MAJOR_VERSION ${PROJECT_VERSION_MAJOR}) set_target_properties(zeep PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} INTERFACE_zeep_MAJOR_VERSION ${PROJECT_VERSION_MAJOR} COMPATIBLE_INTERFACE_STRING zeep_MAJOR_VERSION) # Config file set(LIBZEEP_VERSION ${PROJECT_VERSION}) set(LIBZEEP_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) set(LIBZEEP_VERSION_MINOR ${PROJECT_VERSION_MINOR}) set(LIBZEEP_VERSION_PATCH ${PROJECT_VERSION_PATCH}) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/include/zeep/config.hpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/include/zeep/config.hpp" @ONLY) # Documentation if(ZEEP_BUILD_DOCUMENTATION) add_subdirectory(docs) endif() # Examples if(ZEEP_BUILD_EXAMPLES) add_subdirectory(examples) endif() # Test applications, but only if we're not included as a subproject if(BUILD_TESTING AND PROJECT_IS_TOP_LEVEL) add_subdirectory(test) endif() libzeep-7.3.2/LICENSE_1_0.txt0000664000175000017500000000247215150027072015305 0ustar maartenmaartenBoost Software License - Version 1.0 - August 17th, 2003 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. libzeep-7.3.2/README.md0000664000175000017500000001153715150027072014304 0ustar maartenmaartenlibzeep ======= [![DOI](https://zenodo.org/badge/44161414.svg)](https://zenodo.org/badge/latestdoi/44161414) [![github CI](https://github.com/mhekkel/libzeep/actions/workflows/cmake-multi-platform.yml/badge.svg)](https://github.com/mhekkel/libzeep/actions) [![github CI](https://github.com/mhekkel/libzeep/actions/workflows/build-documentation.yml/badge.svg)](https://github.com/mhekkel/libzeep/actions) TL;DR ----- Libzeep is a web application framework written in C++. To see a starter project visit the [libzeep-webapp-starter](https://github.com/mhekkel/libzeep-webapp-starter.git) page. About ----- Libzeep was originally developed to make it easy to create SOAP servers. And since working with SOAP means working with XML and no decent C++ XML library existed on my radar at that time I created a full XML library as well. Unfortunately (well, considering the work I did), REST proved to be more popular than SOAP, and so I added a better JSON implementation to version 4 of libzeep as well as a way to create REST servers more easily. But then I had to use Spring for some time and was impressed by the simplicity of building interactive web applications and thought I should bring that simplicity to the C++ world. After all, my applications need raw speed and no, Java is not fast. Version 6.0.0 of libzeep was a completely refactored set of libraries. One for manipulating XML, one for handling JSON and one for building web applications. But then I decided it would be better to have the xml code in a separate library and so version 7 now comes without an XML library but uses zeem instead. What remains is a web application library. This one makes it very easy to build a HTTP server that serves HTML but also speaks REST and SOAP. The current implementation consists of a HTTP server class to which you can add controllers. Each controller has a path prefix and handles requests for some entries in this uri path. The base class zeep::http::controller can be used as a base class for a REST controller. The HTML controller can be used as a base class so you can add methods that will be called for certain URI paths. In combination with the available tag processors you can then create and return dynamic XHTML pages. Full documentation can be found at: [mhekkel.github.io/libzeep/](https://mhekkel.github.io/libzeep/) Building libzeep ---------------- To build libzeep you have to have [cmake](https://cmake.org/) installed. And, unless you are using macOS, it is recommended to install [mrc](https://github.com/mhekkel/mrc) in order to have resources support in libzeep. The commands to build libzeep from the command line are e.g.: ```bash git clone https://github.com/mhekkel/libzeep cd libzeep cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build ctest --test-dir build cmake --install build ``` Creating a simple web application --------------------------------- Create a template in xhtml first, store this as `hello.xhtml` in a directory called `docroot`: ```xml Hello

Hello, !

``` Then create a source file called `http-server.cpp` with the following content: ```cpp #define WEBAPP_USES_RESOURCES 0 #include #include #include class hello_controller : public zeep::http::html_controller { public: hello_controller() { map_get("", &hello_controller::handle_index, "name"); map_get("index.html", &hello_controller::handle_index, "name"); map_get("hello/{name}", &hello_controller::handle_index, "name"); } zeep::http::reply handle_index(const zeep::http::scope& scope, std::optional user) { zeep::http::scope sub(scope); sub.put("name", user.value_or("world")); return get_template_processor().create_reply_from_template("hello.xhtml", sub); } }; int main() { zeep::http::server srv(std::filesystem::canonical("docroot")); srv.add_controller(new hello_controller()); srv.bind("::", 8080); srv.run(2); return 0; } ``` Create a `CMakeLists.txt` file: ```cmake cmake_minimum_required(VERSION 3.16) project(http-server LANGUAGES CXX) set(CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(zeep REQUIRED) add_executable(http-server http-server.cpp) target_link_libraries(http-server zeep::zeep) ``` And configure and build the app: ```bash cmake . cmake --build . ``` And then run it: ```bash ./http-server ``` Now you can access the result using the following URL's: * * * libzeep-7.3.2/changelog0000664000175000017500000002572515150027072014703 0ustar maartenmaartenVersion 7.3.2 - Revive daemon-test example application Version 7.3.1 - Fix regression in controller::set_reply for files. - Fix login controller behaviour on invalid password Version 7.3.0 - Using zeem instead of mxml. - Fixing many warnings and a couple of issues found with clang-tidy - Fixed the writing of log files. Version 7.2.0 - zeep::http::controller callbacks (for REST calls) can now have a zeep::http::scope as first parameter. - Added methods to el::object (nullptr constructor and back/front) Version 7.1.0 - Fix huge memory leak Version 7.0.5 - Removed dependency on howard hinnants date library Version 7.0.4 - Dependabot updates - Using date with system time zone db - Fix for building with cmake 4 - Remove forced dependency on nlohmann - Updated documentation Version 7.0.3 - Do not catch exception in controller to allow error handlers to properly handle the exception. - Fix the reload option of daemon Version 7.0.2 - Fix various deamon and log file related problems - Fix serialization of enums Version 7.0.1 - Removed double move of string Version 7.0.0 - Complete rewrite - Removed xml code, now depends on libzeem - Refactored json code into el::object (el = expression language) - Moved legacy html_controller code into a new sub class html_controller_v1 - Removed rest_controller, code is now in the base class controller. - Many small API changes Version 6.1.1 - Fixed copyright on named characters files Version 6.1.0 - Better login handling - Do not specify BOOST_ASIO_STANDALONE by default Version 6.0.16 - Fix the reload of daemons without preforking Version 6.0.15 - Changed the way parsers convert a string to a float, now use std::from_chars for a final conversion to avoid rounding errors. - Fix test on macOS (no /proc filesystem there) - Removed using codecvt_utf8 since it is deprecated - Removed asio::ip::tcp::resolver::query usage - Refactored daemon to no longer expose code that requires POSIX calls like fork/exec on Windows Version 6.0.14 - Fix URI parser, some paths were not absolute - Added variant of http::daemon with forking a daemon but no preforked children. Simply a single instance with multiple threads. Version 6.0.13 - Flush access log after each request. (replacing all instances of std::endl was a bit too drastic). - Better handling of not_found - Don't test when included in other project - Fix redirects after login/logout (this time for real, I hope) Version 6.0.12 - Catch URI parse error in connection Version 6.0.11 - Builds on macOS again, I hope Version 6.0.10 - Fix html_controller and rest_controller to pass path parameters decoded. - No longer use the date library to write out localised date/time formats since the installed date library might contain ONLY_C_LOCALE defined. - Do not read PID file when running the foreground - Renamed the cmake config files for libzeep from CamelCase to kebab-case. The install rules should remove older config files. Version 6.0.9 - Fix writing encoded path segments for URI's Version 6.0.8 - Security fix: redirect to relative URI's only on login - Added a new HTTP status code: 422 Unprocessable Entity Version 6.0.7 - various cmake related fixes - new version string module Version 6.0.6 - Dropped support for GNU autotools, pkgconfig Version 6.0.5 - Fix SONAME (should have been updated to 6 of course) - Changed code in format to no longer use std::codecvt_utf8 - support for building with stand alone ASIO Version 6.0.4.1 - Do not try to build examples, that only works after installing Version 6.0.4 - Fix message parser to accept HTTP messages without a content-length but with a content-type header. - Include at more locations - Include version string code (https://github.com/mhekkel/version-string.cmake) Version 6.0.3 - Fixes in login controller logic. Again. Version 6.0.2 - When processing tags in a HTML5 environment, replace CDATA sections with plain text. CDATA is not supported in HTML5. - Better resource linking Version 6.0.1 - Fixed some issues in serializing and detection templates to enable serializing std::optional. - Added option to timeout JWT access tokens - handle_file of template-processor now uses chunked transfer encoding - Avoid crash when loading a non-regular file - complete rewrite of uri class - Fix formatDecimal for negative numbers Version 6.0.0 - Dropped boost::date_time and other boost libraries - Fix daemon::reload - New html_controller routines that mimic the rest_controller mapping - Added access control object, for CORS handling - Changed serialising of std::chrono time_point values. - Redesigned login_controller Version 5.1.8 - Fix bug in parsing binary multipart/form-data parameters Version 5.1.7 - Fix dependency on std::filesystem library Version 5.1.6 - Fix the visibility of types in zeep::json::detail::iterator_impl - Reintroduced resolving of bind addresses, using "localhost" is easier than only numerical addresses. - Return correct status code in case of catching an exception in rest controllers. - Fix dependency in .cmake config file for Threads - Generate config.hpp file. Version 5.1.5 - update zeepConfig.cmake to include required link file - fix infinite loop in processing incorrect :inline constructs Version 5.1.4 - Update cmakefile to work more reliably Version 5.1.3 - Update SONAME to 5.1 - Create reproducible builds of documentation (and thus whole package) Version 5.1.2 - Fix glob code to match empty path specifications for controllers - Change CMakeLists file to generate only shared or static libs, but not both - Generate pkgconfig file again Version 5.1.1 - Removed uriparser again. URI implementation is now regex based. - Replaced GNU configure with cmake Version 5.1.0 - Added base32 encoding/decoding - Various REST controller fixes, mainly in accepting parameters - The library is now always compiled with PIC - Requred boost version is now 71 - Ignore SIGCHLD in foreground mode, signals are now handled by cross platform implementation - reintroduced a Windows version - Fixed a couple of security issues, all caused by incorrectly parsing uri's. Switched to using liburiparser for now. Version 5.0.2 - Add support for building shared libraries - Decoupled example code from rest, should now be build after installation, or use the STAGE=1 option to make. - rest controller can now return a reply object, adding flexibility Version 5.0.1 - Update makefile to include changes made for the Debian package - Fix writing HTML, proper empty elements - Added some workarounds to build on macOS - Fixed endianness issue in sha implementation Version 5.0.0 - Total rewrite of about everything - Controllers are now the main handlers of requests, three major variants for HTML, REST and SOAP. - Implemented some cryptographic routines in order to drop dependency on libcrypto++ - Redesigned authentication, dropped HTTP digest and opted for JWT, added security_context class for managing all of this - Code now requires a c++17 compiler - Lots of test code added - Added some real world examples - Tested with boost 1.65.1 up to 1.73 - Refactored request, it is now a class and credentials are always stored if a valid access-token was detected. - A bunch of fixes to make web application work behind a reverse proxy. Version 4.0.0 - Major rewrite, may break code. - Added a JSON parser and compatible internal object, is analogous to the version of nlohmann. Replaces the old element class in webapp. - Removed parameter_map, get request parameters from request itself. - Reorganized code, separate folder for lib and examples. - Refactored webapp and move the tag processing into a separate class. Added a second tag processor that mimics thymeleaf. Version 3.0.2 - Change in zeep/xml/serialize.hpp for gcc 4.7 compiler Version 3.0.1 - added cast to uint32 in webapp-el to allow compilation on s390 Version 3.0 - Support for non-intrusive serialization. The call to serialize is now done by the templated struct zeep::xml::struct_serializer. You can create a specialization for this struct to do something else than calling MyClass::serialize. - xml::document now has serialize and deserialize members. - A streaming input added, process_document_elements calls the callback for all elements that match a given xpath. - ISO8859-1 support (finally) - some xpath additions (matches e.g.) - changed signature of various find routines to work with const char* - changed authentication mechanism in webapp to allow multiple realms - some small changes in writing out XML documents/xml::writer - added line number to validation error messages - process value tag of mrs:option tag - el processing returns original string if it does not contain an expression - in expression language, support var1[var2] constructs - fix in writing doctype declaration - insert/erase implementations of zeep::xml::node... - fixed bug in el implementation (dividing numbers) - extended log format of HTTP server to allow better awstat logs (using the extra fields for SOAP calls). Also writes the X-Forwarded-For client if any. - Patches provided by Patrick Rotsaert: serializer for xsd:time and optional data types based on boost::optional. - Split out log_request as a virtual method in http::server - Added quick and dirty test for requests from mobile clients - Added virtual destructors to all base classes. - OPTIONS and HEAD support in web server Version 2.9.0 - Added some calls to xml::writer to write e.g. xml-decl and doctypes Version 2.8.2 - Fix in unicode support code - Preliminary support for handling authentication Version 2.8.1 - removed boost::ptr_vector/ptr_list. - work around a crashing bug in el::object[string] when compiling with icpc Version 2.8.0 - write_content added. - nullptr instead of nil, added a stub for old compilers. - fix in el::object (mixing up uint64 and size_t) Version 2.6.3 - Fix for stack overflow in delete large XML documents Version 2.6.2 - Apparently the word size has changed on amd64/GNUC targets. I've switched to a more robust template selection algorithm for WSDL generation. Version 2.6.1 - Fix in keep-alive (clear reply object after each served reply) - Implemented missing at() virtual method for el::vector - Writing comments now validates output - check mounted paths instead of only the root for handlers - optimization flags in makefile Version 2.6.0 - Changed parameter_map (for webapp) into a multimap Version 2.5.2 - Throw exception when attempting to write null character. Version 2.5.1 - Removed the use of split_iterator from webapp since it generated crashes when built as a shared library... Version 2.5.0 - added webapp, a base class used to create web applications, it uses XHTML templates to fill in. It uses a script language to enable interaction with the C++ code. Version 2.1.0 - support for HTTP/1.1 - added multiplication in xpath expression language... oops - revised interface for container::iterator, now it is possible to use more STL and boost functions on a container directly, like: xml::container cnt = ...; foreach (node* n, cnt) { cout << n->name() << endl; } libzeep-7.3.2/cmake/0000775000175000017500000000000015150027072014076 5ustar maartenmaartenlibzeep-7.3.2/cmake/CPM.cmake0000664000175000017500000012251115150027072015521 0ustar maartenmaarten# CPM.cmake - CMake's missing package manager # =========================================== # See https://github.com/cpm-cmake/CPM.cmake for usage and update instructions. # # MIT License # ----------- #[[ Copyright (c) 2019-2023 Lars Melchior and contributors 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. ]] cmake_minimum_required(VERSION 3.14 FATAL_ERROR) # Initialize logging prefix if(NOT CPM_INDENT) set(CPM_INDENT "CPM:" CACHE INTERNAL "" ) endif() if(NOT COMMAND cpm_message) function(cpm_message) message(${ARGV}) endfunction() endif() if(DEFINED EXTRACTED_CPM_VERSION) set(CURRENT_CPM_VERSION "${EXTRACTED_CPM_VERSION}${CPM_DEVELOPMENT}") else() set(CURRENT_CPM_VERSION 0.40.8) endif() get_filename_component(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" REALPATH) if(CPM_DIRECTORY) if(NOT CPM_DIRECTORY STREQUAL CPM_CURRENT_DIRECTORY) if(CPM_VERSION VERSION_LESS CURRENT_CPM_VERSION) message( AUTHOR_WARNING "${CPM_INDENT} \ A dependency is using a more recent CPM version (${CURRENT_CPM_VERSION}) than the current project (${CPM_VERSION}). \ It is recommended to upgrade CPM to the most recent version. \ See https://github.com/cpm-cmake/CPM.cmake for more information." ) endif() if(${CMAKE_VERSION} VERSION_LESS "3.17.0") include(FetchContent) endif() return() endif() get_property( CPM_INITIALIZED GLOBAL "" PROPERTY CPM_INITIALIZED SET ) if(CPM_INITIALIZED) return() endif() endif() if(CURRENT_CPM_VERSION MATCHES "development-version") message( WARNING "${CPM_INDENT} Your project is using an unstable development version of CPM.cmake. \ Please update to a recent release if possible. \ See https://github.com/cpm-cmake/CPM.cmake for details." ) endif() set_property(GLOBAL PROPERTY CPM_INITIALIZED true) macro(cpm_set_policies) # the policy allows us to change options without caching cmake_policy(SET CMP0077 NEW) set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # the policy allows us to change set(CACHE) without caching if(POLICY CMP0126) cmake_policy(SET CMP0126 NEW) set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) endif() # The policy uses the download time for timestamp, instead of the timestamp in the archive. This # allows for proper rebuilds when a projects url changes if(POLICY CMP0135) cmake_policy(SET CMP0135 NEW) set(CMAKE_POLICY_DEFAULT_CMP0135 NEW) endif() # treat relative git repository paths as being relative to the parent project's remote if(POLICY CMP0150) cmake_policy(SET CMP0150 NEW) set(CMAKE_POLICY_DEFAULT_CMP0150 NEW) endif() endmacro() cpm_set_policies() option(CPM_USE_LOCAL_PACKAGES "Always try to use `find_package` to get dependencies" $ENV{CPM_USE_LOCAL_PACKAGES} ) option(CPM_LOCAL_PACKAGES_ONLY "Only use `find_package` to get dependencies" $ENV{CPM_LOCAL_PACKAGES_ONLY} ) option(CPM_DOWNLOAD_ALL "Always download dependencies from source" $ENV{CPM_DOWNLOAD_ALL}) option(CPM_DONT_UPDATE_MODULE_PATH "Don't update the module path to allow using find_package" $ENV{CPM_DONT_UPDATE_MODULE_PATH} ) option(CPM_DONT_CREATE_PACKAGE_LOCK "Don't create a package lock file in the binary path" $ENV{CPM_DONT_CREATE_PACKAGE_LOCK} ) option(CPM_INCLUDE_ALL_IN_PACKAGE_LOCK "Add all packages added through CPM.cmake to the package lock" $ENV{CPM_INCLUDE_ALL_IN_PACKAGE_LOCK} ) option(CPM_USE_NAMED_CACHE_DIRECTORIES "Use additional directory of package name in cache on the most nested level." $ENV{CPM_USE_NAMED_CACHE_DIRECTORIES} ) set(CPM_VERSION ${CURRENT_CPM_VERSION} CACHE INTERNAL "" ) set(CPM_DIRECTORY ${CPM_CURRENT_DIRECTORY} CACHE INTERNAL "" ) set(CPM_FILE ${CMAKE_CURRENT_LIST_FILE} CACHE INTERNAL "" ) set(CPM_PACKAGES "" CACHE INTERNAL "" ) set(CPM_DRY_RUN OFF CACHE INTERNAL "Don't download or configure dependencies (for testing)" ) if(DEFINED ENV{CPM_SOURCE_CACHE}) set(CPM_SOURCE_CACHE_DEFAULT $ENV{CPM_SOURCE_CACHE}) else() set(CPM_SOURCE_CACHE_DEFAULT OFF) endif() set(CPM_SOURCE_CACHE ${CPM_SOURCE_CACHE_DEFAULT} CACHE PATH "Directory to download CPM dependencies" ) if(NOT CPM_DONT_UPDATE_MODULE_PATH AND NOT DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR) set(CPM_MODULE_PATH "${CMAKE_BINARY_DIR}/CPM_modules" CACHE INTERNAL "" ) # remove old modules file(REMOVE_RECURSE ${CPM_MODULE_PATH}) file(MAKE_DIRECTORY ${CPM_MODULE_PATH}) # locally added CPM modules should override global packages set(CMAKE_MODULE_PATH "${CPM_MODULE_PATH};${CMAKE_MODULE_PATH}") endif() if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) set(CPM_PACKAGE_LOCK_FILE "${CMAKE_BINARY_DIR}/cpm-package-lock.cmake" CACHE INTERNAL "" ) file(WRITE ${CPM_PACKAGE_LOCK_FILE} "# CPM Package Lock\n# This file should be committed to version control\n\n" ) endif() include(FetchContent) # Try to infer package name from git repository uri (path or url) function(cpm_package_name_from_git_uri URI RESULT) if("${URI}" MATCHES "([^/:]+)/?.git/?$") set(${RESULT} ${CMAKE_MATCH_1} PARENT_SCOPE ) else() unset(${RESULT} PARENT_SCOPE) endif() endfunction() # Try to infer package name and version from a url function(cpm_package_name_and_ver_from_url url outName outVer) if(url MATCHES "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|zip|ZIP)(\\?|/|$)") # We matched an archive set(filename "${CMAKE_MATCH_1}") if(filename MATCHES "([a-zA-Z0-9_\\.-]+)[_-]v?(([0-9]+\\.)*[0-9]+[a-zA-Z0-9]*)") # We matched - (ie foo-1.2.3) set(${outName} "${CMAKE_MATCH_1}" PARENT_SCOPE ) set(${outVer} "${CMAKE_MATCH_2}" PARENT_SCOPE ) elseif(filename MATCHES "(([0-9]+\\.)+[0-9]+[a-zA-Z0-9]*)") # We couldn't find a name, but we found a version # # In many cases (which we don't handle here) the url would look something like # `irrelevant/ACTUAL_PACKAGE_NAME/irrelevant/1.2.3.zip`. In such a case we can't possibly # distinguish the package name from the irrelevant bits. Moreover if we try to match the # package name from the filename, we'd get bogus at best. unset(${outName} PARENT_SCOPE) set(${outVer} "${CMAKE_MATCH_1}" PARENT_SCOPE ) else() # Boldly assume that the file name is the package name. # # Yes, something like `irrelevant/ACTUAL_NAME/irrelevant/download.zip` will ruin our day, but # such cases should be quite rare. No popular service does this... we think. set(${outName} "${filename}" PARENT_SCOPE ) unset(${outVer} PARENT_SCOPE) endif() else() # No ideas yet what to do with non-archives unset(${outName} PARENT_SCOPE) unset(${outVer} PARENT_SCOPE) endif() endfunction() function(cpm_find_package NAME VERSION) string(REPLACE " " ";" EXTRA_ARGS "${ARGN}") find_package(${NAME} ${VERSION} ${EXTRA_ARGS} QUIET) if(${CPM_ARGS_NAME}_FOUND) if(DEFINED ${CPM_ARGS_NAME}_VERSION) set(VERSION ${${CPM_ARGS_NAME}_VERSION}) endif() cpm_message(STATUS "${CPM_INDENT} Using local package ${CPM_ARGS_NAME}@${VERSION}") CPMRegisterPackage(${CPM_ARGS_NAME} "${VERSION}") set(CPM_PACKAGE_FOUND YES PARENT_SCOPE ) else() set(CPM_PACKAGE_FOUND NO PARENT_SCOPE ) endif() endfunction() # Create a custom FindXXX.cmake module for a CPM package This prevents `find_package(NAME)` from # finding the system library function(cpm_create_module_file Name) if(NOT CPM_DONT_UPDATE_MODULE_PATH) if(DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR) # Redirect find_package calls to the CPM package. This is what FetchContent does when you set # OVERRIDE_FIND_PACKAGE. The CMAKE_FIND_PACKAGE_REDIRECTS_DIR works for find_package in CONFIG # mode, unlike the Find${Name}.cmake fallback. CMAKE_FIND_PACKAGE_REDIRECTS_DIR is not defined # in script mode, or in CMake < 3.24. # https://cmake.org/cmake/help/latest/module/FetchContent.html#fetchcontent-find-package-integration-examples string(TOLOWER ${Name} NameLower) file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${NameLower}-config.cmake "include(\"\${CMAKE_CURRENT_LIST_DIR}/${NameLower}-extra.cmake\" OPTIONAL)\n" "include(\"\${CMAKE_CURRENT_LIST_DIR}/${Name}Extra.cmake\" OPTIONAL)\n" ) file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${NameLower}-config-version.cmake "set(PACKAGE_VERSION_COMPATIBLE TRUE)\n" "set(PACKAGE_VERSION_EXACT TRUE)\n" ) else() file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake "include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)" ) endif() endif() endfunction() # Find a package locally or fallback to CPMAddPackage function(CPMFindPackage) set(oneValueArgs NAME VERSION GIT_TAG FIND_PACKAGE_ARGUMENTS) cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "" ${ARGN}) if(NOT DEFINED CPM_ARGS_VERSION) if(DEFINED CPM_ARGS_GIT_TAG) cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) endif() endif() set(downloadPackage ${CPM_DOWNLOAD_ALL}) if(DEFINED CPM_DOWNLOAD_${CPM_ARGS_NAME}) set(downloadPackage ${CPM_DOWNLOAD_${CPM_ARGS_NAME}}) elseif(DEFINED ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) set(downloadPackage $ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) endif() if(downloadPackage) CPMAddPackage(${ARGN}) cpm_export_variables(${CPM_ARGS_NAME}) return() endif() cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) if(NOT CPM_PACKAGE_FOUND) CPMAddPackage(${ARGN}) cpm_export_variables(${CPM_ARGS_NAME}) endif() endfunction() # checks if a package has been added before function(cpm_check_if_package_already_added CPM_ARGS_NAME CPM_ARGS_VERSION) if("${CPM_ARGS_NAME}" IN_LIST CPM_PACKAGES) CPMGetPackageVersion(${CPM_ARGS_NAME} CPM_PACKAGE_VERSION) if("${CPM_PACKAGE_VERSION}" VERSION_LESS "${CPM_ARGS_VERSION}") message( WARNING "${CPM_INDENT} Requires a newer version of ${CPM_ARGS_NAME} (${CPM_ARGS_VERSION}) than currently included (${CPM_PACKAGE_VERSION})." ) endif() cpm_get_fetch_properties(${CPM_ARGS_NAME}) set(${CPM_ARGS_NAME}_ADDED NO) set(CPM_PACKAGE_ALREADY_ADDED YES PARENT_SCOPE ) cpm_export_variables(${CPM_ARGS_NAME}) else() set(CPM_PACKAGE_ALREADY_ADDED NO PARENT_SCOPE ) endif() endfunction() # Parse the argument of CPMAddPackage in case a single one was provided and convert it to a list of # arguments which can then be parsed idiomatically. For example gh:foo/bar@1.2.3 will be converted # to: GITHUB_REPOSITORY;foo/bar;VERSION;1.2.3 function(cpm_parse_add_package_single_arg arg outArgs) # Look for a scheme if("${arg}" MATCHES "^([a-zA-Z]+):(.+)$") string(TOLOWER "${CMAKE_MATCH_1}" scheme) set(uri "${CMAKE_MATCH_2}") # Check for CPM-specific schemes if(scheme STREQUAL "gh") set(out "GITHUB_REPOSITORY;${uri}") set(packageType "git") elseif(scheme STREQUAL "gl") set(out "GITLAB_REPOSITORY;${uri}") set(packageType "git") elseif(scheme STREQUAL "bb") set(out "BITBUCKET_REPOSITORY;${uri}") set(packageType "git") # A CPM-specific scheme was not found. Looks like this is a generic URL so try to determine # type elseif(arg MATCHES ".git/?(@|#|$)") set(out "GIT_REPOSITORY;${arg}") set(packageType "git") else() # Fall back to a URL set(out "URL;${arg}") set(packageType "archive") # We could also check for SVN since FetchContent supports it, but SVN is so rare these days. # We just won't bother with the additional complexity it will induce in this function. SVN is # done by multi-arg endif() else() if(arg MATCHES ".git/?(@|#|$)") set(out "GIT_REPOSITORY;${arg}") set(packageType "git") else() # Give up message(FATAL_ERROR "${CPM_INDENT} Can't determine package type of '${arg}'") endif() endif() # For all packages we interpret @... as version. Only replace the last occurrence. Thus URIs # containing '@' can be used string(REGEX REPLACE "@([^@]+)$" ";VERSION;\\1" out "${out}") # Parse the rest according to package type if(packageType STREQUAL "git") # For git repos we interpret #... as a tag or branch or commit hash string(REGEX REPLACE "#([^#]+)$" ";GIT_TAG;\\1" out "${out}") elseif(packageType STREQUAL "archive") # For archives we interpret #... as a URL hash. string(REGEX REPLACE "#([^#]+)$" ";URL_HASH;\\1" out "${out}") # We don't try to parse the version if it's not provided explicitly. cpm_get_version_from_url # should do this at a later point else() # We should never get here. This is an assertion and hitting it means there's a problem with the # code above. A packageType was set, but not handled by this if-else. message(FATAL_ERROR "${CPM_INDENT} Unsupported package type '${packageType}' of '${arg}'") endif() set(${outArgs} ${out} PARENT_SCOPE ) endfunction() # Check that the working directory for a git repo is clean function(cpm_check_git_working_dir_is_clean repoPath gitTag isClean) find_package(Git REQUIRED) if(NOT GIT_EXECUTABLE) # No git executable, assume directory is clean set(${isClean} TRUE PARENT_SCOPE ) return() endif() # check for uncommitted changes execute_process( COMMAND ${GIT_EXECUTABLE} status --porcelain RESULT_VARIABLE resultGitStatus OUTPUT_VARIABLE repoStatus OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET WORKING_DIRECTORY ${repoPath} ) if(resultGitStatus) # not supposed to happen, assume clean anyway message(WARNING "${CPM_INDENT} Calling git status on folder ${repoPath} failed") set(${isClean} TRUE PARENT_SCOPE ) return() endif() if(NOT "${repoStatus}" STREQUAL "") set(${isClean} FALSE PARENT_SCOPE ) return() endif() # check for committed changes execute_process( COMMAND ${GIT_EXECUTABLE} diff -s --exit-code ${gitTag} RESULT_VARIABLE resultGitDiff OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_QUIET WORKING_DIRECTORY ${repoPath} ) if(${resultGitDiff} EQUAL 0) set(${isClean} TRUE PARENT_SCOPE ) else() set(${isClean} FALSE PARENT_SCOPE ) endif() endfunction() # Add PATCH_COMMAND to CPM_ARGS_UNPARSED_ARGUMENTS. This method consumes a list of files in ARGN # then generates a `PATCH_COMMAND` appropriate for `ExternalProject_Add()`. This command is appended # to the parent scope's `CPM_ARGS_UNPARSED_ARGUMENTS`. function(cpm_add_patches) # Return if no patch files are supplied. if(NOT ARGN) return() endif() # Find the patch program. find_program(PATCH_EXECUTABLE patch) if(CMAKE_HOST_WIN32 AND NOT PATCH_EXECUTABLE) # The Windows git executable is distributed with patch.exe. Find the path to the executable, if # it exists, then search `../usr/bin` and `../../usr/bin` for patch.exe. find_package(Git QUIET) if(GIT_EXECUTABLE) get_filename_component(extra_search_path ${GIT_EXECUTABLE} DIRECTORY) get_filename_component(extra_search_path_1up ${extra_search_path} DIRECTORY) get_filename_component(extra_search_path_2up ${extra_search_path_1up} DIRECTORY) find_program( PATCH_EXECUTABLE patch HINTS "${extra_search_path_1up}/usr/bin" "${extra_search_path_2up}/usr/bin" ) endif() endif() if(NOT PATCH_EXECUTABLE) message(FATAL_ERROR "Couldn't find `patch` executable to use with PATCHES keyword.") endif() # Create a temporary set(temp_list ${CPM_ARGS_UNPARSED_ARGUMENTS}) # Ensure each file exists (or error out) and add it to the list. set(first_item True) foreach(PATCH_FILE ${ARGN}) # Make sure the patch file exists, if we can't find it, try again in the current directory. if(NOT EXISTS "${PATCH_FILE}") if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}") message(FATAL_ERROR "Couldn't find patch file: '${PATCH_FILE}'") endif() set(PATCH_FILE "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}") endif() # Convert to absolute path for use with patch file command. get_filename_component(PATCH_FILE "${PATCH_FILE}" ABSOLUTE) # The first patch entry must be preceded by "PATCH_COMMAND" while the following items are # preceded by "&&". if(first_item) set(first_item False) list(APPEND temp_list "PATCH_COMMAND") else() list(APPEND temp_list "&&") endif() # Add the patch command to the list list(APPEND temp_list "${PATCH_EXECUTABLE}" "-p1" "<" "${PATCH_FILE}") endforeach() # Move temp out into parent scope. set(CPM_ARGS_UNPARSED_ARGUMENTS ${temp_list} PARENT_SCOPE ) endfunction() # method to overwrite internal FetchContent properties, to allow using CPM.cmake to overload # FetchContent calls. As these are internal cmake properties, this method should be used carefully # and may need modification in future CMake versions. Source: # https://github.com/Kitware/CMake/blob/dc3d0b5a0a7d26d43d6cfeb511e224533b5d188f/Modules/FetchContent.cmake#L1152 function(cpm_override_fetchcontent contentName) cmake_parse_arguments(PARSE_ARGV 1 arg "" "SOURCE_DIR;BINARY_DIR" "") if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "") message(FATAL_ERROR "${CPM_INDENT} Unsupported arguments: ${arg_UNPARSED_ARGUMENTS}") endif() string(TOLOWER ${contentName} contentNameLower) set(prefix "_FetchContent_${contentNameLower}") set(propertyName "${prefix}_sourceDir") define_property( GLOBAL PROPERTY ${propertyName} BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" ) set_property(GLOBAL PROPERTY ${propertyName} "${arg_SOURCE_DIR}") set(propertyName "${prefix}_binaryDir") define_property( GLOBAL PROPERTY ${propertyName} BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" ) set_property(GLOBAL PROPERTY ${propertyName} "${arg_BINARY_DIR}") set(propertyName "${prefix}_populated") define_property( GLOBAL PROPERTY ${propertyName} BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" ) set_property(GLOBAL PROPERTY ${propertyName} TRUE) endfunction() # Download and add a package from source function(CPMAddPackage) cpm_set_policies() list(LENGTH ARGN argnLength) if(argnLength EQUAL 1) cpm_parse_add_package_single_arg("${ARGN}" ARGN) # The shorthand syntax implies EXCLUDE_FROM_ALL and SYSTEM set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;") endif() set(oneValueArgs NAME FORCE VERSION GIT_TAG DOWNLOAD_ONLY GITHUB_REPOSITORY GITLAB_REPOSITORY BITBUCKET_REPOSITORY GIT_REPOSITORY SOURCE_DIR FIND_PACKAGE_ARGUMENTS NO_CACHE SYSTEM GIT_SHALLOW EXCLUDE_FROM_ALL SOURCE_SUBDIR CUSTOM_CACHE_KEY ) set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND PATCHES) cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") # Set default values for arguments if(NOT DEFINED CPM_ARGS_VERSION) if(DEFINED CPM_ARGS_GIT_TAG) cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) endif() endif() if(CPM_ARGS_DOWNLOAD_ONLY) set(DOWNLOAD_ONLY ${CPM_ARGS_DOWNLOAD_ONLY}) else() set(DOWNLOAD_ONLY NO) endif() if(DEFINED CPM_ARGS_GITHUB_REPOSITORY) set(CPM_ARGS_GIT_REPOSITORY "https://github.com/${CPM_ARGS_GITHUB_REPOSITORY}.git") elseif(DEFINED CPM_ARGS_GITLAB_REPOSITORY) set(CPM_ARGS_GIT_REPOSITORY "https://gitlab.com/${CPM_ARGS_GITLAB_REPOSITORY}.git") elseif(DEFINED CPM_ARGS_BITBUCKET_REPOSITORY) set(CPM_ARGS_GIT_REPOSITORY "https://bitbucket.org/${CPM_ARGS_BITBUCKET_REPOSITORY}.git") endif() if(DEFINED CPM_ARGS_GIT_REPOSITORY) list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_REPOSITORY ${CPM_ARGS_GIT_REPOSITORY}) if(NOT DEFINED CPM_ARGS_GIT_TAG) set(CPM_ARGS_GIT_TAG v${CPM_ARGS_VERSION}) endif() # If a name wasn't provided, try to infer it from the git repo if(NOT DEFINED CPM_ARGS_NAME) cpm_package_name_from_git_uri(${CPM_ARGS_GIT_REPOSITORY} CPM_ARGS_NAME) endif() endif() set(CPM_SKIP_FETCH FALSE) if(DEFINED CPM_ARGS_GIT_TAG) list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_TAG ${CPM_ARGS_GIT_TAG}) # If GIT_SHALLOW is explicitly specified, honor the value. if(DEFINED CPM_ARGS_GIT_SHALLOW) list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW ${CPM_ARGS_GIT_SHALLOW}) endif() endif() if(DEFINED CPM_ARGS_URL) # If a name or version aren't provided, try to infer them from the URL list(GET CPM_ARGS_URL 0 firstUrl) cpm_package_name_and_ver_from_url(${firstUrl} nameFromUrl verFromUrl) # If we fail to obtain name and version from the first URL, we could try other URLs if any. # However multiple URLs are expected to be quite rare, so for now we won't bother. # If the caller provided their own name and version, they trump the inferred ones. if(NOT DEFINED CPM_ARGS_NAME) set(CPM_ARGS_NAME ${nameFromUrl}) endif() if(NOT DEFINED CPM_ARGS_VERSION) set(CPM_ARGS_VERSION ${verFromUrl}) endif() list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS URL "${CPM_ARGS_URL}") endif() # Check for required arguments if(NOT DEFINED CPM_ARGS_NAME) message( FATAL_ERROR "${CPM_INDENT} 'NAME' was not provided and couldn't be automatically inferred for package added with arguments: '${ARGN}'" ) endif() # Check if package has been added before cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") if(CPM_PACKAGE_ALREADY_ADDED) cpm_export_variables(${CPM_ARGS_NAME}) return() endif() # Check for manual overrides if(NOT CPM_ARGS_FORCE AND NOT "${CPM_${CPM_ARGS_NAME}_SOURCE}" STREQUAL "") set(PACKAGE_SOURCE ${CPM_${CPM_ARGS_NAME}_SOURCE}) set(CPM_${CPM_ARGS_NAME}_SOURCE "") CPMAddPackage( NAME "${CPM_ARGS_NAME}" SOURCE_DIR "${PACKAGE_SOURCE}" EXCLUDE_FROM_ALL "${CPM_ARGS_EXCLUDE_FROM_ALL}" SYSTEM "${CPM_ARGS_SYSTEM}" PATCHES "${CPM_ARGS_PATCHES}" OPTIONS "${CPM_ARGS_OPTIONS}" SOURCE_SUBDIR "${CPM_ARGS_SOURCE_SUBDIR}" DOWNLOAD_ONLY "${DOWNLOAD_ONLY}" FORCE True ) cpm_export_variables(${CPM_ARGS_NAME}) return() endif() # Check for available declaration if(NOT CPM_ARGS_FORCE AND NOT "${CPM_DECLARATION_${CPM_ARGS_NAME}}" STREQUAL "") set(declaration ${CPM_DECLARATION_${CPM_ARGS_NAME}}) set(CPM_DECLARATION_${CPM_ARGS_NAME} "") CPMAddPackage(${declaration}) cpm_export_variables(${CPM_ARGS_NAME}) # checking again to ensure version and option compatibility cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") return() endif() if(NOT CPM_ARGS_FORCE) if(CPM_USE_LOCAL_PACKAGES OR CPM_LOCAL_PACKAGES_ONLY) cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) if(CPM_PACKAGE_FOUND) cpm_export_variables(${CPM_ARGS_NAME}) return() endif() if(CPM_LOCAL_PACKAGES_ONLY) message( SEND_ERROR "${CPM_INDENT} ${CPM_ARGS_NAME} not found via find_package(${CPM_ARGS_NAME} ${CPM_ARGS_VERSION})" ) endif() endif() endif() CPMRegisterPackage("${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}") if(DEFINED CPM_ARGS_GIT_TAG) set(PACKAGE_INFO "${CPM_ARGS_GIT_TAG}") elseif(DEFINED CPM_ARGS_SOURCE_DIR) set(PACKAGE_INFO "${CPM_ARGS_SOURCE_DIR}") else() set(PACKAGE_INFO "${CPM_ARGS_VERSION}") endif() if(DEFINED FETCHCONTENT_BASE_DIR) # respect user's FETCHCONTENT_BASE_DIR if set set(CPM_FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR}) else() set(CPM_FETCHCONTENT_BASE_DIR ${CMAKE_BINARY_DIR}/_deps) endif() cpm_add_patches(${CPM_ARGS_PATCHES}) if(DEFINED CPM_ARGS_DOWNLOAD_COMMAND) list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND}) elseif(DEFINED CPM_ARGS_SOURCE_DIR) list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${CPM_ARGS_SOURCE_DIR}) if(NOT IS_ABSOLUTE ${CPM_ARGS_SOURCE_DIR}) # Expand `CPM_ARGS_SOURCE_DIR` relative path. This is important because EXISTS doesn't work # for relative paths. get_filename_component( source_directory ${CPM_ARGS_SOURCE_DIR} REALPATH BASE_DIR ${CMAKE_CURRENT_BINARY_DIR} ) else() set(source_directory ${CPM_ARGS_SOURCE_DIR}) endif() if(NOT EXISTS ${source_directory}) string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) # remove timestamps so CMake will re-download the dependency file(REMOVE_RECURSE "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild") endif() elseif(CPM_SOURCE_CACHE AND NOT CPM_ARGS_NO_CACHE) string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) set(origin_parameters ${CPM_ARGS_UNPARSED_ARGUMENTS}) list(SORT origin_parameters) if(CPM_ARGS_CUSTOM_CACHE_KEY) # Application set a custom unique directory name set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${CPM_ARGS_CUSTOM_CACHE_KEY}) elseif(CPM_USE_NAMED_CACHE_DIRECTORIES) string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG") set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME}) else() string(SHA1 origin_hash "${origin_parameters}") set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}) endif() # Expand `download_directory` relative path. This is important because EXISTS doesn't work for # relative paths. get_filename_component(download_directory ${download_directory} ABSOLUTE) list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${download_directory}) if(CPM_SOURCE_CACHE) file(LOCK ${download_directory}/../cmake.lock) endif() if(EXISTS ${download_directory}) if(CPM_SOURCE_CACHE) file(LOCK ${download_directory}/../cmake.lock RELEASE) endif() cpm_store_fetch_properties( ${CPM_ARGS_NAME} "${download_directory}" "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" ) cpm_get_fetch_properties("${CPM_ARGS_NAME}") if(DEFINED CPM_ARGS_GIT_TAG AND NOT (PATCH_COMMAND IN_LIST CPM_ARGS_UNPARSED_ARGUMENTS)) # warn if cache has been changed since checkout cpm_check_git_working_dir_is_clean(${download_directory} ${CPM_ARGS_GIT_TAG} IS_CLEAN) if(NOT ${IS_CLEAN}) message( WARNING "${CPM_INDENT} Cache for ${CPM_ARGS_NAME} (${download_directory}) is dirty" ) endif() endif() cpm_add_subdirectory( "${CPM_ARGS_NAME}" "${DOWNLOAD_ONLY}" "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" "${${CPM_ARGS_NAME}_BINARY_DIR}" "${CPM_ARGS_EXCLUDE_FROM_ALL}" "${CPM_ARGS_SYSTEM}" "${CPM_ARGS_OPTIONS}" ) set(PACKAGE_INFO "${PACKAGE_INFO} at ${download_directory}") # As the source dir is already cached/populated, we override the call to FetchContent. set(CPM_SKIP_FETCH TRUE) cpm_override_fetchcontent( "${lower_case_name}" SOURCE_DIR "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" BINARY_DIR "${${CPM_ARGS_NAME}_BINARY_DIR}" ) else() # Enable shallow clone when GIT_TAG is not a commit hash. Our guess may not be accurate, but # it should guarantee no commit hash get mis-detected. if(NOT DEFINED CPM_ARGS_GIT_SHALLOW) cpm_is_git_tag_commit_hash("${CPM_ARGS_GIT_TAG}" IS_HASH) if(NOT ${IS_HASH}) list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW TRUE) endif() endif() # remove timestamps so CMake will re-download the dependency file(REMOVE_RECURSE ${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild) set(PACKAGE_INFO "${PACKAGE_INFO} to ${download_directory}") endif() endif() if(NOT "${DOWNLOAD_ONLY}") cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")") endif() if(CPM_PACKAGE_LOCK_ENABLED) if((CPM_ARGS_VERSION AND NOT CPM_ARGS_SOURCE_DIR) OR CPM_INCLUDE_ALL_IN_PACKAGE_LOCK) cpm_add_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") elseif(CPM_ARGS_SOURCE_DIR) cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "local directory") else() cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") endif() endif() cpm_message( STATUS "${CPM_INDENT} Adding package ${CPM_ARGS_NAME}@${CPM_ARGS_VERSION} (${PACKAGE_INFO})" ) if(NOT CPM_SKIP_FETCH) # CMake 3.28 added EXCLUDE, SYSTEM (3.25), and SOURCE_SUBDIR (3.18) to FetchContent_Declare. # Calling FetchContent_MakeAvailable will then internally forward these options to # add_subdirectory. Up until these changes, we had to call FetchContent_Populate and # add_subdirectory separately, which is no longer necessary and has been deprecated as of 3.30. # A Bug in CMake prevents us to use the non-deprecated functions until 3.30.3. set(fetchContentDeclareExtraArgs "") if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.30.3") if(${CPM_ARGS_EXCLUDE_FROM_ALL}) list(APPEND fetchContentDeclareExtraArgs EXCLUDE_FROM_ALL) endif() if(${CPM_ARGS_SYSTEM}) list(APPEND fetchContentDeclareExtraArgs SYSTEM) endif() if(DEFINED CPM_ARGS_SOURCE_SUBDIR) list(APPEND fetchContentDeclareExtraArgs SOURCE_SUBDIR ${CPM_ARGS_SOURCE_SUBDIR}) endif() # For CMake version <3.28 OPTIONS are parsed in cpm_add_subdirectory if(CPM_ARGS_OPTIONS AND NOT DOWNLOAD_ONLY) foreach(OPTION ${CPM_ARGS_OPTIONS}) cpm_parse_option("${OPTION}") set(${OPTION_KEY} "${OPTION_VALUE}") endforeach() endif() endif() cpm_declare_fetch( "${CPM_ARGS_NAME}" ${fetchContentDeclareExtraArgs} "${CPM_ARGS_UNPARSED_ARGUMENTS}" ) cpm_fetch_package("${CPM_ARGS_NAME}" ${DOWNLOAD_ONLY} populated ${CPM_ARGS_UNPARSED_ARGUMENTS}) if(CPM_SOURCE_CACHE AND download_directory) file(LOCK ${download_directory}/../cmake.lock RELEASE) endif() if(${populated} AND ${CMAKE_VERSION} VERSION_LESS "3.30.3") cpm_add_subdirectory( "${CPM_ARGS_NAME}" "${DOWNLOAD_ONLY}" "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" "${${CPM_ARGS_NAME}_BINARY_DIR}" "${CPM_ARGS_EXCLUDE_FROM_ALL}" "${CPM_ARGS_SYSTEM}" "${CPM_ARGS_OPTIONS}" ) endif() cpm_get_fetch_properties("${CPM_ARGS_NAME}") endif() set(${CPM_ARGS_NAME}_ADDED YES) cpm_export_variables("${CPM_ARGS_NAME}") endfunction() # Fetch a previously declared package macro(CPMGetPackage Name) if(DEFINED "CPM_DECLARATION_${Name}") CPMAddPackage(NAME ${Name}) else() message(SEND_ERROR "${CPM_INDENT} Cannot retrieve package ${Name}: no declaration available") endif() endmacro() # export variables available to the caller to the parent scope expects ${CPM_ARGS_NAME} to be set macro(cpm_export_variables name) set(${name}_SOURCE_DIR "${${name}_SOURCE_DIR}" PARENT_SCOPE ) set(${name}_BINARY_DIR "${${name}_BINARY_DIR}" PARENT_SCOPE ) set(${name}_ADDED "${${name}_ADDED}" PARENT_SCOPE ) set(CPM_LAST_PACKAGE_NAME "${name}" PARENT_SCOPE ) endmacro() # declares a package, so that any call to CPMAddPackage for the package name will use these # arguments instead. Previous declarations will not be overridden. macro(CPMDeclarePackage Name) if(NOT DEFINED "CPM_DECLARATION_${Name}") set("CPM_DECLARATION_${Name}" "${ARGN}") endif() endmacro() function(cpm_add_to_package_lock Name) if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) cpm_prettify_package_arguments(PRETTY_ARGN false ${ARGN}) file(APPEND ${CPM_PACKAGE_LOCK_FILE} "# ${Name}\nCPMDeclarePackage(${Name}\n${PRETTY_ARGN})\n") endif() endfunction() function(cpm_add_comment_to_package_lock Name) if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) cpm_prettify_package_arguments(PRETTY_ARGN true ${ARGN}) file(APPEND ${CPM_PACKAGE_LOCK_FILE} "# ${Name} (unversioned)\n# CPMDeclarePackage(${Name}\n${PRETTY_ARGN}#)\n" ) endif() endfunction() # includes the package lock file if it exists and creates a target `cpm-update-package-lock` to # update it macro(CPMUsePackageLock file) if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) get_filename_component(CPM_ABSOLUTE_PACKAGE_LOCK_PATH ${file} ABSOLUTE) if(EXISTS ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) include(${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) endif() if(NOT TARGET cpm-update-package-lock) add_custom_target( cpm-update-package-lock COMMAND ${CMAKE_COMMAND} -E copy ${CPM_PACKAGE_LOCK_FILE} ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH} ) endif() set(CPM_PACKAGE_LOCK_ENABLED true) endif() endmacro() # registers a package that has been added to CPM function(CPMRegisterPackage PACKAGE VERSION) list(APPEND CPM_PACKAGES ${PACKAGE}) set(CPM_PACKAGES ${CPM_PACKAGES} CACHE INTERNAL "" ) set("CPM_PACKAGE_${PACKAGE}_VERSION" ${VERSION} CACHE INTERNAL "" ) endfunction() # retrieve the current version of the package to ${OUTPUT} function(CPMGetPackageVersion PACKAGE OUTPUT) set(${OUTPUT} "${CPM_PACKAGE_${PACKAGE}_VERSION}" PARENT_SCOPE ) endfunction() # declares a package in FetchContent_Declare function(cpm_declare_fetch PACKAGE) if(${CPM_DRY_RUN}) cpm_message(STATUS "${CPM_INDENT} Package not declared (dry run)") return() endif() FetchContent_Declare(${PACKAGE} ${ARGN}) endfunction() # returns properties for a package previously defined by cpm_declare_fetch function(cpm_get_fetch_properties PACKAGE) if(${CPM_DRY_RUN}) return() endif() set(${PACKAGE}_SOURCE_DIR "${CPM_PACKAGE_${PACKAGE}_SOURCE_DIR}" PARENT_SCOPE ) set(${PACKAGE}_BINARY_DIR "${CPM_PACKAGE_${PACKAGE}_BINARY_DIR}" PARENT_SCOPE ) endfunction() function(cpm_store_fetch_properties PACKAGE source_dir binary_dir) if(${CPM_DRY_RUN}) return() endif() set(CPM_PACKAGE_${PACKAGE}_SOURCE_DIR "${source_dir}" CACHE INTERNAL "" ) set(CPM_PACKAGE_${PACKAGE}_BINARY_DIR "${binary_dir}" CACHE INTERNAL "" ) endfunction() # adds a package as a subdirectory if viable, according to provided options function( cpm_add_subdirectory PACKAGE DOWNLOAD_ONLY SOURCE_DIR BINARY_DIR EXCLUDE SYSTEM OPTIONS ) if(NOT DOWNLOAD_ONLY AND EXISTS ${SOURCE_DIR}/CMakeLists.txt) set(addSubdirectoryExtraArgs "") if(EXCLUDE) list(APPEND addSubdirectoryExtraArgs EXCLUDE_FROM_ALL) endif() if("${SYSTEM}" AND "${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.25") # https://cmake.org/cmake/help/latest/prop_dir/SYSTEM.html#prop_dir:SYSTEM list(APPEND addSubdirectoryExtraArgs SYSTEM) endif() if(OPTIONS) foreach(OPTION ${OPTIONS}) cpm_parse_option("${OPTION}") set(${OPTION_KEY} "${OPTION_VALUE}") endforeach() endif() set(CPM_OLD_INDENT "${CPM_INDENT}") set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:") add_subdirectory(${SOURCE_DIR} ${BINARY_DIR} ${addSubdirectoryExtraArgs}) set(CPM_INDENT "${CPM_OLD_INDENT}") endif() endfunction() # downloads a previously declared package via FetchContent and exports the variables # `${PACKAGE}_SOURCE_DIR` and `${PACKAGE}_BINARY_DIR` to the parent scope function(cpm_fetch_package PACKAGE DOWNLOAD_ONLY populated) set(${populated} FALSE PARENT_SCOPE ) if(${CPM_DRY_RUN}) cpm_message(STATUS "${CPM_INDENT} Package ${PACKAGE} not fetched (dry run)") return() endif() FetchContent_GetProperties(${PACKAGE}) string(TOLOWER "${PACKAGE}" lower_case_name) if(NOT ${lower_case_name}_POPULATED) if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.30.3") if(DOWNLOAD_ONLY) # MakeAvailable will call add_subdirectory internally which is not what we want when # DOWNLOAD_ONLY is set. Populate will only download the dependency without adding it to the # build FetchContent_Populate( ${PACKAGE} SOURCE_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-src" BINARY_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" SUBBUILD_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild" ${ARGN} ) else() FetchContent_MakeAvailable(${PACKAGE}) endif() else() FetchContent_Populate(${PACKAGE}) endif() set(${populated} TRUE PARENT_SCOPE ) endif() cpm_store_fetch_properties( ${CPM_ARGS_NAME} ${${lower_case_name}_SOURCE_DIR} ${${lower_case_name}_BINARY_DIR} ) set(${PACKAGE}_SOURCE_DIR ${${lower_case_name}_SOURCE_DIR} PARENT_SCOPE ) set(${PACKAGE}_BINARY_DIR ${${lower_case_name}_BINARY_DIR} PARENT_SCOPE ) endfunction() # splits a package option function(cpm_parse_option OPTION) string(REGEX MATCH "^[^ ]+" OPTION_KEY "${OPTION}") string(LENGTH "${OPTION}" OPTION_LENGTH) string(LENGTH "${OPTION_KEY}" OPTION_KEY_LENGTH) if(OPTION_KEY_LENGTH STREQUAL OPTION_LENGTH) # no value for key provided, assume user wants to set option to "ON" set(OPTION_VALUE "ON") else() math(EXPR OPTION_KEY_LENGTH "${OPTION_KEY_LENGTH}+1") string(SUBSTRING "${OPTION}" "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE) endif() set(OPTION_KEY "${OPTION_KEY}" PARENT_SCOPE ) set(OPTION_VALUE "${OPTION_VALUE}" PARENT_SCOPE ) endfunction() # guesses the package version from a git tag function(cpm_get_version_from_git_tag GIT_TAG RESULT) string(LENGTH ${GIT_TAG} length) if(length EQUAL 40) # GIT_TAG is probably a git hash set(${RESULT} 0 PARENT_SCOPE ) else() string(REGEX MATCH "v?([0123456789.]*).*" _ ${GIT_TAG}) set(${RESULT} ${CMAKE_MATCH_1} PARENT_SCOPE ) endif() endfunction() # guesses if the git tag is a commit hash or an actual tag or a branch name. function(cpm_is_git_tag_commit_hash GIT_TAG RESULT) string(LENGTH "${GIT_TAG}" length) # full hash has 40 characters, and short hash has at least 7 characters. if(length LESS 7 OR length GREATER 40) set(${RESULT} 0 PARENT_SCOPE ) else() if(${GIT_TAG} MATCHES "^[a-fA-F0-9]+$") set(${RESULT} 1 PARENT_SCOPE ) else() set(${RESULT} 0 PARENT_SCOPE ) endif() endif() endfunction() function(cpm_prettify_package_arguments OUT_VAR IS_IN_COMMENT) set(oneValueArgs NAME FORCE VERSION GIT_TAG DOWNLOAD_ONLY GITHUB_REPOSITORY GITLAB_REPOSITORY BITBUCKET_REPOSITORY GIT_REPOSITORY SOURCE_DIR FIND_PACKAGE_ARGUMENTS NO_CACHE SYSTEM GIT_SHALLOW EXCLUDE_FROM_ALL SOURCE_SUBDIR ) set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND) cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) foreach(oneArgName ${oneValueArgs}) if(DEFINED CPM_ARGS_${oneArgName}) if(${IS_IN_COMMENT}) string(APPEND PRETTY_OUT_VAR "#") endif() if(${oneArgName} STREQUAL "SOURCE_DIR") string(REPLACE ${CMAKE_SOURCE_DIR} "\${CMAKE_SOURCE_DIR}" CPM_ARGS_${oneArgName} ${CPM_ARGS_${oneArgName}} ) endif() string(APPEND PRETTY_OUT_VAR " ${oneArgName} ${CPM_ARGS_${oneArgName}}\n") endif() endforeach() foreach(multiArgName ${multiValueArgs}) if(DEFINED CPM_ARGS_${multiArgName}) if(${IS_IN_COMMENT}) string(APPEND PRETTY_OUT_VAR "#") endif() string(APPEND PRETTY_OUT_VAR " ${multiArgName}\n") foreach(singleOption ${CPM_ARGS_${multiArgName}}) if(${IS_IN_COMMENT}) string(APPEND PRETTY_OUT_VAR "#") endif() string(APPEND PRETTY_OUT_VAR " \"${singleOption}\"\n") endforeach() endif() endforeach() if(NOT "${CPM_ARGS_UNPARSED_ARGUMENTS}" STREQUAL "") if(${IS_IN_COMMENT}) string(APPEND PRETTY_OUT_VAR "#") endif() string(APPEND PRETTY_OUT_VAR " ") foreach(CPM_ARGS_UNPARSED_ARGUMENT ${CPM_ARGS_UNPARSED_ARGUMENTS}) string(APPEND PRETTY_OUT_VAR " ${CPM_ARGS_UNPARSED_ARGUMENT}") endforeach() string(APPEND PRETTY_OUT_VAR "\n") endif() set(${OUT_VAR} ${PRETTY_OUT_VAR} PARENT_SCOPE ) endfunction() libzeep-7.3.2/cmake/VersionString.cmake0000664000175000017500000002273715150027072017727 0ustar maartenmaarten# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2021-2023 Maarten L. Hekkelman # 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 OWNER 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. # This cmake extension writes out a revision.hpp file in a specified directory. # The file will contain a C++ inline function that can be used to write out # version information. cmake_minimum_required(VERSION 3.15) # We want the revision.hpp file to be updated whenever the status of the # git repository changes. Use the same technique as in GetGitRevisionDescription.cmake # from https://github.com/rpavlik/cmake-modules #[=======================================================================[.rst: .. command:: write_version_header Write a file named revision.hpp containing version info:: write_version_header( [FILE_NAME ] [LIB_NAME ] ) This command will generate the code to write a file name revision.hpp in the directory ````. ``FILE_NAME`` Specify the name of the file to create, default is ``revision.hpp``. ``LIB_NAME`` Specify the library name which will be used as a prefix part for the variables contained in the revision file. #]=======================================================================] # Record the location of this module now, not at the time the CMakeLists.txt # is being processed get_filename_component(_current_cmake_module_dir ${CMAKE_CURRENT_LIST_FILE} PATH) # First locate a .git file or directory. function(_get_git_dir _start_dir _variable) set(cur_dir "${_start_dir}") set(git_dir "${_start_dir}/.git") while(NOT EXISTS "${git_dir}") # .git dir not found, search parent directories set(prev_dir "${cur_dir}") get_filename_component(cur_dir "${cur_dir}" DIRECTORY) if(cur_dir STREQUAL prev_dir OR cur_dir STREQUAL ${_start_dir}) # we are not in git since we either hit root or # the ${_start_dir} which should be the top set(${_variable} "" PARENT_SCOPE) return() endif() set(git_dir "${cur_dir}/.git") endwhile() set(${_variable} "${git_dir}" PARENT_SCOPE) endfunction() # Locate the git refspec hash and load the hash # This code locates the file containing the git refspec/hash # and loads it. Doing it this way assures that each time the git # repository changes the revision.hpp file gets out of date. function(_get_git_hash _data_dir _variable) # Be pessimistic set(_variable "" PARENT_SCOPE) # Load git package if needed if(NOT GIT_FOUND) find_package(Git QUIET) endif() # And fail if not found if(NOT GIT_FOUND) return() endif() # Locate the nearest .git file or directory _get_git_dir(${CMAKE_CURRENT_SOURCE_DIR} GIT_DIR) # And fail if not found if("${GIT_DIR}" STREQUAL "") return() endif() # Check if the current source dir is a git submodule or a worktree. # In both cases .git is a file instead of a directory. # if(IS_DIRECTORY ${GIT_DIR}) set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") else() # The following git command will return a non empty string that # points to the super project working tree if the current # source dir is inside a git submodule. # Otherwise the command will return an empty string. # execute_process( COMMAND "${GIT_EXECUTABLE}" rev-parse --show-superproject-working-tree WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT "${out}" STREQUAL "") # If out is not empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule file(READ ${GIT_DIR} submodule) string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE ${submodule}) string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE) get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") else() # GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree file(READ ${GIT_DIR} worktree_ref) # The .git directory contains a path to the worktree information directory # inside the parent git repo of the worktree. # string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir ${worktree_ref}) string(STRIP ${git_worktree_dir} git_worktree_dir) _get_git_dir("${git_worktree_dir}" GIT_DIR) set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD") endif() endif() # Fail if the 'head' file was not found if(NOT EXISTS "${HEAD_SOURCE_FILE}") return() endif() # Make a copy of the head file set(HEAD_FILE "${_data_dir}/HEAD") configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY) # Now we create a cmake file that will read the contents of this # head file in the appropriate way file(WRITE "${_data_dir}/grab-ref.cmake.in" [[ set(HEAD_HASH) file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) if(HEAD_CONTENTS MATCHES "ref") # named branch string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") if(EXISTS "@GIT_DIR@/${HEAD_REF}") configure_file("@GIT_DIR@/${HEAD_REF}" "@VERSION_STRING_DATA@/head-ref" COPYONLY) else() configure_file("@GIT_DIR@/packed-refs" "@VERSION_STRING_DATA@/packed-refs" COPYONLY) file(READ "@VERSION_STRING_DATA@/packed-refs" PACKED_REFS) if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") set(HEAD_HASH "${CMAKE_MATCH_1}") endif() endif() else() # detached HEAD configure_file("@GIT_DIR@/HEAD" "@VERSION_STRING_DATA@/head-ref" COPYONLY) endif() if(NOT HEAD_HASH) file(READ "@VERSION_STRING_DATA@/head-ref" HEAD_HASH LIMIT 1024) string(STRIP "${HEAD_HASH}" HEAD_HASH) endif() ]]) configure_file("${VERSION_STRING_DATA}/grab-ref.cmake.in" "${VERSION_STRING_DATA}/grab-ref.cmake" @ONLY) # Include the aforementioned file, this will define # the HEAD_HASH variable we're looking for include("${VERSION_STRING_DATA}/grab-ref.cmake") set(${_variable} "${HEAD_HASH}" PARENT_SCOPE) endfunction() # Create a revision file, containing the current git version info, if any function(write_version_header dir) set(flags ) set(options LIB_NAME FILE_NAME) set(sources ) cmake_parse_arguments(VERSION_STRING_OPTION "${flags}" "${options}" "${sources}" ${ARGN}) # parameter check if(NOT IS_DIRECTORY ${dir}) message(FATAL_ERROR "First parameter to write_version_header should be a directory where the final revision.hpp file will be placed") endif() if(VERSION_STRING_OPTION_FILE_NAME) set(file_name "${VERSION_STRING_OPTION_FILE_NAME}") else() set(file_name "revision.hpp") endif() # Where to store intermediate files set(VERSION_STRING_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/VersionString") if(NOT EXISTS "${VERSION_STRING_DATA}") file(MAKE_DIRECTORY "${VERSION_STRING_DATA}") endif() # Load the git hash using the wizzard-like code above. _get_git_hash("${VERSION_STRING_DATA}" GIT_HASH) # If git was found, fetch the git description string if(GIT_HASH) execute_process( COMMAND "${GIT_EXECUTABLE}" describe --dirty --match=build WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(res EQUAL 0) set(REVISION_STRING "${out}") endif() endif() # Check the revision string, if it matches we fill in the required info if(REVISION_STRING MATCHES "build-([0-9]+)-g([0-9a-f]+)(-dirty)?") set(BUILD_NUMBER ${CMAKE_MATCH_1}) if(CMAKE_MATCH_3) set(REVISION_GIT_TAGREF "${CMAKE_MATCH_2}*") else() set(REVISION_GIT_TAGREF "${CMAKE_MATCH_2}") endif() string(TIMESTAMP REVISION_DATE_TIME "%Y-%m-%dT%H:%M:%SZ" UTC) else() set(REVISION_GIT_TAGREF "") set(BUILD_NUMBER 0) set(REVISION_DATE_TIME "") endif() if(VERSION_STRING_OPTION_LIB_NAME) set(VAR_PREFIX "${VERSION_STRING_OPTION_LIB_NAME}") set(IDENT_PREFIX "${VERSION_STRING_OPTION_LIB_NAME}_") set(BOOL_IS_MAIN "false") else() set(VAR_PREFIX "") set(IDENT_PREFIX "") set(BOOL_IS_MAIN "true") endif() configure_file("${_current_cmake_module_dir}/revision.hpp.in" "${dir}/${file_name}" @ONLY) endfunction() libzeep-7.3.2/cmake/asio-boost.hpp.in0000664000175000017500000000132515150027072017274 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2023-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once #if ZEEP_BOOST_ASIO_STANDALONE # define BOOST_ASIO_STANDALONE 1 #endif // This header is missing in some asio versions #include // NOLINT(unused-includes) #ifdef __GNUC__ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wsuggest-override" #endif // IWYU pragma: begin_exports #include // IWYU pragma: end_exports #ifdef __GNUC__ # pragma GCC diagnostic pop #endif namespace asio_ns = ::boost::asio; namespace asio_system_ns = ::boost::system; libzeep-7.3.2/cmake/asio.hpp.in0000664000175000017500000000073515150027072016154 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2023-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once #ifdef __GNUC__ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wsuggest-override" #endif #include #ifdef __GNUC__ # pragma GCC diagnostic pop #endif namespace asio_ns = ::asio; namespace asio_system_ns = ::std; libzeep-7.3.2/cmake/revision.hpp.in0000664000175000017500000000634515150027072017062 0ustar maartenmaarten// This file was generated by VersionString.cmake #pragma once #include constexpr const char k@VAR_PREFIX@ProjectName[] = "@PROJECT_NAME@"; constexpr const char k@VAR_PREFIX@VersionNumber[] = "@PROJECT_VERSION@"; constexpr int k@VAR_PREFIX@BuildNumber = @BUILD_NUMBER@; constexpr const char k@VAR_PREFIX@RevisionGitTag[] = "@REVISION_GIT_TAGREF@"; constexpr const char k@VAR_PREFIX@RevisionDate[] = "@REVISION_DATE_TIME@"; #ifndef VERSION_INFO_DEFINED #define VERSION_INFO_DEFINED 1 namespace version_info_v1_1 { class version_info_base { public: static void write_version_string(std::ostream &os, bool verbose) { auto s_main = registered_main(); if (s_main != nullptr) s_main->write(os, verbose); if (verbose) { for (auto lib = registered_libraries(); lib != nullptr; lib = lib->m_next) { os << "-\n"; lib->write(os, verbose); } } } protected: version_info_base(const char *name, const char *version, int build_number, const char *git_tag, const char *revision_date, bool is_main) : m_name(name) , m_version(version) , m_build_number(build_number) , m_git_tag(git_tag) , m_revision_date(revision_date) { if (is_main) registered_main() = this; else { auto &s_head = registered_libraries(); m_next = s_head; s_head = this; } } void write(std::ostream &os, bool verbose) { os << m_name << " version " << m_version << '\n'; if (verbose) { if (m_build_number != 0) { os << "build: " << m_build_number << ' ' << m_revision_date << '\n'; if (m_git_tag[0] != 0) os << "git tag: " << m_git_tag << '\n'; } } } using version_info_ptr = version_info_base *; static version_info_ptr ®istered_main() { static version_info_ptr s_main = nullptr; return s_main; } static version_info_ptr ®istered_libraries() { static version_info_ptr s_head = nullptr; return s_head; } const char *m_name; const char *m_version; int m_build_number; const char *m_git_tag; const char *m_revision_date; version_info_base *m_next = nullptr; }; template class version_info : public version_info_base { public: using implementation_type = T; version_info(const char *name, const char *version, int build_number, const char *git_tag, const char *revision_date, bool is_main) : version_info_base(name, version, build_number, git_tag, revision_date, is_main) { } struct register_object { register_object() { static implementation_type s_instance; } }; template struct reference_object; static register_object s_registered_object; static reference_object s_referenced_object; }; template typename version_info::register_object version_info::s_registered_object; } // namespace version_info_v1_1 inline void write_version_string(std::ostream &os, bool verbose) { version_info_v1_1::version_info_base::write_version_string(os, verbose); } #endif class version_info_@IDENT_PREFIX@impl : public version_info_v1_1::version_info { public: version_info_@IDENT_PREFIX@impl() : version_info(k@VAR_PREFIX@ProjectName, k@VAR_PREFIX@VersionNumber, k@VAR_PREFIX@BuildNumber, k@VAR_PREFIX@RevisionGitTag, k@VAR_PREFIX@RevisionDate, @BOOL_IS_MAIN@) { } };libzeep-7.3.2/cmake/test-chrono-zoned_time.cpp0000664000175000017500000000043115150027072021200 0ustar maartenmaarten#include #include int main() { auto now = std::chrono::system_clock::now(); auto t = std::chrono::zoned_time{ std::chrono::current_zone(), std::chrono::floor(now) }; std::cout << std::format("{:%d/%b/%Y:%H:%M:%S %Ez}", t); return 0; }libzeep-7.3.2/cmake/zeep-config.cmake.in0000664000175000017500000000036315150027072017715 0ustar maartenmaarten@PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(Threads) @FIND_DEPENDENCY_BOOST@ @FIND_DEPENDENCY_DATE@ find_dependency(zeem REQUIRED) include("${CMAKE_CURRENT_LIST_DIR}/zeep-targets.cmake") check_required_components(zeep) libzeep-7.3.2/docs/0000775000175000017500000000000015150027072013746 5ustar maartenmaartenlibzeep-7.3.2/docs/CMakeLists.txt0000664000175000017500000000405215150027072016507 0ustar maartenmaartencmake_minimum_required(VERSION 3.28) project(zeep-docs) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) find_package(Doxygen REQUIRED) find_package(Sphinx REQUIRED) # Find all the public headers # get_target_property(ZEEP_PUBLIC_HEADER_DIR libzeep INTERFACE_INCLUDE_DIRECTORIES) set(ZEEP_PUBLIC_HEADER_DIR ${PROJECT_SOURCE_DIR}/include/zeep) file(GLOB_RECURSE ZEEP_PUBLIC_HEADERS ${ZEEP_PUBLIC_HEADER_DIR}/*.hpp) set(DOXYGEN_INPUT_DIR ${ZEEP_PUBLIC_HEADER_DIR}) set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/xml) set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}/index.xml) set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) #Replace variables inside @@ with the current values configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY) add_custom_command( OUTPUT ${DOXYGEN_OUTPUT_DIR} COMMAND ${CMAKE_COMMAND} -E make_directory ${DOXYGEN_OUTPUT_DIR}) add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE} # BYPRODUCTS ${DOXYGEN_OUTPUT_DIR} DEPENDS ${ZEEP_PUBLIC_HEADERS} ${DOXYGEN_OUTPUT_DIR} ${DOXYFILE_OUT} COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN} COMMENT "Generating docs") add_custom_target("Doxygen-libzeep" ALL DEPENDS ${DOXYGEN_INDEX_FILE}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in ${CMAKE_CURRENT_SOURCE_DIR}/conf.py @ONLY) set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/sphinx) add_custom_target("Sphinx-libzeep" ALL COMMAND ${SPHINX_EXECUTABLE} -b html -Dbreathe_projects.libzeep=${DOXYGEN_OUTPUT_DIR} ${SPHINX_SOURCE} ${SPHINX_BUILD} DEPENDS ${DOXYGEN_INDEX_FILE} BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/api WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating documentation with Sphinx") install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/sphinx/ DESTINATION share/doc/libzeep PATTERN .doctrees EXCLUDE) libzeep-7.3.2/docs/Doxyfile.in0000664000175000017500000000043515150027072016063 0ustar maartenmaartenEXCLUDE_SYMBOLS = std*, zeep::json::detail* FILE_PATTERNS = *.hpp PREDEFINED += and=&& or=|| not=! GENERATE_XML = YES GENERATE_HTML = NO GENERATE_LATEX = NO GENERATE_TODOLIST = NO INPUT = @DOXYGEN_INPUT_DIR@ libzeep-7.3.2/docs/_static/0000775000175000017500000000000015150027072015374 5ustar maartenmaartenlibzeep-7.3.2/docs/_static/logo.png0000664000175000017500000000552015150027072017044 0ustar maartenmaartenPNG  IHDRd25~bKGD pHYs  tIME -4Ab#iTXtCommentCreated with GIMPd.e IDATx[aLSW~jR(*BqJ9]’-G5(.5%e X5dc2'lC3 |?~PD}9==s$ L@6 Addx"z=bcc$߅"Jǎg+DV>Ӕ1~B1j/ރy? <9~'NX4 ztdy?T fۈU*<'"7Ȕ)Si#k" 2|IkCdR[[ BΜ93v< j54 ^|EDgϞjj}V#Fq?i2Dxnqi<ذa|޳^pL̛bOP;2Niiibv*ݍ1H6.٧̟?_}z|A:ܟ|VىAVnׯG}}=`֭())_z%m;wXk ,#~NןO:gE?m2$-[P]] G}:fBpp0ʕ+hn͆[nFl(߿{OwލÇVX*qGB9sJ+W_ػwFG_|߆lڴiC<(%@LLLdCC޽KK.l6 mNNkYߋ/--%Ɗ ]zz:/_~ǹiӦҐdFFМ>}]]]t\y&7o,xM,kՓ4 InxxJRdggt d2ywxhͪU[bb"'dw R__B.]f͢J̙3i4yf^|YRA/_.www?^A-qoڍr ;z$?** mmmL/!AdLywP(;v)NΠ#$$=<>޽{j8vze0{oJJJ/-@سg$xdqqסF&%%Q155$oaNN͛={KUV`0Pղ$Cx"hn4n``9s-Y1ݿ?bj,//Nc^^D˰0޼y+]Gdhܘ׭['b555l6YYYr o߾Ht\t I^cMM hġdq^\JJ |MjZFh6988H^xgrss_Mb~^?{,.]:N; 2!fh4AXp!N8kh4oEAAΝ >** +WDPPT*p 8đLϣNѠC``X BCCQZZ ͆F N'~[Vܹj]]]=pAqڵkBƍGƳxƤ$^~$o0==$#IܹcCC$oݺEq?'|}st뫑8Ɂ677_}4DFOk׮155UR… G!ӧOG{{;ZZZp8w bV,[ gFGGSN`nGAAoƽ{vc$&&t:Eݻغu+m+))sh"dee9кAW^o0$f3fΜ-[n\.֮]ȗy)Ν;˗/je~~>cq첷t,d``LL ˽h-[F^ϣGr͚5j寿@FGGKtq4lll2MRŋUUUngaa!,X@Ns߾}CO}u%^I磀jg?1 ~'qld"ʐ ?U}MJIENDB`libzeep-7.3.2/docs/cmake/0000775000175000017500000000000015150027072015026 5ustar maartenmaartenlibzeep-7.3.2/docs/cmake/FindSphinx.cmake0000664000175000017500000000053415150027072020104 0ustar maartenmaarten# Look for an executable called sphinx-build find_program(SPHINX_EXECUTABLE NAMES sphinx-build DOC "Path to sphinx-build executable") include(FindPackageHandleStandardArgs) # Handle standard arguments to find_package like REQUIRED and QUIET find_package_handle_standard_args(Sphinx "Failed to find sphinx-build executable" SPHINX_EXECUTABLE) libzeep-7.3.2/docs/conf.py.in0000664000175000017500000000347315150027072015661 0ustar maartenmaartenproject = 'libzeep-doc' copyright = '2023, Maarten L. Hekkelman' author = 'Maarten L. Hekkelman' release = '@PROJECT_VERSION@' # -- General configuration --------------------------------------------------- extensions = [ "breathe", "exhale", "myst_parser" ] breathe_projects = { "libzeep": "../build/docs/xml" } myst_enable_extensions = [ "colon_fence" ] breathe_default_project = "libzeep" # Setup the exhale extension exhale_args = { # These arguments are required "containmentFolder": "./api", "rootFileName": "library_root.rst", "doxygenStripFromPath": "../include/", # Heavily encouraged optional argument (see docs) "rootFileTitle": "API Reference", # Suggested optional arguments # "createTreeView": True, # TIP: if using the sphinx-bootstrap-theme, you need # "treeViewIsBootstrap": True, "exhaleExecutesDoxygen": False, "contentsDirectives" : False, "verboseBuild": False } # Tell sphinx what the primary language being documented is. primary_domain = 'cpp' # Tell sphinx what the pygments highlight language should be. highlight_language = 'cpp' templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] html_theme_options = { } cpp_index_common_prefix = [ 'zeep::', 'zeep::http::', 'zeep::xml::', 'zeep::json::' ] libzeep-7.3.2/docs/genindex.rst0000664000175000017500000000001415150027072016274 0ustar maartenmaartenIndex ===== libzeep-7.3.2/docs/index.rst0000664000175000017500000000410015150027072015602 0ustar maartenmaartenIntroduction ============ Libzeep started as a spin-off of `MRS `_ which is a tool to index and search large text-based bioinformatics databanks. The code that generates a SOAP server in compile time in MRS was needed in another project and this is how libzeep started. BTW, zeep is the dutch word for soap. One of the major parts of libzeep used to be the XML library. It contains a full validating parser with support for XML 1.0 and 1.1 as well as a DOM API for manipulating XML based data structures in memory. The XML part of libzeep has been split off in version 7 and libzeep now uses `libzeem ` for the manipulation of XML. You can use libzeep for building web applications in C++ including a web server implementation, SOAP and REST controller support and a templating engine looking suspisciously like `Thymeleaf `_. Lots of the concepts used in libzeep are inspired by the Java based `Spring framework `_. This library contains a web server implementation. There's also code to create daemon processes and run a preforked webserver. The design follows a bit the one from Spring and so there's a HTTP server class that delegates requests to controllers. A security context class helps in limiting access to authorized users only. Three specialized controller classes provide HTML templates, REST and SOAP services. The template language implementation attempts to be source code compatible with Thymeleaf. The base controller class is a REST controller and maps member function calls to the HTTP URI space and translates HTTP parameters and HTTP form content into function variables and it provides transparent and automatic translation of result types into JSON. The SOAP controller is like the REST controller, but now digests requests wrapped in SOAP envelopes, delegates them to handler functions and returns the result back wrapped in SOAP envelopes. .. toctree:: :maxdepth: 2 :caption: Contents self lib-http lib-generic api/library_root.rst genindex libzeep-7.3.2/docs/lib-generic.rst0000664000175000017500000000352115150027072016661 0ustar maartenmaartenGeneric ======= Introduction ------------ Originally libzeep came as a single library. But if you only need the XML functionality it might not be very useful to include all the networking code for the HTTP server. And so the libraries were split into modules that can be used independently from each other. There are some files that are shared by all libraries though The configuration file ^^^^^^^^^^^^^^^^^^^^^^ In :ref:`file_zeep_config.hpp` you can find a couple of flags that influence what parts of libzeep should be left out. The first is to enable building a :cpp:class:`zeep::http::preforked_server` class, probably only useful in a UNIX context. The other flag allows the compilation of code that uses resources. Resources in a libzeep context are a bit different from their counterparts in MacOS and Windows. Libzeep uses *mrc* to bundle resources in an executable. Especially for small web applications this makes installation very easy at the cost of configurability. See `github pages for mrc `_ for more information. character array streambuf ^^^^^^^^^^^^^^^^^^^^^^^^^ Sometimes it is very convenient to have a `std::istream` reading from a `const char*` buffer, the class :cpp:class:`zeep::char_streambuf` allows you to do just that. .. code-block:: cpp auto sb = zeep::char_streambuf("Hello, world!"); auto is = std::istream(&sb); std::string line; std::getline(is, line); Unicode/text support ^^^^^^^^^^^^^^^^^^^^ Various simple routines used when working with UTF-8 encoded Unicode text. Routines that are so common, you really ask yourself why these are not part of the standard yet. Serialization support ^^^^^^^^^^^^^^^^^^^^^ The serialization code in libzeep builds upon the code in libzeem. You serialize and deserialize objects into the internal el::object format which can be converted into JSON. libzeep-7.3.2/docs/lib-http.rst0000664000175000017500000021164215150027072016231 0ustar maartenmaartenHTTP ==== Introduction -------------------------------------- The goal of libzeep is to provide a library that makes creating web applications as easy as possible. A lot of frameworks already exist to help building these interactive web apps written in languages ranging from Java to Python to more exotic stuff like Ruby and Laravel. The `Spring `_ version stands out between these since it is well designed and offers tons of features and still is fairly easy to work with. But all of these have one flaw in common, they're not written in C++ and thus lack the raw performance. Libzeep tries to implement some of the design patterns found in Spring. There is a very basic HTTP server class with some additional classes that help in creating daemon processes when running a UNIX or lookalike. This HTTP server class delegates requests to controller classes that each process requests in their own corner of the URI space occupied by your server. There are three main controller classes, each targeted at a different task. The first is the :cpp:class:`zeep::http::controller`. This one maps requests to functions, the parameters in the request are automatically translated into function parameters and the result of the function is converted back into JSON automatically. Named parameters can be passed in the payload of a POST request, as query parameters in a GET request or as parts of the URI path, as in ``GET /book/1234/title`` where you request the title of book number 1234. The second controller class is :cpp:class:`zeep::http::html_controller` maps requests to functions that take a :cpp:class:`zeep::http::scope` as well as named parameters just like the REST controller and return a :cpp:class:`zeep::http::reply`. Various routines are available to help constructing XHTML based replies based on XHTML template files. These files can contain special tags that will be processed and the values can be expressed in *expression language*. The third controller is the :cpp:class:`zeep::http::soap_controller`. Similar to the REST controller, but this time the translation is between SOAP XML messages and parameters and vice versa. HTTP server -------------------------------------- threads to run simultaneously. Something like this: .. literalinclude:: ../examples/http-server-0.cpp :start-after: //[ most_simple_http_server :end-before: //] :language: cpp Running this code will create a server that listens to port 8080 on localhost and will return ``NOT FOUND`` for all requests. Not particularly useful of course. It is possible to derive a class Controllers -------------------------------------- that handles any request, since it has a prefix that is effectively the same as the root. It returns a simple reply. .. literalinclude:: ../examples/http-server-1.cpp :start-after: //[ simple_http_server :end-before: //] :language: c++ Still a not so useful example. Fortunately there are several implementations of :cpp:class:`zeep::http::controller` that we can use. HTML Controller ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The :cpp:class:`zeep::http::html_controller` class allows you to *mount* a request handler on a URI path, the result is that this request handler, which is a method of your controller class, will be called whenever a HTTP request with a matching URI comes in. The handler method has next to the :cpp:class:`zeep::http::request` and :cpp:class:`zeep::http::reply` parameter an additional :cpp:class:`zeep::http::scope` parameter. This scope is a kind of nested map of variable names and values. The scope is *const*, if you want to add data to the scope you should create your own sub scope and pass the original in the constructor. A handler can of course create simple replies, just as in the previous example. But you can also use templates. Note that the constructor of :cpp:class:`zeep::http::html_controller` takes a second parameter that is called docroot. This should contain the path to the directory containing the templates. .. note:: The docroot parameter is ignored when you create a html controller based on resources, see section on resources further in this documentation. Our :cpp:class:`zeep::http::html_controller` indirectly inherits :cpp:type:`zeep::http::template_processor` and this is the class that uses the `docroot` parameter. This class takes care of processing template files. It loads them and uses the registered tag processors and the `scope` to fill in the blanks and process the constructs found in the template. .. literalinclude:: ../examples/http-server-2.cpp :language: c++ :start-after: //[ simple_http_server_2 :end-before: //] This example uses the file docroot/hello.xhtml which should contain: .. code-block:: xml Hello

Hello, !

Now build and run this code, and you can access your welcome page at [If you want to see another name, use e.g. instead. Several remarks here. The server object is created with a ``docroot`` parameter. That parameter tells the server to create a default :cpp:type:`zeep::http::template_processor` for use by the :cpp:class:`zeep::http::html_controller` objects. As you can see in the handler code, a check is made for a parameter called ``name``. When present, its value is stored in the newly created sub-scope object. The template file contains a construct in the ```` element that tests for the availability of this variable and uses the default ``'world'`` otherwise. For more information on templates see the section on :ref:`xhtml-template`. The path specified in `mount` is `{,index,index.html}` which is a glob pattern, this pattern can accept the following constructs: +--------------------+-------------------------------------------------------+ | path | matches | +====================+=======================================================+ | `**/*.js` | matches `x.js`, `a/b/c.js`, etc | +--------------------+-------------------------------------------------------+ | `{css,scripts}/` | matches e.g. `css/1/first.css` and `scripts/index.js` | +--------------------+-------------------------------------------------------+ | `a;b;c` | matches either `a`, `b` or `c` | +--------------------+-------------------------------------------------------+ The way _mount_ works in the :cpp:class:`zeep::http::html_controller` class was a bit oldfashioned and inflexible. Especially compared to the :cpp:class:`zeep::http::rest_controller` class. So version 6 of libzeep brings a new way of *mounting*, to avoid conflicts now called *mapping*. The signature of handlers is now changed to take a couple of arguments, using std::optional if they are not required. Conversion of types is done automatically. The handler also takes a :cpp:class:`zeep::http::scope` as first parameter and returns the :cpp:class:`zeep::http::reply` object. How this works in practice is what you can see here: .. literalinclude:: ../examples/http-server-3.cpp :start-after: //[ simple_http_server_3 :end-before: //] :language: c++ Request and Reply -------------------------------------- An implementation of the HTTP standard will need various data types. There are :cpp:class:`zeep::http::request` and :cpp:class:`zeep::http::reply`. And these contain :cpp:class:`zeep::http::header` but the `method` specifier (which was changed to a `std::string` in a recent update to libzeep). The HTTP specification for :cpp:class:`zeep::http::request` and :cpp:class:`zeep::http::reply` are sufficiently similar to allow for a common `:cpp:class:`zeep::http::parser`. The parser for requests supports `chunked transfer encoding `_. request ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The :cpp:class:`zeep::http::request` encapsulates what was received. The standard HTTP request contains a method, like ``GET`` or ``POST``. In this version of libzeep only a limited subset of methods are supported. The next part is the ``uri`` that was requested. Then we have the version, usually 1.0 or 1.1. Libzeep does not currently support anything else. When 1.1 was used, libzeep will honour the keep-alive flag. Headers are stored in an array and can be accessed using :cpp:func:`zeep::http::request::get_header`. Cookies stored in the headers can be accessed using :cpp:func:`zeep::http::request::get_cookie`. A :cpp:class:`zeep::http::request` may also contain a payload, usually only in case of a ``POST`` or ``PUT``. Requests can have parameters. These can be passed url-encoded in the uri, or they can be encoded in the payload using ``application/x-www-form-urlencoded`` or ``multipart/form-data`` encoding. The various :cpp:func:`zeep::http::request::get_parameter` members allow retrieving these parameters by name, optinally passing in a default value in case the parameter was not part of the request. A special case are file parameters, these are retrieved using :cpp:func:`zeep::http::request::get_file_parameter`. This returns a :cpp:class:`zeep::http::file_param` struct that contains information about the uploaded file. Using the [{ref}`zeep:cpp:class:`::char_streambuf `char_streambuf`] class`` you can efficiently read the contents of such a file: .. code-block:: cpp zeep::file_param f = req.get_file_parameter("upoad"); zeep::char_streambuf sb(f.data, f.length); std::istream is(&sb); Many other convenience accessors are available but data is also directly accessible since this is a `struct`. There are some functions to set data. Those are probably only useful if you write your own code to send out HTTP requests to other servers. reply ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The :cpp:class:`zeep::http::reply` object is the object you need to fill in. Replies contain a status line, headers and optionally a payload. There is a static member called :cpp:func:`zeep::http::reply::stock_reply` that allows you to create a complete reply from a status code and an optional message. The :cpp:func:`zeep::http::reply::set_header` and :cpp:func:`zeep::http::reply::set_cookie` member functions take care of setting headers and cookies respectively. The content of the payload can be set using the various :cpp:func:`zeep::http::reply::set_content` methods. They will set the content type header according to the data passed in. If you specify a `std::istream*` as content, and the version is set to ``1.1`` then the data stream will be sent in chunked transfer-encoding. .. _xhtml-template: XHTML Template Processing -------------------------------------- Many web application frameworks provide a way of templating, write some boilerplate HTML and fill in the details at the moment a page is requested. Apart from that, a page may contains lots of external scripts, stylesheets, images and fonts. For these two tasks libzeep comes with a :cpp:type:`zeep::http::template_processor` class. loading ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Starting with the second task just mentioned, the :cpp:type:`zeep::http::template_processor` takes a ``docroot`` parameter in its constructor. This docroot is the location on disk where files are located. But it is also possible to build libzeep with in-memory resources by using `mrc `_. Have a look at the example code for usage. The :cpp:func:`zeep::http::basic_template_processor::load_file` member of :cpp:type:`zeep::http::template_processor` loads a file from disk (or compiled resources), the :cpp:func:`zeep::http::basic_template_processor::file_time` member can be used to get the file time of a file. This can be used to generate ``304 not modified`` replies instead. The :cpp:func:`zeep::http::basic_template_processor::load_template` member loads a template file from docroot and parses the XML contained in this file into a :cpp:class:`zeep::xml::document`. templates ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Since we're using a XML parser/library to load template, they should be strict XMTML. It is possible to make these files somewhat HTML 5 like by adding the doctype .. code-block:: xml The tags inside a template can be processed using a tag_processor. Tag processors are linked to element and attributes in the template using XML namespaces. The method :cpp:func:`zeep::http::basic_template_processor::create_reply_from_template` can be used to convert a template into a reply using the data store in a :cpp:class:`zeep::http::scope`. tag processing ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Libzeep comes with one tag_processor implementation, there used to be two, but the legacy one has been removed. The one remaining, :cpp:class:`zeep::http::tag_processor`, is inspired by [@https://www.thymeleaf.org]. Using ``el`` script -------------------------------------- ``el`` is the abbreviation for *Expression Language*. It is a script language that tries to be like `Unified Expression Language `_. libzeep comes with code to evaluate ``el`` expressions. The language has the concept of variables and these can be created in the C++ code using the :cpp:class:`zeep::json::element` class. Variables created this way are then stored in a :cpp:class:`zeep::http::scope` object and passed along to the processing code. To give an example: .. literalinclude:: ../examples/synopsis-el-1.cpp :language: c++ :start-after: //[ fill_scope :end-before: //] And then you can process some ``expression language`` construct like this: .. literalinclude:: ../examples/synopsis-el-1.cpp :language: c++ :start-after: //[ evaluate_el :end-before: //] And if you then print out the result it should give you something like: "1: 1, 2: 2" ``el`` syntax ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Most often you will use simple expressions: +--------------+------------------------------------------------------------------------------------------------------------------------+ | expression | evaluates to | +==============+========================================================================================================================+ | ``${ ... }`` | variable | +--------------+------------------------------------------------------------------------------------------------------------------------+ | ``*{ ... }`` | selection variable (lookup is done in the scope of variables that were selected with ``z:select``, v2 processor only ) | +--------------+------------------------------------------------------------------------------------------------------------------------+ | ``@{ ... }`` | link URL | +--------------+------------------------------------------------------------------------------------------------------------------------+ | ``~{ ... }`` | fragment | +--------------+------------------------------------------------------------------------------------------------------------------------+ | ``#{ ... }`` | message (not supported yet in libzeep) | +--------------+------------------------------------------------------------------------------------------------------------------------+ The language has literals: +---------------------+----------------------------------------------------------------------------------------------------------------+ | expression | evaluates to | +=====================+================================================================================================================+ | `'my text string'`` | Text literal | +---------------------+----------------------------------------------------------------------------------------------------------------+ | `42`` | Numeric literal | +---------------------+----------------------------------------------------------------------------------------------------------------+ | `3.14`` | Numeric literal, note that scientific notation is not supported | +---------------------+----------------------------------------------------------------------------------------------------------------+ | `true`` | Boolean literal | +---------------------+----------------------------------------------------------------------------------------------------------------+ | `null`` | Null literal | +---------------------+----------------------------------------------------------------------------------------------------------------+ | `user greeting`` | token | +---------------------+----------------------------------------------------------------------------------------------------------------+ Text operations supported are: +---------------------+----------------------------------------------------------------------------------------------------------------+ | construct | description | +=====================+================================================================================================================+ | ``'a' + ' b'`` | concatenation, result is 'a b' | +---------------------+----------------------------------------------------------------------------------------------------------------+ | ``|hello ${user}|`` | literal substitution, if variable user contains 'scott', result is 'hello scott' | +---------------------+----------------------------------------------------------------------------------------------------------------+ Operators: +----------------------------+--------------------------------------------------------+ | operators | type | +============================+========================================================+ | ``+ - * / %`` | binary operators for standard arithmetic operations | +----------------------------+--------------------------------------------------------+ | ``-`` | unary operator, minus sign | +----------------------------+--------------------------------------------------------+ | ``and or`` | binary operators for standard boolean operations | +----------------------------+--------------------------------------------------------+ | ``! not`` | unary operators, negate | +----------------------------+--------------------------------------------------------+ | ``> < >= <= gt lt ge le`` | operators to compare values | +----------------------------+--------------------------------------------------------+ | ``== != eq ne`` | operators to check for equality | +----------------------------+--------------------------------------------------------+ | a ``?`` b | conditional operator: if a then return b else null | +----------------------------+--------------------------------------------------------+ | a ``?`` b ``:`` c | conditional operator: if a then return b else return c | +----------------------------+--------------------------------------------------------+ | a ``?:`` b | conditional operator: if a then return a else return b | +----------------------------+--------------------------------------------------------+ When using variables, accessing their content follows the same rules as in Javascript. Arrays have a member function ``length``. :: _predefined-objects: A few predefined utility objects are predefined. These are ``#dates``, ``#numbers``, ``#request`` and ``#security``. +------------------------------------------------------+------------------------------------------------------------------------------------------+ | object.method | Description | +======================================================+==========================================================================================+ | ``#dates.format(date,format)`` | This method takes two parameters, a preferrably ISO formatted date and a format string. | | | The result will be the output of `std::put_time`_. | +------------------------------------------------------+------------------------------------------------------------------------------------------+ |``#numbers.formatDecimal(number,int_digits,decimals)``| This method takes up to three parameters, a number that needs to be formatted, an | | | int_digits parameter that specifies the minimum number of integral digits to use and | | | a decimals parameter that specifies how many decimals to use. | | | | | | The number is formatted using the locale matching the language specified in the Accept | | | HTTP request header. However, if that locale is not available the default locale is used.| | | | | | Defaults for int_digits is 1 and decimals is 0. | | | | | | Example output: ``${#numbers.formatDecimal(pi,2,4)}`` is **03.1415** when the locale to | | | use is en_US. | +------------------------------------------------------+------------------------------------------------------------------------------------------+ | ``#numbers.formatDiskSize(number,decimals)`` | This method takes up to two parameters, a number that needs to be formatted, | | | and a decimals parameter that specifies how many decimals to use. | | | | | | The number is divided by 1024 until it fits three int digits and the suffix is | | | adjusted accordingly. | | | Default for decimals is 0. | | | | | | Example output: ``${#numbers.formatDiskSize(20480,2)}`` is **2.00K** when the locale to | | | use is en_US. | +------------------------------------------------------+------------------------------------------------------------------------------------------+ | ``#request.getRequestURI()`` | Returns the original URI in the HTTP request. | +------------------------------------------------------+------------------------------------------------------------------------------------------+ | ``#security.authorized()`` | Returns whether the uses has successfully logged in. | +------------------------------------------------------+------------------------------------------------------------------------------------------+ | ``#security.username()`` | Returns the username for the current user. | +------------------------------------------------------+------------------------------------------------------------------------------------------+ | ``#security.hasRole(role)`` | Returns whether the uses has the role as specified by the parameter. | +------------------------------------------------------+------------------------------------------------------------------------------------------+ .. _std::put_time: https://en.cppreference.com/w/cpp/io/manip/put_time tag_processor -------------------------------------- Tag processor version 2 is an implementation of the documentation for `Thymeleaf `_. This documententation is a bit sparse, for now you might be better off reading the one at the Thymeleaf site. There are some notable differences between Thymeleaf and libzeep though, libzeep does not support the concept of ``messages`` yet, so using this for localization is not going to work. Furthermore, the Thymeleaf library is written in Java and assumes all data constructs are Java object. Of course that is different in libzeep. tags ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There is only one tag this tag processor processes, which is ````, for the rest this processor only processes attributes. attributes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Some attributes are treated special, these are listed below. For the other tags the general rule is that if the tag has the prefix for the ``v2`` namespace, the value of the attribute will be evaluated and the result will be placed in an attribute without the prefix. Possibly overwriting an already existing attribute with that name. So, e.g. if you have .. code-block:: xml and the variable ``id`` contains ``'two'`` the result will be .. code-block:: xml special attributes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. table:: processed attributes +----------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ | attribute | remarks + +================+=================================================================================================================================================+ | ``assert`` | If the value of this attribute evaluates to `true`, an exception will be thrown. | +----------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ | ``attr`` | The value of this attribute is an expression consisting of one or more comma separated statements that assign a value to an attribute. e.g. | | | | | | .. code-block:: xml | | | | | | | | | | | | will result in the following when the value of the variables ``width`` and ``height`` is `100`. | | | | | | .. code-block:: xml | | | | | | | | | | | | | | | | | | | | | will result in | | | | | | .. code-block:: xml | | | | | | | | | 1 | | | aap | | | | | | | | | 2 | | | noot | | | | | | | | | 3 | | | mies | | | | | | | | | The iterator-info variable can be used to get info about the current value. | | | | | | .. table:: iterator members | | | | | | +-------------+----------------------------------------------------+ | | | | name | description | | | | +=============+====================================================+ | | | | ``count`` | counting number starting at one. | | | | +-------------+----------------------------------------------------+ | | | | ``index`` | counting number starting at zero. | | | | +-------------+----------------------------------------------------+ | | | | ``even`` | boolean indicating whether this is an even element | | | | +-------------+----------------------------------------------------+ | | | | ``odd`` | boolean indicating whether this is an odd element | | | | +-------------+----------------------------------------------------+ | | | | ``size`` | size of the total array/collection | | | | +-------------+----------------------------------------------------+ | | | | ``first`` | boolean indicating the first element | | | | +-------------+----------------------------------------------------+ | | | | ``last`` | boolean indicating the last element | | | | +-------------+----------------------------------------------------+ | | | | ``current`` | the value of the current element | | | | +-------------+----------------------------------------------------+ | +----------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ | ``if``, | The attribute value is evaluated and if the result is `true` respectively `false` the containing tag is preserved, otherwise it is deleted. | | ``unless`` | | +----------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ | ``include``, | These three statements are used to pull in fragments of markup. The value of the attribute is evaluated and should contain a fragment | | ``insert``, | specification. The contents of this fragment are then copied to the destination. The three attributes differ in the following way: | | ``replace`` | | | | .. table:: variable list | | | | | | +---------+-------------------------------------------------------------------------------+ | | | | insert | The complete fragment is inserted inside the body of the containing tag | | | | +---------+-------------------------------------------------------------------------------+ | | | | replace | The complete fragment is replaces the containing tag | | | | +---------+-------------------------------------------------------------------------------+ | | | | include | The content of the fragment is inserted inside the body of the containing tag | | | | +---------+-------------------------------------------------------------------------------+ | | | | | | Example, when the fragment is | | | | | | .. code-block:: xml | | | | | | hello | | | | | | the following markup: | | | | | | .. code-block:: xml | | | | | |
| | |
| | |
| | | | | | will result in: | | | | | | .. code-block:: xml | | | | | |
hello
| | | hello | | |
hello
| +----------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ | ``inline`` | The processor processes occurrences of \[\[ ... \]\] or \[\( ... \)\] by evaluating what is in between those brackets. | | | | | | .. code-block:: xml | | | | | |
Hello, [[${name ?: 'world'}]]
| | | | | | will result in (when name = 'scott'): | | | | | | .. code-block:: xml | | | | | |
Hello, scott
| | | | | | Using this attribute, you can do even more fancy things. If the value of this attribute is ``javascript``, the replacement will be valid in a | | | javascript context by properly quoting double quotes e.g. And it will process commented values properly, as in: | | | | | | .. code-block:: xml | | | | | | | | | | | | Might result in: | | | | | | .. code-block:: xml | | | | | | | | | | | | If the inline attribute has value ``text``, the whole body of the tag will be evaluated as ``el``. | +----------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ | ``switch``, | Example: | | ``case`` | | | | .. code-block:: xml | | | | | |
| | | Admin | | | Some other user | | |
| +----------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ | ``text``, | The simplest, replace the body of the tag with the result of the evaluation of the content of this attribute. | | ``utext`` | | | | .. code-block:: xml | | | | | | | | | | | | Will result in: | | | | | | .. code-block:: xml | | | | | | scott | | | | | | The ``text`` variant will quote special characters like <, > and &. The ``utext`` variant will not, but beware, if the result is not valid XML | | | an exception is thrown. | +----------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ | ``with`` | Assign a value to a variable. Example: | | | | | | .. code-block:: xml | | | | | |
| | | | | |
| +----------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ REST Controller -------------------------------------- The :cpp:class:`zeep::http::rest_controller` class is similar to the :cpp:class:`zeep::http::html_controller` in that it allows you to map a request to a member function. The difference however is that the REST controller translates parameters from HTTP requests into arguments for your method and translates the result of the method back into JSON to be returned to the client. Lets walk through an example again to show how this works. We begin our example by declaring some shopping cart objects. These are plain structs that also define a `serialize` method for use with serialization. .. literalinclude:: ../examples/rest-sample.cpp :start-after: //[ cart_items :end-before: //] :language: c++ Now we create a REST controller that will handle the creation of a new cart and adding and deleting items from this cart. We use standard CRUD REST syntax for this, so e.g. the cart ID is part of the path in the URI for adding and deleting items. .. literalinclude:: ../examples/rest-sample.cpp :start-after: //[ shop_rest_controller :end-before: //] :language: c++ The calls to this rest controller are in the ``scripts/shop.js`` file. Have a look at that file to see how it works. To give you an idea, this is the snippet that is called after clicking the _add_ link for an item. .. code-block:: javascript addToCart(item) { const fd = new FormData(); fd.append("name", item); fetch(`/cart/${this.cartID}/item`, { method: "POST", body: fd}) .then(r => r.json()) .then(order => this.updateOrder(order)) .catch(err => { console.log(err); alert(`Failed to add ${item} to cart`); }); } The page, script and stylesheet are served by a :cpp:class:`zeep::http::html_controller`. .. literalinclude:: ../examples/rest-sample.cpp :start-after: //[ shop_html_controller :end-before: //] :language: c++ And tie everything together. .. literalinclude:: ../examples/rest-sample.cpp :start-after: //[ shop_main :end-before: //] :language: c++ REST Controller (CRUD) -------------------------------------- The previous example is a rough example on how to use the :cpp:class:`zeep::http::rest_controller`, it assumes you pass in the parameters using form data or query parameters. There's another approach, that is more elegant and easier for the developer: create a `CRUD `_ interface and pass the data encoded in JSON format. To make this work, we rework the previous example. The data items ``Cart`` and ``Item`` remain the same, they already have a `serialize` method. The real difference is in the JavaScript code, in the previous example all work was done by the server, the JavaScript only called separate methods to fill the items list and the server responded with the new Cart content. In this example however, management of the cart content is done by the JavaScript and the updated cart is then sent using a ``PUT`` to the server. The server therefore looks like this: .. literalinclude:: ../examples/rest-sample-2.cpp :language: c++ :start-after: //[ shop_rest_controller_2 :end-before: //] Some ceveats: this works probably only well if you have a single ``JSON`` (or compatible) data type as main parameter and optionally one or more path parameters. The request should also have ``content-type`` equal to ``application/json`` to work properly. SOAP Controller -------------------------------------- Creating SOAP controllers is also easy. But that will have to wait a bit. Error handling -------------------------------------- During the processing of a request, an error may occur, often by throwing an std::exception. The default :cpp:class:`zeep::http::error_handler` class takes care of catching exceptions and ``docroot``, or a built in template if that file does not exist. You can derive your own error handler from :cpp:class:`zeep::http::error_handler` and implement a ``create_error_reply`` member to handle some errors differently. The error handlers will be called in the reverse order of being added allowing you to override default behaviour. Security -------------------------------------- `:cpp:class:`zeep::http::security_context``. The :cpp:class:`zeep::http::security_context` itself uses a :cpp:class:`zeep::http::user_service` to provide __user_details__ structs containing the actual data for a user. The __user_details__ structure contains the ``username``, encrypted ``password`` and a list of ``roles`` this user can play. The roles are simple text strings and should preferrably be very short, like ``'USER'`` or ``'ADMIN'``. The :cpp:class:`zeep::http::user_service` class returns __user_details__ based on a ``username`` via the pure virtual method :cpp:func:`zeep::http::user_service::load_user`. A :cpp:class:`zeep::http::simple_user_service` class is available to create very simple user services based on static data. In a real world application you should implement your own user_service and store user information in e.g. a database. example ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Let us walk through an example of an application using security. This web application will have two pages, a landing page at the URI =/= (but also at ``/index`` and ``/index.html``) and an admin page at ``/admin``. The latter of course will only be accessible by our admin who is called _scott_ and he uses the password _tiger_. [note The code for all example code used in this documentation can be found in the doc/examples subdirectory of the libzeep distribution. ] First start by writing some template code. For this example we will have a common menu template and two templates for the two pages respectively. The interesting part of the menu template is this: .. code-block:: xml We're using `W3.CSS `_ as CSS library, albeit we have stored a copy in our own docroot. The two last links in this navigation bar have the ``z:if``"..."= argument checking whether the current user is authorized. These attributes help in select which of the two will be visible, login or logout, based on the current authentication state. The ``#security`` class in our ``el`` library has two more methods called ``username`` and ``hasRole``. The last one returns true when a user has the role asked for. Next we define a simple :cpp:class:`zeep::http::html_controller` that handles the two pages and also serves stylesheets and scripts. .. literalinclude:: ../examples/security-sample.cpp :language: c++ :start-after: //[ sample_security_controller :end-before: //] Nothing fancy here, just a simple controller returning pages based on templates. In the template we add a salutation: .. code-block:: xml

Hello, !

And here we see the call to ``#security.username()``. Note also the use of the elvis operator, if username is not set, 'world' is used instead. Now, in the `main` of our application we first create a :cpp:class:`zeep::http::user_service`. .. literalinclude:: ../examples/security-sample.cpp :language: c++ :start-after: //[ create_user_service :end-before: //] We use the :cpp:class:`zeep::http::simple_user_service` class and provide a static list of users. The :cpp:class:`zeep::http::user_service` should return __user_details__ with an encrypted password and therefore we encrypt the plain text password here. Normally you would store this password encrypted of course. For encrypting password we use the :cpp:class:`zeep::http::pbkdf2_sha256_password_encoder` class. You can add other password encoders based on other algorithms like bcrypt but you then have to register these yourself using :cpp:func:`zeep::http::security_context::register_password_encoder`; ownership. .. literalinclude:: ../examples/security-sample.cpp :language: c++ :start-after: //[ create_security_context :end-before: //] The secret passed to the security_context is used in creating signatures for the `JWT token `_. If you store this secret, the sessions of your users will persist reboots of the server. But in this case we create a new secret after each launch and thus the tokens will expire. Now we add access rules. .. literalinclude:: ../examples/security-sample.cpp :language: c++ :start-after: //[ add_access_rules :end-before: //] A rule specifies for a glob pattern which users can access it based on the roles these users have. If the list of roles is empty, this means all users should be able to access this URI. When a request is received, the rules are checked in order of which they were added. The first match will be used to check the roles. In this example ``/admin`` is only accessible by users having role *ADMIN*. All other URI's are allowed by everyone. Note that we could have also created the :cpp:class:`zeep::http::security_context` with the parameter defaultAccessAllowed as `true`, we then would not have needed that last rule. .. literalinclude:: ../examples/security-sample.cpp :language: c++ :start-after: //[ start_server :end-before: //] Note that we add the default :cpp:class:`zeep::http::login_controller`. This controller takes care of handling ``/login`` and ``/logout`` requests. It will also add the required rule for ``/login`` to the :cpp:class:`zeep::http::security_context` using `add_rule("/login", {});` since otherwise the login page would not be reachable. Make sure you do not add your own rules that prevent access to this page. And that's all. You can now start this server and see that you can access ``/`` and ``/login`` without any problem but ``/admin`` will give you an authentication error. When you login using the credentials ``scott/tiger`` you can access the ``/admin`` page and you can now also click the Logout button. CSRF protection -------------------------------------- The :cpp:class:`zeep::http::security_context` class contains some rudimentary support for protecting against `CSRF attacks `_. The way it works is that the server class add a special `csrf-token` cookie to a session. This cookie is stored in the browser with the flags `SameSite=Lax` and `HttpOnly` which makes it unavailable to malicious scripts that might have been injected in your pages. If a value has been set to this cookie and the :cpp:class:`zeep::http::security_context` class has the `set_validate_csrf` flag set, each `POST` or `SUBMIT` will be checked if there is a `_csrf` parameter and this should contain the same value as the `csrf-token` cookie. So, to use this functionality, call the :cpp:func:`zeep::http::security_context::set_validate_csrf` method on a newly created :cpp:class:`zeep::http::security_context` instance. Next you should make sure each form or `POST` call should contain a `_csrf` parameter with the value stored in the session cookie `csrf-token`. This value can be obtained by calling :cpp:func:`zeep::http::scope::get_csrf_token`. Cryptographic routines -------------------------------------- A limited number of cryptographic routines are available in :ref:`file_zeep_crypto.hpp`. These can be divided in the following categories: Encoding ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The functions encode and decode functions take a std::string and return the encoded or decoded content. There are three encoding schemes, `hex`, `base64` and `base64url`. The latter is simply `base64` but with a different characterset and without the trailing `=` characters allowing their use in a URL. Hashing ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There are three hash algorithm implementations. These are `md5`, `sha1` and `sha256`. All of them take a std::string and return a std::string with the resulting hash. Note that these strings are not human readable and may contain null characters. Therefore you need the encoding routines to convert a hash into something you can print to the screen e.g. HMac ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Hashed message authentication codes can be calculated using the available hash functions. Again, these functions take std::string parameters for the message and the key. The result is again a binary std::string. Key derivation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Two key derivation routines are on offer, both of them PBKDF2, one using HMAC SHA1 and the other HMAC SHA256. libzeep-7.3.2/docs/requirements.in0000664000175000017500000000010315150027072017013 0ustar maartenmaartensphinx<5 exhale==0.3.6 myst-parser breathe sphinx_rtd_theme==1.3.0 libzeep-7.3.2/docs/requirements.txt0000664000175000017500000000346615150027072017243 0ustar maartenmaarten# # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile --output-file=requirements.txt requirements.in # alabaster==0.7.13 # via sphinx babel==2.12.1 # via sphinx beautifulsoup4==4.12.2 # via exhale breathe==4.35.0 # via # -r requirements.in # exhale certifi==2024.7.4 # via requests charset-normalizer==3.2.0 # via requests docutils==0.17.1 # via # breathe # exhale # myst-parser # sphinx # sphinx-rtd-theme exhale==0.3.6 # via -r requirements.in idna==3.7 # via requests imagesize==1.4.1 # via sphinx jinja2==3.1.6 # via # myst-parser # sphinx lxml==4.9.3 # via exhale markdown-it-py==2.2.0 # via # mdit-py-plugins # myst-parser markupsafe==2.1.3 # via jinja2 mdit-py-plugins==0.3.5 # via myst-parser mdurl==0.1.2 # via markdown-it-py myst-parser==0.18.1 # via -r requirements.in packaging==23.1 # via sphinx pygments==2.16.1 # via sphinx pyyaml==6.0.1 # via myst-parser requests==2.32.4 # via sphinx six==1.16.0 # via exhale snowballstemmer==2.2.0 # via sphinx soupsieve==2.4.1 # via beautifulsoup4 sphinx==4.5.0 # via # -r requirements.in # breathe # exhale # myst-parser # sphinx-rtd-theme # sphinxcontrib-jquery sphinx-rtd-theme==1.3.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx sphinxcontrib-htmlhelp==2.0.1 # via sphinx sphinxcontrib-jquery==4.1 # via sphinx-rtd-theme sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx typing-extensions==4.7.1 # via myst-parser urllib3==2.6.3 # via requests libzeep-7.3.2/examples/0000775000175000017500000000000015150027072014634 5ustar maartenmaartenlibzeep-7.3.2/examples/CMakeLists.txt0000664000175000017500000000203615150027072017375 0ustar maartenmaarten# Copyright Maarten L. Hekkelman, 2022-2026 # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) cmake_minimum_required(VERSION 3.16) project(libzeep-examples LANGUAGES CXX) set(CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Only try to find package if we're not included from the main libzeep CMakeLists.txt if(PROJECT_IS_TOP_LEVEL) find_package(zeep REQUIRED) endif() if(UNIX) find_file(HAVE_UNISTD_H "unistd.h") if(HAVE_UNISTD_H) set(HTTP_HAS_UNIX_DAEMON 1) endif() endif() list(APPEND examples http-server-0 http-server-1 http-server-2 http-server-3 security-sample rest-sample rest-sample-2 # soap-sample ) if(HTTP_HAS_UNIX_DAEMON) list(APPEND examples daemon-sample) endif() foreach(EXAMPLE IN LISTS examples) add_executable(${EXAMPLE} ${CMAKE_CURRENT_SOURCE_DIR}/${EXAMPLE}.cpp) target_link_libraries(${EXAMPLE} zeep::zeep) endforeach() libzeep-7.3.2/examples/daemon-sample.cpp0000664000175000017500000000550015150027072020062 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2022-2025 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include class hello_controller : public zeep::http::html_controller { public: /* Specify the root path as prefix, will handle any request URI */ hello_controller() { /* Mount the handler `handle_index` on `/`, `/index` and `/index.html` */ map_get("{,index,index.html}", &hello_controller::handle_index, "name"); /* Add REST call */ map_get_request("restcall", &hello_controller::handle_rest_call); } zeep::http::reply handle_index(const zeep::http::scope &scope, std::optional name) { zeep::http::scope sub(scope); if (name.has_value()) sub.put("name", *name); return get_template_processor().create_reply_from_template("hello.xhtml", sub); } std::string handle_rest_call() { return "Hello, world!"; } }; int main(int argc, char *const argv[]) { using namespace std::literals; if (argc != 2) { std::cout << "No command specified, use of of start, stop, status or reload\n"; exit(1); } // -------------------------------------------------------------------- std::string command = argv[1]; // Avoid the need for super powers std::filesystem::path docRoot = std::filesystem::current_path() / "docroot", pidFile = std::filesystem::temp_directory_path() / "zeep-daemon-test.pid", logFile = std::filesystem::temp_directory_path() / "zeep-daemon-test.log", errFile = std::filesystem::temp_directory_path() / "zeep-daemon-test.err"; if (not std::filesystem::exists(docRoot)) { std::cout << "docroot directory not found in current directory, cannot continue\n"; exit(1); } zeep::http::daemon server([&]() { auto s = new zeep::http::server(docRoot.string()); // Force using the file based template processor s->set_template_processor(new zeep::http::file_based_html_template_processor(docRoot.string())); s->add_controller(new hello_controller()); return s; }, pidFile.string(), logFile.string(), errFile.string()); int result; if (command == "start") { std::string address = "localhost"; unsigned short port = 10330; std::string user /* = "www-data" */; // Using a non-empty username requires super user powers std::cout << "starting server at http://" << address << ':' << port << "\n"; result = server.start(address, port, 1, 2, user); } else if (command == "stop") result = server.stop(); else if (command == "status") result = server.status(); else if (command == "reload") result = server.reload(); else { std::clog << "Invalid command\n"; result = 1; } return result; } libzeep-7.3.2/examples/docroot/0000775000175000017500000000000015150027072016305 5ustar maartenmaartenlibzeep-7.3.2/examples/docroot/css/0000775000175000017500000000000015150027072017075 5ustar maartenmaartenlibzeep-7.3.2/examples/docroot/css/w3.css0000664000175000017500000005550115150027072020146 0ustar maartenmaarten/* W3.CSS 4.13 June 2019 by Jan Egil and Borge Refsnes */ html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item} audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline} audio:not([controls]){display:none;height:0}[hidden],template{display:none} a{background-color:transparent}a:active,a:hover{outline-width:0} abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted} b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000} small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none} code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible} button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold} button,input{overflow:visible}button,select{text-transform:none} button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button} button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0} button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText} fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em} legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto} [type=checkbox],[type=radio]{padding:0} [type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto} [type=search]{-webkit-appearance:textfield;outline-offset:-2px} [type=search]::-webkit-search-decoration{-webkit-appearance:none} ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit} /* End extract */ html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden} h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}.w3-serif{font-family:serif} h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px} hr{border:0;border-top:1px solid #eee;margin:20px 0} .w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit} .w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc} .w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1} .w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1} .w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center} .w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top} .w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px} .w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap} .w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)} .w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} .w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none} .w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none} .w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%} .w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none} .w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block} .w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s} .w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%} .w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc} .w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer} .w3-dropdown-hover:hover .w3-dropdown-content{display:block} .w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000} .w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000} .w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1} .w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px} .w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto} .w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%} .w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%} .w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px} .w3-main,#main{transition:margin-left .4s} .w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)} .w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px} .w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto} .w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0} .w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left} .w3-bar .w3-button{white-space:normal} .w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0} .w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%} .w3-responsive{display:block;overflow-x:auto} .w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before, .w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both} .w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%} .w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%} .w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%} .w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%} @media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%} .w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%} .w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}} @media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%} .w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%} .w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}} .w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px} .w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px} .w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell} .w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom} .w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important} @media (max-width:1205px){.w3-auto{max-width:95%}} @media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px} .w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative} .w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center} .w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}} @media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}} @media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}} @media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}} @media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}} .w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0} .w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2} .w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0} .w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0} .w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)} .w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)} .w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)} .w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} .w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} .w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none} .w3-display-position{position:absolute} .w3-circle{border-radius:50%} .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} .w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)} .w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)} .w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}} .w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} .w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}} .w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}} .w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}} .w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}} .w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}} .w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}} .w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important} .w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1} .w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75} .w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)} .w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)} .w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)} .w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important} .w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important} .w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important} .w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important} .w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important} .w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important} .w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important} .w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important} .w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important} .w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important} .w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important} .w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important} .w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important} .w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important} .w3-padding-64{padding-top:64px!important;padding-bottom:64px!important} .w3-left{float:left!important}.w3-right{float:right!important} .w3-button:hover{color:#000!important;background-color:#ccc!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-hover-none:hover{box-shadow:none!important} /* Colors */ .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} .w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important} .w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important} .w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important} .w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important} .w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important} .w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important} .w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important} .w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important} .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important} .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} .w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important} .w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important} .w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important} .w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important} .w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important} .w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important} .w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important} .w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important} .w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important} .w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important} .w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important} .w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important} .w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important} .w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important} .w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important} .w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important} .w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important} .w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important} .w3-text-red,.w3-hover-text-red:hover{color:#f44336!important} .w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important} .w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important} .w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important} .w3-text-white,.w3-hover-text-white:hover{color:#fff!important} .w3-text-black,.w3-hover-text-black:hover{color:#000!important} .w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important} .w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important} .w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important} .w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important} .w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important} .w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important} .w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important} .w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important} .w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important} .w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important} .w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important} .w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important} .w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important} .w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important} .w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important} .w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important} .w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important} .w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important} .w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important} .w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important} .w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important} .w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important} .w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important} .w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important} .w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important} .w3-border-black,.w3-hover-border-black:hover{border-color:#000!important} .w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important} .w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important} .w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important} .w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important} .w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}libzeep-7.3.2/examples/docroot/hello.xhtml0000664000175000017500000000036215150027072020467 0ustar maartenmaarten Hello

Hello, !

libzeep-7.3.2/examples/docroot/scripts/0000775000175000017500000000000015150027072017774 5ustar maartenmaartenlibzeep-7.3.2/examples/docroot/scripts/security.js0000664000175000017500000000000015150027072022167 0ustar maartenmaartenlibzeep-7.3.2/examples/docroot/scripts/shop-2.js0000664000175000017500000000576415150027072021456 0ustar maartenmaarten/* simple shopping cart example */ class ShoppingCart { constructor(cart) { this.cartContent = cart; this.cartContent.items = []; const accountForm = document.getElementById('account-form'); accountForm.style.display = 'none'; const shoppingPage = document.getElementById('shopping-page'); shoppingPage.style.display = 'unset'; document.getElementById('user-name').textContent = name; [...document.getElementsByClassName('shopping-item')] .forEach(item => { item.addEventListener('click', (evt) => { evt.preventDefault(); this.addToCart(item.dataset.item); }) }); } addToCart(item) { const ix = this.cartContent.items.findIndex((i) => i.name === item); if (ix < 0) this.cartContent.items.push({ name: item, count: 1 }); else this.cartContent.items[ix].count += 1; this.updateOrder(); } deleteCartItem(item) { const ix = this.cartContent.items.findIndex((i) => i.name === item); if (ix < 0) this.cartContent.items.push({ name: item, count: 1 }); else if (this.cartContent.items[ix].count == 1) this.cartContent.items.splice(ix, 1); else this.cartContent.items[ix].count -= 1; this.updateOrder(); } updateOrder() { fetch(`/cart/${this.cartContent.cartID}`, { method: "PUT", headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(this.cartContent) }) .then(r => { if (! r.ok) throw 'error'; const cartListContainer = document.getElementById('cart-list-container'); if (this.cartContent.items.length == 0) cartListContainer.style.display = 'none'; else { cartListContainer.style.display = 'initial'; const cartList = document.getElementById('cart-list'); [...cartList.querySelectorAll('li:not(:first-child)')] .forEach(li => li.remove()); const li = cartListContainer.querySelector('li'); this.cartContent.items.forEach(item => { const lic = li.cloneNode(true); lic.querySelector('span.text-placeholder').textContent = `${item.name} (${item.count})`; const removeBtn = lic.querySelector('span.cart-item'); removeBtn.addEventListener('click', (li) => this.deleteCartItem(item.name)); cartList.append(lic); }); } }) .catch(err => { console.log(err); alert('Failed to add item to cart'); }); } }; window.addEventListener('load', () => { const createCartBtn = document.getElementById('create-cart-btn'); createCartBtn.addEventListener('click', (evt) => { evt.preventDefault(); const client = document.forms['client-form']['client-name'].value; if (client == "") alert("Please enter a user name"); else { const newCart = { client: client }; fetch('/cart', { method: "POST", headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newCart) }) .then(r => r.json()) .then(cartID => { newCart.cartID = cartID; new ShoppingCart(newCart); }) .catch(err => { console.log(err); alert("failed to create shopping cart"); }) } }); });libzeep-7.3.2/examples/docroot/scripts/shop.js0000664000175000017500000000503015150027072021301 0ustar maartenmaarten/* simple shopping cart example */ class ShoppingCart { constructor(name, id) { this.name = name; this.cartID = id; const accountForm = document.getElementById('account-form'); accountForm.style.display = 'none'; const shoppingPage = document.getElementById('shopping-page'); shoppingPage.style.display = 'unset'; document.getElementById('user-name').textContent = name; [...document.getElementsByClassName('shopping-item')] .forEach(item => { item.addEventListener('click', (evt) => { evt.preventDefault(); this.addToCart(item.dataset.item); }) }); } addToCart(item) { const fd = new FormData(); fd.append("name", item); fetch(`/cart/${this.cartID}/item`, { method: "POST", body: fd}) .then(r => r.json()) .then(order => this.updateOrder(order)) .catch(err => { console.log(err); alert(`Failed to add ${item} to cart`); }); } deleteCartItem(item) { const fd = new FormData(); fd.append("name", item); fetch(`/cart/${this.cartID}/item`, { method: "DELETE", body: fd}) .then(r => r.json()) .then(order => this.updateOrder(order)) .catch(err => { console.log(err); alert(`Failed to remove ${item} from cart`); }); } updateOrder(order) { const cartListContainer = document.getElementById('cart-list-container'); if (order.items.length == 0) cartListContainer.style.display = 'none'; else { cartListContainer.style.display = 'initial'; const cartList = document.getElementById('cart-list'); [...cartList.querySelectorAll('li:not(:first-child)')] .forEach(li => li.remove()); const li = cartListContainer.querySelector('li'); order.items.forEach(item => { const lic = li.cloneNode(true); lic.querySelector('span.text-placeholder').textContent = `${item.name} (${item.count})`; const removeBtn = lic.querySelector('span.cart-item'); removeBtn.addEventListener('click', (li) => this.deleteCartItem(item.name)); cartList.append(lic); }); } console.log(order); } }; window.addEventListener('load', () => { const createCartBtn = document.getElementById('create-cart-btn'); createCartBtn.addEventListener('click', (evt) => { evt.preventDefault(); const client = document.forms['client-form']['client-name'].value; if (client == "") alert("Please enter a user name"); else { fetch('/cart', { method: "POST" }) .then(r => r.json()) .then(cartID => new ShoppingCart(client, cartID)) .catch(err => { console.log(err); alert("failed to create shopping cart"); }) } }); });libzeep-7.3.2/examples/docroot/security-admin.xhtml0000664000175000017500000000074315150027072022324 0ustar maartenmaarten Admin page

Hello, , you're now on the Admin page

libzeep-7.3.2/examples/docroot/security-hello.xhtml0000664000175000017500000000071015150027072022331 0ustar maartenmaarten Landing page

Hello, !

libzeep-7.3.2/examples/docroot/security-menu.xhtml0000664000175000017500000000137615150027072022203 0ustar maartenmaarten Landing page libzeep-7.3.2/examples/docroot/shop-2.xhtml0000664000175000017500000000435015150027072020475 0ustar maartenmaarten ]> Hello

Account info

libzeep-7.3.2/examples/docroot/shop.xhtml0000664000175000017500000000434615150027072020343 0ustar maartenmaarten ]> Hello

Account info

libzeep-7.3.2/examples/http-server-0.cpp0000664000175000017500000000100115150027072017750 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2022-2025 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) //[ most_simple_http_server_start #include #include #include int main() { try { zeep::http::server srv; srv.bind("::", 8080); srv.run(2); } catch (const std::exception &ex) { std::cerr << ex.what() << "\n"; } return 0; } //]libzeep-7.3.2/examples/http-server-1.cpp0000664000175000017500000000214315150027072017761 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2022-2025 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) //[ simple_http_server #include #include #include #include #include #include class hello_controller : public zeep::http::controller { public: /* Specify the root path as prefix, will handle any request URI */ hello_controller() : controller("/") { } bool handle_request([[maybe_unused]] zeep::http::request &req, zeep::http::reply &rep) override { /* Construct a simple reply with status OK (200) and content string */ rep = zeep::http::reply::stock_reply(zeep::http::status_type::ok); rep.set_content("Hello", "text/plain"); return true; } }; int main() { try { zeep::http::server srv; srv.add_controller(new hello_controller()); srv.bind("::", 8080); srv.run(2); } catch (const std::exception &ex) { std::cerr << ex.what() << '\n'; } return 0; } //]libzeep-7.3.2/examples/http-server-2.cpp0000664000175000017500000000274315150027072017770 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2022-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // In this example we don't want to use rsrc based templates #undef WEBAPP_USES_RESOURCES #define WEBAPP_USES_RESOURCES 0 //[ simple_http_server_2 #include #include #include #include #include #include #include #include #include class hello_controller : public zeep::http::html_controller { public: hello_controller() { /* Mount the handler `handle_index` on `/`, `/index` and `/index.html` */ map_get("{,index,index.html}", &hello_controller::handle_index, "name"); } zeep::http::reply handle_index(const zeep::http::scope &scope, std::optional name) { zeep::http::scope sub(scope); if (name.has_value()) sub.put("name", *name); return get_template_processor().create_reply_from_template("hello.xhtml", sub); } }; int main() { try { /* Use the server constructor that takes the path to a docroot so it will construct a template processor */ zeep::http::server srv("docroot"); srv.add_controller(new hello_controller()); srv.bind("::", 8080); srv.run(2); } catch (const std::exception &ex) { std::cerr << ex.what() << '\n'; } return 0; } //]libzeep-7.3.2/examples/http-server-3.cpp0000664000175000017500000000337615150027072017774 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2025-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // In this example we don't want to use rsrc based templates #undef WEBAPP_USES_RESOURCES #define WEBAPP_USES_RESOURCES 0 //[ simple_http_server_3 #include #include #include #include #include #include #include #include #include #include class hello_controller : public zeep::http::html_controller { public: hello_controller() { /* Mount the handler `handle_index`, this is on `/` (i.e. the root) */ map_get("", &hello_controller::handle_index, "name"); /* Mount the handler on =/index.html= as well */ map_get("index.html", &hello_controller::handle_index, "name"); /* And mount the handler on a path containing the 'name' */ map_get("hello/{name}", &hello_controller::handle_index, "name"); } zeep::http::reply handle_index(const zeep::http::scope &scope, const std::optional &user) { zeep::http::scope sub(scope); sub.put("name", user.value_or("world")); return get_template_processor().create_reply_from_template("hello.xhtml", sub); } }; int main() { try { /* Use the server constructor that takes the path to a docroot so it will construct a template processor */ zeep::http::server srv(std::filesystem::canonical("docroot").string()); srv.add_controller(new hello_controller()); srv.bind("::", 8080); srv.run(2); } catch (const std::exception &ex) { std::cerr << ex.what() << '\n'; } return 0; } //]libzeep-7.3.2/examples/rest-sample-2.cpp0000664000175000017500000000673315150027072017744 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2022-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // In this example we don't want to use rsrc based templates #undef WEBAPP_USES_RESOURCES #define WEBAPP_USES_RESOURCES 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include //[ cart_items_2 struct Item { std::string name; uint32_t count; template void serialize(Archive &ar, uint64_t /*version*/) { // clang-format off ar & zeem::name_value_pair("name", name) & zeem::name_value_pair("count", count); // clang-format on } }; struct Cart { int id; std::string client; std::vector items; template void serialize(Archive &ar, uint64_t /*version*/) { // clang-format off ar & zeem::name_value_pair("id", id) & zeem::name_value_pair("client", client) & zeem::name_value_pair("items", items); // clang-format on } }; //] //[ shop_rest_controller_2 class shop_rest_controller : public zeep::http::controller { public: shop_rest_controller() : zeep::http::controller("/cart") { // CRUD example interface map_post_request("", &shop_rest_controller::create_cart, "cart"); map_get_request("{id}", &shop_rest_controller::retrieve_cart, "id"); map_put_request("{id}", &shop_rest_controller::update_cart, "id", "cart"); map_delete_request("{id}", &shop_rest_controller::delete_cart, "id"); } int create_cart(Cart cart) { int result = cart.id = sNextCartID++; m_carts.push_back(std::move(cart)); return result; } Cart &retrieve_cart(int cartID) { auto oi = std::ranges::find_if(m_carts, [&](auto &o) { return o.id == cartID; }); if (oi == m_carts.end()) throw std::invalid_argument("No such cart"); return *oi; } void update_cart(int cartID, const Cart &cart) { auto oi = std::ranges::find_if(m_carts, [&](auto &o) { return o.id == cartID; }); if (oi == m_carts.end()) throw std::invalid_argument("No such cart"); oi->client = cart.client; oi->items = cart.items; } void delete_cart(int cartID) { std::erase_if(m_carts, [cartID](auto &cart) { return cart.id == cartID; }); } private: static int sNextCartID; std::vector m_carts; }; //] int shop_rest_controller::sNextCartID = 1; //[ shop_html_controller_2 class shop_html_controller : public zeep::http::html_controller { public: shop_html_controller() { map_get("", &shop_html_controller::handle_index); map_get_file("{css,scripts}/"); } zeep::http::reply handle_index(const zeep::http::scope &scope) { return get_template_processor().create_reply_from_template("shop-2.xhtml", scope); } }; //] //[ shop_main_2 int main() { try { /* Use the server constructor that takes the path to a docroot so it will construct a template processor */ zeep::http::server srv("docroot"); srv.add_controller(new shop_html_controller()); srv.add_controller(new shop_rest_controller()); srv.bind("::", 8080); // Note that the rest controller above is not thread safe! srv.run(1); } catch (const std::exception &ex) { std::cerr << ex.what() << '\n'; } return 0; } //] libzeep-7.3.2/examples/rest-sample.cpp0000664000175000017500000000722315150027072017600 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2022-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // In this example we don't want to use rsrc based templates #undef WEBAPP_USES_RESOURCES #define WEBAPP_USES_RESOURCES 0 #include #include #include #include #include #include #include #include #include #include #include #include //[ cart_items struct Item { std::string name; uint32_t count; template void serialize(Archive &ar, uint64_t /*version*/) { // clang-format off ar & zeem::name_value_pair("name", name) & zeem::name_value_pair("count", count); // clang-format on } }; struct Cart { int id; std::string client; std::vector items; template void serialize(Archive &ar, uint64_t /*version*/) { // clang-format off ar & zeem::name_value_pair("id", id) & zeem::name_value_pair("client", client) & zeem::name_value_pair("items", items); // clang-format on } }; //] //[ shop_rest_controller class shop_rest_controller : public zeep::http::controller { public: shop_rest_controller() : zeep::http::controller("/cart") { map_post_request("", &shop_rest_controller::create_cart, "client"); map_get_request("{id}", &shop_rest_controller::get_cart, "id"); map_post_request("{id}/item", &shop_rest_controller::add_cart_item, "id", "name"); map_delete_request("{id}/item", &shop_rest_controller::delete_cart_item, "id", "name"); } int create_cart(const std::string &client) { int cartID = sNextCartID++; m_carts.push_back({ .id = cartID, .client = client, .items = {} }); return cartID; } Cart &get_cart(int cartID) { auto oi = std::ranges::find_if(m_carts, [&](auto &o) { return o.id == cartID; }); if (oi == m_carts.end()) throw std::invalid_argument("No such cart"); return *oi; } Cart add_cart_item(int cartID, const std::string &item) { Cart &cart = get_cart(cartID); auto ii = std::ranges::find_if(cart.items, [&](auto &i) { return i.name == item; }); if (ii == cart.items.end()) cart.items.push_back({ item, 1 }); else ii->count += 1; return cart; } Cart delete_cart_item(int cartID, const std::string &item) { Cart &cart = get_cart(cartID); auto ii = std::ranges::find_if(cart.items, [&](auto &i) { return i.name == item; }); if (ii != cart.items.end()) { if (--ii->count == 0) cart.items.erase(ii); } return cart; } private: static int sNextCartID; std::vector m_carts; }; //] int shop_rest_controller::sNextCartID = 1; //[ shop_html_controller class shop_html_controller : public zeep::http::html_controller { public: shop_html_controller() { map_get("", &shop_html_controller::handle_index); map_get_file("{css,scripts}/"); } zeep::http::reply handle_index(const zeep::http::scope &scope) { return get_template_processor().create_reply_from_template("shop.xhtml", scope); } }; //] //[ shop_main int main() { try { /* Use the server constructor that takes the path to a docroot so it will construct a template processor */ zeep::http::server srv("docroot"); srv.add_controller(new shop_html_controller()); srv.add_controller(new shop_rest_controller()); srv.bind("::", 8080); // Note that the rest controller above is not thread safe! srv.run(1); } catch (const std::exception &ex) { std::cerr << ex.what() << '\n'; } return 0; } //] libzeep-7.3.2/examples/security-sample.cpp0000664000175000017500000000465015150027072020473 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2022-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // In this example we don't want to use rsrc based templates #include #undef WEBAPP_USES_RESOURCES #define WEBAPP_USES_RESOURCES 0 #include "zeep/crypto.hpp" #include "zeep/http/reply.hpp" #include #include #include #include #include #include #include #include #include //[ sample_security_controller class hello_controller : public zeep::http::html_controller { public: hello_controller() { // Mount the handler `handle_index` on /, /index and /index.html map_get("{,index,index.html}", &hello_controller::handle_index); // This admin page will only be accessible by authorized users map_get("admin", &hello_controller::handle_admin); // scripts & css map_get_file("{css,scripts}/"); } zeep::http::reply handle_index(const zeep::http::scope &scope) { return get_template_processor().create_reply_from_template("security-hello.xhtml", scope); } zeep::http::reply handle_admin(const zeep::http::scope &scope) { return get_template_processor().create_reply_from_template("security-admin.xhtml", scope); } }; //] int main() { try { //[ create_user_service // Create a user service with a single user zeep::http::simple_user_service users({ { "scott", zeep::http::pbkdf2_sha256_password_encoder().encode("tiger"), { "USER", "ADMIN" } } }); //] //[ create_security_context // Create a security context with a secret and users std::string secret = zeep::random_hash(); auto sc = std::make_unique(secret, users, false); //] //[ add_access_rules // Add the rule, sc->add_rule("/admin", "ADMIN"); sc->add_rule("/", {}); //] //[ start_server /* Use the server constructor that takes the path to a docroot so it will construct a template processor */ zeep::http::server srv(sc.release(), "docroot"); srv.add_controller(new hello_controller()); srv.add_controller(new zeep::http::login_controller()); srv.bind("::", 8080); srv.run(2); //] } catch (const std::exception &ex) { std::cerr << ex.what() << '\n'; } return 0; }libzeep-7.3.2/examples/soap-sample.cpp0000664000175000017500000000625715150027072017573 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2022-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include #include //[ cart_items struct Item { std::string name; uint32_t count; template void serialize(Archive &ar, unsigned long version) { // clang-format off ar & zeep::name_value_pair("name", name) & zeep::name_value_pair("count", count); // clang-format on } }; struct Cart { int id; std::string client; std::vector items{}; template void serialize(Archive &ar, unsigned long version) { // clang-format off ar & zeep::name_value_pair("id", id) & zeep::name_value_pair("client", client) & zeep::name_value_pair("items", items); // clang-format on } }; //] //[ shop_soap_controller class shop_soap_controller : public zeep::http::soap_controller { public: shop_soap_controller() : zeep::http::soap_controller("/ws", "cart", "https://www.hekkelman.com/libzeep/soap-sample") { map_action("create", &shop_soap_controller::create_cart, "client"); map_action("retrieve", &shop_soap_controller::get_cart, "id"); map_action("update", &shop_soap_controller::add_cart_item, "id", "name"); map_action("delete", &shop_soap_controller::delete_cart_item, "id", "name"); } int create_cart(const std::string &client) { int cartID = sNextCartID++; m_carts.push_back({ cartID, client }); return cartID; } Cart get_cart(int cartID) { auto oi = std::find_if(m_carts.begin(), m_carts.end(), [&](auto &o) { return o.id == cartID; }); if (oi == m_carts.end()) throw std::invalid_argument("No such cart"); return *oi; } Cart add_cart_item(int cartID, const std::string &item) { auto oi = std::find_if(m_carts.begin(), m_carts.end(), [&](auto &o) { return o.id == cartID; }); if (oi == m_carts.end()) throw std::invalid_argument("No such cart"); Cart &cart = *oi; auto ii = std::find_if(cart.items.begin(), cart.items.end(), [&](auto &i) { return i.name == item; }); if (ii == cart.items.end()) cart.items.push_back({ item, 1 }); else ii->count += 1; return cart; } Cart delete_cart_item(int cartID, const std::string &item) { auto oi = std::find_if(m_carts.begin(), m_carts.end(), [&](auto &o) { return o.id == cartID; }); if (oi == m_carts.end()) throw std::invalid_argument("No such cart"); Cart &cart = *oi; auto ii = std::find_if(cart.items.begin(), cart.items.end(), [&](auto &i) { return i.name == item; }); if (ii != cart.items.end()) { if (--ii->count == 0) cart.items.erase(ii); } return cart; } private: static int sNextCartID; std::vector m_carts; }; //] int shop_soap_controller::sNextCartID = 1; //[ shop_main int main() { /* Use the server constructor that takes the path to a docroot so it will construct a template processor */ zeep::http::server srv("docroot"); srv.add_controller(new shop_soap_controller()); srv.bind("::", 8080); // Note that the rest controller above is not thread safe! srv.run(1); return 0; } //] libzeep-7.3.2/include/0000775000175000017500000000000015150027072014441 5ustar maartenmaartenlibzeep-7.3.2/include/zeep/0000775000175000017500000000000015150027072015404 5ustar maartenmaartenlibzeep-7.3.2/include/zeep/config.hpp.in0000664000175000017500000000425215150027072017772 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2023 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) /// \file /// Generic configuration file, contains defines and (probably obsolete) stuff for msvc #pragma once /// The http server implementation in libzeep can use a /// preforked mode. That means the main process listens to /// a network port and passes the socket to a client process /// for doing the actual handling. The advantages for a setup /// like this is that if the client fails, the server can detect /// this and restart the client thereby guaranteeing a better /// uptime. #ifndef HTTP_SERVER_HAS_PREFORK # cmakedefine01 HTTP_SERVER_HAS_PREFORK #endif /// The http::daemon class can use fork/exec to spawn a /// process in the background. That's not going to work /// on Windows for now. #ifndef HTTP_HAS_UNIX_DAEMON # cmakedefine01 HTTP_HAS_UNIX_DAEMON #endif /// The webapp class in libzeep can use resources to load files. /// In this case you need the resource compiler 'mrc' obtainable /// from https://github.com/mhekkel/mrc #ifndef WEBAPP_USES_RESOURCES # cmakedefine01 WEBAPP_USES_RESOURCES #endif /// We can use resources to store the contents of a docroot e.g. /// which makes web applications really portable. However, for /// this feature we need a working mrc /// (https://github.com/mhekkel/mrc) and that is limited to /// Windows and operating systems using the ELF executable format. #ifndef USE_RSRC # cmakedefine01 USE_RSRC #endif /// The current version of libzeep #define LIBZEEP_VERSION "@LIBZEEP_VERSION@" #define LIBZEEP_VERSION_MAJOR @LIBZEEP_VERSION_MAJOR @ #define LIBZEEP_VERSION_MINOR @LIBZEEP_VERSION_MINOR @ #define LIBZEEP_VERSION_PATCH @LIBZEEP_VERSION_PATCH @ // Some warnings that we don't like in MSVC: #if defined(_MSC_VER) # define _CRT_SECURE_NO_WARNINGS 1 # define NOMINMAX 1 # pragma warning(disable : 4996) // stl copy() # pragma warning(disable : 4100) // unreferenced formal parameter # pragma warning(disable : 4101) // unreferenced local variable #endif libzeep-7.3.2/include/zeep/crypto.hpp0000664000175000017500000001302615150027072017437 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2019-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // // -------------------------------------------------------------------- #pragma once /// \file crypto.hpp /// This file contains an interface to the crypto related routines used /// throughout libzeep. #include #include #include #include #include namespace zeep { // -------------------------------------------------------------------- // encoding/decoding /// \brief Thrown when the input does not contain valid base64 encoded data class invalid_base64 : public std::exception { public: invalid_base64() = default; [[nodiscard]] const char *what() const noexcept override { return "invalid base64 input"; } }; /// \brief encode \a data in base64 format /// /// \param data The string containing data to encode /// \param wrap_width If this value is not zero, lines in the output will be wrapped to this width. std::string encode_base64(std::string_view data, size_t wrap_width = 0); /// \brief decode data from base64 format, will throw invalid_base64 in case of invalid input /// /// \param data The string containing data to decode std::string decode_base64(std::string_view data); // The base64url versions are slightly different /// \brief encode \a data in base64url format (see https://tools.ietf.org/html/rfc4648#section-5) /// /// \param data The string containing data to encode std::string encode_base64url(std::string_view data); /// \brief decode \a data from base64url format (see https://tools.ietf.org/html/rfc4648#section-5) /// /// \param data The string containing data to decode std::string decode_base64url(std::string data); // And base32 might be handy as well, RFC 4648 (see https://en.wikipedia.org/wiki/Base32) /// \brief Thrown when the input does not contain valid base32 encoded data class invalid_base32 : public std::exception { public: invalid_base32() = default; [[nodiscard]] const char *what() const noexcept override { return "invalid base32 input"; } }; /// \brief encode \a data in base32 format /// /// \param data The string containing data to encode /// \param wrap_width If this value is not zero, lines in the output will be wrapped to this width. std::string encode_base32(std::string_view data, size_t wrap_width = 0); /// \brief decode data from base32 format, will throw invalid_base32 in case of invalid input /// /// \param data The string containing data to decode std::string decode_base32(std::string_view data); /// \brief Thrown when the input does not contain valid hexadecimal encoded data class invalid_hex : public std::exception { public: invalid_hex() = default; [[nodiscard]] const char *what() const noexcept override { return "invalid hexadecimal input"; } }; /// \brief encode \a data in hexadecimal format /// /// \param data The string containing data to encode std::string encode_hex(std::string_view data); /// \brief decode \a data from hexadecimal format /// /// \param data The string containing data to decode std::string decode_hex(std::string_view data); // -------------------------------------------------------------------- // random bytes /// \brief return a string containing some random bytes std::string random_hash(); // -------------------------------------------------------------------- // hashing /// \brief return the MD5 hash of \a data std::string md5(std::string_view data); /// \brief return the SHA1 hash of \a data std::string sha1(std::string_view data); /// \brief return the SHA1 hash of \a data std::string sha1(std::streambuf &data); /// \brief return the SHA256 hash of \a data std::string sha256(std::string_view data); // -------------------------------------------------------------------- // hmac /// \brief return the HMAC using an MD5 hash of \a message signed with \a key std::string hmac_md5(std::string_view message, std::string_view key); /// \brief return the HMAC using an SHA1 hash of \a message signed with \a key std::string hmac_sha1(std::string_view message, std::string_view key); /// \brief return the HMAC using an SHA256 hash of \a message signed with \a key std::string hmac_sha256(std::string_view message, std::string_view key); // -------------------------------------------------------------------- // key derivation based on password (PBKDF2) /// \brief create password hash according to PBKDF2 with HmacSHA1 /// /// This algorithm can be used to create keys for symmetric encryption. /// But you can also use it to store hashed passwords for user authentication. /// /// \param salt the salt to use /// \param password the password /// \param iterations number of iterations, use a value of at least 30000 /// \param keyLength the requested key length that will be returned std::string pbkdf2_hmac_sha1(std::string_view salt, std::string_view password, unsigned iterations, unsigned keyLength); /// \brief create password hash according to PBKDF2 with HmacSHA256 /// /// This algorithm can be used to create keys for symmetric encryption. /// But you can also use it to store hashed passwords for user authentication. /// /// \param salt the salt to use /// \param password the password /// \param iterations number of iterations, use a value of at least 30000 /// \param keyLength the requested key length that will be returned std::string pbkdf2_hmac_sha256(std::string_view salt, std::string_view password, unsigned iterations, unsigned keyLength); } // namespace zeep libzeep-7.3.2/include/zeep/el/0000775000175000017500000000000015150027072016004 5ustar maartenmaartenlibzeep-7.3.2/include/zeep/el/object.hpp0000664000175000017500000006632715150027072020001 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2025-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once #include "zeep/streambuf.hpp" #include #include #include #include #if __has_include() # include # define HAVE_NLOHMANN_JSON 1 #endif #include #include #include #include #include #include #include #include #include namespace zeep::el { class object; // concepts template concept BooleanType = std::is_same_v>; template concept ObjectType = std::is_same_v>; template concept NumberType = ((std::is_integral_v> or std::is_floating_point_v>) and not std::is_same_v, bool>); template concept StringType = (std::is_assignable_v and not std::is_integral_v and not std::is_floating_point_v); // -------------------------------------------------------------------- class object { public: enum class value_type { null, object, array, string, number_int, number_float, boolean }; inline constexpr friend bool operator<(value_type lhs, value_type rhs) noexcept { const uint8_t order[] = { 0, // null 3, // object 4, // array 5, // string 2, // number_int 2, // number_float 1 // boolean }; const auto lix = static_cast(lhs); const auto rix = static_cast(rhs); return lix < sizeof(order) and rix < sizeof(order) and order[lix] < order[rix]; } using nullptr_type = std::nullptr_t; using object_type = std::map; using array_type = std::vector; using string_type = std::string; using int_type = int64_t; using float_type = double; using boolean_type = bool; using pointer = object *; using const_pointer = const object *; using difference_type = std::ptrdiff_t; using size_type = std::size_t; using reference = object &; using const_reference = const object &; // -------------------------------------------------------------------- template struct iterator_impl { friend class object; using iterator_category = std::bidirectional_iterator_tag; using difference_type = T::difference_type; using pointer = typename std::conditional_t, typename T::const_pointer, typename T::pointer>; using reference = typename std::conditional_t, typename T::const_reference, typename T::reference>; using value_type = std::remove_cv_t; iterator_impl() = default; explicit iterator_impl(pointer obj) noexcept : m_obj(obj) { assert(m_obj); switch (m_obj->m_data.m_type) { case object::value_type::array: m_it.m_array_it = m_obj->m_data.m_value.m_array->begin(); break; case object::value_type::object: m_it.m_object_it = m_obj->m_data.m_value.m_object->begin(); break; default: m_it.m_p = 0; break; } } iterator_impl(pointer obj, [[maybe_unused]] int dummy) noexcept : m_obj(obj) { assert(m_obj); switch (m_obj->m_data.m_type) { case object::value_type::array: m_it.m_array_it = m_obj->m_data.m_value.m_array->end(); break; case object::value_type::object: m_it.m_object_it = m_obj->m_data.m_value.m_object->end(); break; case object::value_type::null: m_it.m_p = 0; break; default: m_it.m_p = 1; break; } } iterator_impl(const iterator_impl &i) : m_obj(i.m_obj) , m_it(i.m_it) { } iterator_impl operator--(int) { auto result(*this); operator--(); return result; } iterator_impl &operator--() { assert(m_obj); switch (m_obj->m_data.m_type) { case object::value_type::array: std::advance(m_it.m_array_it, -1); break; case object::value_type::object: std::advance(m_it.m_object_it, -1); break; default: --m_it.m_p; break; } return *this; } iterator_impl operator++(int) { auto result(*this); operator++(); return result; } iterator_impl &operator++() { assert(m_obj); switch (m_obj->m_data.m_type) { case object::value_type::array: std::advance(m_it.m_array_it, +1); break; case object::value_type::object: std::advance(m_it.m_object_it, +1); break; default: ++m_it.m_p; break; } return *this; } reference operator*() const { assert(m_obj); switch (m_obj->m_data.m_type) { case object::value_type::array: assert(m_it.m_array_it != m_obj->m_data.m_value.m_array->end()); return *m_it.m_array_it; break; case object::value_type::object: assert(m_it.m_object_it != m_obj->m_data.m_value.m_object->end()); return m_it.m_object_it->second; break; case object::value_type::null: throw std::runtime_error("Cannot get value"); default: if (m_it.m_p == 0) return *m_obj; throw std::runtime_error("Cannot get value"); } } pointer operator->() const { assert(m_obj); switch (m_obj->m_data.m_type) { case object::value_type::array: assert(m_it.m_array_it != m_obj->m_data.m_value.m_array->end()); return &(*m_it.m_array_it); break; case object::value_type::object: assert(m_it.m_object_it != m_obj->m_data.m_value.m_object->end()); return &(m_it.m_object_it->second); break; case object::value_type::null: throw std::runtime_error("Cannot get value"); default: if (m_it.m_p == 0) return m_obj; throw std::runtime_error("Cannot get value"); } } bool operator==(const iterator_impl &other) const { if (m_obj != other.m_obj) throw std::runtime_error("Containers are not the same"); assert(m_obj); switch (m_obj->m_data.m_type) { case object::value_type::array: return m_it.m_array_it == other.m_it.m_array_it; case object::value_type::object: return m_it.m_object_it == other.m_it.m_object_it; default: return m_it.m_p == other.m_it.m_p; } } auto operator<=>(const iterator_impl &other) const { if (m_obj != other.m_obj) throw std::runtime_error("Containers are not the same"); assert(m_obj); switch (m_obj->m_data.m_type) { case object::value_type::array: return m_it.m_array_it <=> other.m_it.m_array_it; case object::value_type::object: throw std::runtime_error("Cannot compare order of object iterators"); default: return m_it.m_p <=> other.m_it.m_p; } } iterator_impl &operator+=(difference_type i) { assert(m_obj); switch (m_obj->m_data.m_type) { case object::value_type::array: std::advance(m_it.m_array_it, i); case object::value_type::object: throw std::runtime_error("Cannot use offsets with object iterators"); default: m_it.m_p += i; } return *this; } iterator_impl &operator-=(difference_type i) { operator+=(-i); return *this; } iterator_impl operator+(difference_type i) const { auto result = *this; result += i; return result; } friend iterator_impl operator+(difference_type i, const iterator_impl &iter) { auto result = iter; result += i; return result; } iterator_impl operator-(difference_type i) const { auto result = *this; result -= i; return result; } friend iterator_impl operator-(difference_type i, const iterator_impl &iter) { auto result = iter; result -= i; return result; } difference_type operator-(const iterator_impl &other) const { assert(m_obj); switch (m_obj->m_data.m_type) { case object::value_type::array: return m_it.m_array_it - other.m_it.m_array_it; case object::value_type::object: throw std::runtime_error("Cannot use offsets with object iterators"); default: return m_it.m_p - other.m_it.m_p; } } reference operator[](difference_type i) const { assert(m_obj); switch (m_obj->m_data.m_type) { case object::value_type::array: *std::next(m_it.m_array_it, i); case object::value_type::object: throw std::runtime_error("Cannot use offsets with object iterators"); default: if (m_it.m_p == -i) return *m_obj; throw std::runtime_error("Cannot get value"); } } [[nodiscard]] const std::string &key() const { assert(m_obj); if (not m_obj->is_object()) throw std::runtime_error("Can only use key() on object iterators"); return m_it.m_object_it->first; } [[nodiscard]] reference value() const { return operator*(); } private: pointer m_obj = nullptr; using array_iterator_type = typename T::array_type::iterator; using object_iterator_type = typename T::object_type::iterator; union { array_iterator_type m_array_it; object_iterator_type m_object_it; difference_type m_p; } m_it = {}; }; using iterator = iterator_impl; using const_iterator = iterator_impl; static_assert(std::input_iterator); static_assert(std::input_iterator); // -------------------------------------------------------------------- object() noexcept = default; object(value_type t) noexcept : m_data(t) { } object(const object &o) { m_data.m_type = o.m_data.m_type; switch (m_data.m_type) { case value_type::null: break; case value_type::array: m_data.m_value = *o.m_data.m_value.m_array; break; case value_type::object: m_data.m_value = *o.m_data.m_value.m_object; break; case value_type::string: m_data.m_value = *o.m_data.m_value.m_string; break; case value_type::number_int: m_data.m_value = o.m_data.m_value.m_int; break; case value_type::number_float: m_data.m_value = o.m_data.m_value.m_float; break; case value_type::boolean: m_data.m_value = o.m_data.m_value.m_boolean; break; } } object(const std::vector &v) { m_data.m_type = value_type::array; m_data.m_value = v; } object(std::initializer_list init) { bool isAnObject = std::ranges::all_of(init, [](auto &ref) { return ref.is_array() and ref.m_data.m_value.m_array->size() == 2 and ref.m_data.m_value.m_array->front().is_string(); }); if (isAnObject) { m_data.m_type = value_type::object; m_data.m_value = value_type::object; for (auto &el : init) { m_data.m_value.m_object->emplace( std::move(*el.m_data.m_value.m_array->front().m_data.m_value.m_string), std::move(el.m_data.m_value.m_array->back())); } } else { m_data.m_type = value_type::array; m_data.m_value.m_array = create(init.begin(), init.end()); } } object(std::nullptr_t) { m_data.m_type = value_type::null; } template object(const T &s) { m_data.m_type = value_type::string; m_data.m_value = std::string{ s }; } template object(T v) { if constexpr (std::is_integral_v) { m_data.m_type = value_type::number_int; m_data.m_value = static_cast(v); } else if constexpr (std::is_floating_point_v) { m_data.m_type = value_type::number_float; m_data.m_value = static_cast(v); } else assert(false); } template object(T b) { m_data.m_type = value_type::boolean; m_data.m_value = static_cast(b); } #if HAVE_NLOHMANN_JSON object(const nlohmann::json &j) { // to be implemented switch (j.type()) { case nlohmann::json::value_t::null: m_data.m_type = value_type::null; break; case nlohmann::json::value_t::object: for (auto i = j.begin(); i != j.end(); ++i) operator[](i.key()) = object(i.value()); break; case nlohmann::json::value_t::array: for (auto &e : j) push_back(object(e)); break; case nlohmann::json::value_t::string: m_data.m_type = value_type::string; m_data.m_value = j.template get(); break; case nlohmann::json::value_t::boolean: m_data.m_type = value_type::boolean; m_data.m_value = j.template get(); break; case nlohmann::json::value_t::number_integer: m_data.m_type = value_type::number_int; m_data.m_value = j.template get(); break; case nlohmann::json::value_t::number_unsigned: m_data.m_type = value_type::number_int; m_data.m_value = static_cast(j.template get()); break; case nlohmann::json::value_t::number_float: m_data.m_type = value_type::number_float; m_data.m_value = j.template get(); break; case nlohmann::json::value_t::binary: case nlohmann::json::value_t::discarded: assert(false); break; } } #endif object(object &&rhs) noexcept { swap(*this, rhs); } object &operator=(object rhs) noexcept { swap(*this, rhs); return *this; } // -------------------------------------------------------------------- [[nodiscard]] constexpr bool is_null() const noexcept { return m_data.m_type == value_type::null; } [[nodiscard]] constexpr bool is_object() const noexcept { return m_data.m_type == value_type::object; } [[nodiscard]] constexpr bool is_array() const noexcept { return m_data.m_type == value_type::array; } [[nodiscard]] constexpr bool is_string() const noexcept { return m_data.m_type == value_type::string; } [[nodiscard]] constexpr bool is_number() const noexcept { return is_number_int() or is_number_float(); } [[nodiscard]] constexpr bool is_number_int() const noexcept { return m_data.m_type == value_type::number_int; } [[nodiscard]] constexpr bool is_number_float() const noexcept { return m_data.m_type == value_type::number_float; } [[nodiscard]] constexpr bool is_true() const noexcept { return is_boolean() and m_data.m_value.m_boolean == true; } [[nodiscard]] constexpr bool is_false() const noexcept { return is_boolean() and m_data.m_value.m_boolean == false; } [[nodiscard]] constexpr bool is_boolean() const noexcept { return m_data.m_type == value_type::boolean; } [[nodiscard]] constexpr value_type type() const { return m_data.m_type; } explicit operator bool() const noexcept { bool result; switch (m_data.m_type) { case value_type::null: result = false; break; case value_type::boolean: result = m_data.m_value.m_boolean; break; case value_type::number_int: result = m_data.m_value.m_int != 0; break; case value_type::number_float: result = m_data.m_value.m_float != 0; break; case value_type::string: result = not m_data.m_value.m_string->empty(); break; default: result = not empty(); break; } return result; } // -------------------------------------------------------------------- template [[nodiscard]] inline std::string get() const { if (m_data.m_type == value_type::string) return *m_data.m_value.m_string; return get_JSON(); } template [[nodiscard]] inline bool get() const { switch (m_data.m_type) { case value_type::boolean: return m_data.m_value.m_boolean; case value_type::number_int: return m_data.m_value.m_int != 0; case value_type::number_float: return m_data.m_value.m_float != 0; default: return not empty(); } } template [[nodiscard]] std::remove_cvref_t get() const { switch (m_data.m_type) { case value_type::boolean: return m_data.m_value.m_boolean; case value_type::number_int: return m_data.m_value.m_int; case value_type::number_float: return m_data.m_value.m_float; default: return not empty(); } } // -------------------------------------------------------------------- friend void swap(object &a, object &b) noexcept { std::swap(a.m_data.m_type, b.m_data.m_type); std::swap(a.m_data.m_value, b.m_data.m_value); } // -------------------------------------------------------------------- // arithmetic operators object &operator-() { switch (m_data.m_type) { case value_type::number_int: m_data.m_value.m_int = -m_data.m_value.m_int; break; case value_type::number_float: m_data.m_value.m_float = -m_data.m_value.m_float; break; default: throw std::runtime_error("Can only negate numbers"); } return *this; } friend object operator+(const_reference &lhs, const_reference &rhs); template friend object operator+(const_reference &lhs, const T &rhs) { return lhs + object(rhs); } template friend object operator+(const T &lhs, const_reference &rhs) { return object(lhs) + rhs; } friend object operator-(const_reference &lhs, const_reference &rhs); template friend object operator-(const_reference &lhs, const T &rhs) { return lhs - object(rhs); } template friend object operator-(const T &lhs, const_reference &rhs) { return object(lhs) - rhs; } friend object operator*(const_reference &lhs, const_reference &rhs); template friend object operator*(const_reference &lhs, const T &rhs) { return lhs * object(rhs); } template friend object operator*(const T &lhs, const_reference &rhs) { return object(lhs) * rhs; } friend object operator/(const_reference &lhs, const_reference &rhs); template friend object operator/(const_reference &lhs, const T &rhs) { return lhs / object(rhs); } template friend object operator/(const T &lhs, const_reference &rhs) { return object(lhs) / rhs; } friend object operator%(const_reference &lhs, const_reference &rhs); template friend object operator%(const_reference &lhs, const T &rhs) { return lhs % object(rhs); } template friend object operator%(const T &lhs, const_reference &rhs) { return object(lhs) % rhs; } friend bool operator==(const_reference &lhs, const_reference &rhs) noexcept; template friend bool operator==(const_reference &lhs, const T &rhs) noexcept { return lhs == object(rhs); } template friend bool operator==(const T &lhs, const_reference &rhs) noexcept { return object(lhs) == rhs; } friend std::partial_ordering operator<=>(const_reference &lhs, const_reference &rhs) noexcept; template friend std::partial_ordering operator<=>(const_reference &lhs, const T &rhs) noexcept { return lhs <=> object(rhs); } template friend std::partial_ordering operator<=>(const T &lhs, const_reference &rhs) noexcept { return object(lhs) <=> rhs; } // -------------------------------------------------------------------- // array/object interface [[nodiscard]] bool contains(const object &test) const; [[nodiscard]] bool empty() const noexcept; [[nodiscard]] size_t size() const noexcept; [[nodiscard]] size_t max_size() const noexcept; [[nodiscard]] reference at(const std::string &key); [[nodiscard]] const_reference at(const std::string &key) const; [[nodiscard]] reference operator[](const std::string &key); [[nodiscard]] const_reference operator[](const std::string &key) const; // access to array objects [[nodiscard]] reference at(size_t index); [[nodiscard]] const_reference at(size_t index) const; [[nodiscard]] reference operator[](size_t index); [[nodiscard]] const_reference operator[](size_t index) const; void push_back(object &&val); void push_back(const object &val); template std::pair emplace(Args &&...args) { if (is_null()) { m_data.m_type = value_type::object; m_data.m_value = value_type::object; } else if (not is_object()) throw std::runtime_error("emplace only works with object type"); auto r = m_data.m_value.m_object->emplace(std::forward(args)...); auto i = begin(); i.m_it.m_object_it = r.first; return { i, r.second }; } template object &emplace_back(Args &&...args) { if (not(is_null() or is_array())) throw std::runtime_error("emplace_back only works with array type"); if (is_null()) { m_data.m_type = value_type::array; m_data.m_value = value_type::array; } return m_data.m_value.m_array->emplace_back(std::forward(args)...); } template requires std::is_same_v or std::is_same_v Iterator erase(Iterator pos) { if (pos.m_obj != this) throw std::runtime_error("Invalid iterator"); auto result = end(); switch (m_data.m_type) { case value_type::array: result.m_it.m_array_it = m_data.m_value.m_array->erase(pos.m_it.m_array_it); break; case value_type::object: result.m_it.m_object_it = m_data.m_value.m_object->erase(pos.m_it.m_object_it); break; case value_type::null: throw std::runtime_error("Cannot erase in null values"); default: if (pos.m_it.m_p != 0) throw std::runtime_error("Iterator out of range"); if (m_data.m_type == value_type::string) { std::allocator alloc; std::allocator_traits::destroy(alloc, m_data.m_value.m_string); std::allocator_traits::deallocate(alloc, m_data.m_value.m_string, 1); m_data.m_value.m_string = nullptr; } m_data.m_type = value_type::null; break; } return result; } template requires std::is_same_v or std::is_same_v Iterator erase(Iterator first, Iterator last) { if (first.m_obj != this or last.m_obj != this) throw std::runtime_error("Invalid iterator"); auto result = end(); switch (m_data.m_type) { case value_type::array: result.m_it.m_array_it = m_data.m_value.m_array->erase(first.m_it.m_array_it, last.m_it.m_array_it); break; case value_type::object: result.m_it.m_object_it = m_data.m_value.m_object->erase(first.m_it.m_object_it, last.m_it.m_object_it); break; case value_type::null: throw std::runtime_error("Cannot erase in null values"); default: if (first.m_it.m_p != 0 or last.m_it.m_p != 0) throw std::runtime_error("Iterator out of range"); if (m_data.m_type == value_type::string) { std::allocator alloc; std::allocator_traits::destroy(alloc, m_data.m_value.m_string); std::allocator_traits::deallocate(alloc, m_data.m_value.m_string, 1); m_data.m_value.m_string = nullptr; } m_data.m_type = value_type::null; break; } return result; } size_type erase(const std::string &key) { if (is_object()) return m_data.m_value.m_object->erase(key); throw std::runtime_error("erase with a string key only works with object type"); } void erase(const size_type index) { if (is_array()) { if (index >= size()) throw std::runtime_error("Index out of range"); m_data.m_value.m_array->erase(m_data.m_value.m_array->begin() + static_cast(index)); } else throw std::runtime_error("erase with an index only works wiht array type"); } // -------------------------------------------------------------------- [[nodiscard]] iterator begin() { return iterator(this); } [[nodiscard]] iterator end() { return { this, 1 }; } [[nodiscard]] const_iterator begin() const { return const_iterator(this); } [[nodiscard]] const_iterator end() const { return { this, 1 }; } [[nodiscard]] const_iterator cbegin() { return const_iterator(this); } [[nodiscard]] const_iterator cend() { return { this, 1 }; } [[nodiscard]] object &front() { return *begin(); } [[nodiscard]] const object &front() const { return *begin(); } [[nodiscard]] object &back() { return *--end(); } [[nodiscard]] const object &back() const { return *--end(); } // I/O friend void serialize(std::ostream &os, const object &o); friend void deserialize(std::istream &is, object &o); // And some more alternatives static object parse_JSON(std::istream &is) { object result; deserialize(is, result); return result; } static object parse_JSON(std::string_view s) { char_streambuf b(s.data(), s.length()); std::istream is(&b); return parse_JSON(is); } // And get the object as a JSON string [[nodiscard]] std::string get_JSON() const { std::ostringstream os; serialize(os, *this); return os.str(); } // convenience friend std::ostream &operator<<(std::ostream &os, const object &o) { serialize(os, o); return os; } private: union object_value { object_type *m_object; array_type *m_array; string_type *m_string; int64_t m_int; double m_float; bool m_boolean; object_value() noexcept : m_object(nullptr) { } object_value(bool v) noexcept : m_boolean(v) { } object_value(int64_t v) noexcept : m_int(v) { } object_value(double v) noexcept : m_float(v) { } object_value(value_type t) { switch (t) { case value_type::array: m_array = create(); break; case value_type::boolean: m_boolean = false; break; case value_type::null: m_object = nullptr; break; case value_type::number_float: m_float = 0; break; case value_type::number_int: m_int = 0; break; case value_type::object: m_object = create(); break; case value_type::string: m_string = create(); break; } } object_value(const object_type &v) { m_object = create(v); } object_value(object_type &&v) { m_object = create(std::move(v)); } object_value(const string_type &v) { m_string = create(v); } object_value(string_type &&v) { m_string = create(std::move(v)); } object_value(const array_type &v) { m_array = create(v); } object_value(array_type &&v) { m_array = create(std::move(v)); } void destroy(value_type t) noexcept { switch (t) { case value_type::object: { std::allocator alloc; std::allocator_traits::destroy(alloc, m_object); std::allocator_traits::deallocate(alloc, m_object, 1); break; } case value_type::array: { std::allocator alloc; std::allocator_traits::destroy(alloc, m_array); std::allocator_traits::deallocate(alloc, m_array, 1); break; } case value_type::string: { std::allocator alloc; std::allocator_traits::destroy(alloc, m_string); std::allocator_traits::deallocate(alloc, m_string, 1); break; } default: break; } } }; struct object_data { value_type m_type = value_type::null; object_value m_value{}; object_data(const value_type t) : m_type(t) , m_value(t) { } object_data(size_type cnt, const object &val) : m_type(value_type::array) { m_value.m_array = create(cnt, val); } object_data() noexcept = default; object_data(object_data &&) noexcept = default; object_data(const object_data &) noexcept = delete; object_data &operator=(object_data &&) noexcept = delete; object_data &operator=(const object_data &) noexcept = delete; ~object_data() noexcept { m_value.destroy(m_type); } } m_data{}; template [[nodiscard]] static T *create(Args &&...args) { // return new T(args...); std::allocator alloc; using AllocatorTraits = std::allocator_traits>; auto deleter = [&](T *object) { AllocatorTraits::deallocate(alloc, object, 1); }; std::unique_ptr object(AllocatorTraits::allocate(alloc, 1), deleter); assert(object != nullptr); AllocatorTraits::construct(alloc, object.get(), std::forward(args)...); return object.release(); } }; } // namespace zeep::el libzeep-7.3.2/include/zeep/el/processing.hpp0000664000175000017500000001252615150027072020677 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2014-2026 // Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // // expression language support // #pragma once /// \file /// definition of the routines that can parse and interpret el (expression language) code in a web application context #include "zeep/el/object.hpp" #include namespace zeep::http { class scope; class basic_server; using object = el::object; /// \brief Process the text in \a text and return `true` if the result is /// not empty, zero or false. /// /// The expression in \a text is processed and if the result of this /// expression is empty, false or zero then `false` is returned. /// \param scope_ The scope for this el script /// \param text The el script /// \return The result of the script bool process_el(const scope &scope_, std::string &text); /// \brief Process the text in \a text and return the result if the expression is valid, /// the value of \a text otherwise. /// /// If the expression in \a text is valid, it is processed and the result /// is returned, otherwise simply returns the text. /// \param scope_ The scope for this el script /// \param text The el script /// \return The result of the script std::string process_el_2(const scope &scope_, const std::string &text); /// \brief Process the text in \a text. The result is put in \a result /// /// The expression in \a text is processed and the result is returned /// in \a result. /// \param scope_ The scope for this el script /// \param text The el script /// \result The result of the script object evaluate_el(const scope &scope_, const std::string &text); /// \brief Process the text in \a text and return a list of name/value pairs /// /// The expressions found in \a text are processed and the result is /// returned as a list of name/value pairs to be used in e.g. /// processing a m2:attr attribute. /// \param scope_ The scope for the el scripts /// \param text The text optionally containing el scripts. /// \return list of name/value pairs std::vector> evaluate_el_attr(const scope &scope_, const std::string &text); /// \brief Process the text in \a text. This should be a comma separated list /// of expressions that each should evaluate to true. /// /// The expression in \a text is processed and the result is false if /// one of the expressions in the comma separated list evaluates to false. /// /// in \a result. /// \param scope_ The scope for this el script /// \param text The el script /// \return True in case all the expressions evaluate to true bool evaluate_el_assert(const scope &scope_, const std::string &text); /// \brief Process the text in \a text and put the resulting z:with expressions in the scope /// /// The expressions found in \a text are processed and the result is /// returned as a list of name/value pairs to be used in e.g. /// processing a m2:attr attribute. /// \param scope_ The scope for the el scripts /// \param text The text containing el scripts in the form var=val(,var=val)*. void evaluate_el_with(scope &scope_, const std::string &text); /// \brief Evaluate the text in \a text as a potential link template /// /// The expression found in \a text is processed and the result is /// returned as a link template object. This function is called from /// el::include, el::replace and el::insert attributes. /// /// \param scope_ The scope for the el scripts /// \param text The text containing the link specification /// \result The resulting link object evaluate_el_link(const scope &scope_, const std::string &text); // -------------------------------------------------------------------- /// \brief Base class for utility objects, objects that are exposed as /// objects in the Expression Language API. class expression_utility_object_base { public: virtual ~expression_utility_object_base() = default; static object evaluate(const scope &scope_, const std::string &className, const std::string &methodName, const std::vector ¶meters) { for (auto inst = s_head; inst != nullptr; inst = inst->m_next) { if (className == inst->m_name) return inst->m_obj->evaluate(scope_, methodName, parameters); } return {}; } protected: [[nodiscard]] virtual object evaluate(const scope &scope_, const std::string &methodName, const std::vector ¶meters) const = 0; /// Struct used to store the instances of the derived classes along with /// their name struct instance { expression_utility_object_base *m_obj = nullptr; const char *m_name{}; instance *m_next = nullptr; }; static instance *s_head; }; /// \brief The actual base class for utility objects, objects that are exposed as /// objects in the Expression Language API. /// Uses the https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern template class expression_utility_object : public expression_utility_object_base { public: using implementation_type = OBJ; protected: expression_utility_object() noexcept // NOLINT(bugprone-crtp-constructor-accessibility) { static instance s_next{ this, implementation_type::name(), s_head }; s_head = &s_next; } }; } // namespace zeep::http libzeep-7.3.2/include/zeep/el/serializer.hpp0000664000175000017500000002234115150027072020670 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2019-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the serializer classes that help serialize data into and out of our el script objects #include "zeep/el/object.hpp" #include #include // -------------------------------------------------------------------- namespace zeep { template using name_value_pair = zeem::name_value_pair; template using value_serializer = zeem::value_serializer; } // namespace zeep // -------------------------------------------------------------------- namespace zeep::el { template struct serializer; struct object_serializer; struct object_deserializer; // -------------------------------------------------------------------- /// Struct used to detect if there is a value_serializer for type \a T template using vs_to_string_function = decltype(value_serializer::to_string(std::declval())); template using vs_from_string_function = decltype(value_serializer::from_string(std::declval())); template struct has_value_serializer { static constexpr bool value = zeem::detail::is_detected_v and zeem::detail::is_detected_v; }; template inline constexpr bool has_value_serializer_v = has_value_serializer::value; // -------------------------------------------------------------------- template using serialize_function = decltype(std::declval().serialize(std::declval(), std::declval())); template struct has_serialize : std::false_type { }; template struct has_serialize>> { static constexpr bool value = zeem::detail::is_detected_v; }; template inline constexpr bool has_serialize_v = has_serialize::value; // -------------------------------------------------------------------- template using mapped_type_t = typename T::mapped_type; template using key_type_t = typename T::key_type; template struct is_serializable_map_type : std::false_type { }; template struct is_serializable_map_type and zeem::detail::is_detected_v and zeem::detail::is_detected_v>> { static constexpr bool value = std::is_same_v and (std::is_constructible_v or has_serialize_v); }; template inline constexpr bool is_serializable_map_type_v = is_serializable_map_type::value; // -------------------------------------------------------------------- template struct is_serializable_array_type : std::false_type { }; template struct is_serializable_array_type and not zeem::detail::is_detected_v and zeem::detail::is_detected_v and zeem::detail::is_detected_v and not zeem::detail::is_detected_v>> { static constexpr bool value = std::is_constructible_v or has_serialize_v; }; template inline constexpr bool is_serializable_array_type_v = is_serializable_array_type::value; // -------------------------------------------------------------------- struct object_serializer { object_serializer() = default; template object_serializer &operator&(name_value_pair &&nvp) { serialize(nvp.name(), nvp.value()); return *this; } template void serialize(std::string name, const T &data) { using serializer_impl = serializer; m_elem.emplace(name, serializer_impl::serialize(data)); } template static void serialize(object &o, const T &v) { using serializer_impl = serializer; o = serializer_impl::serialize(v); } object m_elem; }; struct object_deserializer { explicit object_deserializer(const object &o) : m_elem(o) { } template object_deserializer &operator&(name_value_pair &&nvp) { deserialize(nvp.name(), nvp.value()); return *this; } template void deserialize(const std::string &name, T &data) { if (not m_elem.is_object() or m_elem.empty()) return; using serializer_impl = serializer; auto value = m_elem[name]; if (value.is_null()) return; data = serializer_impl::deserialize(value); } const object &m_elem; }; // -------------------------------------------------------------------- template requires( not std::is_constructible_v and has_value_serializer_v and not std::is_enum_v) struct serializer { static object serialize(const T &v) { return object(value_serializer::to_string(v)); } static object serialize(T &&v) { return object(value_serializer::to_string(std::forward(v))); } static T deserialize(const object &o) { return value_serializer::from_string(o.get()); } }; template requires( std::is_constructible_v and not std::is_same_v> and not std::is_same_v and not is_serializable_map_type_v and not is_serializable_array_type_v and not std::is_enum_v) struct serializer { static object serialize(const T &v) { return object(v); } static T deserialize(const object &o) { return o.get(); } }; template requires zeem::has_serialize_v struct serializer { static object serialize(const T &v) { object_serializer s; const_cast(v).serialize(s, 0); return s.m_elem; } static T deserialize(const object &o) { object_deserializer s(o); T result{}; const_cast(result).serialize(s, 0); return result; } }; template requires is_serializable_map_type_v struct serializer { static object serialize(const T &v) { using value_serializer_impl = serializer; object e = object::value_type::object; for (auto &i : v) e.emplace(i.first, value_serializer_impl::serialize(i.second)); return e; } static T deserialize(const object &o) { using value_deserializer_impl = serializer; T result{}; for (auto i = o.begin(); i != o.end(); ++i) result[i.key()] = value_deserializer_impl::deserialize(i.value()); return result; } }; template requires is_serializable_array_type_v struct serializer { static object serialize(const T &v) { using value_serializer_impl = serializer; object o = object::value_type::array; for (auto &i : v) o.push_back(value_serializer_impl::serialize(i)); return o; } static T deserialize(const object &o) { using value_deserializer_impl = serializer; T result{}; for (auto &i : o) result.emplace_back(value_deserializer_impl::deserialize(i)); return result; } }; template struct serializer> { static object serialize(const std::optional &v) { using value_serializer_impl = serializer; object result; if (v) result = value_serializer_impl::serialize(*v); return result; } static std::optional deserialize(const object &o) { using value_serializer_impl = serializer; std::optional result; if (not o.is_null()) result = value_serializer_impl::deserialize(o); return result; } }; template requires(std::is_enum_v and has_value_serializer_v) struct serializer { static object serialize(T v) { return object(value_serializer::to_string(v)); } static T deserialize(const object &o) { return value_serializer::from_string(o.get()); } }; // -------------------------------------------------------------------- template using serialize_to_object_function = decltype(zeep::el::serializer::serialize(std::declval())); template struct is_serializable_to_object { static constexpr bool value = zeem::detail::is_detected_v; }; template inline constexpr bool is_serializable_to_object_v = is_serializable_to_object::value; // -------------------------------------------------------------------- template object to_object(const T &v) requires(is_serializable_to_object_v) { using value_serializer_impl = serializer; return value_serializer_impl::serialize(v); } template T from_object(const object &o) requires(is_serializable_to_object_v) { using value_serializer_impl = serializer; return value_serializer_impl::deserialize(o); } } // namespace zeep::el libzeep-7.3.2/include/zeep/exception.hpp0000664000175000017500000000155015150027072020114 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of zeep::exception, base class for exceptions thrown by libzeep #include "zeep/config.hpp" #include #include namespace zeep { /// \brief base class of the exceptions thrown by libzeep class exception : public std::exception { public: /// \brief Create an exception with the message in \a message exception(std::string message) : m_message(std::move(message)) {} [[nodiscard]] const char* what() const noexcept override { return m_message.c_str(); } protected: std::string m_message; }; } // namespace zeep libzeep-7.3.2/include/zeep/http/0000775000175000017500000000000015150027072016363 5ustar maartenmaartenlibzeep-7.3.2/include/zeep/http/access-control.hpp0000664000175000017500000000427415150027072022022 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2025-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the zeep::http::access_control class, that handles CORS for HTTP connections #include "zeep/unicode-support.hpp" #include #include #include #include namespace zeep::http { class reply; /// The zeep::http::access_control class handles CORS for HTTP connections class access_control { public: access_control() = default; virtual ~access_control() = default; access_control(access_control &&) = default; access_control &operator=(access_control &&) = default; /// Constructor with a default \a allow_origin string and a flag \a allow_credentials /// that will trigger addition of a "Access-Control-Allow-Credentials" header access_control(std::string allow_origin, bool allow_credentials) : m_allow_origin(std::move(allow_origin)) , m_allowed_headers({ "Keep-Alive", "User-Agent", "If-Modified-Since", "Cache-Control", "Content-Type" }) , m_allow_credentials(allow_credentials) { } /// Set the "Access-Control-Allow-Origin" header to \a allow_origin void set_allow_origin(std::string allow_origin) { m_allow_origin = std::move(allow_origin); } /// Set the "Access-Control-Allow-Credentials" header corresponding to \a allow_credentials void set_allow_credentials(bool allow_credentials) { m_allow_credentials = allow_credentials; } /// Set the "Access-Control-Allow-Headers" header to \a allowed_headers void set_allowed_headers(std::string_view allowed_headers) { split(m_allowed_headers, allowed_headers, ","); } /// Add \a allowed_headers to the list in the header "Access-Control-Allow-Headers" void add_allowed_header(std::string allowed_header) { m_allowed_headers.emplace_back(std::move(allowed_header)); } /// Add the specified headers to @ref reply \a rep virtual void get_access_control_headers(reply &rep) const; private: std::string m_allow_origin; std::vector m_allowed_headers; bool m_allow_credentials = false; }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/connection.hpp0000664000175000017500000000254115150027072021235 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2014-2026 // Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the zeep::http::connection class, that handles HTTP connections #include "zeep/http/message-parser.hpp" #include namespace zeep::http { class basic_server; /// The HTTP server implementation of libzeep is inspired by the example code /// as provided by boost::asio. These objects are not to be used directly. class connection : public std::enable_shared_from_this { public: connection(connection &) = delete; connection &operator=(connection &) = delete; connection(asio_ns::io_context &service, basic_server &handler); void start(); void handle_read(asio_system_ns::error_code ec, size_t bytes_transferred); void handle_write(asio_system_ns::error_code ec, size_t bytes_transferred); asio_ns::ip::tcp::socket &get_socket() { return m_socket; } private: asio_ns::ip::tcp::socket m_socket; basic_server &m_server; reply m_reply; request_parser m_request_parser; bool m_keep_alive = false; asio_ns::streambuf m_buffer; asio_ns::streambuf::mutable_buffers_type m_bufs; }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/controller.hpp0000664000175000017500000004026215150027072021263 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the base class zeep::http::controller, used by e.g. controller and soap_controller #include "zeep/el/processing.hpp" #include "zeep/el/serializer.hpp" #include "zeep/http/asio.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/http/scope.hpp" #include "zeep/http/server.hpp" #include "zeep/unicode-support.hpp" #include "zeep/uri.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace zeep::http { /// \brief A base class for controllers, classes that handle a request /// /// This concept is inspired by the Spring way of delegating the work to /// controller classes. /// /// There can be multiple controllers in a web application, each is connected /// to a certain prefix-path. This is the leading part of the request URI. /// /// The base class controller can be used as a REST controller. To process /// web pages, use the derived class zeep::http::html_controller. For /// processing SOAP requests there is a zeep::http::soap_controller class. class controller { public: controller(const controller &) = delete; controller &operator=(const controller &) = delete; /// \brief constructor /// /// \param prefix_path The prefix path this controller is bound to controller(const std::string &prefix_path); virtual ~controller(); /// \brief Calls handle_request but stores a pointer to the request first virtual bool dispatch_request(asio_ns::ip::tcp::socket &socket, request &req, reply &rep); /// \brief The virtual method that actually handles the request virtual bool handle_request(request &req, reply &rep); /// \brief returns the defined prefix path [[nodiscard]] uri get_prefix() const { return m_prefix_path; } /// \brief return whether this uri request path matches our prefix [[nodiscard]] bool path_matches_prefix(const uri &path) const; /// \brief return the path with the prefix path stripped off [[nodiscard]] uri get_prefixless_path(const request &req) const; /// \brief bind this controller to \a server virtual void set_server(basic_server *server) { m_server = server; } /// \brief return the server object we're bound to [[nodiscard]] basic_server *get_server() const { return m_server; } /// \brief return the context name, if specified. Empty string otherwise [[nodiscard]] std::string get_context_name() const { return m_server ? m_server->get_context_name() : ""; } /// \brief Fill in the OPTIONS in reply \a rep for a request \a req virtual void get_options(const request &req, reply &rep); protected: /// @cond /// \brief abstract base class for mount points, derived classes should /// derive from mount_point instead of this class struct mount_point_base { mount_point_base(std::string path, std::string method) : m_path(std::move(path)) , m_method(std::move(method)) { } virtual ~mount_point_base() = default; virtual reply call(const scope &scope) = 0; void set_names() {} template void set_names(Names... names) { std::filesystem::path p = m_path; for (auto name : { names... }) m_names.emplace_back(name); // construct a regex for matching paths std::string ps; for (const auto &pp : p) { if (pp.empty()) continue; if (not ps.empty()) ps += '/'; if (pp.string().front() == '{' and pp.string().back() == '}') { auto param = pp.string().substr(1, pp.string().length() - 2); // Be carefull, this param may contain a single parameter name, or a group of allowed fixed strings. // If there is a comma, it must be the latter if (param.find(',') != std::string::npos) { std::vector options; split(options, param, ",", false); ps += '(' + join(options, "|") + ')'; } else { auto i = std::ranges::find(m_names, param); if (i == m_names.end()) { assert(false); throw std::runtime_error("Invalid path for mount point, a parameter was not found in the list of parameter names"); } size_t ni = i - m_names.begin(); m_path_params.emplace_back(m_names[ni]); ps += "([^/]*)"; } } else ps += pp.string(); } m_rx.assign(ps); } bool get_parameter(const scope &scope, const std::string &name, bool result) { try { auto v = scope.get_parameter(name).value_or("false"); result = v == "true" or v == "1" or v == "on"; } catch (const std::exception &e) { using namespace std::literals::string_literals; throw std::runtime_error("Invalid value passed for parameter "s + name); } return result; } std::string get_parameter(const scope &scope, const std::string &name, std::string result) { try { result = scope.get_parameter(name).value_or(""); } catch (const std::exception &) { using namespace std::literals::string_literals; throw std::runtime_error("Invalid value passed for parameter "s + name); } return result; } file_param get_parameter(const scope &scope, const std::string &name, file_param result) { try { result = scope.get_file_parameter(name); } catch (const std::exception &e) { using namespace std::literals::string_literals; throw std::runtime_error("Invalid value passed for parameter "s + name); } return result; } std::vector get_parameter(const scope &scope, const std::string &name, std::vector result) { try { result = scope.get_file_parameters(name); } catch (const std::exception &e) { using namespace std::literals::string_literals; throw std::runtime_error("Invalid value passed for parameter "s + name); } return result; } el::object get_parameter(const scope &scope, const std::string &name, el::object result) { try { auto p = scope.get_parameter(name); if (p.has_value()) result = el::object::parse_JSON(*p); } catch (const std::exception &e) { using namespace std::literals::string_literals; throw std::runtime_error("Invalid value passed for parameter "s + name); } return result; } template std::optional get_parameter(const scope &scope, const std::string &name, std::optional result) { try { auto v = scope.get_parameter(name); if (v.has_value()) result = zeem::value_serializer::from_string(*v); } catch (const std::exception &e) { using namespace std::literals::string_literals; throw std::runtime_error("Invalid value passed for parameter "s + name); } return result; } std::optional get_parameter(const scope &scope, const std::string &name, std::optional result) { try { result = scope.get_parameter(name); } catch (const std::exception &e) { using namespace std::literals::string_literals; throw std::runtime_error("Invalid value passed for parameter "s + name); } return result; } template requires el::has_value_serializer_v T get_parameter(const scope &scope, const std::string &name, T result) { try { auto p = scope.get_parameter(name); if (p.has_value()) result = value_serializer::from_string(*p); } catch (const std::exception &e) { using namespace std::literals::string_literals; throw std::runtime_error("Invalid value passed for parameter "s + name); } return result; } template requires zeem::has_serialize_v> or zeem::is_serializable_array_type_v> T get_parameter(const scope &scope, const std::string &name, const T & /*result*/) { el::object v; if (iequals(scope.get_header("content-type"), "application/json")) v = el::object::parse_JSON(scope.get_payload()); else { auto p = scope.get_parameter(name); if (p.has_value()) v = el::object::parse_JSON(*p); } return el::serializer::deserialize(v); } reply set_reply(std::filesystem::path &&v) { reply rep(status_type::ok); rep.set_content(new std::ifstream(std::forward(v), std::ios::binary), "application/octet-stream"); return rep; } reply set_reply(el::object &&v) { reply rep(status_type::ok); rep.set_content(std::forward(v)); return rep; } template reply set_reply(T &&v) { reply rep(status_type::ok); rep.set_content(el::serializer::serialize(std::forward(v))); return rep; } std::string m_path; std::string m_method; std::regex m_rx; std::vector m_path_params; std::vector m_names; }; template struct mount_point { }; /// \brief templated abstract base class for mount points template struct mount_point : mount_point_base { using Sig = Result (ControllerType::*)(Args...); using ArgsTuple = std::tuple>...>; using ResultType = typename std::remove_const_t>; using Callback = std::function; static constexpr size_t N = sizeof...(Args); template mount_point(std::string path, std::string method, controller *owner, Sig sig, Names... names) : mount_point_base(std::move(path), std::move(method)) { static_assert(sizeof...(Names) == sizeof...(Args), "Number of names should be equal to number of arguments of callback function"); auto *controller = dynamic_cast(owner); if (controller == nullptr) throw std::runtime_error("Invalid controller for callback"); m_callback = [controller, sig](Args... args) { return (controller->*sig)(std::move(args)...); }; set_names(names...); } reply call(const scope &scope) override { if constexpr (std::is_void_v) { std::apply(m_callback, collect_arguments(scope, std::make_index_sequence())); return reply::stock_reply(status_type::ok); } else if constexpr (std::is_same_v) return std::apply(m_callback, collect_arguments(scope, std::make_index_sequence())); else return set_reply(std::apply(m_callback, collect_arguments(scope, std::make_index_sequence()))); } template ArgsTuple collect_arguments(const scope &scope, std::index_sequence /*unused*/) { // return std::make_tuple(params.get_parameter(m_names[I])...); return std::make_tuple(get_parameter(scope, m_names[I], typename std::tuple_element_t{})...); } Callback m_callback; }; /// \brief Same, but then for callbacks that need to have a scope as first parameter /// \brief templated abstract base class for mount points template struct mount_point : mount_point_base { using Sig = Result (ControllerType::*)(const scope &, Args...); using ArgsTuple = std::tuple>...>; using ResultType = typename std::remove_const_t>; using Callback = std::function; static constexpr size_t N = sizeof...(Args); template mount_point(std::string path, std::string method, controller *owner, Sig sig, Names... names) : mount_point_base(std::move(path), std::move(method)) { static_assert(sizeof...(Names) == sizeof...(Args), "Number of names should be equal to number of arguments of callback function"); auto *controller = dynamic_cast(owner); if (controller == nullptr) throw std::runtime_error("Invalid controller for callback"); m_callback = [controller, sig](const scope &scope, Args... args) { return (controller->*sig)(scope, args...); }; set_names(names...); } reply call(const scope &scope) override { if constexpr (std::is_void_v) { std::apply(m_callback, collect_arguments(scope, std::make_index_sequence())); return reply::stock_reply(status_type::ok); } else if constexpr (std::is_same_v) return std::apply(m_callback, collect_arguments(scope, std::make_index_sequence())); else return set_reply(std::apply(m_callback, collect_arguments(scope, std::make_index_sequence()))); } template auto collect_arguments(const scope &scope, std::index_sequence /*unused*/) { return std::make_tuple(std::ref(scope), get_parameter(scope, m_names[I], typename std::tuple_element_t{})...); } Callback m_callback; }; /// @endcond /// The \a mountPoint parameter is the local part of the mount point. /// It can contain parameters enclosed in curly brackets. /// /// For example, say we need a REST call to get the status of shoppingcart /// where the browser will send: /// /// GET /ajax/cart/1234/status /// /// Our callback will look like this, for a class my_ajax_handler constructed /// with prefixPath `/ajax`: /// \code{.cpp} /// CartStatus my_ajax_handler::handle_get_status(int id); /// \endcode /// Then we mount this callback like this: /// \code{.cpp} /// map_get_request("/cart/{id}/status", &my_ajax_handler::handle_get_status, "id"); /// \endcode /// /// The number of \a names of the paramers specified should be equal to the number of /// actual arguments for the callback, otherwise the compiler will complain. /// /// Arguments not found in the path will be fetched from the payload in case of a POST /// or from the URI parameters otherwise. /// \brief map \a mountPoint in URI space to \a callback and map the arguments in this callback to parameters passed with \a names template void map_request(std::string mountPoint, std::string method, Callback callback, ArgNames... names) { m_mountpoints.emplace_back(new mount_point(std::move(mountPoint), std::move(method), this, callback, names...)); } /// \brief map a POST to \a mountPoint in URI space to \a callback and map the arguments in this callback to parameters passed with \a names template void map_post_request(std::string mountPoint, Callback callback, ArgNames... names) { map_request(std::move(mountPoint), "POST", callback, names...); } /// \brief map a PUT to \a mountPoint in URI space to \a callback and map the arguments in this callback to parameters passed with \a names template void map_put_request(std::string mountPoint, Sig callback, ArgNames... names) { map_request(std::move(mountPoint), "PUT", callback, names...); } /// \brief map a GET to \a mountPoint in URI space to \a callback and map the arguments in this callback to parameters passed with \a names template void map_get_request(std::string mountPoint, Sig callback, ArgNames... names) { map_request(std::move(mountPoint), "GET", callback, names...); } /// \brief map a DELETE to \a mountPoint in URI space to \a callback and map the arguments in this callback to parameters passed with \a names template void map_delete_request(std::string mountPoint, Sig callback, ArgNames... names) { map_request(std::move(mountPoint), "DELETE", callback, names...); } // -------------------------------------------------------------------- virtual reply call_mount_point(mount_point_base *mp, const scope &scope); virtual void init_scope(scope &scope); protected: uri m_prefix_path; basic_server *m_server = nullptr; std::list m_mountpoints; }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/daemon.hpp0000664000175000017500000001100415150027072020333 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// /// Source code specifically for Unix/Linux. /// Utility routines to build daemon processes #include "zeep/config.hpp" #include #include #include #include namespace zeep::http { class basic_server; /// \brief A class to create daemon processes easily /// /// In UNIX a daemon is a process that runs in the background. /// In the case of libzeep this is of course serving HTTP requests. /// stderr and stdout are captured and written to the log files /// specified and a process ID is store in the pid file which /// allows checking the status of a running daemon. class daemon { public: /// \brief The factory for creating server instances. using server_factory_type = std::function; /// \brief constructor with separately specified files /// /// \param factory The function object that creates server instances /// \param pid_file The file that will contain the process ID, usually in /var/run/{process_name} /// \param stdout_log_file The file that will contain the stdout log, usually in /var/log/{process_name}/access.log /// \param stderr_log_file The file that will contain the stderr log, usually in /var/log/{process_name}/error.log daemon(server_factory_type &&factory, std::string pid_file, std::string stdout_log_file, std::string stderr_log_file); /// \brief constructor with default files /// /// \param factory The function object that creates server instances /// \param name The _process name_ to use, will be used to form default file locations daemon(server_factory_type &&factory, const std::string &name); /// \brief Avoid excessive automatic restart due to failing to start up /// /// \param nr_of_restarts The max number of attempts to take to start up a daemon process /// \param within_nr_of_seconds The restart counter will only consider a failed restart if it fails /// starting up within this period of time. void set_max_restarts(int nr_of_restarts, int within_nr_of_seconds) { m_max_restarts = nr_of_restarts; m_restart_time_window = within_nr_of_seconds; } #if HTTP_HAS_UNIX_DAEMON /// \brief Start the daemon, forking off in the background with multiple preforked servers /// /// \param address The address to bind to /// \param port The port number to bind to /// \param nr_of_procs The number of worker processes to fork /// \param nr_of_threads The number of threads to pass to the server class /// \param run_as_user The user to run the forked process. Daemons are usually /// started as root and should drop their privileges as soon /// as possible. int start(std::string_view address, uint16_t port, int nr_of_procs, int nr_of_threads, const std::string &run_as_user); /// \brief Start the daemon, forking off in the background with single process /// /// \param address The address to bind to /// \param port The port number to bind to /// \param nr_of_threads The number of threads to pass to the server class /// \param run_as_user The user to run the forked process. Daemons are usually /// started as root and should drop their privileges as soon /// as possible. int start(std::string_view address, uint16_t port, int nr_of_threads, const std::string &run_as_user); /// \brief Stop a running daemon process. Returns 0 in case of successfully stopping a process. int stop(); /// \brief Returns 0 if the daemon is running int status(); /// \brief Force the running daemon to restart int reload(); #endif /// \brief Run the server without forking to the background /// /// For debugging purposes it is sometimes useful to start a server /// without forking so you can see the stdout and stderr. Often this /// is done by adding a --no-daemon flag to the program options. int run_foreground(std::string_view address, uint16_t port); private: #if HTTP_HAS_UNIX_DAEMON int daemonize(); void open_log_file(); bool run_main_loop(std::string_view address, uint16_t port, int nr_of_procs, int nr_of_threads, const std::string &run_as_user); bool pid_is_for_executable(); #endif private: server_factory_type m_factory; const std::string m_pid_file, m_stdout_log_file, m_stderr_log_file; int m_max_restarts = 5, m_restart_time_window = 10; }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/error-handler.hpp0000664000175000017500000001006615150027072021643 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the base class zeep::error_handler, the default /// creates very simple HTTP replies. Override to do something more fancy. #include "zeep/http/server.hpp" #include "zeep/http/template-processor.hpp" namespace zeep::http { /// \brief A base class for error-handler classes /// /// To handle errors decently when there are multiple controllers. class error_handler { public: error_handler(const error_handler &) = delete; error_handler &operator=(const error_handler &) = delete; virtual ~error_handler() = default; /// \brief set the server object we're bound to void set_server(basic_server *s) { m_server = s; } /// \brief get the server object we're bound to [[nodiscard]] basic_server *get_server() { return m_server; } /// \brief set the server object we're bound to [[nodiscard]] const basic_server *get_server() const { return m_server; } /// \brief Create an error reply for an exception /// /// This function is called by server with the captured exception. /// \param req The request that triggered this call /// \param eptr The captured exception, use std::rethrow_exception to use this /// \param rep Write the reply in this object /// \return Return true if the reply was created successfully [[nodiscard]] virtual bool create_error_reply(const request &req, const std::exception_ptr &eptr, reply &rep); /// \brief Create an error reply for the error containing a validation header /// /// When a authentication violation is encountered, this function is called to generate /// the appropriate reply. /// \param req The request that triggered this call /// \param rep Write the reply in this object /// \return Return true if the reply was created successfully [[nodiscard]] virtual bool create_unauth_reply(const request &req, reply &rep); /// \brief Create an error reply for the error /// /// An error should be returned with HTTP status code \a status. This method will create a default error page. /// \param req The request that triggered this call /// \param status The status code, describing the error /// \param rep Write the reply in this object /// \return Return true if the reply was created successfully [[nodiscard]] virtual bool create_error_reply(const request &req, status_type status, reply &rep); /// \brief Create an error reply for the error with an additional message for the user /// /// An error should be returned with HTTP status code \a status and additional information \a message. /// This method will create a default error page. /// \param req The request that triggered this call /// \param status The error that triggered this call /// \param message The message describing the error /// \param rep Write the reply in this object /// \return Return true if the reply was created successfully [[nodiscard]] virtual bool create_error_reply(const request &req, status_type status, std::string message, reply &rep); protected: /// \brief constructor /// /// If \a error_template is not empty, the error handler will try to /// load this XHTML template using the server's template_processor. /// If that fails or error_template is empty, a simple stock message /// is returned. error_handler(std::string error_template = "error"); basic_server *m_server = nullptr; std::string m_error_template; }; // -------------------------------------------------------------------- /// \brief A default implementation of @ref error_handler /// /// This default handler will reply with a HTML page rendered using /// a template named "error" class default_error_handler : public error_handler { public: default_error_handler(std::string error_template = "error") : error_handler(std::move(error_template)) { } [[nodiscard]] bool create_error_reply(const request &req, const std::exception_ptr &eptr, reply &rep) override; }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/header.hpp0000664000175000017500000000156215150027072020330 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the zeep::http::header class #include #include namespace zeep::http { /// The header object contains the header lines as found in a /// HTTP Request. The lines are parsed into name / value pairs. struct header { std::string name; std::string value; header() = default; header(const header &) = default; header &operator=(const header &) = default; header &operator=(header &&) = default; header(std::string name, std::string value) : name(std::move(name)) , value(std::move(value)) { } }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/html-controller.hpp0000664000175000017500000003347415150027072022234 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the zeep::controller class. This class takes /// care of handling requests that are mapped to call back functions /// and provides code to return XHTML formatted replies. #include "zeep/http/controller.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/http/scope.hpp" #include #include #include #include #include #include #include #include #include #include // -------------------------------------------------------------------- // namespace zeep::http { class basic_template_processor; // -------------------------------------------------------------------- /// \brief base class for a webapp controller that uses XHTML templates /// /// html::controller is used to create XHTML web pages based on the contents of a /// template file and the parameters passed in the request and calculated data stored /// in a scope object. class html_controller : public controller { public: html_controller(const std::string &prefix_path = "/") : controller(prefix_path) { } /// \brief return the basic_template_processor of the server basic_template_processor &get_template_processor(); /// \brief return the basic_template_processor of the server [[nodiscard]] const basic_template_processor &get_template_processor() const; /// \brief default file handling /// /// This method will ask the server for the default template processor /// to load the actual file. If there is no template processor set, /// it will therefore throw an exception. virtual reply handle_file(const scope &scope_); // -------------------------------------------------------------------- public: /// @cond template struct html_mount_point { }; /// \brief templated base class for mount points template struct html_mount_point : mount_point_base { using Sig = reply (ControllerType::*)(const scope &, Args...); using ArgsTuple = std::tuple>...>; using Callback = std::function; static constexpr size_t N = sizeof...(Args); template html_mount_point(std::string path, std::string method, html_controller *owner, Sig sig, Names... names) : mount_point_base(std::move(path), std::move(method)) { static_assert(sizeof...(Names) == sizeof...(Args), "Number of names should be equal to number of arguments of callback function"); auto *controller = dynamic_cast(owner); if (controller == nullptr) throw std::runtime_error("Invalid controller for callback"); m_callback = [controller, sig](const scope &scope_, Args... args) { return (controller->*sig)(scope_, std::move(args)...); }; set_names(names...); } reply call(const scope &scope) override { auto args = collect_arguments(scope, std::make_index_sequence()); return std::apply(m_callback, std::move(args)); } template auto collect_arguments(const scope &scope, std::index_sequence /*unused*/) { return std::make_tuple(scope, get_parameter(scope, m_names[I].c_str(), typename std::tuple_element_t{})...); } Callback m_callback; }; /// @endcond /// assign a handler function to a path in the server's namespace, new version /// Usually called like this: /// \code{.cpp} /// map("page", &my_controller::page_handler, "param"); /// \endcode /// Where page_handler is defined as: /// \code{.cpp} /// zeep::http::reply my_controller::page_handler(const scope& scope, std::optional param); /// \endcode /// Note, the first parameter is a glob pattern, similar to Ant matching rules. /// Supported operators are \*, \*\* and ?. As an addition curly bracketed optional objects are allowed /// as well as semi-colons that define separate paths. /// Also, patterns ending in / are interpreted as ending in /\*\* /// /// path | matches /// ---------------- | -------------------------------------------- /// **/*.js | matches x.js, a/b/c.js, etc /// {css,scripts}/ | matches e.g. css/1/first.css and scripts/index.js /// a;b;c | matches either a, b or c /// /// The \a mountPoint parameter is the local part of the mount point. /// It can contain parameters enclosed in curly brackets. /// /// For example, say we need a REST call to get the status of shoppingcart /// where the browser will send: /// /// GET /ajax/cart/1234/status /// /// Our callback will look like this, for a class my_ajax_handler constructed /// with prefixPath `/ajax`: /// \code{.cpp} /// CartStatus my_ajax_handler::handle_get_status(int id); /// \endcode /// Then we mount this callback like this: /// \code{.cpp} /// map_get("/cart/{id}/status", &my_ajax_handler::handle_get_status, "id"); /// \endcode /// /// The number of \a names of the paramers specified should be equal to the number of /// actual arguments for the callback, otherwise the compiler will complain. /// /// Arguments not found in the path will be fetched from the payload in case of a POST /// or from the URI parameters otherwise. /// \brief map \a mountPoint in URI space to \a callback and map the arguments in this callback to parameters passed with \a names template void map(std::string mountPoint, std::string method, Callback callback, ArgNames... names) { m_mountpoints.emplace_back(new html_mount_point(std::move(mountPoint), std::move(method), this, callback, names...)); } /// \brief map a POST to \a mountPoint in URI space to \a callback and map the arguments in this callback to parameters passed with \a names template void map_post(std::string mountPoint, Callback callback, ArgNames... names) { map(std::move(mountPoint), "POST", callback, names...); } /// \brief map a PUT to \a mountPoint in URI space to \a callback and map the arguments in this callback to parameters passed with \a names template void map_put(std::string mountPoint, Sig callback, ArgNames... names) { map(std::move(mountPoint), "PUT", callback, names...); } /// \brief map a GET to \a mountPoint in URI space to \a callback and map the arguments in this callback to parameters passed with \a names template void map_get(std::string mountPoint, Sig callback, ArgNames... names) { map(std::move(mountPoint), "GET", callback, names...); } /// \brief map a DELETE to \a mountPoint in URI space to \a callback and map the arguments in this callback to parameters passed with \a names template void map_delete(std::string mountPoint, Sig callback, ArgNames... names) { map(std::move(mountPoint), "DELETE", callback, names...); } /// \brief map a GET for files found in docroot void map_get_file(std::string mountPoint) { map(std::move(mountPoint), "GET", &html_controller::handle_file); } // -------------------------------------------------------------------- /// assign a default handler function to a path in the server's namespace /// Usually called like this: /// \code{.cpp} /// map("page", "page.html"); /// \endcode /// Or even more simple: /// \code{.cpp} /// map("page", "page"); /// \endcode /// Where page is the name of a template file. /// /// Note, the first parameter is a glob pattern, similar to Ant matching rules. Similar to the previous map calls. /// @cond struct html_mount_point_simple : public mount_point_base { html_mount_point_simple(std::string path, std::string method, std::string templateName, html_controller &controller) : mount_point_base(std::move(path), std::move(method)) , m_template(std::move(templateName)) , m_controller(controller) { } reply call(const scope &scope) override; std::string m_template; html_controller &m_controller; }; /// @endcond /// \brief map a simple page to a URI. void map_get_simple(std::string mountPoint, std::string templateName) { m_mountpoints.emplace_back(new html_mount_point_simple(std::move(mountPoint), "GET", std::move(templateName), *this)); } void map_post_simple(std::string mountPoint, std::string templateName) { m_mountpoints.emplace_back(new html_mount_point_simple(std::move(mountPoint), "POST", std::move(templateName), *this)); } void map_simple(const std::string &mountPoint, const std::string &templateName) { map_get_simple(mountPoint, templateName); map_post_simple(mountPoint, templateName); } // -------------------------------------------------------------------- protected: /// \brief Initialize the scope object /// /// Initialize scope, derived classes should call this first void init_scope(scope & /*scope*/) override; }; // -------------------------------------------------------------------- // legacy html_controller support class html_controller_v1 : public html_controller { public: html_controller_v1(const std::string &prefix_path = "/") : html_controller(prefix_path) { } /// \brief Dispatch and handle the request bool handle_request(request &req, reply &reply_) override; public: /// \brief html_controller works with 'handlers' that are methods 'mounted' on a path in the requested URI using handler_type = std::function; /// assign a handler function to a path in the server's namespace /// Usually called like this: /// \code{.cpp} /// mount(std::move(")page", std::bind(&page_handler, this, _1, _2, _3)); /// \endcode /// Where page_handler is defined as: /// \code{.cpp} /// void session_server::page_handler(const request& request, const scope& scope, reply& reply); /// \endcode /// Note, the first parameter is a glob pattern, similar to Ant matching rules. /// Supported operators are \*, \*\* and ?. As an addition curly bracketed optional objects are allowed /// as well as semi-colons that define separate paths. /// Also, patterns ending in / are interpreted as ending in /\*\* /// /// path | matches /// ---------------- | -------------------------------------------- /// **/*.js | matches x.js, a/b/c.js, etc /// {css,scripts}/ | matches e.g. css/1/first.css and scripts/index.js /// a;b;c | matches either a, b or c /// \brief mount a callback on URI path \a path for any HTTP method template void mount(std::string path, void (Class::*callback)(const request &req, const scope &sc, reply &rep)) { static_assert(std::is_base_of_v, "This call can only be used for methods in classes derived from html_controller"); mount(std::move(path), "UNDEFINED", [server = static_cast(this), callback](const request &req, const scope &sc, reply &rep) { (server->*callback)(req, sc, rep); }); } /// \brief mount a callback on URI path \a path for HTTP GET method template void mount_get(std::string path, void (Class::*callback)(const request &req, const scope &sc, reply &rep)) { static_assert(std::is_base_of_v, "This call can only be used for methods in classes derived from html_controller"); mount(std::move(path), "GET", [server = static_cast(this), callback](const request &req, const scope &sc, reply &rep) { (server->*callback)(req, sc, rep); }); } /// \brief mount a callback on URI path \a path for HTTP POST method template void mount_post(std::string path, void (Class::*callback)(const request &req, const scope &sc, reply &rep)) { static_assert(std::is_base_of_v, "This call can only be used for methods in classes derived from html_controller"); mount(std::move(path), "POST", [server = static_cast(this), callback](const request &req, const scope &sc, reply &rep) { (server->*callback)(req, sc, rep); }); } /// \brief mount a callback on URI path \a path for HTTP method \a method template void mount(std::string path, std::string method, void (Class::*callback)(const request &req, const scope &sc, reply &rep)) { static_assert(std::is_base_of_v, "This call can only be used for methods in classes derived from html_controller"); mount(std::move(path), std::move(method), [server = static_cast(this), callback](const request &req, const scope &sc, reply &rep) { (server->*callback)(req, sc, rep); }); } /// \brief mount a handler on URI path \a path for HTTP method \a method void mount(std::string path, std::string method, const handler_type &handler) { auto mpi = std::ranges::find_if(m_dispatch_table, [&path, &method](auto &mp) { return mp.path == path and (mp.method == method or mp.method == "UNDEFINED" or method == "UNDEFINED"); }); if (mpi == m_dispatch_table.end()) m_dispatch_table.emplace_back(std::move(path), std::move(method), handler); else { if (mpi->method != method) throw std::logic_error("cannot mix method UNDEFINED with something else"); mpi->handler = handler; } } private: /// @cond struct mount_point_v1 { mount_point_v1(std::string path, std::string method, handler_type handler) : path(std::move(path)) , method(std::move(method)) , handler(std::move(handler)) { } std::string path; std::string method; handler_type handler; }; using mount_point_list = std::vector; mount_point_list m_dispatch_table; /// @endcond }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/login-controller.hpp0000664000175000017500000000512415150027072022367 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the zeep::login_controller class. This class inherits from /// html::controller and provides a default for /login and /logout handling. #include "zeep/http/html-controller.hpp" #include "zeep/http/reply.hpp" #include #include // -------------------------------------------------------------------- // namespace zeep::http { class request; class scope; // -------------------------------------------------------------------- /// \brief http controller that handles login and logout /// /// There is a html version of this controller as well, that one is a bit nicer class login_controller : public html_controller { public: login_controller(const std::string &prefix_path = "/"); /// \brief bind this controller to \a server /// /// Makes sure the server has a security context and adds rules /// to this security context to allow access to the /login page void set_server(basic_server *server) override; /// \brief return the XHTML login form, subclasses can override this to provide custom login forms /// /// The document returned should have input fields for 'username', 'password' and a hidden '_csrf' /// and 'uri' value. /// /// The _csrf value is used to guard against CSRF attacks. The uri is the location to redirect to /// in case of a valid login. /// /// \param req The request that triggered this call [[nodiscard]] virtual zeem::document load_login_form(const request &req) const; /// \brief Create an error reply for an unauthorized access /// /// An error handler may call this method to create a decent login screen. /// \param req The request that triggered this call /// \param rep Write the reply in this object virtual void create_unauth_reply(const request &req, reply &rep); /// \brief Handle a GET on /login [[nodiscard]] reply handle_get_login(const scope &scope_); /// \brief Handle a POST on /login [[nodiscard]] reply handle_post_login(const scope &scope_, const std::string &username, const std::string &password); /// \brief Handle a GET or POST on /logout [[nodiscard]] reply handle_logout(const scope &scope_); /// \brief Return a reply for a redirect to the requested or default destination. [[nodiscard]] reply create_redirect_for_request(const request &req) const; }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/message-parser.hpp0000664000175000017500000001034515150027072022015 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the zeep::http::{request,reply}_parser classes that parse HTTP input/output #include "zeep/http/header.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include #include #include #include namespace zeep::http { /// An HTTP message parser with support for Transfer-Encoding: Chunked // -------------------------------------------------------------------- // A simple reimplementation of boost::tribool class parse_result { public: enum value_type { true_value, false_value, indeterminate_value } m_value; constexpr parse_result() noexcept : m_value(false_value) { } constexpr parse_result(bool init) noexcept : m_value(init ? true_value : false_value) { } constexpr parse_result(value_type init) noexcept : m_value(init) { } constexpr explicit operator bool() const noexcept { return m_value == true_value; } constexpr bool operator not() const noexcept { return m_value == false_value; } }; constexpr parse_result::value_type indeterminate = parse_result::indeterminate_value; constexpr parse_result operator and(parse_result lhs, parse_result rhs) { return (static_cast(not lhs) or static_cast(not rhs)) ? parse_result(false) : ((static_cast(lhs) and static_cast(rhs)) ? parse_result(true) : indeterminate); } constexpr parse_result operator and(parse_result lhs, bool rhs) { return rhs ? lhs : parse_result(false); } constexpr parse_result operator and(bool lhs, parse_result rhs) { return lhs ? rhs : parse_result(false); } constexpr parse_result operator or(parse_result lhs, parse_result rhs) { return (static_cast(not lhs) and static_cast(not rhs)) ? parse_result(false) : ((static_cast(lhs) or static_cast(rhs)) ? parse_result(true) : indeterminate); } constexpr parse_result operator or(parse_result lhs, bool rhs) { return rhs ? parse_result(true) : lhs; } constexpr parse_result operator or(bool lhs, parse_result rhs) { return lhs ? parse_result(true) : rhs; } constexpr parse_result operator==(parse_result lhs, parse_result::value_type rhs) { return lhs.m_value == rhs; } constexpr parse_result operator==(parse_result lhs, parse_result rhs) { return (lhs == indeterminate or rhs == indeterminate) ? indeterminate : ((lhs and rhs) or (not lhs and not rhs)); } // -------------------------------------------------------------------- /// \brief Base class for message parsers. class parser { public: virtual ~parser() = default; virtual void reset(); [[nodiscard]] parse_result parse_header_lines(char ch); [[nodiscard]] parse_result parse_chunk(char ch); [[nodiscard]] parse_result parse_footer(char ch); [[nodiscard]] parse_result parse_content(char ch); protected: using state_parser = parse_result (parser::*)(char ch); parser() = default; parse_result post_process_headers(); [[nodiscard]] bool find_last_token(const header &h, std::string_view t) const; state_parser m_parser = nullptr; int m_state = 0; unsigned int m_chunk_size = 0; std::string m_data; std::string m_uri; std::string m_method; bool m_parsing_content = false; bool m_collect_payload = true; int m_http_version_major = 1, m_http_version_minor = 0; std::vector
m_headers; std::string m_payload; }; /// \brief Parser for request messages class request_parser : public parser { public: request_parser() = default; parse_result parse(std::streambuf &text); [[nodiscard]] request get_request(); private: parse_result parse_initial_line(char ch); // parse_result post_process_headers() override; }; /// \brief Parser for reply messages class reply_parser : public parser { public: reply_parser() = default; parse_result parse(std::streambuf &text); [[nodiscard]] reply get_reply(); void reset() override; private: parse_result parse_initial_line(char ch); int m_status = 0; std::string m_status_line; }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/preforked-server.hpp0000664000175000017500000000532215150027072022363 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// Code for a preforked http server implementation #include "zeep/config.hpp" #include "zeep/http/server.hpp" #if HTTP_SERVER_HAS_PREFORK namespace zeep::http { /// \brief class to create a preforked HTTP server /// /// A preforked server means you have a master process that listens to a port /// and whenever a request comes in, the socket is passed to a client. This /// client will then process the request. /// This approach has several advantages related to security and stability. /// /// The way it works in libzeep is, you still create a server that derives /// from zeep::server (if you need a SOAP server) or zeep::http::server (if /// you only want a HTTP server). You then create a /// zeep::http::preforked_server instance passing in the /// parameters required and then call run() on this preforked server. /// /// The preforked_server class records the way your server needs to be /// constructed. When this preforked_server is run, it forks and then /// constructs your server class in the child process. /// /// Example: /// \code{.cpp} /// class my_server { /// public: /// my_server(const string& my_param); /// /// .... /// /// zeep::http::preforked_server server( /// []() { return new my_server("my param value"); } /// ); /// /// // all addresses, port 10333 and two listener threads /// std::thread t(std::bind(&zeep::http::preforked_server::run, &server, "0.0.0.0", 10333, 2)); /// /// ... // wait for signal to stop /// /// server.stop(); /// t.join(); /// \endcode class child_process; class preforked_server { public: preforked_server(const preforked_server &) = delete; preforked_server &operator=(const preforked_server &) = delete; /// \brief constructor /// /// The constructor takes one argument, a function object that creates /// a server class instance. preforked_server(std::function server_factory); virtual ~preforked_server() = default; /// \brief forks \a nr_of_child_processes children and starts listening, should be a separate thread virtual void run(std::string_view address, uint16_t port, int nr_of_child_processes, int nr_of_threads); virtual void start(); ///< signal the thread it can start listening: virtual void stop(); ///< stop the running thread private: std::function m_constructor; std::mutex m_lock; asio_ns::io_context m_io_context; }; } // namespace zeep::http #endif libzeep-7.3.2/include/zeep/http/reply.hpp0000664000175000017500000001250715150027072020234 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the zeep::http::reply class encapsulating a valid HTTP reply #include "zeep/el/object.hpp" #include "zeep/http/asio.hpp" #include "zeep/http/header.hpp" #include "zeep/http/status.hpp" #include "zeep/uri.hpp" #include namespace zeep::http { /// the class containing everything you need to generate a HTTP reply /// /// Create a HTTP reply, should be either HTTP 1.0 or 1.1 class reply { public: using cookie_directive = header; /// Create a reply, default is HTTP 1.0. Use 1.1 if you want to use keep alive e.g. reply(status_type status = status_type::ok, std::tuple version = { 1, 0 }); /// Create a reply with \a status, \a version, \a headers and a \a payload reply(status_type status, std::tuple version, std::vector
&&headers, std::string &&payload); reply(const reply &rhs); reply(reply &&rhs) noexcept { swap(*this, rhs); } ~reply() = default; reply &operator=(reply rhs) { swap(*this, rhs); return *this; } /// Swap two replies friend void swap(reply &a, reply &b) noexcept { std::swap(a.m_status, b.m_status); std::swap(a.m_version_minor, b.m_version_minor); std::swap(a.m_headers, b.m_headers); std::swap(a.m_data, b.m_data); std::swap(a.m_buffer, b.m_buffer); std::swap(a.m_content, b.m_content); std::swap(a.m_chunked, b.m_chunked); } /// Simple way to check if a reply is valid explicit operator bool() const { return m_status == status_type::ok; } /// Set the version to \a version_major . \a version_minor void set_version(int version_major, int version_minor); /// Set version to \a version void set_version(std::tuple version) { set_version(std::get<0>(version), std::get<1>(version)); } /// Add a header with name \a name and value \a value void set_header(std::string name, std::string value); /// Return the value of the header with name \a name [[nodiscard]] std::string get_header(std::string_view name) const; /// Remove the header with name \a name from the list of headers void remove_header(std::string_view name); /// Set a cookie void set_cookie(std::string_view name, const std::string &value, std::initializer_list directives = {}); /// Set a header to delete the \a name cookie void set_delete_cookie(std::string_view name); /// Get a cookie [[nodiscard]] std::string get_cookie(std::string_view name) const; /// Return the value of the header named content-type [[nodiscard]] std::string get_content_type() const { return get_header("Content-Type"); } /// Set the Content-Type header to \a type void set_content_type(std::string type) { set_header("Content-Type", std::move(type)); } /// Set the content and the content-type header depending on the content of doc (might be xhtml) void set_content(zeem::document &doc); /// Set the content and the content-type header to text/xml void set_content(const zeem::element &data); /// Set the content and the content-type header based on JSON data void set_content(const el::object &data); /// Set the content and the content-type header void set_content(std::string data, std::string contentType); /// Set the content by copying \a data and the content-type header void set_content(const char *data, size_t size, std::string contentType); /// To send a stream of data, with unknown size (using chunked transfer). /// reply takes ownership of \a data and deletes it when done. void set_content(std::istream *data, std::string contentType); /// return the content, only useful if the content was set with /// some constant string data. [[nodiscard]] const std::string &get_content() const { return m_content; } /// return the content of the reply as an array of std::string_view objects [[nodiscard]] std::vector to_buffers() const; /// for istream data, if the returned buffer array is empty, the data is done [[nodiscard]] std::vector data_to_buffers(); /// Create a standard reply based on a HTTP status code static reply stock_reply(status_type inStatus); static reply stock_reply(status_type inStatus, const std::string &info); /// Create a standard redirect reply with the specified \a location static reply redirect(const uri &location); static reply redirect(const uri &location, status_type status); void set_status(status_type status) { m_status = status; } [[nodiscard]] status_type get_status() const { return m_status; } /// return the size of the reply, only correct if the reply is fully memory based (no streams) [[nodiscard]] size_t size() const; /// Return true if the content will be sent chunked encoded [[nodiscard]] bool get_chunked() const { return m_chunked; } /// for debugging friend std::ostream &operator<<(std::ostream &os, const reply &rep); private: friend class reply_parser; status_type m_status{ status_type::bad_request }; int m_version_major = 0, m_version_minor = 0; std::vector
m_headers; std::shared_ptr m_data; std::vector m_buffer; std::string m_content; bool m_chunked = false; }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/request.hpp0000664000175000017500000002010115150027072020556 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the zeep::http::request class encapsulating a valid HTTP request #include "zeep/el/object.hpp" #include "zeep/http/asio.hpp" #include "zeep/http/header.hpp" #include "zeep/uri.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace zeep::http { // -------------------------------------------------------------------- // TODO: maarten - one day this should be able to work with temporary files /// \brief container for file parameter information /// /// Files submitted using multipart/form-data contain a filename and /// mimetype that might be interesting to the client. struct file_param { std::string filename; std::string mimetype; const char *data; size_t length; explicit operator bool() const { return data != nullptr; } }; // Some type traits to detect arrays of file_params. // Should eventually be made more generic for all request parameters template struct is_file_param_array_type : std::false_type { }; template struct is_file_param_array_type and zeem::detail::is_detected_v and not zeem::detail::is_detected_v>> { static constexpr bool value = std::is_same_v; }; template inline constexpr bool is_file_param_array_type_v = is_file_param_array_type::value; // -------------------------------------------------------------------- /// request contains the parsed original HTTP request as received /// by the server. class request { public: friend class message_parser; friend class request_parser; friend class basic_server; using param = header; // alias name using cookie_directive = header; request(std::string method, uri uri_, std::tuple version = { 1, 0 }, std::vector
&&headers = {}, std::string &&payload = {}); request(const request &req) = default; request(request &&rhs) noexcept { swap(*this, rhs); } request &operator=(request rhs) noexcept { swap(*this, rhs); return *this; } friend void swap(request &lhs, request &rhs) noexcept; /// \brief Fetch the local address from the connected socket void set_local_endpoint(std::string address, uint16_t port) { m_local_address = std::move(address); m_local_port = port; } [[nodiscard]] std::tuple get_local_endpoint() const { return { m_local_address, m_local_port }; } /// \brief Get the HTTP version requested [[nodiscard]] std::tuple get_version() const { return { m_version[0] - '0', m_version[2] - '0' }; } /// \brief Set the METHOD type (POST, GET, etc) void set_method(std::string method) { m_method = std::move(method); } /// \brief Return the METHOD type (POST, GET, etc) [[nodiscard]] const std::string &get_method() const { return m_method; } /// \brief Return the original URI as requested [[nodiscard]] const uri &get_uri() const { return m_uri; } /// \brief Set the URI void set_uri(const uri &uri_) { m_uri = uri_; } /// \brief Get the address of the connecting remote [[nodiscard]] const std::string &get_remote_address() const { return m_remote_address; } /// \brief Get the entire request line (convenience method) [[nodiscard]] std::string get_request_line() const { return m_method + ' ' + m_uri.string() + " HTTP/" + std::string(m_version.data(), m_version.data() + m_version.size()); } /// \brief Return the payload [[nodiscard]] const std::string &get_payload() const { return m_payload; } /// \brief Set the payload void set_payload(std::string payload) { m_payload = std::move(payload); } /// \brief Return the time at which this request was received [[nodiscard]] std::chrono::system_clock::time_point get_timestamp() const { return m_timestamp; } /// \brief Return the value in the Accept header for type [[nodiscard]] float get_accept(std::string_view type) const; /// \brief Check for Connection: keep-alive header [[nodiscard]] bool keep_alive() const; /// \brief Set or replace a named header void set_header(std::string name, std::string value); /// \brief Return the list of headers [[nodiscard]] auto get_headers() const { return m_headers; } /// \brief Return the named header [[nodiscard]] std::string get_header(std::string_view name) const; /// \brief Remove this header from the list of headers void remove_header(std::string_view name); /// \brief Get the credentials. This is filled in if the request was validated [[nodiscard]] el::object get_credentials() const { return m_credentials; } /// \brief Set the credentials for the request void set_credentials(el::object &&credentials) { m_credentials = std::move(credentials); } /// \brief Return the named parameter /// /// Fetch parameters from a request, either from the URL or from the payload in case /// the request contains a url-encoded or multi-part content-type header [[nodiscard]] std::optional get_parameter(std::string_view name) const; /// \brief Return the value of the parameter named \a name or the \a defaultValue if this parameter was not found [[nodiscard]] std::string get_parameter(std::string_view name, const std::string &defaultValue) const { return get_parameter(name).value_or(defaultValue); } /// \brief Return a std::multimap of name/value pairs for all parameters [[nodiscard]] std::multimap get_parameters() const; /// \brief Return the info for a file parameter with name \a name /// [[nodiscard]] file_param get_file_parameter(std::string name) const; /// \brief Return the info for all file parameters with name \a name /// [[nodiscard]] std::vector get_file_parameters(std::string name) const; /// \brief Return the value of HTTP Cookie with name \a name [[nodiscard]] std::string get_cookie(std::string_view name) const; /// \brief Set the value of HTTP Cookie with name \a name to \a value void set_cookie(const std::string &name, std::string value); /// \brief Return the content of this request in a sequence of const_buffers /// /// Can be used in code that sends HTTP requests [[nodiscard]] std::vector to_buffers() const; /// \brief Return the Accept-Language header value in the request as a std::locale object [[nodiscard]] std::locale get_locale() const; /// \brief For debugging purposes friend std::ostream &operator<<(std::ostream &io, const request &req); /// \brief suppose we want to construct requests... void set_content(std::string text, std::string contentType) { set_header("content-type", std::move(contentType)); set_header("content-length", std::to_string(text.length())); m_payload = std::move(text); } private: void set_remote_address(std::string address) { m_remote_address = std::move(address); } std::string m_local_address; ///< Local endpoint address uint16_t m_local_port = 80; ///< Local endpoint port std::string m_method = "UNDEFINED"; ///< POST, GET, etc. uri m_uri; ///< The uri as requested std::array m_version{}; ///< The version string std::vector
m_headers; ///< A list with zeep::http::header values std::string m_payload; ///< For POST requests bool m_close = false; ///< Whether 'Connection: close' was specified std::chrono::system_clock::time_point m_timestamp = std::chrono::system_clock::now(); el::object m_credentials; ///< The credentials as found in the validated access-token std::string m_remote_address; ///< Address of connecting client }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/scope.hpp0000664000175000017500000001731515150027072020214 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2025-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once #include "zeep/el/object.hpp" #include "zeep/http/header.hpp" #include "zeep/http/request.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace zeep::http { class basic_server; // -------------------------------------------------------------------- /// \brief The class that stores variables for the current scope /// /// When processing tags and in expression language constructs we use /// variables. These are stored in scope instances. class scope { public: scope &operator=(const scope &) = delete; using param = header; /// \brief simple constructor, used where there's no request available scope(); /// \brief constructor to be used only in debugging /// /// \param req The incomming HTTP request explicit scope(const request &req); /// \brief constructor used in a HTTP request context /// /// \param server The server that handles the incomming request /// \param req The incomming HTTP request scope(const basic_server &server, const request &req) : scope(&server, req) { } /// \brief constructor used in a HTTP request context /// /// \param server The server that handles the incomming request, pointer version /// \param req The incomming HTTP request scope(const basic_server *server, const request &req); /// \brief chaining constructor /// /// Scopes can be nested, introducing new namespaces /// \param next The next scope up the chain. explicit scope(const scope &next); /// \brief add a path parameter, should only be used by controller::handle_request void add_path_param(std::string name, std::string value); /// \brief Return the list of headers [[nodiscard]] auto get_headers() const { return m_req->get_headers(); } /// \brief Return the named header [[nodiscard]] std::string get_header(std::string_view name) const { return m_req->get_header(name); } /// \brief Return the payload [[nodiscard]] const std::string &get_payload() const { return m_req->get_payload(); } /// \brief Return the Accept-Language header value in the request as a std::locale object [[nodiscard]] std::locale get_locale() const { return m_req->get_locale(); } /// \brief get the optional parameter value for @a name [[nodiscard]] std::optional get_parameter(std::string_view name) const { std::optional result; auto p = std::ranges::find_if(m_path_parameters, [name](auto &pp) { return pp.name == name; }); if (p == m_path_parameters.end()) result = m_req->get_parameter(name); else if (not p->value.empty()) result = p->value; return result; } /// \brief get all parameter values for @a name [[nodiscard]] std::vector get_parameters(std::string_view name) const { auto p = std::ranges::find_if(m_path_parameters, [name](auto &pp) { return pp.name == name; }); if (p != m_path_parameters.end()) return { p->value }; else { std::vector result; for (const auto &[p_name, p_value] : m_req->get_parameters()) { if (p_name != name) continue; result.push_back(p_value); } return result; } } /// \brief get the file parameter value for @a name [[nodiscard]] file_param get_file_parameter(std::string name) const { return m_req->get_file_parameter(std::move(name)); } /// \brief get all file parameters value for @a name [[nodiscard]] std::vector get_file_parameters(std::string name) const { return m_req->get_file_parameters(std::move(name)); } /// \brief put variable in the scope with \a name and \a value template void put(const std::string &name, const T &value) requires(std::is_assignable_v) { m_data[name] = value; } /// \brief put variable in the scope with \a name and \a value void put(const std::string &name, el::object &&value) { m_data[name] = std::move(value); } /// \brief put variable in the scope with \a name and \a value void put(const std::string &name, const el::object &value) { m_data[name] = value; } /// \brief put variable of type array in the scope with \a name and values from \a begin to \a end template void put(const std::string &name, ForwardIterator begin, ForwardIterator end); /// \brief return variable with \a name /// /// \param name The name of the variable to return /// \param includeSelected If this is true, and the variable was not found as a regular variable /// in the current scope, the selected el::objects will be searched for members /// with \a name This is used by the tag processing lib v2 in _z2:el::object_ /// \return The value found or null if there was no such variable. [[nodiscard]] const el::object &lookup(const std::string &name, bool includeSelected = false) const; /// \brief return variable with \a name [[nodiscard]] const el::object &operator[](const std::string &name) const; /// \brief return variable with \a name /// /// \param name The name of the variable to return /// \return The value found or null if there was no such variable. [[nodiscard]] el::object &lookup(const std::string &name); /// \brief return variable with \a name [[nodiscard]] el::object &operator[](const std::string &name); /// \brief return the HTTP request, will throw if the scope chain was not created with a request [[nodiscard]] const request &get_request() const; /// \brief return the context_name of the server [[nodiscard]] std::string get_context_name() const; /// \brief return the credentials of the current user [[nodiscard]] el::object get_credentials() const; /// \brief returns whether the current user has role \a role [[nodiscard]] bool has_role(std::string_view role) const; /// \brief select el::object \a o , used in z2:el::object constructs void select_object(const el::object &o); /// \brief a nodeset for a selector, cached to avoid recusive expansion /// /// In tag processors it is sometimes needed to take a selection of zeem::nodes /// and reuse these, as a copy when inserting templates e.g. using node_set_type = zeem::element; /// \brief return the node_set_type with name \a name [[nodiscard]] node_set_type get_nodeset(const std::string &name) const; /// \brief store node_set_type \a nodes with name \a name void set_nodeset(const std::string &name, node_set_type &&nodes); /// \brief return whether a node_set with name \a name is stored [[nodiscard]] bool has_nodeset(const std::string &name) const { return m_nodesets.count(name) or (m_next != nullptr and m_next->has_nodeset(name)); } /// \brief get the CSRF token from the request burried in \a scope [[nodiscard]] std::string get_csrf_token() const; private: /// for debugging purposes friend std::ostream &operator<<(std::ostream &lhs, const scope &rhs); using data_map = std::map; data_map m_data; scope *m_next; unsigned m_depth; const request *m_req; const basic_server *m_server; std::vector m_path_parameters; el::object m_selected; using nodeset_map = std::map; nodeset_map m_nodesets; }; template inline void scope::put(const std::string &name, ForwardIterator begin, ForwardIterator end) { std::vector elements; while (begin != end) elements.push_back(el::object(*begin++)); m_data.emplace(name, std::move(elements)); } // -------------------------------------------------------------------- } // namespace zeep::http libzeep-7.3.2/include/zeep/http/security.hpp0000664000175000017500000002677515150027072020764 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of various classes that help in handling HTTP authentication. #include "zeep/crypto.hpp" #include "zeep/el/processing.hpp" #include "zeep/exception.hpp" #include "zeep/http/status.hpp" #include #include #include #include #include #include #include #include #include #include // -------------------------------------------------------------------- // namespace zeep::http { class reply; class request; /// \brief exception thrown when unauthorized access is detected /// /// when using authentication, this exception is thrown for unauthorized access struct unauthorized_exception : public http_status_exception { /// \brief constructor unauthorized_exception() : http_status_exception(status_type::unauthorized) { } }; // -------------------------------------------------------------------- /// \brief Base class for password encoders class password_encoder { public: virtual ~password_encoder() = default; [[nodiscard]] virtual std::string encode(const std::string &password) const = 0; [[nodiscard]] virtual bool matches(const std::string &raw_password, const std::string &stored_password) const = 0; }; // -------------------------------------------------------------------- /// \brief Implementation of @ref zeep::http::password_encoder for the PBKDF2-SHA256 algorithm /// https://en.wikipedia.org/wiki/PBKDF2 class pbkdf2_sha256_password_encoder : public password_encoder { public: static inline constexpr const char *name() { return "pbkdf2_sha256"; }; pbkdf2_sha256_password_encoder(int iterations = 30000, int key_length = 32) : m_iterations(iterations) , m_key_length(key_length) { } [[nodiscard]] std::string encode(const std::string &password) const override { using namespace std::literals; auto salt = zeep::encode_base64(zeep::random_hash()).substr(12); auto pw = zeep::encode_base64(zeep::pbkdf2_hmac_sha256(salt, password, m_iterations, m_key_length)); return "pbkdf2_sha256$" + std::to_string(m_iterations) + '$' + salt + '$' + pw; } [[nodiscard]] bool matches(const std::string &raw_password, const std::string &stored_password) const override { using namespace std::literals; bool result = false; std::regex rx(R"(pbkdf2_sha256\$(\d+)\$([^$]+)\$(.+))"); std::smatch m; if (std::regex_match(stored_password, m, rx)) { auto salt = m[2].str(); auto iterations = std::stoul(m[1]); auto test = zeep::pbkdf2_hmac_sha256(salt, raw_password, iterations, m_key_length); test = zeep::encode_base64(test); result = (m[3] == test); } return result; } private: int m_iterations, m_key_length; }; // -------------------------------------------------------------------- /// \brief simple storage class for user details, returned by user_service /// /// The user_details struct contains all the information needed to allow /// access to a resource based on username. The password is the encrypted /// password. struct user_details { user_details() = default; user_details(std::string username, std::string password, std::set roles) : username(std::move(username)) , password(std::move(password)) , roles(std::move(roles)) { } std::string username; std::string password; std::set roles; }; // -------------------------------------------------------------------- class authentication_exception : public zeep::exception { public: authentication_exception(std::string msg) : zeep::exception(std::move(msg)) { } }; /// \brief exception thrown by user_service when trying to load user_details for an unknown user class user_unknown_exception : public authentication_exception { public: user_unknown_exception() : authentication_exception("user unknown") {}; }; /// \brief exception thrown by security_context when a username/password combo is not valid class invalid_password_exception : public authentication_exception { public: invalid_password_exception() : authentication_exception("invalid password") {}; }; // -------------------------------------------------------------------- /// \brief The user service class, provding user data used for authentication /// /// This is an abstract base class for a user service. class user_service { public: user_service() = default; virtual ~user_service() = default; /// \brief return the user_details for a user named \a username [[nodiscard]] virtual user_details load_user(const std::string &username) const = 0; /// \brief return true if the credentials in \a credentials are still sufficient to access this web application [[nodiscard]] virtual bool user_is_valid(const el::object &credentials) const; /// \brief return true if a user named \a username is allowed to access this web application [[nodiscard]] virtual bool user_is_valid(const std::string &username) const; }; // -------------------------------------------------------------------- /// \brief A very simple implementation of the user service class /// /// This implementation of a user service can be used to jump start a /// project. Normally you would implement something more robust. class simple_user_service : public user_service { public: simple_user_service(std::initializer_list>> users) { for (auto const &[username, password, roles] : users) add_user(username, password, roles); } /// \brief return the user_details for a user named \a username [[nodiscard]] user_details load_user(const std::string &username) const override { user_details result = {}; auto ui = std::ranges::find_if(m_users, [username](const user_details &u) { return u.username == username; }); if (ui != m_users.end()) result = *ui; return result; } void add_user(std::string username, std::string password, std::set roles) { m_users.emplace_back(std::move(username), std::move(password), std::move(roles)); } protected: std::vector m_users; }; // -------------------------------------------------------------------- /// \brief class that manages security in a HTTP scope /// /// Add this to a HTTP server and it will check authentication. /// Access to certain paths can be limited by specifying which /// 'roles' are allowed. /// /// The authentication mechanism used is based on JSON Web Tokens, JWT in short. class security_context { public: security_context(const security_context &) = delete; security_context &operator=(const security_context &) = delete; /// \brief constructor taking a validator /// /// Create a security context for server \a s with validator \a validator and /// a flag \a defaultAccessAllowed indicating if non-matched uri's should be allowed security_context(std::string secret, user_service &users, bool defaultAccessAllowed = false); /// \brief register a custom password encoder /// /// The password encoder should derive from the abstract password encoder class above /// and also implement the name() method. template void register_password_encoder() { m_known_password_encoders.emplace_back(PWEncoder::name(), new PWEncoder()); } /// \brief Add a new rule for access /// /// A new rule will be added to the list, allowing access to \a glob_pattern /// to users having role \a role /// /// \a glob_pattern should start with a slash void add_rule(std::string glob_pattern, std::string role) { add_rule(std::move(glob_pattern), { std::move(role) }); } /// \brief Add a new rule for access /// /// A new rule will be added to the list, allowing access to \a glob_pattern /// to users having a role in \a roles /// /// If \a roles is empty, access is allowed to anyone. /// /// \a glob_pattern should start with a slash void add_rule(std::string glob_pattern, std::initializer_list roles) { assert(glob_pattern.front() == '/'); m_rules.emplace_back(rule{ std::move(glob_pattern), roles }); } /// \brief Validate the request \a req against the stored rules /// /// This method will validate the request in \a req agains the stored rules /// and will throw an exception if access is not allowed. /// The request \a req will be updated with the credentials for further use. /// If the validate CSRF is set, the CSRF token will also be validated. void validate_request(request &req) const; /// \brief Add e.g. headers to reply for an authorized request /// /// When validation succeeds, a HTTP reply is send to the user and this routine will be /// called to augment the reply with additional information. /// /// \param rep Then zeep::http::reply object that will be send to the user /// \param user The authorized user details void add_authorization_headers(reply &rep, const user_details &user); /// \brief Add e.g. headers to reply for an authorized request, with an expiration parameter /// /// When validation succeeds, a HTTP reply is send to the user and this routine will be /// called to augment the reply with additional information. /// /// \param rep Then zeep::http::reply object that will be send to the user /// \param user The authorized user details /// \param exp The maximum lifetime for the access token void add_authorization_headers(reply &rep, const user_details user, std::chrono::system_clock::duration exp); /// \brief verify the username/password combination and set a cookie in the reply in case of success /// /// When validation succeeds, add_authorization_headers is called, otherwise an exception is thrown. /// /// \param username The name for the user /// \param password The password for the user /// \param rep Then zeep::http::reply object that will be send back to the browser void verify_username_password(const std::string &username, const std::string &password, reply &rep); /// \brief verify the username/password combination and return true if valid /// /// \param username The name for the user /// \param password The password for the user /// \result True in case of valid combination [[nodiscard]] bool verify_username_password(const std::string &username, const std::string &password); /// \brief return reference to the user_service object [[nodiscard]] user_service &get_user_service() const { return m_users; } /// \brief Get or create a CSRF token for the current request /// /// Return a CSRF token. If this was not present in the request, a new will be generated /// \param req The HTTP request /// \return A std::pair containing the CSRF token and a flag indicating the token is new [[nodiscard]] std::pair get_csrf_token(request &req); /// \brief To automatically validate CSRF tokens, set this flag void set_validate_csrf(bool validate) { m_validate_csrf = validate; } [[nodiscard]] bool get_validate_csrf() const { return m_validate_csrf; } [[nodiscard]] std::chrono::system_clock::duration get_jwt_exp() const { return m_default_jwt_exp; } void set_jwt_exp(std::chrono::system_clock::duration exp) { m_default_jwt_exp = exp; } private: struct rule { std::string m_pattern; std::set m_roles; }; std::string m_secret; user_service &m_users; bool m_default_allow; bool m_validate_csrf = false; std::vector m_rules; std::vector>> m_known_password_encoders; std::chrono::system_clock::duration m_default_jwt_exp; }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/server.hpp0000664000175000017500000002054715150027072020412 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the zeep::http::server class #include "zeep/http/access-control.hpp" #include "zeep/http/asio.hpp" #include "zeep/http/template-processor.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace zeep::http { class connection; class controller; class error_handler; class reply; class request; class security_context; /// \brief The libzeep HTTP server implementation. Originally based on example code found in boost::asio. /// /// The server class is a simple, stand alone HTTP server. Call bind to make it listen to an address/port /// combination. Add controller classes to do the actual work. These controllers will be tried in the order /// at which they were added to see if they want to process a request. class basic_server { public: /// \brief Simple server, no security, no template processor basic_server(); /// \brief Simple server, no security, create default template processor with \a docroot basic_server(std::string docroot) : basic_server() { set_template_processor(new template_processor(std::move(docroot))); } /// \brief server with a security context for limited access basic_server(security_context *s_ctxt); /// \brief server with a security context for limited access, create default template processor with \a docroot basic_server(security_context *s_ctxt, std::string docroot) : basic_server(s_ctxt) { set_template_processor(new template_processor(std::move(docroot))); } basic_server(const basic_server &) = delete; basic_server &operator=(const basic_server &) = delete; virtual ~basic_server(); /// \brief Get the security context provided in the constructor [[nodiscard]] security_context &get_security_context() { return *m_security_context; } /// \brief Test if a security context was provided in the constructor [[nodiscard]] bool has_security_context() const { return m_security_context != nullptr; } /// \brief Set the set of allowed methods (default is "GET", "POST", "PUT", "OPTIONS", "HEAD", "DELETE") void set_allowed_methods(const std::set &methods) { m_allowed_methods = methods; } /// \brief Get the set of allowed methods [[nodiscard]] std::set get_allowed_methods() const { return m_allowed_methods; } /// \brief Set the access_control object void set_access_control(access_control *ac) { m_access_control.reset(ac); } /// \brief Fill in the OPTIONS for a request \a req into reply \a rep virtual void get_options_for_request(const request &req, reply &rep); /// \brief Set the CORS headers for a request \a req into reply \a rep virtual void set_access_control_headers(const request &req, reply &rep); /// \brief Set the context_name /// /// The context name is used in constructing relative URL's that start with a forward slash void set_context_name(std::string context_name) { m_context_name = std::move(context_name); } /// \brief Get the context_name /// /// The context name is used in constructing relative URL's that start with a forward slash [[nodiscard]] std::string get_context_name() const { return m_context_name; } /// \brief Add controller to the list of controllers /// /// When a request is received, the list of controllers get a chance /// of handling it, in the order of which they were added to this server. /// If none of the controller handle the request the not_found error is returned. void add_controller(controller *c); /// \brief Add an error handler /// /// Errors are handled by the error handler list. The last added handler /// is called first. void add_error_handler(error_handler *eh); /// \brief Set the template processor /// /// A template processor handles loading templates and processing /// the contents. void set_template_processor(basic_template_processor *template_processor); /// \brief Get the template processor /// /// A template processor handles loading templates and processing /// the contents. This will throw if the processor has not been set /// yet. [[nodiscard]] basic_template_processor &get_template_processor() { if (not m_template_processor) throw std::logic_error("Template processor not specified yet"); return *m_template_processor; } /// \brief Get the template processor /// /// A template processor handles loading templates and processing /// the contents. This will throw if the processor has not been set /// yet. [[nodiscard]] const basic_template_processor &get_template_processor() const { if (not m_template_processor) throw std::logic_error("Template processor not specified yet"); return *m_template_processor; } /// \brief returns whether template processor has been set [[nodiscard]] bool has_template_processor() const { return m_template_processor != nullptr; } /// \brief Bind the server to address \a address and port \a port virtual void bind(std::string_view address, unsigned short port); /// \brief Run as many as \a nr_of_threads threads simultaneously virtual void run(int nr_of_threads); /// \brief Stop all threads and stop listening virtual void stop(); /// \brief log_forwarded tells the HTTP server to use the last entry in X-Forwarded-For as client log entry void set_log_forwarded(bool v) { m_log_forwarded = v; } /// \brief returns the address as specified in bind [[nodiscard]] std::string get_address() const { return m_address; } /// \brief returns the port as specified in bind [[nodiscard]] uint16_t get_port() const { return m_port; } /// \brief get_io_context has to be public since we need it to call notify_fork from child code [[nodiscard]] virtual asio_ns::io_context &get_io_context() = 0; /// \brief get_executor has to be public since we need it to call notify_fork from child code [[nodiscard]] asio_ns::io_context::executor_type get_executor() { return get_io_context().get_executor(); } protected: /// \brief the default entry logger virtual void log_request(std::string_view client, const request &req, const reply &rep, std::chrono::system_clock::time_point start, std::string_view referer, std::string_view userAgent, std::string_view entry) noexcept; private: friend class preforked_server_base; friend class connection; virtual void handle_request(asio_ns::ip::tcp::socket &socket, request &req, reply &rep); void handle_accept(asio_system_ns::error_code ec); std::shared_ptr m_acceptor; std::list m_threads; std::shared_ptr m_new_connection; std::string m_address; uint16_t m_port = 0; bool m_log_forwarded; std::string m_context_name; /// \brief This is required for proxied servers e.g. std::unique_ptr m_security_context; std::unique_ptr m_template_processor; std::list m_controllers; std::list m_error_handlers; std::set m_allowed_methods; std::unique_ptr m_access_control; }; // -------------------------------------------------------------------- /// \brief The most often used server class, contains its own io_context. class server : public basic_server { public: /// \brief Simple server, no security, no template processor server() = default; /// \brief Simple server, no security, create default template processor with \a docroot server(std::string docroot) : basic_server(std::move(docroot)) { } /// \brief server with a security context for limited access server(security_context *s_ctxt) : basic_server(s_ctxt) { } /// \brief server with a security context for limited access, create default template processor with \a docroot server(security_context *s_ctxt, std::string docroot) : basic_server(s_ctxt, std::move(docroot)) { } ~server() override { m_io_context.stop(); } asio_ns::io_context &get_io_context() override { return m_io_context; } /// \brief Stop the server and also stop the io_context void stop() override { m_io_context.stop(); basic_server::stop(); } private: asio_ns::io_context m_io_context; }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/soap-controller.hpp0000664000175000017500000002527015150027072022225 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the zeep::soap_controller class. /// Instances of this class take care of mapping member functions to /// SOAP calls automatically converting in- and output data #include "zeep/config.hpp" #include "zeep/http/controller.hpp" #include namespace zeep::http { /// soap_envelope is a wrapper around a SOAP envelope. Use it for /// input and output of correctly formatted SOAP messages. class soap_envelope { public: soap_envelope(const soap_envelope &) = delete; soap_envelope &operator=(const soap_envelope &) = delete; /// \brief Create an empty envelope soap_envelope(); /// \brief Parse a SOAP message from the payload received from a client, /// throws an exception if the envelope is empty or invalid. soap_envelope(std::string &payload); // /// \brief Parse a SOAP message received from a client, // /// throws an exception if the envelope is empty or invalid. // envelope(zeem::document& data); /// \brief The request element as contained in the original SOAP message zeem::element &request() { return *m_request; } private: zeem::document m_payload; zeem::element *m_request; }; // -------------------------------------------------------------------- /// Wrap data into a SOAP envelope /// /// \param data The zeem::element object to wrap into the envelope /// \return A new zeem::element object containing the envelope. zeem::element make_envelope(zeem::element &&data); // zeem::element make_envelope(const zeem::element& data); /// Create a standard SOAP Fault message for the string parameter /// /// \param message The string object containing a descriptive error message. /// \return A new zeem::element object containing the fault envelope. zeem::element make_fault(std::string message); /// Create a standard SOAP Fault message for the exception object /// /// \param ex The exception object that was catched. /// \return A new zeem::element object containing the fault envelope. zeem::element make_fault(const std::exception &ex); // -------------------------------------------------------------------- /// \brief class that helps with handling SOAP requests /// /// This controller will handle SOAP requests automatically handing the packing /// and unpacking of XML envelopes. /// /// To use this, create a subclass and add some methods that should be exposed. /// Then _map_ these methods on a path that optionally contains parameter values. /// /// See the chapter on SOAP controllers in the documention for more information. class soap_controller : public controller { public: /// \brief constructor /// /// \param prefix_path This is the leading part of the request URI for each mount point /// \param service The name of the service /// \param ns This is the XML Namespace for our SOAP calls soap_controller(std::string prefix_path, std::string service, std::string ns) : controller(std::move(prefix_path)) , m_ns(std::move(ns)) , m_service(std::move(service)) { // while (m_prefix_path.front() == '/') // m_prefix_path.erase(0, 1); m_location = m_prefix_path.string(); } ~soap_controller() { for (auto mp : m_mountpoints) delete mp; } /// \brief Set the external address at which this service is visible void set_location(std::string location) { m_location = std::move(location); } /// \brief Set the service name void set_service(std::string service) { m_service = std::move(service); } /// \brief map a SOAP action to \a callback using \a names for mapping the arguments /// /// The method in \a callback should be a method of the derived class having as many /// arguments as the number of specified \a names. template void map_action(const char *actionName, Callback callback, ArgNames... names) { m_mountpoints.emplace_back(new mount_point(actionName, this, callback, names...)); } /// \brief Create a WSDL based on the registered actions zeem::element make_wsdl(); /// \brief Handle the SOAP request virtual bool handle_request(request &req, reply &reply_); protected: /// @cond using type_map = std::map; using message_map = std::map; struct mount_point_base { mount_point_base(const char *action) : m_action(action) { } virtual ~mount_point_base() {} virtual void call(const zeem::element &request, reply &reply_, std::string_view ns) = 0; virtual void describe(type_map &types, message_map &messages, zeem::element &portType, zeem::element &binding) = 0; std::string m_action; }; template struct mount_point { }; /// \brief templated abstract base class for mount points template struct mount_point : mount_point_base { using Sig = Result (ControllerType::*)(Args...); using ArgsTuple = std::tuple>...>; using Callback = std::function; static constexpr size_t N = sizeof...(Args); mount_point(const char *action, soap_controller *owner, Sig sig) : mount_point_base(action) { ControllerType *controller = dynamic_cast(owner); if (controller == nullptr) throw std::runtime_error("Invalid controller for callback"); m_callback = [controller, sig](Args... args) { return (controller->*sig)(args...); }; } template mount_point(const char *action, soap_controller *owner, Sig sig, Names... names) : mount_point(action, owner, sig) { static_assert(sizeof...(Names) == sizeof...(Args), "Number of names should be equal to number of arguments of callback function"); // for (auto name: {...names }) size_t i = 0; for (auto name : { names... }) m_names[i++] = name; } virtual void call(const zeem::element &request, reply &rep, std::string_view ns) { rep.set_status(ok); ArgsTuple args = collect_arguments(request, std::make_index_sequence()); invoke(std::move(args), rep, ns); } template , int> = 0> void invoke(ArgsTuple &&args, reply &rep, std::string_view ns) { std::apply(m_callback, std::forward(args)); zeem::element response(m_action + "Response"); response.move_to_name_space("m", ns, false, false); rep.set_content(make_envelope(std::move(response))); } template , int> = 0> void invoke(ArgsTuple &&args, reply &rep, std::string_view ns) { auto result = std::apply(m_callback, std::forward(args)); // and serialize the result back into XML zeem::element response(m_action + "Response"); zeem::serializer sr(response); sr.serialize_element(result); response.move_to_name_space("m", ns, true, true); auto envelope = make_envelope(std::move(response)); rep.set_content(std::move(envelope)); } template ArgsTuple collect_arguments(const zeem::element &request, std::index_sequence) { zeem::deserializer ds(request); return std::make_tuple(get_parameter>(ds, m_names[I])...); } template T get_parameter(zeem::deserializer &ds, const char *name) { T v = {}; ds.deserialize_element(name, v); return v; } virtual void collect_types(type_map &types, zeem::element &seq, std::string_view ns) { if constexpr (sizeof...(Args) > 0) collect_types(types, seq, ns, std::make_index_sequence()); } template void collect_types(type_map &types, zeem::element &seq, std::string_view ns, std::index_sequence /*ix*/) { (collect_type(types, seq, ns), ...); } template void collect_type(type_map &types, zeem::element &seq, std::string_view /*ns*/) { using type = typename std::tuple_element_t; zeem::schema_creator sc(types, seq); sc.add_element(m_names[I], type{}); } virtual void describe(type_map &types, message_map &messages, zeem::element &portType, zeem::element &binding) { // the request type zeem::element requestType("xsd:element", { { "name", m_action } }); auto complexType = requestType.emplace_back("xsd:complexType"); collect_types(types, complexType.emplace_back("xsd:sequence"), "ns"); types[m_action + "Request"] = requestType; // and the response type zeem::element responseType("xsd:element", { { "name", m_action + "Response" } }); if constexpr (not std::is_void_v) { auto complexType2 = responseType.emplace_back("xsd:complexType"); auto sequence = complexType2->emplace_back("xsd:sequence"); zeem::schema_creator sc(types, sequence); sc.add_element("Response", Result{}); } types[m_action + "Response"] = responseType; // now the wsdl operations zeem::element message("wsdl:message", {{ "name", m_action + "RequestMessage"}}); message.emplace_back("wsdl:part", { {"name", "parameters"}, { "element", "ns:" + m_action }}); messages[m_action + "RequestMessage"] = message; message = zeem::element("wsdl:message", {{ "name", m_action + "Message" }}); message.emplace_back("wsdl:part", {{ "name", "parameters"}, {"element", "ns:" + m_action }}); messages[m_action + "Message"] = message; // port type zeem::element operation("wsdl:operation", { { "name", m_action } }); operation.emplace_back("wsdl:input", { { "message", "ns:" + m_action + "RequestMessage" } }); operation.emplace_back("wsdl:output", { { "message", "ns:" + m_action + "Message" } }); portType.emplace_back(std::move(operation)); // and the soap operations operation = { "wsdl:operation", { { "name", m_action } } }; operation.emplace_back("soap:operation", { { "soapAction", "" }, { "style", "document" } }); zeem::element body("soap:body"); body.set_attribute("use", "literal"); zeem::element input("wsdl:input"); input.push_back(body); operation.emplace_back(std::move(input)); zeem::element output("wsdl:output"); output.emplace_back(std::move(body)); operation.emplace_back(std::move(output)); binding.emplace_back(std::move(operation)); } Callback m_callback; std::array m_names; }; std::list m_mountpoints; std::string m_ns, m_location, m_service; /// @endcond }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/status.hpp0000664000175000017500000000777515150027072020437 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once #include "zeep/exception.hpp" #include #include #include namespace zeep::http { /// Various predefined HTTP status codes enum status_type { cont = 100, ok = 200, created = 201, accepted = 202, no_content = 204, multiple_choices = 300, moved_permanently = 301, moved_temporarily = 302, see_other = 303, not_modified = 304, bad_request = 400, unauthorized = 401, forbidden = 403, not_found = 404, method_not_allowed = 405, unprocessable_entity = 422, proxy_authentication_required = 407, internal_server_error = 500, not_implemented = 501, bad_gateway = 502, service_unavailable = 503 }; /** * @brief The implementation for @ref config_category error messages * */ class status_type_impl : public std::error_category { public: /** * @brief User friendly name * * @return const char* */ [[nodiscard]] const char *name() const noexcept override { return "http status"; } /** * @brief Provide the error message as a string for the error code @a ev * * @param ev The error code * @return std::string */ [[nodiscard]] std::string message(int ev) const override { switch (static_cast(ev)) { case status_type::cont: return "Continue"; case status_type::ok: return "OK"; case status_type::created: return "Created"; case status_type::accepted: return "Accepted"; case status_type::no_content: return "No Content"; case status_type::multiple_choices: return "Multiple Choices"; case status_type::moved_permanently: return "Moved Permanently"; case status_type::moved_temporarily: return "Found"; case status_type::see_other: return "See Other"; case status_type::not_modified: return "Not Modified"; case status_type::bad_request: return "Bad Request"; case status_type::unauthorized: return "Unauthorized"; case status_type::proxy_authentication_required: return "Proxy Authentication Required"; case status_type::forbidden: return "Forbidden"; case status_type::not_found: return "Not Found"; case status_type::method_not_allowed: return "Method not allowed"; case status_type::unprocessable_entity: return "Unprocessable Entity"; case status_type::internal_server_error: return "Internal Server Error"; case status_type::not_implemented: return "Not Implemented"; case status_type::bad_gateway: return "Bad Gateway"; case status_type::service_unavailable: return "Service Unavailable"; default: return "unknown status code"; } } /** * @brief Return whether two error codes are equivalent, always false in this case * */ [[nodiscard]] bool equivalent(const std::error_code & /*code*/, int /*condition*/) const noexcept override { return false; } }; /** * @brief Return the implementation for the config_category * * @return std::error_category& */ inline std::error_category &status_type_category() { static status_type_impl instance; return instance; } inline std::error_code make_error_code(status_type e) { return { static_cast(e), status_type_category() }; } inline std::error_condition make_error_condition(status_type e) { return { static_cast(e), status_type_category() }; } /// Return the string describing the status_type in more detail std::string get_status_description(status_type status); // http exception class http_status_exception : public exception { public: http_status_exception(std::error_code ec) noexcept : exception(ec.message()) , m_code(ec) { } http_status_exception(status_type status) noexcept : zeep::http::http_status_exception(make_error_code(status)) { } [[nodiscard]] const std::error_code &code() const noexcept { return m_code; } [[nodiscard]] status_type status() const noexcept { return static_cast(m_code.value()); } private: std::error_code m_code; }; } // namespace zeep::httplibzeep-7.3.2/include/zeep/http/tag-processor.hpp0000664000175000017500000001600715150027072021670 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2019-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the zeep::http::tag_processor classes. These classes take care of processing HTML templates #include "zeep/el/processing.hpp" #include "zeep/http/scope.hpp" #include #include #include #include #include #include #include namespace zeep::http { class basic_template_processor; // -------------------------------------------------------------------- // /// \brief Abstract base class for tag_processor. /// /// Note that this class should be light in construction, we create it every time a page is rendered. class tag_processor_base { public: tag_processor_base(const tag_processor_base &) = delete; tag_processor_base &operator=(const tag_processor_base &) = delete; virtual ~tag_processor_base() = default; /// \brief process xml parses the XHTML and fills in the special tags and evaluates the el constructs /// /// This function is called to modify the xml tree in \a node /// /// \param node The XML zeem::node (element) to manipulate /// \param scope The zeep::http::scope containing the variables and request /// \param dir The path to the docroot, the directory containing the XHTML templates /// \param loader The template processor to use to load resources virtual void process_xml(zeem::node *node, const scope &scope, const std::filesystem::path &dir, basic_template_processor &loader) = 0; protected: /// \brief constructor /// /// \param ns Then XML namespace for the tags and attributes that are processed by this tag_processor tag_processor_base(std::string ns) : m_ns(std::move(ns)) { } std::string m_ns; }; // -------------------------------------------------------------------- /// \brief version two of the tag_processor in libzeep /// /// This is the new tag_processor. It is designed to look a bit like /// Thymeleaf (https://www.thymeleaf.org/) /// This processor works on attributes mostly, but can process inline /// el constructs as well. /// /// The documentention contains a section describing all the /// xml tags and attributes this processor handles. class tag_processor : public tag_processor_base { public: /// \brief default namespace for this processor static constexpr const char *ns() { return "http://www.hekkelman.com/libzeep/m2"; } /// \brief each handler returns a code telling the processor what to do with the node enum class AttributeAction { none, remove, replace }; using attr_handler = std::function; /// \brief constructor with default namespace tag_processor(const char *ns = tag_processor::ns()); /// \brief process xml parses the XHTML and fills in the special tags and evaluates the el constructs void process_xml(zeem::node *node, const scope &scope, const std::filesystem::path &dir, basic_template_processor &loader) override; /// \brief It is possible to extend this processor with custom handlers void register_attr_handler(std::string attr, attr_handler &&handler) { m_attr_handlers.emplace(std::move(attr), std::move(handler)); } protected: void process_node(zeem::node *node, const scope &scope, const std::filesystem::path &dir, basic_template_processor &loader); void process_text(zeem::node_with_text &t, const scope &scope); void post_process(zeem::element *e, const scope &parentScope, const std::filesystem::path &dir, basic_template_processor &loader); // zeem::element resolve_fragment_spec(zeem::element* node, const std::filesystem::path &dir, basic_html_controller& controller, const std::string& spec, const scope& scope); zeem::element resolve_fragment_spec(zeem::element *node, const std::filesystem::path &dir, basic_template_processor &loader, const el::object &spec, const scope &scope); zeem::element resolve_fragment_spec(zeem::element *node, const std::filesystem::path &dir, basic_template_processor &loader, const std::string &file, std::string_view selector, bool byID); // virtual void process_node_attr(zeem::node* node, const scope& scope, std::filesystem::path dir); AttributeAction process_attr_if(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader, bool unless); AttributeAction process_attr_assert(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader); AttributeAction process_attr_text(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader, bool escaped); AttributeAction process_attr_switch(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader); AttributeAction process_attr_each(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader); AttributeAction process_attr_attr(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader); AttributeAction process_attr_with(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader); AttributeAction process_attr_generic(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader); AttributeAction process_attr_boolean_value(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader); AttributeAction process_attr_inline(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader); AttributeAction process_attr_append(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader, std::string dest, bool prepend); AttributeAction process_attr_classappend(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader); AttributeAction process_attr_styleappend(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader); AttributeAction process_attr_remove(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader); enum class TemplateIncludeAction { include, insert, replace }; AttributeAction process_attr_include(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader, TemplateIncludeAction tia); std::map m_attr_handlers; zeem::document m_template; // copy of the entire document... }; } // namespace zeep::http libzeep-7.3.2/include/zeep/http/template-processor.hpp0000664000175000017500000001775215150027072022740 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// definition of the zeep::template_processor class. This class /// handles the loading and processing of XHTML files. #include "zeep/config.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/tag-processor.hpp" #include #include #include #include #include #include #include #include // -------------------------------------------------------------------- // namespace zeep::http { class request; class html_controller; // ----------------------------------------------------------------------- /// \brief abstract base class for a resource loader /// /// A resource loader is used to fetch the resources a webapp can serve /// This is an abstract base class, use either file_loader to load files /// from a 'docroot' directory or rsrc_loader to load files from compiled in /// resources. (See https://github.com/mhekkel/mrc for more info on resources) class resource_loader { public: virtual ~resource_loader() = default; resource_loader(const resource_loader &) = delete; resource_loader &operator=(const resource_loader &) = delete; /// \brief return last_write_time of \a file virtual std::filesystem::file_time_type file_time(std::filesystem::path file, std::error_code &ec) noexcept = 0; /// \brief basic loader, returns error in ec if file was not found virtual std::istream *load_file(std::string file, std::error_code &ec) noexcept = 0; protected: resource_loader() = default; }; // ----------------------------------------------------------------------- /// \brief actual implementation of a zeep::resource_loader that loads files from disk /// /// Load the resources from the directory specified in the docroot constructor parameter. class file_loader : public resource_loader { public: /// \brief constructor /// /// \param docroot Path to the directory where the 'resources' are located /// /// Throws a runtime_error if the docroot does not exist file_loader(std::filesystem::path docroot); /// \brief return last_write_time of \a file std::filesystem::file_time_type file_time(std::filesystem::path file, std::error_code &ec) noexcept override; /// \brief basic loader, returns error in ec if file was not found std::istream *load_file(std::string file, std::error_code &ec) noexcept override; private: std::filesystem::path m_docroot; }; #if USE_RSRC // ----------------------------------------------------------------------- /// \brief actual implementation of a zeep::resource_loader that loads resources from memory /// /// Load the resources from resource data created with mrc (see https://github.com/mhekkel/mrc ) class rsrc_loader : public resource_loader { public: /// \brief constructor /// /// The parameter is not used rsrc_loader(const std::filesystem::path &/* unused */); /// \brief return last_write_time of \a file std::filesystem::file_time_type file_time(std::filesystem::path file, std::error_code &ec) noexcept override; /// \brief basic loader, returns error in ec if file was not found std::istream *load_file(std::string file, std::error_code &ec) noexcept override; private: std::filesystem::file_time_type mRsrcWriteTime = {}; }; #endif // -------------------------------------------------------------------- /// \brief base class for template processors /// /// template_processor is used to create XHTML web pages based on the contents of a /// template file and the parameters passed in the request and calculated data stored /// in a scope object. class basic_template_processor { public: basic_template_processor(std::filesystem::path docroot) : m_docroot(std::move(docroot)) { } virtual ~basic_template_processor() = default; /// \brief set the docroot for this processor virtual void set_docroot(std::filesystem::path docroot); /// \brief get the current docroot of this processor [[nodiscard]] std::filesystem::path get_docroot() const { return m_docroot; } // -------------------------------------------------------------------- // tag processor support /// \brief process all the tags in this node virtual void process_tags(zeem::node *node, const scope &scope); protected: std::map> m_tag_processor_creators; /// \brief process only the tags with the specified namespace prefixes virtual void process_tags(zeem::element *node, const scope &scope, std::set registeredNamespaces); public: /// \brief Use to register a new tag_processor and couple it to a namespace template void register_tag_processor(const std::string &ns = TagProcessor::ns()) { m_tag_processor_creators.emplace(ns, [](const std::string &ns) { return new TagProcessor(ns.c_str()); }); } /// \brief Create a tag_processor [[nodiscard]] tag_processor_base *create_tag_processor(const std::string &ns) const { return m_tag_processor_creators.at(ns)(ns); } // -------------------------------------------------------------------- public: /// \brief return last_write_time of \a file virtual std::filesystem::file_time_type file_time(const std::string &file, std::error_code &ec) noexcept = 0; /// \brief return error in ec if file was not found virtual std::istream *load_file(const std::string &file, std::error_code &ec) noexcept = 0; public: /// \brief Use load_template to fetch the XHTML template file virtual void load_template(const std::string &file, zeem::document &doc); /// \brief Check if the argument \a file contains a valid reference to an XHTML template file and return the path, if any. virtual std::optional get_template_file(const std::string &file); /// \brief create a reply based on a template virtual void create_reply_from_template(const std::string &file, const scope &scope, reply &reply); /// \brief create a reply based on a template, alternate version [[nodiscard]] reply create_reply_from_template(const std::string &file, const scope &scope) { reply result = reply::stock_reply(status_type::ok); create_reply_from_template(file, scope, result); return result; } /// \brief Default handler for serving files out of our doc root [[nodiscard]] reply create_reply_for_get_file(const scope &scope); /// \brief Initialize the scope object virtual void init_scope(request &req, scope &scope); protected: std::string m_ns; std::filesystem::path m_docroot; }; // -------------------------------------------------------------------- /// \brief actual implementation of the abstract basic_template_processor template class html_template_processor : public basic_template_processor { public: html_template_processor(const std::filesystem::path &docroot = {}, bool addDefaultTagProcessors = true) : basic_template_processor(docroot) , m_loader(docroot) { if (addDefaultTagProcessors) register_tag_processor(); } ~html_template_processor() override = default; /// return last_write_time of \a file [[nodiscard]] std::filesystem::file_time_type file_time(const std::string &file, std::error_code &ec) noexcept override { return m_loader.file_time(file, ec); } // basic loader, returns error in ec if file was not found [[nodiscard]] std::istream *load_file(const std::string &file, std::error_code &ec) noexcept override { return m_loader.load_file(file, ec); } protected: Loader m_loader; }; using file_based_html_template_processor = html_template_processor; #if USE_RSRC using rsrc_based_html_template_processor = html_template_processor; #endif /// \brief the actual definition of zeep::template_processor #if WEBAPP_USES_RESOURCES and USE_RSRC using template_processor = rsrc_based_html_template_processor; #else using template_processor = file_based_html_template_processor; #endif } // namespace zeep::http libzeep-7.3.2/include/zeep/streambuf.hpp0000664000175000017500000000532515150027072020112 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2025-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// A simple std::streambuf implementation that wraps around const char* data. #include #include #include namespace zeep { // -------------------------------------------------------------------- /// \brief A simple class to use const char buffers as streambuf /// /// It is very often useful to have a streambuf class that can wrap /// wrap around a const char* pointer. class char_streambuf : public std::streambuf { public: /// \brief constructor taking a \a buffer and a \a length char_streambuf(const char *buffer, size_t length) : m_begin(buffer) , m_end(buffer + length) , m_current(buffer) { assert(std::less_equal<>()(m_begin, m_end)); } /// \brief constructor taking a \a buffer using the standard strlen to determine the length char_streambuf(const char *buffer) : m_begin(buffer) , m_end(buffer + strlen(buffer)) , m_current(buffer) { } char_streambuf(const char_streambuf &) = delete; char_streambuf &operator=(const char_streambuf &) = delete; protected: int_type underflow() override { if (m_current == m_end) return traits_type::eof(); return traits_type::to_int_type(*m_current); } int_type uflow() override { if (m_current == m_end) return traits_type::eof(); return traits_type::to_int_type(*m_current++); } int_type pbackfail(int_type ch) override { if (m_current == m_begin or (ch != traits_type::eof() and ch != m_current[-1])) return traits_type::eof(); return traits_type::to_int_type(*--m_current); } std::streamsize showmanyc() override { assert(std::less_equal<>()(m_current, m_end)); return m_end - m_current; } pos_type seekoff(std::streambuf::off_type off, std::ios_base::seekdir dir, std::ios_base::openmode /*which*/) override { switch (dir) { case std::ios_base::beg: m_current = m_begin + off; break; case std::ios_base::end: m_current = m_end + off; break; case std::ios_base::cur: m_current += off; break; default: break; } if (m_current < m_begin) m_current = m_begin; if (m_current > m_end) m_current = m_end; return m_current - m_begin; } pos_type seekpos(std::streambuf::pos_type pos, std::ios_base::openmode /*which*/) override { m_current = m_begin + pos; if (m_current < m_begin) m_current = m_begin; if (m_current > m_end) m_current = m_end; return m_current - m_begin; } private: const char *const m_begin; const char *const m_end; const char *m_current; }; } // namespace zeep libzeep-7.3.2/include/zeep/unicode-support.hpp0000664000175000017500000002235515150027072021264 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// various definitions of data types and routines used to work with Unicode encoded text #include "zeep/config.hpp" #include "zeep/exception.hpp" #include #include #include #include #include namespace zeep { /// \brief typedef of our own unicode type /// /// We use our own unicode type since wchar_t might be too small. /// This type should be able to contain a UCS4 encoded character. using unicode = char32_t; /// \brief the (admittedly limited) set of supported text encodings in libzeep /// /// these are the supported encodings. Perhaps we should extend this list a bit? enum class encoding_type { ASCII, ///< 7-bit ascii UTF8, ///< UTF-8 UTF16BE, ///< UTF-16 Big Endian UTF16LE, ///< UTF 16 Little Endian ISO88591 ///< Default single byte encoding, is a subset of utf-8 }; /// \brief utf-8 is not single byte e.g. constexpr bool is_single_byte_encoding(encoding_type enc) { return enc == encoding_type::ASCII or enc == encoding_type::ISO88591 or enc == encoding_type::UTF8; } /// manipulate UTF-8 encoded strings void append(std::string &s, unicode ch); unicode pop_last_char(std::string &s); template std::tuple get_first_char(Iter ptr, Iter end); /// \brief our own implementation of iequals: compares \a a with \a b case-insensitive /// /// This is a limited use function, works only reliably with ASCII. But that's OK. inline bool iequals(std::string_view a, std::string_view b) { bool equal = a.length() == b.length(); for (std::string::size_type i = 0; equal and i < a.length(); ++i) equal = std::toupper(a[i]) == std::toupper(b[i]); return equal; } // inlines /// \brief Append a single unicode character to an utf-8 string inline void append(std::string &s, unicode uc) { if (uc < 0x080) s += (static_cast(uc)); else if (uc < 0x0800) { char ch[2] = { static_cast(0x0c0 | (uc >> 6)), static_cast(0x080 | (uc & 0x3f)) }; s.append(ch, 2); } else if (uc < 0x00010000) { char ch[3] = { static_cast(0x0e0 | (uc >> 12)), static_cast(0x080 | ((uc >> 6) & 0x3f)), static_cast(0x080 | (uc & 0x3f)) }; s.append(ch, 3); } else { char ch[4] = { static_cast(0x0f0 | (uc >> 18)), static_cast(0x080 | ((uc >> 12) & 0x3f)), static_cast(0x080 | ((uc >> 6) & 0x3f)), static_cast(0x080 | (uc & 0x3f)) }; s.append(ch, 4); } } /// \brief remove the last unicode character from an utf-8 string inline unicode pop_last_char(std::string &s) { unicode result = 0; if (not s.empty()) { std::string::iterator ch = s.end() - 1; if ((*ch & 0x0080) == 0) { result = *ch; s.erase(ch); } else { int o = 0; do { result |= (*ch & 0x03F) << o; o += 6; --ch; } while (ch != s.begin() and (*ch & 0x0C0) == 0x080); switch (o) { case 6: result |= (*ch & 0x01F) << 6; break; case 12: result |= (*ch & 0x00F) << 12; break; case 18: result |= (*ch & 0x007) << 18; break; default:; } s.erase(ch, s.end()); } } return result; } // I used to have this comment here: // // this code only works if the input is valid utf-8 // // That was a bad idea.... // /// \brief return the first unicode and the advanced pointer from a string template std::tuple get_first_char(Iter ptr, Iter end) { unicode result = static_cast(*ptr); ++ptr; if (result > 0x07f) { unsigned char ch[3]; if ((result & 0x0E0) == 0x0C0) { if (ptr >= end) throw zeep::exception("Invalid utf-8"); ch[0] = static_cast(*ptr); ++ptr; if ((ch[0] & 0x0c0) != 0x080) throw zeep::exception("Invalid utf-8"); result = ((result & 0x01F) << 6) | (ch[0] & 0x03F); } else if ((result & 0x0F0) == 0x0E0) { if (ptr + 1 >= end) throw zeep::exception("Invalid utf-8"); ch[0] = static_cast(*ptr); ++ptr; ch[1] = static_cast(*ptr); ++ptr; if ((ch[0] & 0x0c0) != 0x080 or (ch[1] & 0x0c0) != 0x080) throw zeep::exception("Invalid utf-8"); result = ((result & 0x00F) << 12) | ((ch[0] & 0x03F) << 6) | (ch[1] & 0x03F); } else if ((result & 0x0F8) == 0x0F0) { if (ptr + 2 >= end) throw zeep::exception("Invalid utf-8"); ch[0] = static_cast(*ptr); ++ptr; ch[1] = static_cast(*ptr); ++ptr; ch[2] = static_cast(*ptr); ++ptr; if ((ch[0] & 0x0c0) != 0x080 or (ch[1] & 0x0c0) != 0x080 or (ch[2] & 0x0c0) != 0x080) throw zeep::exception("Invalid utf-8"); result = ((result & 0x007) << 18) | ((ch[0] & 0x03F) << 12) | ((ch[1] & 0x03F) << 6) | (ch[2] & 0x03F); } } return std::make_tuple(result, ptr); } // -------------------------------------------------------------------- /** * @brief Return a std::wstring for the UTF-8 encoded std::string @a s. * @note This simplistic code assumes all unicode characters will fit in a wchar_t * * @param s The input string * @return std::wstring The recoded output string */ inline std::wstring convert_s2w(std::string_view s) { auto b = s.begin(); auto e = s.end(); std::wstring result; while (b != e) { const auto &[uc, i] = get_first_char(b, e); if (not uc) break; result += static_cast(uc); b = i; } return result; } /** * @brief Return a std::string encoded in UTF-8 for the input std::wstring @a s. * @note This simplistic code assumes input contains only UCS-2 characters which are deprecated, I know. * This means UTF-16 surrogates are ruined. * * @param s The input string * @return std::string The recoded output string */ inline std::string convert_w2s(std::wstring_view s) { std::string result; for (unicode ch : s) append(result, ch); return result; } // -------------------------------------------------------------------- /** * @brief Return a hexadecimal string representation for the numerical value in @a i * * @param i The value to convert * @return std::string The hexadecimal representation */ inline std::string to_hex(uint32_t i) { char s[sizeof(i) * 2 + 3]; char *p = s + sizeof(s); *--p = 0; const char kHexChars[] = "0123456789abcdef"; while (i) { *--p = kHexChars[i & 0x0F]; i >>= 4; } *--p = 'x'; *--p = '0'; return p; } // -------------------------------------------------------------------- /// \brief A simple implementation of trim, removing white space from start and end of \a s inline void trim(std::string &s) { std::string::iterator b = s.begin(); while (b != s.end() and *b > 0 and std::isspace(*b)) ++b; std::string::iterator e = s.end(); while (e > b and *(e - 1) > 0 and std::isspace(*(e - 1))) --e; if (b != s.begin() or e != s.end()) s = { b, e }; } // -------------------------------------------------------------------- /// \brief Simplistic implementation of starts_with constexpr inline bool starts_with(std::string_view s, std::string_view p) { return s.starts_with(p); } // -------------------------------------------------------------------- /// \brief Simplistic implementation of ends_with constexpr inline bool ends_with(std::string_view s, std::string_view p) { return s.length() >= p.length() and s.ends_with(p); } // -------------------------------------------------------------------- /// \brief Simplistic implementation of contains constexpr inline bool contains(std::string_view s, std::string_view p) { return s.find(p) != std::string_view::npos; } // -------------------------------------------------------------------- /// \brief Simplistic implementation of split, with std:string in the vector inline void split(std::vector &v, std::string_view s, std::string_view p, bool compress = false) { v.clear(); std::string_view::size_type i = 0; const auto e = s.length(); while (i <= e) { auto n = s.find(p, i); if (n > e) n = e; if (n > i or compress == false) v.emplace_back(s.substr(i, n - i)); if (n == std::string_view::npos) break; i = n + p.length(); } } // -------------------------------------------------------------------- /// \brief Simplistic to_lower function, works for one byte charsets only... inline void to_lower(std::string &s, const std::locale &loc = std::locale()) { for (char &ch : s) ch = std::tolower(ch, loc); } // -------------------------------------------------------------------- /// \brief Simplistic join function template > std::string join(const Container &v, std::string_view d) { std::string result; if (not v.empty()) { auto i = v.begin(); for (;;) { result += *i++; if (i == v.end()) break; result += d; } } return result; } // -------------------------------------------------------------------- /// \brief Simplistic replace_all inline void replace_all(std::string &s, std::string_view p, std::string_view r) { std::string::size_type i = 0; for (;;) { auto l = s.find(p, i); if (l == std::string::npos) break; s.replace(l, p.length(), r); i = l + r.length(); } } } // namespace zeep libzeep-7.3.2/include/zeep/uri.hpp0000664000175000017500000002371315150027072016722 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2021-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once // A simple uri class. #include "zeep/exception.hpp" #include "zeep/unicode-support.hpp" #include namespace zeep { // -------------------------------------------------------------------- /// \brief Simply check the URI in \a uri, returns true if the uri is valid /// \param uri The URI to check bool is_valid_uri(const std::string &uri); /// \brief Check the URI in \a uri, returns true if the uri is fully qualified (has a scheme and path) /// \param uri The URI to check bool is_fully_qualified_uri(const std::string &uri); /// \brief Check the parameter in \a host is of the form HOST:PORT as required by CONNECT /// \param host The host string to check bool is_valid_connect_host(std::string_view host); // -------------------------------------------------------------------- /// \brief Decode a URL using the RFC rules /// \param s The URL that needs to be decoded /// \return The decoded URL std::string decode_url(std::string_view s); /// \brief Encode a URL using the RFC rules /// \param s The URL that needs to be encoded /// \return The encoded URL std::string encode_url(std::string_view s); // -------------------------------------------------------------------- /// \brief the exception thrown by libzeep when an invalid uri is passed to /// the uri constructor. class uri_parse_error : public zeep::exception { public: uri_parse_error() : exception("invalid uri") {}; uri_parse_error(const std::string &u) : exception("invalid uri: " + u) {}; }; // -------------------------------------------------------------------- /// \brief A class modelling a URI based on RFC 3986 https://www.rfc-editor.org/rfc/rfc3986 /// /// All components are stored separately. Scheme and host are converted to lower case. /// Path segments are stored decoded whereas query and fragment are stored encoded. /// This is to avoid double encoding and ease post processing of queries e.g. class uri { public: /// \brief constructor for an empty uri uri() = default; /// \brief constructor that parses the URI in \a s, throws exception if not valid uri(const std::string &s); /// \brief constructor that parses the URI in \a s, throws exception if not valid uri(const char *s); /// \brief constructor that parses the URI in \a s relative to the baseuri in \a base, throws exception if not valid uri(const std::string &s, const uri &base); /// \brief constructor taking two iterators into path segments, for a relative path template uri(InputIterator b, InputIterator e) requires(std::is_constructible_v) : uri() { for (auto i = b; i != e; ++i) m_path.emplace_back(*i); } ~uri() = default; uri(const uri &u) = default; uri(uri &&u) noexcept { swap(*this, u); } uri &operator=(uri u) noexcept { swap(*this, u); return *this; } friend void swap(uri &lhs, uri &rhs) noexcept; // -------------------------------------------------------------------- [[nodiscard]] bool has_scheme() const { return not m_scheme.empty(); } [[nodiscard]] bool has_authority() const { return not(m_userinfo.empty() and m_host.empty() and m_port == 0); } [[nodiscard]] bool has_path() const { return not m_path.empty(); } [[nodiscard]] bool has_query() const { return not m_query.empty(); } [[nodiscard]] bool has_fragment() const { return not m_fragment.empty(); } /// \brief Return true if url is empty [[nodiscard]] bool empty() const { return not( has_scheme() or has_authority() or has_path() or has_query() or has_fragment()); } /// \brief Return true if the path is absolute [[nodiscard]] bool is_absolute() const { return m_absolutePath; } /// \brief Return the scheme [[nodiscard]] const std::string &get_scheme() const { return m_scheme; } /// \brief Set the scheme to \a scheme void set_scheme(std::string scheme) { m_scheme = std::move(scheme); zeep::to_lower(m_scheme); } /// \brief Return the user info [[nodiscard]] const std::string &get_userinfo() const { return m_userinfo; } /// \brief Set the userinfo to \a userinfo void set_userinfo(std::string userinfo) { m_userinfo = std::move(userinfo); } /// \brief Return the host [[nodiscard]] const std::string &get_host() const { return m_host; } /// \brief Set the host to \a host void set_host(std::string host) { m_host = std::move(host); zeep::to_lower(m_host); } /// \brief Return the port [[nodiscard]] uint16_t get_port() const { return m_port; } /// \brief Set the port to \a port void set_port(uint16_t port) { m_port = port; } /// \brief Return a uri containing only the path [[nodiscard]] uri get_path() const; /// \brief Get the individual segments of the path [[nodiscard]] const std::vector &get_segments() const { return m_path; } /// \brief Set the path to \a path void set_path(const std::string &path); /// \brief Return the query [[nodiscard]] std::string get_query(bool decoded) const { return decoded ? decode_url(m_query) : m_query; } /// \brief Set the query to \a query and optionally encode it based on \a encode void set_query(std::string query, bool encode); /// \brief Return the fragment [[nodiscard]] std::string get_fragment(bool decoded) const { return decoded ? decode_url(m_fragment) : m_fragment; } /// \brief Set the fragment to \a fragment and optionally encode it based on \a encode void set_fragment(std::string fragment, bool encode); /// \brief Return the uri as a string [[nodiscard]] std::string string() const; /// \brief Return the uri as a string, without encoded characters [[nodiscard]] std::string unencoded_string() const; /// \brief Write the uri in \a u to the stream \a os friend std::ostream &operator<<(std::ostream &os, const uri &u) { u.write(os, true); return os; } /// \brief Extend path uri &operator/=(const uri &rhs); /// \brief Extend path friend uri operator/(uri lhs, const uri &rhs) { return lhs /= rhs; } /// \brief Comparison [[nodiscard]] bool operator==(const uri &rhs) const { return m_scheme == rhs.m_scheme and m_userinfo == rhs.m_userinfo and m_host == rhs.m_host and m_port == rhs.m_port and m_path == rhs.m_path and m_query == rhs.m_query and m_fragment == rhs.m_fragment and m_absolutePath == rhs.m_absolutePath; } /// \brief return the uri relative from \a base. /// /// If the scheme and authority of this and \a base /// a relative uri will be returned with the path /// of base removed from this path. [[nodiscard]] uri relative(const uri &base) const; private: enum class char_class : uint8_t { gen_delim = 1 << 0, sub_delim = 1 << 1, reserved = gen_delim | sub_delim, unreserved = 1 << 2, scheme = 1 << 3, hexdigit = 1 << 4, alpha = 1 << 5 }; static constexpr uint8_t kCharClassTable[] = { // clang-format off 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 2, 0, 2, 2, 2, 2, 2, 10, 2, 12, 12, 1, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 1, 2, 0, 2, 0, 1, 1, 60, 60, 60, 60, 60, 60, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 1, 0, 1, 0, 4, 0, 60, 60, 60, 60, 60, 60, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 0, 0, 0, 4, 0, // clang-format on }; public: static inline constexpr bool is_char_class(int ch, char_class mask) { return ch > 0 and ch < 128 and (kCharClassTable[static_cast(ch)] bitand static_cast(mask)) != 0; } static inline constexpr bool is_gen_delim(int ch) { return is_char_class(ch, char_class::gen_delim); } static inline constexpr bool is_sub_delim(int ch) { return is_char_class(ch, char_class::sub_delim); } static inline constexpr bool is_reserved(int ch) { return is_char_class(ch, char_class::reserved); } static inline constexpr bool is_unreserved(int ch) { return is_char_class(ch, char_class::unreserved); } static inline constexpr bool is_scheme_start(int ch) { return is_char_class(ch, char_class::alpha); } static inline constexpr bool is_scheme(int ch) { return is_char_class(ch, char_class::scheme); } static inline constexpr bool is_xdigit(int ch) { return is_char_class(ch, char_class::hexdigit); } friend std::string encode_url(std::string_view s); private: // -------------------------------------------------------------------- bool is_pct_encoded(const char *&cp) { bool result = false; if (*cp == '%' and is_xdigit(cp[1]) and is_xdigit(cp[2])) { result = true; cp += 2; } return result; } bool is_userinfo(const char *&cp) { return is_unreserved(*cp) or is_sub_delim(*cp) or *cp == ':' or is_pct_encoded(cp); } bool is_reg_name(const char *&cp) { return is_unreserved(*cp) or is_sub_delim(*cp) or is_pct_encoded(cp); } bool is_pchar(const char *&cp) { return is_unreserved(*cp) or is_sub_delim(*cp) or *cp == ':' or *cp == '@' or is_pct_encoded(cp); } void parse(const char *s); void transform(const uri &base); void remove_dot_segments(); const char *parse_scheme(const char *ch); const char *parse_authority(const char *ch); const char *parse_host(const char *ch); const char *parse_hierpart(const char *ch); const char *parse_segment(const char *ch); const char *parse_segment_nz(const char *ch); const char *parse_segment_nz_nc(const char *ch); void write(std::ostream &os, bool encoded) const; // -------------------------------------------------------------------- std::string m_scheme; std::string m_userinfo; std::string m_host; uint16_t m_port = 0; std::vector m_path; std::string m_query; std::string m_fragment; bool m_absolutePath = false; }; } // namespace zeep libzeep-7.3.2/modules/0000775000175000017500000000000015150027072014466 5ustar maartenmaartenlibzeep-7.3.2/modules/zeep.ixx0000664000175000017500000000005715150027072016165 0ustar maartenmaartenmodule; export module zeep; export int test; libzeep-7.3.2/rsrc/0000775000175000017500000000000015150027072013767 5ustar maartenmaartenlibzeep-7.3.2/rsrc/entities.json0000664000175000017500000043475115150027072016524 0ustar maartenmaarten{ "Æ": { "codepoints": [198], "characters": "\u00C6" }, "Æ": { "codepoints": [198], "characters": "\u00C6" }, "&": { "codepoints": [38], "characters": "\u0026" }, "&": { "codepoints": [38], "characters": "\u0026" }, "Á": { "codepoints": [193], "characters": "\u00C1" }, "Á": { "codepoints": [193], "characters": "\u00C1" }, "Ă": { "codepoints": [258], "characters": "\u0102" }, "Â": { "codepoints": [194], "characters": "\u00C2" }, "Â": { "codepoints": [194], "characters": "\u00C2" }, "А": { "codepoints": [1040], "characters": "\u0410" }, "𝔄": { "codepoints": [120068], "characters": "\uD835\uDD04" }, "À": { "codepoints": [192], "characters": "\u00C0" }, "À": { "codepoints": [192], "characters": "\u00C0" }, "Α": { "codepoints": [913], "characters": "\u0391" }, "Ā": { "codepoints": [256], "characters": "\u0100" }, "⩓": { "codepoints": [10835], "characters": "\u2A53" }, "Ą": { "codepoints": [260], "characters": "\u0104" }, "𝔸": { "codepoints": [120120], "characters": "\uD835\uDD38" }, "⁡": { "codepoints": [8289], "characters": "\u2061" }, "Å": { "codepoints": [197], "characters": "\u00C5" }, "Å": { "codepoints": [197], "characters": "\u00C5" }, "𝒜": { "codepoints": [119964], "characters": "\uD835\uDC9C" }, "≔": { "codepoints": [8788], "characters": "\u2254" }, "Ã": { "codepoints": [195], "characters": "\u00C3" }, "Ã": { "codepoints": [195], "characters": "\u00C3" }, "Ä": { "codepoints": [196], "characters": "\u00C4" }, "Ä": { "codepoints": [196], "characters": "\u00C4" }, "∖": { "codepoints": [8726], "characters": "\u2216" }, "⫧": { "codepoints": [10983], "characters": "\u2AE7" }, "⌆": { "codepoints": [8966], "characters": "\u2306" }, "Б": { "codepoints": [1041], "characters": "\u0411" }, "∵": { "codepoints": [8757], "characters": "\u2235" }, "ℬ": { "codepoints": [8492], "characters": "\u212C" }, "Β": { "codepoints": [914], "characters": "\u0392" }, "𝔅": { "codepoints": [120069], "characters": "\uD835\uDD05" }, "𝔹": { "codepoints": [120121], "characters": "\uD835\uDD39" }, "˘": { "codepoints": [728], "characters": "\u02D8" }, "ℬ": { "codepoints": [8492], "characters": "\u212C" }, "≎": { "codepoints": [8782], "characters": "\u224E" }, "Ч": { "codepoints": [1063], "characters": "\u0427" }, "©": { "codepoints": [169], "characters": "\u00A9" }, "©": { "codepoints": [169], "characters": "\u00A9" }, "Ć": { "codepoints": [262], "characters": "\u0106" }, "⋒": { "codepoints": [8914], "characters": "\u22D2" }, "ⅅ": { "codepoints": [8517], "characters": "\u2145" }, "ℭ": { "codepoints": [8493], "characters": "\u212D" }, "Č": { "codepoints": [268], "characters": "\u010C" }, "Ç": { "codepoints": [199], "characters": "\u00C7" }, "Ç": { "codepoints": [199], "characters": "\u00C7" }, "Ĉ": { "codepoints": [264], "characters": "\u0108" }, "∰": { "codepoints": [8752], "characters": "\u2230" }, "Ċ": { "codepoints": [266], "characters": "\u010A" }, "¸": { "codepoints": [184], "characters": "\u00B8" }, "·": { "codepoints": [183], "characters": "\u00B7" }, "ℭ": { "codepoints": [8493], "characters": "\u212D" }, "Χ": { "codepoints": [935], "characters": "\u03A7" }, "⊙": { "codepoints": [8857], "characters": "\u2299" }, "⊖": { "codepoints": [8854], "characters": "\u2296" }, "⊕": { "codepoints": [8853], "characters": "\u2295" }, "⊗": { "codepoints": [8855], "characters": "\u2297" }, "∲": { "codepoints": [8754], "characters": "\u2232" }, "”": { "codepoints": [8221], "characters": "\u201D" }, "’": { "codepoints": [8217], "characters": "\u2019" }, "∷": { "codepoints": [8759], "characters": "\u2237" }, "⩴": { "codepoints": [10868], "characters": "\u2A74" }, "≡": { "codepoints": [8801], "characters": "\u2261" }, "∯": { "codepoints": [8751], "characters": "\u222F" }, "∮": { "codepoints": [8750], "characters": "\u222E" }, "ℂ": { "codepoints": [8450], "characters": "\u2102" }, "∐": { "codepoints": [8720], "characters": "\u2210" }, "∳": { "codepoints": [8755], "characters": "\u2233" }, "⨯": { "codepoints": [10799], "characters": "\u2A2F" }, "𝒞": { "codepoints": [119966], "characters": "\uD835\uDC9E" }, "⋓": { "codepoints": [8915], "characters": "\u22D3" }, "≍": { "codepoints": [8781], "characters": "\u224D" }, "ⅅ": { "codepoints": [8517], "characters": "\u2145" }, "⤑": { "codepoints": [10513], "characters": "\u2911" }, "Ђ": { "codepoints": [1026], "characters": "\u0402" }, "Ѕ": { "codepoints": [1029], "characters": "\u0405" }, "Џ": { "codepoints": [1039], "characters": "\u040F" }, "‡": { "codepoints": [8225], "characters": "\u2021" }, "↡": { "codepoints": [8609], "characters": "\u21A1" }, "⫤": { "codepoints": [10980], "characters": "\u2AE4" }, "Ď": { "codepoints": [270], "characters": "\u010E" }, "Д": { "codepoints": [1044], "characters": "\u0414" }, "∇": { "codepoints": [8711], "characters": "\u2207" }, "Δ": { "codepoints": [916], "characters": "\u0394" }, "𝔇": { "codepoints": [120071], "characters": "\uD835\uDD07" }, "´": { "codepoints": [180], "characters": "\u00B4" }, "˙": { "codepoints": [729], "characters": "\u02D9" }, "˝": { "codepoints": [733], "characters": "\u02DD" }, "`": { "codepoints": [96], "characters": "\u0060" }, "˜": { "codepoints": [732], "characters": "\u02DC" }, "⋄": { "codepoints": [8900], "characters": "\u22C4" }, "ⅆ": { "codepoints": [8518], "characters": "\u2146" }, "𝔻": { "codepoints": [120123], "characters": "\uD835\uDD3B" }, "¨": { "codepoints": [168], "characters": "\u00A8" }, "⃜": { "codepoints": [8412], "characters": "\u20DC" }, "≐": { "codepoints": [8784], "characters": "\u2250" }, "∯": { "codepoints": [8751], "characters": "\u222F" }, "¨": { "codepoints": [168], "characters": "\u00A8" }, "⇓": { "codepoints": [8659], "characters": "\u21D3" }, "⇐": { "codepoints": [8656], "characters": "\u21D0" }, "⇔": { "codepoints": [8660], "characters": "\u21D4" }, "⫤": { "codepoints": [10980], "characters": "\u2AE4" }, "⟸": { "codepoints": [10232], "characters": "\u27F8" }, "⟺": { "codepoints": [10234], "characters": "\u27FA" }, "⟹": { "codepoints": [10233], "characters": "\u27F9" }, "⇒": { "codepoints": [8658], "characters": "\u21D2" }, "⊨": { "codepoints": [8872], "characters": "\u22A8" }, "⇑": { "codepoints": [8657], "characters": "\u21D1" }, "⇕": { "codepoints": [8661], "characters": "\u21D5" }, "∥": { "codepoints": [8741], "characters": "\u2225" }, "↓": { "codepoints": [8595], "characters": "\u2193" }, "⤓": { "codepoints": [10515], "characters": "\u2913" }, "⇵": { "codepoints": [8693], "characters": "\u21F5" }, "̑": { "codepoints": [785], "characters": "\u0311" }, "⥐": { "codepoints": [10576], "characters": "\u2950" }, "⥞": { "codepoints": [10590], "characters": "\u295E" }, "↽": { "codepoints": [8637], "characters": "\u21BD" }, "⥖": { "codepoints": [10582], "characters": "\u2956" }, "⥟": { "codepoints": [10591], "characters": "\u295F" }, "⇁": { "codepoints": [8641], "characters": "\u21C1" }, "⥗": { "codepoints": [10583], "characters": "\u2957" }, "⊤": { "codepoints": [8868], "characters": "\u22A4" }, "↧": { "codepoints": [8615], "characters": "\u21A7" }, "⇓": { "codepoints": [8659], "characters": "\u21D3" }, "𝒟": { "codepoints": [119967], "characters": "\uD835\uDC9F" }, "Đ": { "codepoints": [272], "characters": "\u0110" }, "Ŋ": { "codepoints": [330], "characters": "\u014A" }, "Ð": { "codepoints": [208], "characters": "\u00D0" }, "Ð": { "codepoints": [208], "characters": "\u00D0" }, "É": { "codepoints": [201], "characters": "\u00C9" }, "É": { "codepoints": [201], "characters": "\u00C9" }, "Ě": { "codepoints": [282], "characters": "\u011A" }, "Ê": { "codepoints": [202], "characters": "\u00CA" }, "Ê": { "codepoints": [202], "characters": "\u00CA" }, "Э": { "codepoints": [1069], "characters": "\u042D" }, "Ė": { "codepoints": [278], "characters": "\u0116" }, "𝔈": { "codepoints": [120072], "characters": "\uD835\uDD08" }, "È": { "codepoints": [200], "characters": "\u00C8" }, "È": { "codepoints": [200], "characters": "\u00C8" }, "∈": { "codepoints": [8712], "characters": "\u2208" }, "Ē": { "codepoints": [274], "characters": "\u0112" }, "◻": { "codepoints": [9723], "characters": "\u25FB" }, "▫": { "codepoints": [9643], "characters": "\u25AB" }, "Ę": { "codepoints": [280], "characters": "\u0118" }, "𝔼": { "codepoints": [120124], "characters": "\uD835\uDD3C" }, "Ε": { "codepoints": [917], "characters": "\u0395" }, "⩵": { "codepoints": [10869], "characters": "\u2A75" }, "≂": { "codepoints": [8770], "characters": "\u2242" }, "⇌": { "codepoints": [8652], "characters": "\u21CC" }, "ℰ": { "codepoints": [8496], "characters": "\u2130" }, "⩳": { "codepoints": [10867], "characters": "\u2A73" }, "Η": { "codepoints": [919], "characters": "\u0397" }, "Ë": { "codepoints": [203], "characters": "\u00CB" }, "Ë": { "codepoints": [203], "characters": "\u00CB" }, "∃": { "codepoints": [8707], "characters": "\u2203" }, "ⅇ": { "codepoints": [8519], "characters": "\u2147" }, "Ф": { "codepoints": [1060], "characters": "\u0424" }, "𝔉": { "codepoints": [120073], "characters": "\uD835\uDD09" }, "◼": { "codepoints": [9724], "characters": "\u25FC" }, "▪": { "codepoints": [9642], "characters": "\u25AA" }, "𝔽": { "codepoints": [120125], "characters": "\uD835\uDD3D" }, "∀": { "codepoints": [8704], "characters": "\u2200" }, "ℱ": { "codepoints": [8497], "characters": "\u2131" }, "ℱ": { "codepoints": [8497], "characters": "\u2131" }, "Ѓ": { "codepoints": [1027], "characters": "\u0403" }, ">": { "codepoints": [62], "characters": "\u003E" }, ">": { "codepoints": [62], "characters": "\u003E" }, "Γ": { "codepoints": [915], "characters": "\u0393" }, "Ϝ": { "codepoints": [988], "characters": "\u03DC" }, "Ğ": { "codepoints": [286], "characters": "\u011E" }, "Ģ": { "codepoints": [290], "characters": "\u0122" }, "Ĝ": { "codepoints": [284], "characters": "\u011C" }, "Г": { "codepoints": [1043], "characters": "\u0413" }, "Ġ": { "codepoints": [288], "characters": "\u0120" }, "𝔊": { "codepoints": [120074], "characters": "\uD835\uDD0A" }, "⋙": { "codepoints": [8921], "characters": "\u22D9" }, "𝔾": { "codepoints": [120126], "characters": "\uD835\uDD3E" }, "≥": { "codepoints": [8805], "characters": "\u2265" }, "⋛": { "codepoints": [8923], "characters": "\u22DB" }, "≧": { "codepoints": [8807], "characters": "\u2267" }, "⪢": { "codepoints": [10914], "characters": "\u2AA2" }, "≷": { "codepoints": [8823], "characters": "\u2277" }, "⩾": { "codepoints": [10878], "characters": "\u2A7E" }, "≳": { "codepoints": [8819], "characters": "\u2273" }, "𝒢": { "codepoints": [119970], "characters": "\uD835\uDCA2" }, "≫": { "codepoints": [8811], "characters": "\u226B" }, "Ъ": { "codepoints": [1066], "characters": "\u042A" }, "ˇ": { "codepoints": [711], "characters": "\u02C7" }, "^": { "codepoints": [94], "characters": "\u005E" }, "Ĥ": { "codepoints": [292], "characters": "\u0124" }, "ℌ": { "codepoints": [8460], "characters": "\u210C" }, "ℋ": { "codepoints": [8459], "characters": "\u210B" }, "ℍ": { "codepoints": [8461], "characters": "\u210D" }, "─": { "codepoints": [9472], "characters": "\u2500" }, "ℋ": { "codepoints": [8459], "characters": "\u210B" }, "Ħ": { "codepoints": [294], "characters": "\u0126" }, "≎": { "codepoints": [8782], "characters": "\u224E" }, "≏": { "codepoints": [8783], "characters": "\u224F" }, "Е": { "codepoints": [1045], "characters": "\u0415" }, "IJ": { "codepoints": [306], "characters": "\u0132" }, "Ё": { "codepoints": [1025], "characters": "\u0401" }, "Í": { "codepoints": [205], "characters": "\u00CD" }, "Í": { "codepoints": [205], "characters": "\u00CD" }, "Î": { "codepoints": [206], "characters": "\u00CE" }, "Î": { "codepoints": [206], "characters": "\u00CE" }, "И": { "codepoints": [1048], "characters": "\u0418" }, "İ": { "codepoints": [304], "characters": "\u0130" }, "ℑ": { "codepoints": [8465], "characters": "\u2111" }, "Ì": { "codepoints": [204], "characters": "\u00CC" }, "Ì": { "codepoints": [204], "characters": "\u00CC" }, "ℑ": { "codepoints": [8465], "characters": "\u2111" }, "Ī": { "codepoints": [298], "characters": "\u012A" }, "ⅈ": { "codepoints": [8520], "characters": "\u2148" }, "⇒": { "codepoints": [8658], "characters": "\u21D2" }, "∬": { "codepoints": [8748], "characters": "\u222C" }, "∫": { "codepoints": [8747], "characters": "\u222B" }, "⋂": { "codepoints": [8898], "characters": "\u22C2" }, "⁣": { "codepoints": [8291], "characters": "\u2063" }, "⁢": { "codepoints": [8290], "characters": "\u2062" }, "Į": { "codepoints": [302], "characters": "\u012E" }, "𝕀": { "codepoints": [120128], "characters": "\uD835\uDD40" }, "Ι": { "codepoints": [921], "characters": "\u0399" }, "ℐ": { "codepoints": [8464], "characters": "\u2110" }, "Ĩ": { "codepoints": [296], "characters": "\u0128" }, "І": { "codepoints": [1030], "characters": "\u0406" }, "Ï": { "codepoints": [207], "characters": "\u00CF" }, "Ï": { "codepoints": [207], "characters": "\u00CF" }, "Ĵ": { "codepoints": [308], "characters": "\u0134" }, "Й": { "codepoints": [1049], "characters": "\u0419" }, "𝔍": { "codepoints": [120077], "characters": "\uD835\uDD0D" }, "𝕁": { "codepoints": [120129], "characters": "\uD835\uDD41" }, "𝒥": { "codepoints": [119973], "characters": "\uD835\uDCA5" }, "Ј": { "codepoints": [1032], "characters": "\u0408" }, "Є": { "codepoints": [1028], "characters": "\u0404" }, "Х": { "codepoints": [1061], "characters": "\u0425" }, "Ќ": { "codepoints": [1036], "characters": "\u040C" }, "Κ": { "codepoints": [922], "characters": "\u039A" }, "Ķ": { "codepoints": [310], "characters": "\u0136" }, "К": { "codepoints": [1050], "characters": "\u041A" }, "𝔎": { "codepoints": [120078], "characters": "\uD835\uDD0E" }, "𝕂": { "codepoints": [120130], "characters": "\uD835\uDD42" }, "𝒦": { "codepoints": [119974], "characters": "\uD835\uDCA6" }, "Љ": { "codepoints": [1033], "characters": "\u0409" }, "<": { "codepoints": [60], "characters": "\u003C" }, "<": { "codepoints": [60], "characters": "\u003C" }, "Ĺ": { "codepoints": [313], "characters": "\u0139" }, "Λ": { "codepoints": [923], "characters": "\u039B" }, "⟪": { "codepoints": [10218], "characters": "\u27EA" }, "ℒ": { "codepoints": [8466], "characters": "\u2112" }, "↞": { "codepoints": [8606], "characters": "\u219E" }, "Ľ": { "codepoints": [317], "characters": "\u013D" }, "Ļ": { "codepoints": [315], "characters": "\u013B" }, "Л": { "codepoints": [1051], "characters": "\u041B" }, "⟨": { "codepoints": [10216], "characters": "\u27E8" }, "←": { "codepoints": [8592], "characters": "\u2190" }, "⇤": { "codepoints": [8676], "characters": "\u21E4" }, "⇆": { "codepoints": [8646], "characters": "\u21C6" }, "⌈": { "codepoints": [8968], "characters": "\u2308" }, "⟦": { "codepoints": [10214], "characters": "\u27E6" }, "⥡": { "codepoints": [10593], "characters": "\u2961" }, "⇃": { "codepoints": [8643], "characters": "\u21C3" }, "⥙": { "codepoints": [10585], "characters": "\u2959" }, "⌊": { "codepoints": [8970], "characters": "\u230A" }, "↔": { "codepoints": [8596], "characters": "\u2194" }, "⥎": { "codepoints": [10574], "characters": "\u294E" }, "⊣": { "codepoints": [8867], "characters": "\u22A3" }, "↤": { "codepoints": [8612], "characters": "\u21A4" }, "⥚": { "codepoints": [10586], "characters": "\u295A" }, "⊲": { "codepoints": [8882], "characters": "\u22B2" }, "⧏": { "codepoints": [10703], "characters": "\u29CF" }, "⊴": { "codepoints": [8884], "characters": "\u22B4" }, "⥑": { "codepoints": [10577], "characters": "\u2951" }, "⥠": { "codepoints": [10592], "characters": "\u2960" }, "↿": { "codepoints": [8639], "characters": "\u21BF" }, "⥘": { "codepoints": [10584], "characters": "\u2958" }, "↼": { "codepoints": [8636], "characters": "\u21BC" }, "⥒": { "codepoints": [10578], "characters": "\u2952" }, "⇐": { "codepoints": [8656], "characters": "\u21D0" }, "⇔": { "codepoints": [8660], "characters": "\u21D4" }, "⋚": { "codepoints": [8922], "characters": "\u22DA" }, "≦": { "codepoints": [8806], "characters": "\u2266" }, "≶": { "codepoints": [8822], "characters": "\u2276" }, "⪡": { "codepoints": [10913], "characters": "\u2AA1" }, "⩽": { "codepoints": [10877], "characters": "\u2A7D" }, "≲": { "codepoints": [8818], "characters": "\u2272" }, "𝔏": { "codepoints": [120079], "characters": "\uD835\uDD0F" }, "⋘": { "codepoints": [8920], "characters": "\u22D8" }, "⇚": { "codepoints": [8666], "characters": "\u21DA" }, "Ŀ": { "codepoints": [319], "characters": "\u013F" }, "⟵": { "codepoints": [10229], "characters": "\u27F5" }, "⟷": { "codepoints": [10231], "characters": "\u27F7" }, "⟶": { "codepoints": [10230], "characters": "\u27F6" }, "⟸": { "codepoints": [10232], "characters": "\u27F8" }, "⟺": { "codepoints": [10234], "characters": "\u27FA" }, "⟹": { "codepoints": [10233], "characters": "\u27F9" }, "𝕃": { "codepoints": [120131], "characters": "\uD835\uDD43" }, "↙": { "codepoints": [8601], "characters": "\u2199" }, "↘": { "codepoints": [8600], "characters": "\u2198" }, "ℒ": { "codepoints": [8466], "characters": "\u2112" }, "↰": { "codepoints": [8624], "characters": "\u21B0" }, "Ł": { "codepoints": [321], "characters": "\u0141" }, "≪": { "codepoints": [8810], "characters": "\u226A" }, "⤅": { "codepoints": [10501], "characters": "\u2905" }, "М": { "codepoints": [1052], "characters": "\u041C" }, " ": { "codepoints": [8287], "characters": "\u205F" }, "ℳ": { "codepoints": [8499], "characters": "\u2133" }, "𝔐": { "codepoints": [120080], "characters": "\uD835\uDD10" }, "∓": { "codepoints": [8723], "characters": "\u2213" }, "𝕄": { "codepoints": [120132], "characters": "\uD835\uDD44" }, "ℳ": { "codepoints": [8499], "characters": "\u2133" }, "Μ": { "codepoints": [924], "characters": "\u039C" }, "Њ": { "codepoints": [1034], "characters": "\u040A" }, "Ń": { "codepoints": [323], "characters": "\u0143" }, "Ň": { "codepoints": [327], "characters": "\u0147" }, "Ņ": { "codepoints": [325], "characters": "\u0145" }, "Н": { "codepoints": [1053], "characters": "\u041D" }, "​": { "codepoints": [8203], "characters": "\u200B" }, "​": { "codepoints": [8203], "characters": "\u200B" }, "​": { "codepoints": [8203], "characters": "\u200B" }, "​": { "codepoints": [8203], "characters": "\u200B" }, "≫": { "codepoints": [8811], "characters": "\u226B" }, "≪": { "codepoints": [8810], "characters": "\u226A" }, " ": { "codepoints": [10], "characters": "\u000A" }, "𝔑": { "codepoints": [120081], "characters": "\uD835\uDD11" }, "⁠": { "codepoints": [8288], "characters": "\u2060" }, " ": { "codepoints": [160], "characters": "\u00A0" }, "ℕ": { "codepoints": [8469], "characters": "\u2115" }, "⫬": { "codepoints": [10988], "characters": "\u2AEC" }, "≢": { "codepoints": [8802], "characters": "\u2262" }, "≭": { "codepoints": [8813], "characters": "\u226D" }, "∦": { "codepoints": [8742], "characters": "\u2226" }, "∉": { "codepoints": [8713], "characters": "\u2209" }, "≠": { "codepoints": [8800], "characters": "\u2260" }, "≂̸": { "codepoints": [8770, 824], "characters": "\u2242\u0338" }, "∄": { "codepoints": [8708], "characters": "\u2204" }, "≯": { "codepoints": [8815], "characters": "\u226F" }, "≱": { "codepoints": [8817], "characters": "\u2271" }, "≧̸": { "codepoints": [8807, 824], "characters": "\u2267\u0338" }, "≫̸": { "codepoints": [8811, 824], "characters": "\u226B\u0338" }, "≹": { "codepoints": [8825], "characters": "\u2279" }, "⩾̸": { "codepoints": [10878, 824], "characters": "\u2A7E\u0338" }, "≵": { "codepoints": [8821], "characters": "\u2275" }, "≎̸": { "codepoints": [8782, 824], "characters": "\u224E\u0338" }, "≏̸": { "codepoints": [8783, 824], "characters": "\u224F\u0338" }, "⋪": { "codepoints": [8938], "characters": "\u22EA" }, "⧏̸": { "codepoints": [10703, 824], "characters": "\u29CF\u0338" }, "⋬": { "codepoints": [8940], "characters": "\u22EC" }, "≮": { "codepoints": [8814], "characters": "\u226E" }, "≰": { "codepoints": [8816], "characters": "\u2270" }, "≸": { "codepoints": [8824], "characters": "\u2278" }, "≪̸": { "codepoints": [8810, 824], "characters": "\u226A\u0338" }, "⩽̸": { "codepoints": [10877, 824], "characters": "\u2A7D\u0338" }, "≴": { "codepoints": [8820], "characters": "\u2274" }, "⪢̸": { "codepoints": [10914, 824], "characters": "\u2AA2\u0338" }, "⪡̸": { "codepoints": [10913, 824], "characters": "\u2AA1\u0338" }, "⊀": { "codepoints": [8832], "characters": "\u2280" }, "⪯̸": { "codepoints": [10927, 824], "characters": "\u2AAF\u0338" }, "⋠": { "codepoints": [8928], "characters": "\u22E0" }, "∌": { "codepoints": [8716], "characters": "\u220C" }, "⋫": { "codepoints": [8939], "characters": "\u22EB" }, "⧐̸": { "codepoints": [10704, 824], "characters": "\u29D0\u0338" }, "⋭": { "codepoints": [8941], "characters": "\u22ED" }, "⊏̸": { "codepoints": [8847, 824], "characters": "\u228F\u0338" }, "⋢": { "codepoints": [8930], "characters": "\u22E2" }, "⊐̸": { "codepoints": [8848, 824], "characters": "\u2290\u0338" }, "⋣": { "codepoints": [8931], "characters": "\u22E3" }, "⊂⃒": { "codepoints": [8834, 8402], "characters": "\u2282\u20D2" }, "⊈": { "codepoints": [8840], "characters": "\u2288" }, "⊁": { "codepoints": [8833], "characters": "\u2281" }, "⪰̸": { "codepoints": [10928, 824], "characters": "\u2AB0\u0338" }, "⋡": { "codepoints": [8929], "characters": "\u22E1" }, "≿̸": { "codepoints": [8831, 824], "characters": "\u227F\u0338" }, "⊃⃒": { "codepoints": [8835, 8402], "characters": "\u2283\u20D2" }, "⊉": { "codepoints": [8841], "characters": "\u2289" }, "≁": { "codepoints": [8769], "characters": "\u2241" }, "≄": { "codepoints": [8772], "characters": "\u2244" }, "≇": { "codepoints": [8775], "characters": "\u2247" }, "≉": { "codepoints": [8777], "characters": "\u2249" }, "∤": { "codepoints": [8740], "characters": "\u2224" }, "𝒩": { "codepoints": [119977], "characters": "\uD835\uDCA9" }, "Ñ": { "codepoints": [209], "characters": "\u00D1" }, "Ñ": { "codepoints": [209], "characters": "\u00D1" }, "Ν": { "codepoints": [925], "characters": "\u039D" }, "Œ": { "codepoints": [338], "characters": "\u0152" }, "Ó": { "codepoints": [211], "characters": "\u00D3" }, "Ó": { "codepoints": [211], "characters": "\u00D3" }, "Ô": { "codepoints": [212], "characters": "\u00D4" }, "Ô": { "codepoints": [212], "characters": "\u00D4" }, "О": { "codepoints": [1054], "characters": "\u041E" }, "Ő": { "codepoints": [336], "characters": "\u0150" }, "𝔒": { "codepoints": [120082], "characters": "\uD835\uDD12" }, "Ò": { "codepoints": [210], "characters": "\u00D2" }, "Ò": { "codepoints": [210], "characters": "\u00D2" }, "Ō": { "codepoints": [332], "characters": "\u014C" }, "Ω": { "codepoints": [937], "characters": "\u03A9" }, "Ο": { "codepoints": [927], "characters": "\u039F" }, "𝕆": { "codepoints": [120134], "characters": "\uD835\uDD46" }, "“": { "codepoints": [8220], "characters": "\u201C" }, "‘": { "codepoints": [8216], "characters": "\u2018" }, "⩔": { "codepoints": [10836], "characters": "\u2A54" }, "𝒪": { "codepoints": [119978], "characters": "\uD835\uDCAA" }, "Ø": { "codepoints": [216], "characters": "\u00D8" }, "Ø": { "codepoints": [216], "characters": "\u00D8" }, "Õ": { "codepoints": [213], "characters": "\u00D5" }, "Õ": { "codepoints": [213], "characters": "\u00D5" }, "⨷": { "codepoints": [10807], "characters": "\u2A37" }, "Ö": { "codepoints": [214], "characters": "\u00D6" }, "Ö": { "codepoints": [214], "characters": "\u00D6" }, "‾": { "codepoints": [8254], "characters": "\u203E" }, "⏞": { "codepoints": [9182], "characters": "\u23DE" }, "⎴": { "codepoints": [9140], "characters": "\u23B4" }, "⏜": { "codepoints": [9180], "characters": "\u23DC" }, "∂": { "codepoints": [8706], "characters": "\u2202" }, "П": { "codepoints": [1055], "characters": "\u041F" }, "𝔓": { "codepoints": [120083], "characters": "\uD835\uDD13" }, "Φ": { "codepoints": [934], "characters": "\u03A6" }, "Π": { "codepoints": [928], "characters": "\u03A0" }, "±": { "codepoints": [177], "characters": "\u00B1" }, "ℌ": { "codepoints": [8460], "characters": "\u210C" }, "ℙ": { "codepoints": [8473], "characters": "\u2119" }, "⪻": { "codepoints": [10939], "characters": "\u2ABB" }, "≺": { "codepoints": [8826], "characters": "\u227A" }, "⪯": { "codepoints": [10927], "characters": "\u2AAF" }, "≼": { "codepoints": [8828], "characters": "\u227C" }, "≾": { "codepoints": [8830], "characters": "\u227E" }, "″": { "codepoints": [8243], "characters": "\u2033" }, "∏": { "codepoints": [8719], "characters": "\u220F" }, "∷": { "codepoints": [8759], "characters": "\u2237" }, "∝": { "codepoints": [8733], "characters": "\u221D" }, "𝒫": { "codepoints": [119979], "characters": "\uD835\uDCAB" }, "Ψ": { "codepoints": [936], "characters": "\u03A8" }, """: { "codepoints": [34], "characters": "\u0022" }, """: { "codepoints": [34], "characters": "\u0022" }, "𝔔": { "codepoints": [120084], "characters": "\uD835\uDD14" }, "ℚ": { "codepoints": [8474], "characters": "\u211A" }, "𝒬": { "codepoints": [119980], "characters": "\uD835\uDCAC" }, "⤐": { "codepoints": [10512], "characters": "\u2910" }, "®": { "codepoints": [174], "characters": "\u00AE" }, "®": { "codepoints": [174], "characters": "\u00AE" }, "Ŕ": { "codepoints": [340], "characters": "\u0154" }, "⟫": { "codepoints": [10219], "characters": "\u27EB" }, "↠": { "codepoints": [8608], "characters": "\u21A0" }, "⤖": { "codepoints": [10518], "characters": "\u2916" }, "Ř": { "codepoints": [344], "characters": "\u0158" }, "Ŗ": { "codepoints": [342], "characters": "\u0156" }, "Р": { "codepoints": [1056], "characters": "\u0420" }, "ℜ": { "codepoints": [8476], "characters": "\u211C" }, "∋": { "codepoints": [8715], "characters": "\u220B" }, "⇋": { "codepoints": [8651], "characters": "\u21CB" }, "⥯": { "codepoints": [10607], "characters": "\u296F" }, "ℜ": { "codepoints": [8476], "characters": "\u211C" }, "Ρ": { "codepoints": [929], "characters": "\u03A1" }, "⟩": { "codepoints": [10217], "characters": "\u27E9" }, "→": { "codepoints": [8594], "characters": "\u2192" }, "⇥": { "codepoints": [8677], "characters": "\u21E5" }, "⇄": { "codepoints": [8644], "characters": "\u21C4" }, "⌉": { "codepoints": [8969], "characters": "\u2309" }, "⟧": { "codepoints": [10215], "characters": "\u27E7" }, "⥝": { "codepoints": [10589], "characters": "\u295D" }, "⇂": { "codepoints": [8642], "characters": "\u21C2" }, "⥕": { "codepoints": [10581], "characters": "\u2955" }, "⌋": { "codepoints": [8971], "characters": "\u230B" }, "⊢": { "codepoints": [8866], "characters": "\u22A2" }, "↦": { "codepoints": [8614], "characters": "\u21A6" }, "⥛": { "codepoints": [10587], "characters": "\u295B" }, "⊳": { "codepoints": [8883], "characters": "\u22B3" }, "⧐": { "codepoints": [10704], "characters": "\u29D0" }, "⊵": { "codepoints": [8885], "characters": "\u22B5" }, "⥏": { "codepoints": [10575], "characters": "\u294F" }, "⥜": { "codepoints": [10588], "characters": "\u295C" }, "↾": { "codepoints": [8638], "characters": "\u21BE" }, "⥔": { "codepoints": [10580], "characters": "\u2954" }, "⇀": { "codepoints": [8640], "characters": "\u21C0" }, "⥓": { "codepoints": [10579], "characters": "\u2953" }, "⇒": { "codepoints": [8658], "characters": "\u21D2" }, "ℝ": { "codepoints": [8477], "characters": "\u211D" }, "⥰": { "codepoints": [10608], "characters": "\u2970" }, "⇛": { "codepoints": [8667], "characters": "\u21DB" }, "ℛ": { "codepoints": [8475], "characters": "\u211B" }, "↱": { "codepoints": [8625], "characters": "\u21B1" }, "⧴": { "codepoints": [10740], "characters": "\u29F4" }, "Щ": { "codepoints": [1065], "characters": "\u0429" }, "Ш": { "codepoints": [1064], "characters": "\u0428" }, "Ь": { "codepoints": [1068], "characters": "\u042C" }, "Ś": { "codepoints": [346], "characters": "\u015A" }, "⪼": { "codepoints": [10940], "characters": "\u2ABC" }, "Š": { "codepoints": [352], "characters": "\u0160" }, "Ş": { "codepoints": [350], "characters": "\u015E" }, "Ŝ": { "codepoints": [348], "characters": "\u015C" }, "С": { "codepoints": [1057], "characters": "\u0421" }, "𝔖": { "codepoints": [120086], "characters": "\uD835\uDD16" }, "↓": { "codepoints": [8595], "characters": "\u2193" }, "←": { "codepoints": [8592], "characters": "\u2190" }, "→": { "codepoints": [8594], "characters": "\u2192" }, "↑": { "codepoints": [8593], "characters": "\u2191" }, "Σ": { "codepoints": [931], "characters": "\u03A3" }, "∘": { "codepoints": [8728], "characters": "\u2218" }, "𝕊": { "codepoints": [120138], "characters": "\uD835\uDD4A" }, "√": { "codepoints": [8730], "characters": "\u221A" }, "□": { "codepoints": [9633], "characters": "\u25A1" }, "⊓": { "codepoints": [8851], "characters": "\u2293" }, "⊏": { "codepoints": [8847], "characters": "\u228F" }, "⊑": { "codepoints": [8849], "characters": "\u2291" }, "⊐": { "codepoints": [8848], "characters": "\u2290" }, "⊒": { "codepoints": [8850], "characters": "\u2292" }, "⊔": { "codepoints": [8852], "characters": "\u2294" }, "𝒮": { "codepoints": [119982], "characters": "\uD835\uDCAE" }, "⋆": { "codepoints": [8902], "characters": "\u22C6" }, "⋐": { "codepoints": [8912], "characters": "\u22D0" }, "⋐": { "codepoints": [8912], "characters": "\u22D0" }, "⊆": { "codepoints": [8838], "characters": "\u2286" }, "≻": { "codepoints": [8827], "characters": "\u227B" }, "⪰": { "codepoints": [10928], "characters": "\u2AB0" }, "≽": { "codepoints": [8829], "characters": "\u227D" }, "≿": { "codepoints": [8831], "characters": "\u227F" }, "∋": { "codepoints": [8715], "characters": "\u220B" }, "∑": { "codepoints": [8721], "characters": "\u2211" }, "⋑": { "codepoints": [8913], "characters": "\u22D1" }, "⊃": { "codepoints": [8835], "characters": "\u2283" }, "⊇": { "codepoints": [8839], "characters": "\u2287" }, "⋑": { "codepoints": [8913], "characters": "\u22D1" }, "Þ": { "codepoints": [222], "characters": "\u00DE" }, "Þ": { "codepoints": [222], "characters": "\u00DE" }, "™": { "codepoints": [8482], "characters": "\u2122" }, "Ћ": { "codepoints": [1035], "characters": "\u040B" }, "Ц": { "codepoints": [1062], "characters": "\u0426" }, " ": { "codepoints": [9], "characters": "\u0009" }, "Τ": { "codepoints": [932], "characters": "\u03A4" }, "Ť": { "codepoints": [356], "characters": "\u0164" }, "Ţ": { "codepoints": [354], "characters": "\u0162" }, "Т": { "codepoints": [1058], "characters": "\u0422" }, "𝔗": { "codepoints": [120087], "characters": "\uD835\uDD17" }, "∴": { "codepoints": [8756], "characters": "\u2234" }, "Θ": { "codepoints": [920], "characters": "\u0398" }, "  ": { "codepoints": [8287, 8202], "characters": "\u205F\u200A" }, " ": { "codepoints": [8201], "characters": "\u2009" }, "∼": { "codepoints": [8764], "characters": "\u223C" }, "≃": { "codepoints": [8771], "characters": "\u2243" }, "≅": { "codepoints": [8773], "characters": "\u2245" }, "≈": { "codepoints": [8776], "characters": "\u2248" }, "𝕋": { "codepoints": [120139], "characters": "\uD835\uDD4B" }, "⃛": { "codepoints": [8411], "characters": "\u20DB" }, "𝒯": { "codepoints": [119983], "characters": "\uD835\uDCAF" }, "Ŧ": { "codepoints": [358], "characters": "\u0166" }, "Ú": { "codepoints": [218], "characters": "\u00DA" }, "Ú": { "codepoints": [218], "characters": "\u00DA" }, "↟": { "codepoints": [8607], "characters": "\u219F" }, "⥉": { "codepoints": [10569], "characters": "\u2949" }, "Ў": { "codepoints": [1038], "characters": "\u040E" }, "Ŭ": { "codepoints": [364], "characters": "\u016C" }, "Û": { "codepoints": [219], "characters": "\u00DB" }, "Û": { "codepoints": [219], "characters": "\u00DB" }, "У": { "codepoints": [1059], "characters": "\u0423" }, "Ű": { "codepoints": [368], "characters": "\u0170" }, "𝔘": { "codepoints": [120088], "characters": "\uD835\uDD18" }, "Ù": { "codepoints": [217], "characters": "\u00D9" }, "Ù": { "codepoints": [217], "characters": "\u00D9" }, "Ū": { "codepoints": [362], "characters": "\u016A" }, "_": { "codepoints": [95], "characters": "\u005F" }, "⏟": { "codepoints": [9183], "characters": "\u23DF" }, "⎵": { "codepoints": [9141], "characters": "\u23B5" }, "⏝": { "codepoints": [9181], "characters": "\u23DD" }, "⋃": { "codepoints": [8899], "characters": "\u22C3" }, "⊎": { "codepoints": [8846], "characters": "\u228E" }, "Ų": { "codepoints": [370], "characters": "\u0172" }, "𝕌": { "codepoints": [120140], "characters": "\uD835\uDD4C" }, "↑": { "codepoints": [8593], "characters": "\u2191" }, "⤒": { "codepoints": [10514], "characters": "\u2912" }, "⇅": { "codepoints": [8645], "characters": "\u21C5" }, "↕": { "codepoints": [8597], "characters": "\u2195" }, "⥮": { "codepoints": [10606], "characters": "\u296E" }, "⊥": { "codepoints": [8869], "characters": "\u22A5" }, "↥": { "codepoints": [8613], "characters": "\u21A5" }, "⇑": { "codepoints": [8657], "characters": "\u21D1" }, "⇕": { "codepoints": [8661], "characters": "\u21D5" }, "↖": { "codepoints": [8598], "characters": "\u2196" }, "↗": { "codepoints": [8599], "characters": "\u2197" }, "ϒ": { "codepoints": [978], "characters": "\u03D2" }, "Υ": { "codepoints": [933], "characters": "\u03A5" }, "Ů": { "codepoints": [366], "characters": "\u016E" }, "𝒰": { "codepoints": [119984], "characters": "\uD835\uDCB0" }, "Ũ": { "codepoints": [360], "characters": "\u0168" }, "Ü": { "codepoints": [220], "characters": "\u00DC" }, "Ü": { "codepoints": [220], "characters": "\u00DC" }, "⊫": { "codepoints": [8875], "characters": "\u22AB" }, "⫫": { "codepoints": [10987], "characters": "\u2AEB" }, "В": { "codepoints": [1042], "characters": "\u0412" }, "⊩": { "codepoints": [8873], "characters": "\u22A9" }, "⫦": { "codepoints": [10982], "characters": "\u2AE6" }, "⋁": { "codepoints": [8897], "characters": "\u22C1" }, "‖": { "codepoints": [8214], "characters": "\u2016" }, "‖": { "codepoints": [8214], "characters": "\u2016" }, "∣": { "codepoints": [8739], "characters": "\u2223" }, "|": { "codepoints": [124], "characters": "\u007C" }, "❘": { "codepoints": [10072], "characters": "\u2758" }, "≀": { "codepoints": [8768], "characters": "\u2240" }, " ": { "codepoints": [8202], "characters": "\u200A" }, "𝔙": { "codepoints": [120089], "characters": "\uD835\uDD19" }, "𝕍": { "codepoints": [120141], "characters": "\uD835\uDD4D" }, "𝒱": { "codepoints": [119985], "characters": "\uD835\uDCB1" }, "⊪": { "codepoints": [8874], "characters": "\u22AA" }, "Ŵ": { "codepoints": [372], "characters": "\u0174" }, "⋀": { "codepoints": [8896], "characters": "\u22C0" }, "𝔚": { "codepoints": [120090], "characters": "\uD835\uDD1A" }, "𝕎": { "codepoints": [120142], "characters": "\uD835\uDD4E" }, "𝒲": { "codepoints": [119986], "characters": "\uD835\uDCB2" }, "𝔛": { "codepoints": [120091], "characters": "\uD835\uDD1B" }, "Ξ": { "codepoints": [926], "characters": "\u039E" }, "𝕏": { "codepoints": [120143], "characters": "\uD835\uDD4F" }, "𝒳": { "codepoints": [119987], "characters": "\uD835\uDCB3" }, "Я": { "codepoints": [1071], "characters": "\u042F" }, "Ї": { "codepoints": [1031], "characters": "\u0407" }, "Ю": { "codepoints": [1070], "characters": "\u042E" }, "Ý": { "codepoints": [221], "characters": "\u00DD" }, "Ý": { "codepoints": [221], "characters": "\u00DD" }, "Ŷ": { "codepoints": [374], "characters": "\u0176" }, "Ы": { "codepoints": [1067], "characters": "\u042B" }, "𝔜": { "codepoints": [120092], "characters": "\uD835\uDD1C" }, "𝕐": { "codepoints": [120144], "characters": "\uD835\uDD50" }, "𝒴": { "codepoints": [119988], "characters": "\uD835\uDCB4" }, "Ÿ": { "codepoints": [376], "characters": "\u0178" }, "Ж": { "codepoints": [1046], "characters": "\u0416" }, "Ź": { "codepoints": [377], "characters": "\u0179" }, "Ž": { "codepoints": [381], "characters": "\u017D" }, "З": { "codepoints": [1047], "characters": "\u0417" }, "Ż": { "codepoints": [379], "characters": "\u017B" }, "​": { "codepoints": [8203], "characters": "\u200B" }, "Ζ": { "codepoints": [918], "characters": "\u0396" }, "ℨ": { "codepoints": [8488], "characters": "\u2128" }, "ℤ": { "codepoints": [8484], "characters": "\u2124" }, "𝒵": { "codepoints": [119989], "characters": "\uD835\uDCB5" }, "á": { "codepoints": [225], "characters": "\u00E1" }, "á": { "codepoints": [225], "characters": "\u00E1" }, "ă": { "codepoints": [259], "characters": "\u0103" }, "∾": { "codepoints": [8766], "characters": "\u223E" }, "∾̳": { "codepoints": [8766, 819], "characters": "\u223E\u0333" }, "∿": { "codepoints": [8767], "characters": "\u223F" }, "â": { "codepoints": [226], "characters": "\u00E2" }, "â": { "codepoints": [226], "characters": "\u00E2" }, "´": { "codepoints": [180], "characters": "\u00B4" }, "´": { "codepoints": [180], "characters": "\u00B4" }, "а": { "codepoints": [1072], "characters": "\u0430" }, "æ": { "codepoints": [230], "characters": "\u00E6" }, "æ": { "codepoints": [230], "characters": "\u00E6" }, "⁡": { "codepoints": [8289], "characters": "\u2061" }, "𝔞": { "codepoints": [120094], "characters": "\uD835\uDD1E" }, "à": { "codepoints": [224], "characters": "\u00E0" }, "à": { "codepoints": [224], "characters": "\u00E0" }, "ℵ": { "codepoints": [8501], "characters": "\u2135" }, "ℵ": { "codepoints": [8501], "characters": "\u2135" }, "α": { "codepoints": [945], "characters": "\u03B1" }, "ā": { "codepoints": [257], "characters": "\u0101" }, "⨿": { "codepoints": [10815], "characters": "\u2A3F" }, "&": { "codepoints": [38], "characters": "\u0026" }, "&": { "codepoints": [38], "characters": "\u0026" }, "∧": { "codepoints": [8743], "characters": "\u2227" }, "⩕": { "codepoints": [10837], "characters": "\u2A55" }, "⩜": { "codepoints": [10844], "characters": "\u2A5C" }, "⩘": { "codepoints": [10840], "characters": "\u2A58" }, "⩚": { "codepoints": [10842], "characters": "\u2A5A" }, "∠": { "codepoints": [8736], "characters": "\u2220" }, "⦤": { "codepoints": [10660], "characters": "\u29A4" }, "∠": { "codepoints": [8736], "characters": "\u2220" }, "∡": { "codepoints": [8737], "characters": "\u2221" }, "⦨": { "codepoints": [10664], "characters": "\u29A8" }, "⦩": { "codepoints": [10665], "characters": "\u29A9" }, "⦪": { "codepoints": [10666], "characters": "\u29AA" }, "⦫": { "codepoints": [10667], "characters": "\u29AB" }, "⦬": { "codepoints": [10668], "characters": "\u29AC" }, "⦭": { "codepoints": [10669], "characters": "\u29AD" }, "⦮": { "codepoints": [10670], "characters": "\u29AE" }, "⦯": { "codepoints": [10671], "characters": "\u29AF" }, "∟": { "codepoints": [8735], "characters": "\u221F" }, "⊾": { "codepoints": [8894], "characters": "\u22BE" }, "⦝": { "codepoints": [10653], "characters": "\u299D" }, "∢": { "codepoints": [8738], "characters": "\u2222" }, "Å": { "codepoints": [197], "characters": "\u00C5" }, "⍼": { "codepoints": [9084], "characters": "\u237C" }, "ą": { "codepoints": [261], "characters": "\u0105" }, "𝕒": { "codepoints": [120146], "characters": "\uD835\uDD52" }, "≈": { "codepoints": [8776], "characters": "\u2248" }, "⩰": { "codepoints": [10864], "characters": "\u2A70" }, "⩯": { "codepoints": [10863], "characters": "\u2A6F" }, "≊": { "codepoints": [8778], "characters": "\u224A" }, "≋": { "codepoints": [8779], "characters": "\u224B" }, "'": { "codepoints": [39], "characters": "\u0027" }, "≈": { "codepoints": [8776], "characters": "\u2248" }, "≊": { "codepoints": [8778], "characters": "\u224A" }, "å": { "codepoints": [229], "characters": "\u00E5" }, "å": { "codepoints": [229], "characters": "\u00E5" }, "𝒶": { "codepoints": [119990], "characters": "\uD835\uDCB6" }, "*": { "codepoints": [42], "characters": "\u002A" }, "≈": { "codepoints": [8776], "characters": "\u2248" }, "≍": { "codepoints": [8781], "characters": "\u224D" }, "ã": { "codepoints": [227], "characters": "\u00E3" }, "ã": { "codepoints": [227], "characters": "\u00E3" }, "ä": { "codepoints": [228], "characters": "\u00E4" }, "ä": { "codepoints": [228], "characters": "\u00E4" }, "∳": { "codepoints": [8755], "characters": "\u2233" }, "⨑": { "codepoints": [10769], "characters": "\u2A11" }, "⫭": { "codepoints": [10989], "characters": "\u2AED" }, "≌": { "codepoints": [8780], "characters": "\u224C" }, "϶": { "codepoints": [1014], "characters": "\u03F6" }, "‵": { "codepoints": [8245], "characters": "\u2035" }, "∽": { "codepoints": [8765], "characters": "\u223D" }, "⋍": { "codepoints": [8909], "characters": "\u22CD" }, "⊽": { "codepoints": [8893], "characters": "\u22BD" }, "⌅": { "codepoints": [8965], "characters": "\u2305" }, "⌅": { "codepoints": [8965], "characters": "\u2305" }, "⎵": { "codepoints": [9141], "characters": "\u23B5" }, "⎶": { "codepoints": [9142], "characters": "\u23B6" }, "≌": { "codepoints": [8780], "characters": "\u224C" }, "б": { "codepoints": [1073], "characters": "\u0431" }, "„": { "codepoints": [8222], "characters": "\u201E" }, "∵": { "codepoints": [8757], "characters": "\u2235" }, "∵": { "codepoints": [8757], "characters": "\u2235" }, "⦰": { "codepoints": [10672], "characters": "\u29B0" }, "϶": { "codepoints": [1014], "characters": "\u03F6" }, "ℬ": { "codepoints": [8492], "characters": "\u212C" }, "β": { "codepoints": [946], "characters": "\u03B2" }, "ℶ": { "codepoints": [8502], "characters": "\u2136" }, "≬": { "codepoints": [8812], "characters": "\u226C" }, "𝔟": { "codepoints": [120095], "characters": "\uD835\uDD1F" }, "⋂": { "codepoints": [8898], "characters": "\u22C2" }, "◯": { "codepoints": [9711], "characters": "\u25EF" }, "⋃": { "codepoints": [8899], "characters": "\u22C3" }, "⨀": { "codepoints": [10752], "characters": "\u2A00" }, "⨁": { "codepoints": [10753], "characters": "\u2A01" }, "⨂": { "codepoints": [10754], "characters": "\u2A02" }, "⨆": { "codepoints": [10758], "characters": "\u2A06" }, "★": { "codepoints": [9733], "characters": "\u2605" }, "▽": { "codepoints": [9661], "characters": "\u25BD" }, "△": { "codepoints": [9651], "characters": "\u25B3" }, "⨄": { "codepoints": [10756], "characters": "\u2A04" }, "⋁": { "codepoints": [8897], "characters": "\u22C1" }, "⋀": { "codepoints": [8896], "characters": "\u22C0" }, "⤍": { "codepoints": [10509], "characters": "\u290D" }, "⧫": { "codepoints": [10731], "characters": "\u29EB" }, "▪": { "codepoints": [9642], "characters": "\u25AA" }, "▴": { "codepoints": [9652], "characters": "\u25B4" }, "▾": { "codepoints": [9662], "characters": "\u25BE" }, "◂": { "codepoints": [9666], "characters": "\u25C2" }, "▸": { "codepoints": [9656], "characters": "\u25B8" }, "␣": { "codepoints": [9251], "characters": "\u2423" }, "▒": { "codepoints": [9618], "characters": "\u2592" }, "░": { "codepoints": [9617], "characters": "\u2591" }, "▓": { "codepoints": [9619], "characters": "\u2593" }, "█": { "codepoints": [9608], "characters": "\u2588" }, "=⃥": { "codepoints": [61, 8421], "characters": "\u003D\u20E5" }, "≡⃥": { "codepoints": [8801, 8421], "characters": "\u2261\u20E5" }, "⌐": { "codepoints": [8976], "characters": "\u2310" }, "𝕓": { "codepoints": [120147], "characters": "\uD835\uDD53" }, "⊥": { "codepoints": [8869], "characters": "\u22A5" }, "⊥": { "codepoints": [8869], "characters": "\u22A5" }, "⋈": { "codepoints": [8904], "characters": "\u22C8" }, "╗": { "codepoints": [9559], "characters": "\u2557" }, "╔": { "codepoints": [9556], "characters": "\u2554" }, "╖": { "codepoints": [9558], "characters": "\u2556" }, "╓": { "codepoints": [9555], "characters": "\u2553" }, "═": { "codepoints": [9552], "characters": "\u2550" }, "╦": { "codepoints": [9574], "characters": "\u2566" }, "╩": { "codepoints": [9577], "characters": "\u2569" }, "╤": { "codepoints": [9572], "characters": "\u2564" }, "╧": { "codepoints": [9575], "characters": "\u2567" }, "╝": { "codepoints": [9565], "characters": "\u255D" }, "╚": { "codepoints": [9562], "characters": "\u255A" }, "╜": { "codepoints": [9564], "characters": "\u255C" }, "╙": { "codepoints": [9561], "characters": "\u2559" }, "║": { "codepoints": [9553], "characters": "\u2551" }, "╬": { "codepoints": [9580], "characters": "\u256C" }, "╣": { "codepoints": [9571], "characters": "\u2563" }, "╠": { "codepoints": [9568], "characters": "\u2560" }, "╫": { "codepoints": [9579], "characters": "\u256B" }, "╢": { "codepoints": [9570], "characters": "\u2562" }, "╟": { "codepoints": [9567], "characters": "\u255F" }, "⧉": { "codepoints": [10697], "characters": "\u29C9" }, "╕": { "codepoints": [9557], "characters": "\u2555" }, "╒": { "codepoints": [9554], "characters": "\u2552" }, "┐": { "codepoints": [9488], "characters": "\u2510" }, "┌": { "codepoints": [9484], "characters": "\u250C" }, "─": { "codepoints": [9472], "characters": "\u2500" }, "╥": { "codepoints": [9573], "characters": "\u2565" }, "╨": { "codepoints": [9576], "characters": "\u2568" }, "┬": { "codepoints": [9516], "characters": "\u252C" }, "┴": { "codepoints": [9524], "characters": "\u2534" }, "⊟": { "codepoints": [8863], "characters": "\u229F" }, "⊞": { "codepoints": [8862], "characters": "\u229E" }, "⊠": { "codepoints": [8864], "characters": "\u22A0" }, "╛": { "codepoints": [9563], "characters": "\u255B" }, "╘": { "codepoints": [9560], "characters": "\u2558" }, "┘": { "codepoints": [9496], "characters": "\u2518" }, "└": { "codepoints": [9492], "characters": "\u2514" }, "│": { "codepoints": [9474], "characters": "\u2502" }, "╪": { "codepoints": [9578], "characters": "\u256A" }, "╡": { "codepoints": [9569], "characters": "\u2561" }, "╞": { "codepoints": [9566], "characters": "\u255E" }, "┼": { "codepoints": [9532], "characters": "\u253C" }, "┤": { "codepoints": [9508], "characters": "\u2524" }, "├": { "codepoints": [9500], "characters": "\u251C" }, "‵": { "codepoints": [8245], "characters": "\u2035" }, "˘": { "codepoints": [728], "characters": "\u02D8" }, "¦": { "codepoints": [166], "characters": "\u00A6" }, "¦": { "codepoints": [166], "characters": "\u00A6" }, "𝒷": { "codepoints": [119991], "characters": "\uD835\uDCB7" }, "⁏": { "codepoints": [8271], "characters": "\u204F" }, "∽": { "codepoints": [8765], "characters": "\u223D" }, "⋍": { "codepoints": [8909], "characters": "\u22CD" }, "\": { "codepoints": [92], "characters": "\u005C" }, "⧅": { "codepoints": [10693], "characters": "\u29C5" }, "⟈": { "codepoints": [10184], "characters": "\u27C8" }, "•": { "codepoints": [8226], "characters": "\u2022" }, "•": { "codepoints": [8226], "characters": "\u2022" }, "≎": { "codepoints": [8782], "characters": "\u224E" }, "⪮": { "codepoints": [10926], "characters": "\u2AAE" }, "≏": { "codepoints": [8783], "characters": "\u224F" }, "≏": { "codepoints": [8783], "characters": "\u224F" }, "ć": { "codepoints": [263], "characters": "\u0107" }, "∩": { "codepoints": [8745], "characters": "\u2229" }, "⩄": { "codepoints": [10820], "characters": "\u2A44" }, "⩉": { "codepoints": [10825], "characters": "\u2A49" }, "⩋": { "codepoints": [10827], "characters": "\u2A4B" }, "⩇": { "codepoints": [10823], "characters": "\u2A47" }, "⩀": { "codepoints": [10816], "characters": "\u2A40" }, "∩︀": { "codepoints": [8745, 65024], "characters": "\u2229\uFE00" }, "⁁": { "codepoints": [8257], "characters": "\u2041" }, "ˇ": { "codepoints": [711], "characters": "\u02C7" }, "⩍": { "codepoints": [10829], "characters": "\u2A4D" }, "č": { "codepoints": [269], "characters": "\u010D" }, "ç": { "codepoints": [231], "characters": "\u00E7" }, "ç": { "codepoints": [231], "characters": "\u00E7" }, "ĉ": { "codepoints": [265], "characters": "\u0109" }, "⩌": { "codepoints": [10828], "characters": "\u2A4C" }, "⩐": { "codepoints": [10832], "characters": "\u2A50" }, "ċ": { "codepoints": [267], "characters": "\u010B" }, "¸": { "codepoints": [184], "characters": "\u00B8" }, "¸": { "codepoints": [184], "characters": "\u00B8" }, "⦲": { "codepoints": [10674], "characters": "\u29B2" }, "¢": { "codepoints": [162], "characters": "\u00A2" }, "¢": { "codepoints": [162], "characters": "\u00A2" }, "·": { "codepoints": [183], "characters": "\u00B7" }, "𝔠": { "codepoints": [120096], "characters": "\uD835\uDD20" }, "ч": { "codepoints": [1095], "characters": "\u0447" }, "✓": { "codepoints": [10003], "characters": "\u2713" }, "✓": { "codepoints": [10003], "characters": "\u2713" }, "χ": { "codepoints": [967], "characters": "\u03C7" }, "○": { "codepoints": [9675], "characters": "\u25CB" }, "⧃": { "codepoints": [10691], "characters": "\u29C3" }, "ˆ": { "codepoints": [710], "characters": "\u02C6" }, "≗": { "codepoints": [8791], "characters": "\u2257" }, "↺": { "codepoints": [8634], "characters": "\u21BA" }, "↻": { "codepoints": [8635], "characters": "\u21BB" }, "®": { "codepoints": [174], "characters": "\u00AE" }, "Ⓢ": { "codepoints": [9416], "characters": "\u24C8" }, "⊛": { "codepoints": [8859], "characters": "\u229B" }, "⊚": { "codepoints": [8858], "characters": "\u229A" }, "⊝": { "codepoints": [8861], "characters": "\u229D" }, "≗": { "codepoints": [8791], "characters": "\u2257" }, "⨐": { "codepoints": [10768], "characters": "\u2A10" }, "⫯": { "codepoints": [10991], "characters": "\u2AEF" }, "⧂": { "codepoints": [10690], "characters": "\u29C2" }, "♣": { "codepoints": [9827], "characters": "\u2663" }, "♣": { "codepoints": [9827], "characters": "\u2663" }, ":": { "codepoints": [58], "characters": "\u003A" }, "≔": { "codepoints": [8788], "characters": "\u2254" }, "≔": { "codepoints": [8788], "characters": "\u2254" }, ",": { "codepoints": [44], "characters": "\u002C" }, "@": { "codepoints": [64], "characters": "\u0040" }, "∁": { "codepoints": [8705], "characters": "\u2201" }, "∘": { "codepoints": [8728], "characters": "\u2218" }, "∁": { "codepoints": [8705], "characters": "\u2201" }, "ℂ": { "codepoints": [8450], "characters": "\u2102" }, "≅": { "codepoints": [8773], "characters": "\u2245" }, "⩭": { "codepoints": [10861], "characters": "\u2A6D" }, "∮": { "codepoints": [8750], "characters": "\u222E" }, "𝕔": { "codepoints": [120148], "characters": "\uD835\uDD54" }, "∐": { "codepoints": [8720], "characters": "\u2210" }, "©": { "codepoints": [169], "characters": "\u00A9" }, "©": { "codepoints": [169], "characters": "\u00A9" }, "℗": { "codepoints": [8471], "characters": "\u2117" }, "↵": { "codepoints": [8629], "characters": "\u21B5" }, "✗": { "codepoints": [10007], "characters": "\u2717" }, "𝒸": { "codepoints": [119992], "characters": "\uD835\uDCB8" }, "⫏": { "codepoints": [10959], "characters": "\u2ACF" }, "⫑": { "codepoints": [10961], "characters": "\u2AD1" }, "⫐": { "codepoints": [10960], "characters": "\u2AD0" }, "⫒": { "codepoints": [10962], "characters": "\u2AD2" }, "⋯": { "codepoints": [8943], "characters": "\u22EF" }, "⤸": { "codepoints": [10552], "characters": "\u2938" }, "⤵": { "codepoints": [10549], "characters": "\u2935" }, "⋞": { "codepoints": [8926], "characters": "\u22DE" }, "⋟": { "codepoints": [8927], "characters": "\u22DF" }, "↶": { "codepoints": [8630], "characters": "\u21B6" }, "⤽": { "codepoints": [10557], "characters": "\u293D" }, "∪": { "codepoints": [8746], "characters": "\u222A" }, "⩈": { "codepoints": [10824], "characters": "\u2A48" }, "⩆": { "codepoints": [10822], "characters": "\u2A46" }, "⩊": { "codepoints": [10826], "characters": "\u2A4A" }, "⊍": { "codepoints": [8845], "characters": "\u228D" }, "⩅": { "codepoints": [10821], "characters": "\u2A45" }, "∪︀": { "codepoints": [8746, 65024], "characters": "\u222A\uFE00" }, "↷": { "codepoints": [8631], "characters": "\u21B7" }, "⤼": { "codepoints": [10556], "characters": "\u293C" }, "⋞": { "codepoints": [8926], "characters": "\u22DE" }, "⋟": { "codepoints": [8927], "characters": "\u22DF" }, "⋎": { "codepoints": [8910], "characters": "\u22CE" }, "⋏": { "codepoints": [8911], "characters": "\u22CF" }, "¤": { "codepoints": [164], "characters": "\u00A4" }, "¤": { "codepoints": [164], "characters": "\u00A4" }, "↶": { "codepoints": [8630], "characters": "\u21B6" }, "↷": { "codepoints": [8631], "characters": "\u21B7" }, "⋎": { "codepoints": [8910], "characters": "\u22CE" }, "⋏": { "codepoints": [8911], "characters": "\u22CF" }, "∲": { "codepoints": [8754], "characters": "\u2232" }, "∱": { "codepoints": [8753], "characters": "\u2231" }, "⌭": { "codepoints": [9005], "characters": "\u232D" }, "⇓": { "codepoints": [8659], "characters": "\u21D3" }, "⥥": { "codepoints": [10597], "characters": "\u2965" }, "†": { "codepoints": [8224], "characters": "\u2020" }, "ℸ": { "codepoints": [8504], "characters": "\u2138" }, "↓": { "codepoints": [8595], "characters": "\u2193" }, "‐": { "codepoints": [8208], "characters": "\u2010" }, "⊣": { "codepoints": [8867], "characters": "\u22A3" }, "⤏": { "codepoints": [10511], "characters": "\u290F" }, "˝": { "codepoints": [733], "characters": "\u02DD" }, "ď": { "codepoints": [271], "characters": "\u010F" }, "д": { "codepoints": [1076], "characters": "\u0434" }, "ⅆ": { "codepoints": [8518], "characters": "\u2146" }, "‡": { "codepoints": [8225], "characters": "\u2021" }, "⇊": { "codepoints": [8650], "characters": "\u21CA" }, "⩷": { "codepoints": [10871], "characters": "\u2A77" }, "°": { "codepoints": [176], "characters": "\u00B0" }, "°": { "codepoints": [176], "characters": "\u00B0" }, "δ": { "codepoints": [948], "characters": "\u03B4" }, "⦱": { "codepoints": [10673], "characters": "\u29B1" }, "⥿": { "codepoints": [10623], "characters": "\u297F" }, "𝔡": { "codepoints": [120097], "characters": "\uD835\uDD21" }, "⇃": { "codepoints": [8643], "characters": "\u21C3" }, "⇂": { "codepoints": [8642], "characters": "\u21C2" }, "⋄": { "codepoints": [8900], "characters": "\u22C4" }, "⋄": { "codepoints": [8900], "characters": "\u22C4" }, "♦": { "codepoints": [9830], "characters": "\u2666" }, "♦": { "codepoints": [9830], "characters": "\u2666" }, "¨": { "codepoints": [168], "characters": "\u00A8" }, "ϝ": { "codepoints": [989], "characters": "\u03DD" }, "⋲": { "codepoints": [8946], "characters": "\u22F2" }, "÷": { "codepoints": [247], "characters": "\u00F7" }, "÷": { "codepoints": [247], "characters": "\u00F7" }, "÷": { "codepoints": [247], "characters": "\u00F7" }, "⋇": { "codepoints": [8903], "characters": "\u22C7" }, "⋇": { "codepoints": [8903], "characters": "\u22C7" }, "ђ": { "codepoints": [1106], "characters": "\u0452" }, "⌞": { "codepoints": [8990], "characters": "\u231E" }, "⌍": { "codepoints": [8973], "characters": "\u230D" }, "$": { "codepoints": [36], "characters": "\u0024" }, "𝕕": { "codepoints": [120149], "characters": "\uD835\uDD55" }, "˙": { "codepoints": [729], "characters": "\u02D9" }, "≐": { "codepoints": [8784], "characters": "\u2250" }, "≑": { "codepoints": [8785], "characters": "\u2251" }, "∸": { "codepoints": [8760], "characters": "\u2238" }, "∔": { "codepoints": [8724], "characters": "\u2214" }, "⊡": { "codepoints": [8865], "characters": "\u22A1" }, "⌆": { "codepoints": [8966], "characters": "\u2306" }, "↓": { "codepoints": [8595], "characters": "\u2193" }, "⇊": { "codepoints": [8650], "characters": "\u21CA" }, "⇃": { "codepoints": [8643], "characters": "\u21C3" }, "⇂": { "codepoints": [8642], "characters": "\u21C2" }, "⤐": { "codepoints": [10512], "characters": "\u2910" }, "⌟": { "codepoints": [8991], "characters": "\u231F" }, "⌌": { "codepoints": [8972], "characters": "\u230C" }, "𝒹": { "codepoints": [119993], "characters": "\uD835\uDCB9" }, "ѕ": { "codepoints": [1109], "characters": "\u0455" }, "⧶": { "codepoints": [10742], "characters": "\u29F6" }, "đ": { "codepoints": [273], "characters": "\u0111" }, "⋱": { "codepoints": [8945], "characters": "\u22F1" }, "▿": { "codepoints": [9663], "characters": "\u25BF" }, "▾": { "codepoints": [9662], "characters": "\u25BE" }, "⇵": { "codepoints": [8693], "characters": "\u21F5" }, "⥯": { "codepoints": [10607], "characters": "\u296F" }, "⦦": { "codepoints": [10662], "characters": "\u29A6" }, "џ": { "codepoints": [1119], "characters": "\u045F" }, "⟿": { "codepoints": [10239], "characters": "\u27FF" }, "⩷": { "codepoints": [10871], "characters": "\u2A77" }, "≑": { "codepoints": [8785], "characters": "\u2251" }, "é": { "codepoints": [233], "characters": "\u00E9" }, "é": { "codepoints": [233], "characters": "\u00E9" }, "⩮": { "codepoints": [10862], "characters": "\u2A6E" }, "ě": { "codepoints": [283], "characters": "\u011B" }, "≖": { "codepoints": [8790], "characters": "\u2256" }, "ê": { "codepoints": [234], "characters": "\u00EA" }, "ê": { "codepoints": [234], "characters": "\u00EA" }, "≕": { "codepoints": [8789], "characters": "\u2255" }, "э": { "codepoints": [1101], "characters": "\u044D" }, "ė": { "codepoints": [279], "characters": "\u0117" }, "ⅇ": { "codepoints": [8519], "characters": "\u2147" }, "≒": { "codepoints": [8786], "characters": "\u2252" }, "𝔢": { "codepoints": [120098], "characters": "\uD835\uDD22" }, "⪚": { "codepoints": [10906], "characters": "\u2A9A" }, "è": { "codepoints": [232], "characters": "\u00E8" }, "è": { "codepoints": [232], "characters": "\u00E8" }, "⪖": { "codepoints": [10902], "characters": "\u2A96" }, "⪘": { "codepoints": [10904], "characters": "\u2A98" }, "⪙": { "codepoints": [10905], "characters": "\u2A99" }, "⏧": { "codepoints": [9191], "characters": "\u23E7" }, "ℓ": { "codepoints": [8467], "characters": "\u2113" }, "⪕": { "codepoints": [10901], "characters": "\u2A95" }, "⪗": { "codepoints": [10903], "characters": "\u2A97" }, "ē": { "codepoints": [275], "characters": "\u0113" }, "∅": { "codepoints": [8709], "characters": "\u2205" }, "∅": { "codepoints": [8709], "characters": "\u2205" }, "∅": { "codepoints": [8709], "characters": "\u2205" }, " ": { "codepoints": [8196], "characters": "\u2004" }, " ": { "codepoints": [8197], "characters": "\u2005" }, " ": { "codepoints": [8195], "characters": "\u2003" }, "ŋ": { "codepoints": [331], "characters": "\u014B" }, " ": { "codepoints": [8194], "characters": "\u2002" }, "ę": { "codepoints": [281], "characters": "\u0119" }, "𝕖": { "codepoints": [120150], "characters": "\uD835\uDD56" }, "⋕": { "codepoints": [8917], "characters": "\u22D5" }, "⧣": { "codepoints": [10723], "characters": "\u29E3" }, "⩱": { "codepoints": [10865], "characters": "\u2A71" }, "ε": { "codepoints": [949], "characters": "\u03B5" }, "ε": { "codepoints": [949], "characters": "\u03B5" }, "ϵ": { "codepoints": [1013], "characters": "\u03F5" }, "≖": { "codepoints": [8790], "characters": "\u2256" }, "≕": { "codepoints": [8789], "characters": "\u2255" }, "≂": { "codepoints": [8770], "characters": "\u2242" }, "⪖": { "codepoints": [10902], "characters": "\u2A96" }, "⪕": { "codepoints": [10901], "characters": "\u2A95" }, "=": { "codepoints": [61], "characters": "\u003D" }, "≟": { "codepoints": [8799], "characters": "\u225F" }, "≡": { "codepoints": [8801], "characters": "\u2261" }, "⩸": { "codepoints": [10872], "characters": "\u2A78" }, "⧥": { "codepoints": [10725], "characters": "\u29E5" }, "≓": { "codepoints": [8787], "characters": "\u2253" }, "⥱": { "codepoints": [10609], "characters": "\u2971" }, "ℯ": { "codepoints": [8495], "characters": "\u212F" }, "≐": { "codepoints": [8784], "characters": "\u2250" }, "≂": { "codepoints": [8770], "characters": "\u2242" }, "η": { "codepoints": [951], "characters": "\u03B7" }, "ð": { "codepoints": [240], "characters": "\u00F0" }, "ð": { "codepoints": [240], "characters": "\u00F0" }, "ë": { "codepoints": [235], "characters": "\u00EB" }, "ë": { "codepoints": [235], "characters": "\u00EB" }, "€": { "codepoints": [8364], "characters": "\u20AC" }, "!": { "codepoints": [33], "characters": "\u0021" }, "∃": { "codepoints": [8707], "characters": "\u2203" }, "ℰ": { "codepoints": [8496], "characters": "\u2130" }, "ⅇ": { "codepoints": [8519], "characters": "\u2147" }, "≒": { "codepoints": [8786], "characters": "\u2252" }, "ф": { "codepoints": [1092], "characters": "\u0444" }, "♀": { "codepoints": [9792], "characters": "\u2640" }, "ffi": { "codepoints": [64259], "characters": "\uFB03" }, "ff": { "codepoints": [64256], "characters": "\uFB00" }, "ffl": { "codepoints": [64260], "characters": "\uFB04" }, "𝔣": { "codepoints": [120099], "characters": "\uD835\uDD23" }, "fi": { "codepoints": [64257], "characters": "\uFB01" }, "fj": { "codepoints": [102, 106], "characters": "\u0066\u006A" }, "♭": { "codepoints": [9837], "characters": "\u266D" }, "fl": { "codepoints": [64258], "characters": "\uFB02" }, "▱": { "codepoints": [9649], "characters": "\u25B1" }, "ƒ": { "codepoints": [402], "characters": "\u0192" }, "𝕗": { "codepoints": [120151], "characters": "\uD835\uDD57" }, "∀": { "codepoints": [8704], "characters": "\u2200" }, "⋔": { "codepoints": [8916], "characters": "\u22D4" }, "⫙": { "codepoints": [10969], "characters": "\u2AD9" }, "⨍": { "codepoints": [10765], "characters": "\u2A0D" }, "½": { "codepoints": [189], "characters": "\u00BD" }, "½": { "codepoints": [189], "characters": "\u00BD" }, "⅓": { "codepoints": [8531], "characters": "\u2153" }, "¼": { "codepoints": [188], "characters": "\u00BC" }, "¼": { "codepoints": [188], "characters": "\u00BC" }, "⅕": { "codepoints": [8533], "characters": "\u2155" }, "⅙": { "codepoints": [8537], "characters": "\u2159" }, "⅛": { "codepoints": [8539], "characters": "\u215B" }, "⅔": { "codepoints": [8532], "characters": "\u2154" }, "⅖": { "codepoints": [8534], "characters": "\u2156" }, "¾": { "codepoints": [190], "characters": "\u00BE" }, "¾": { "codepoints": [190], "characters": "\u00BE" }, "⅗": { "codepoints": [8535], "characters": "\u2157" }, "⅜": { "codepoints": [8540], "characters": "\u215C" }, "⅘": { "codepoints": [8536], "characters": "\u2158" }, "⅚": { "codepoints": [8538], "characters": "\u215A" }, "⅝": { "codepoints": [8541], "characters": "\u215D" }, "⅞": { "codepoints": [8542], "characters": "\u215E" }, "⁄": { "codepoints": [8260], "characters": "\u2044" }, "⌢": { "codepoints": [8994], "characters": "\u2322" }, "𝒻": { "codepoints": [119995], "characters": "\uD835\uDCBB" }, "≧": { "codepoints": [8807], "characters": "\u2267" }, "⪌": { "codepoints": [10892], "characters": "\u2A8C" }, "ǵ": { "codepoints": [501], "characters": "\u01F5" }, "γ": { "codepoints": [947], "characters": "\u03B3" }, "ϝ": { "codepoints": [989], "characters": "\u03DD" }, "⪆": { "codepoints": [10886], "characters": "\u2A86" }, "ğ": { "codepoints": [287], "characters": "\u011F" }, "ĝ": { "codepoints": [285], "characters": "\u011D" }, "г": { "codepoints": [1075], "characters": "\u0433" }, "ġ": { "codepoints": [289], "characters": "\u0121" }, "≥": { "codepoints": [8805], "characters": "\u2265" }, "⋛": { "codepoints": [8923], "characters": "\u22DB" }, "≥": { "codepoints": [8805], "characters": "\u2265" }, "≧": { "codepoints": [8807], "characters": "\u2267" }, "⩾": { "codepoints": [10878], "characters": "\u2A7E" }, "⩾": { "codepoints": [10878], "characters": "\u2A7E" }, "⪩": { "codepoints": [10921], "characters": "\u2AA9" }, "⪀": { "codepoints": [10880], "characters": "\u2A80" }, "⪂": { "codepoints": [10882], "characters": "\u2A82" }, "⪄": { "codepoints": [10884], "characters": "\u2A84" }, "⋛︀": { "codepoints": [8923, 65024], "characters": "\u22DB\uFE00" }, "⪔": { "codepoints": [10900], "characters": "\u2A94" }, "𝔤": { "codepoints": [120100], "characters": "\uD835\uDD24" }, "≫": { "codepoints": [8811], "characters": "\u226B" }, "⋙": { "codepoints": [8921], "characters": "\u22D9" }, "ℷ": { "codepoints": [8503], "characters": "\u2137" }, "ѓ": { "codepoints": [1107], "characters": "\u0453" }, "≷": { "codepoints": [8823], "characters": "\u2277" }, "⪒": { "codepoints": [10898], "characters": "\u2A92" }, "⪥": { "codepoints": [10917], "characters": "\u2AA5" }, "⪤": { "codepoints": [10916], "characters": "\u2AA4" }, "≩": { "codepoints": [8809], "characters": "\u2269" }, "⪊": { "codepoints": [10890], "characters": "\u2A8A" }, "⪊": { "codepoints": [10890], "characters": "\u2A8A" }, "⪈": { "codepoints": [10888], "characters": "\u2A88" }, "⪈": { "codepoints": [10888], "characters": "\u2A88" }, "≩": { "codepoints": [8809], "characters": "\u2269" }, "⋧": { "codepoints": [8935], "characters": "\u22E7" }, "𝕘": { "codepoints": [120152], "characters": "\uD835\uDD58" }, "`": { "codepoints": [96], "characters": "\u0060" }, "ℊ": { "codepoints": [8458], "characters": "\u210A" }, "≳": { "codepoints": [8819], "characters": "\u2273" }, "⪎": { "codepoints": [10894], "characters": "\u2A8E" }, "⪐": { "codepoints": [10896], "characters": "\u2A90" }, ">": { "codepoints": [62], "characters": "\u003E" }, ">": { "codepoints": [62], "characters": "\u003E" }, "⪧": { "codepoints": [10919], "characters": "\u2AA7" }, "⩺": { "codepoints": [10874], "characters": "\u2A7A" }, "⋗": { "codepoints": [8919], "characters": "\u22D7" }, "⦕": { "codepoints": [10645], "characters": "\u2995" }, "⩼": { "codepoints": [10876], "characters": "\u2A7C" }, "⪆": { "codepoints": [10886], "characters": "\u2A86" }, "⥸": { "codepoints": [10616], "characters": "\u2978" }, "⋗": { "codepoints": [8919], "characters": "\u22D7" }, "⋛": { "codepoints": [8923], "characters": "\u22DB" }, "⪌": { "codepoints": [10892], "characters": "\u2A8C" }, "≷": { "codepoints": [8823], "characters": "\u2277" }, "≳": { "codepoints": [8819], "characters": "\u2273" }, "≩︀": { "codepoints": [8809, 65024], "characters": "\u2269\uFE00" }, "≩︀": { "codepoints": [8809, 65024], "characters": "\u2269\uFE00" }, "⇔": { "codepoints": [8660], "characters": "\u21D4" }, " ": { "codepoints": [8202], "characters": "\u200A" }, "½": { "codepoints": [189], "characters": "\u00BD" }, "ℋ": { "codepoints": [8459], "characters": "\u210B" }, "ъ": { "codepoints": [1098], "characters": "\u044A" }, "↔": { "codepoints": [8596], "characters": "\u2194" }, "⥈": { "codepoints": [10568], "characters": "\u2948" }, "↭": { "codepoints": [8621], "characters": "\u21AD" }, "ℏ": { "codepoints": [8463], "characters": "\u210F" }, "ĥ": { "codepoints": [293], "characters": "\u0125" }, "♥": { "codepoints": [9829], "characters": "\u2665" }, "♥": { "codepoints": [9829], "characters": "\u2665" }, "…": { "codepoints": [8230], "characters": "\u2026" }, "⊹": { "codepoints": [8889], "characters": "\u22B9" }, "𝔥": { "codepoints": [120101], "characters": "\uD835\uDD25" }, "⤥": { "codepoints": [10533], "characters": "\u2925" }, "⤦": { "codepoints": [10534], "characters": "\u2926" }, "⇿": { "codepoints": [8703], "characters": "\u21FF" }, "∻": { "codepoints": [8763], "characters": "\u223B" }, "↩": { "codepoints": [8617], "characters": "\u21A9" }, "↪": { "codepoints": [8618], "characters": "\u21AA" }, "𝕙": { "codepoints": [120153], "characters": "\uD835\uDD59" }, "―": { "codepoints": [8213], "characters": "\u2015" }, "𝒽": { "codepoints": [119997], "characters": "\uD835\uDCBD" }, "ℏ": { "codepoints": [8463], "characters": "\u210F" }, "ħ": { "codepoints": [295], "characters": "\u0127" }, "⁃": { "codepoints": [8259], "characters": "\u2043" }, "‐": { "codepoints": [8208], "characters": "\u2010" }, "í": { "codepoints": [237], "characters": "\u00ED" }, "í": { "codepoints": [237], "characters": "\u00ED" }, "⁣": { "codepoints": [8291], "characters": "\u2063" }, "î": { "codepoints": [238], "characters": "\u00EE" }, "î": { "codepoints": [238], "characters": "\u00EE" }, "и": { "codepoints": [1080], "characters": "\u0438" }, "е": { "codepoints": [1077], "characters": "\u0435" }, "¡": { "codepoints": [161], "characters": "\u00A1" }, "¡": { "codepoints": [161], "characters": "\u00A1" }, "⇔": { "codepoints": [8660], "characters": "\u21D4" }, "𝔦": { "codepoints": [120102], "characters": "\uD835\uDD26" }, "ì": { "codepoints": [236], "characters": "\u00EC" }, "ì": { "codepoints": [236], "characters": "\u00EC" }, "ⅈ": { "codepoints": [8520], "characters": "\u2148" }, "⨌": { "codepoints": [10764], "characters": "\u2A0C" }, "∭": { "codepoints": [8749], "characters": "\u222D" }, "⧜": { "codepoints": [10716], "characters": "\u29DC" }, "℩": { "codepoints": [8489], "characters": "\u2129" }, "ij": { "codepoints": [307], "characters": "\u0133" }, "ī": { "codepoints": [299], "characters": "\u012B" }, "ℑ": { "codepoints": [8465], "characters": "\u2111" }, "ℐ": { "codepoints": [8464], "characters": "\u2110" }, "ℑ": { "codepoints": [8465], "characters": "\u2111" }, "ı": { "codepoints": [305], "characters": "\u0131" }, "⊷": { "codepoints": [8887], "characters": "\u22B7" }, "Ƶ": { "codepoints": [437], "characters": "\u01B5" }, "∈": { "codepoints": [8712], "characters": "\u2208" }, "℅": { "codepoints": [8453], "characters": "\u2105" }, "∞": { "codepoints": [8734], "characters": "\u221E" }, "⧝": { "codepoints": [10717], "characters": "\u29DD" }, "ı": { "codepoints": [305], "characters": "\u0131" }, "∫": { "codepoints": [8747], "characters": "\u222B" }, "⊺": { "codepoints": [8890], "characters": "\u22BA" }, "ℤ": { "codepoints": [8484], "characters": "\u2124" }, "⊺": { "codepoints": [8890], "characters": "\u22BA" }, "⨗": { "codepoints": [10775], "characters": "\u2A17" }, "⨼": { "codepoints": [10812], "characters": "\u2A3C" }, "ё": { "codepoints": [1105], "characters": "\u0451" }, "į": { "codepoints": [303], "characters": "\u012F" }, "𝕚": { "codepoints": [120154], "characters": "\uD835\uDD5A" }, "ι": { "codepoints": [953], "characters": "\u03B9" }, "⨼": { "codepoints": [10812], "characters": "\u2A3C" }, "¿": { "codepoints": [191], "characters": "\u00BF" }, "¿": { "codepoints": [191], "characters": "\u00BF" }, "𝒾": { "codepoints": [119998], "characters": "\uD835\uDCBE" }, "∈": { "codepoints": [8712], "characters": "\u2208" }, "⋹": { "codepoints": [8953], "characters": "\u22F9" }, "⋵": { "codepoints": [8949], "characters": "\u22F5" }, "⋴": { "codepoints": [8948], "characters": "\u22F4" }, "⋳": { "codepoints": [8947], "characters": "\u22F3" }, "∈": { "codepoints": [8712], "characters": "\u2208" }, "⁢": { "codepoints": [8290], "characters": "\u2062" }, "ĩ": { "codepoints": [297], "characters": "\u0129" }, "і": { "codepoints": [1110], "characters": "\u0456" }, "ï": { "codepoints": [239], "characters": "\u00EF" }, "ï": { "codepoints": [239], "characters": "\u00EF" }, "ĵ": { "codepoints": [309], "characters": "\u0135" }, "й": { "codepoints": [1081], "characters": "\u0439" }, "𝔧": { "codepoints": [120103], "characters": "\uD835\uDD27" }, "ȷ": { "codepoints": [567], "characters": "\u0237" }, "𝕛": { "codepoints": [120155], "characters": "\uD835\uDD5B" }, "𝒿": { "codepoints": [119999], "characters": "\uD835\uDCBF" }, "ј": { "codepoints": [1112], "characters": "\u0458" }, "є": { "codepoints": [1108], "characters": "\u0454" }, "κ": { "codepoints": [954], "characters": "\u03BA" }, "ϰ": { "codepoints": [1008], "characters": "\u03F0" }, "ķ": { "codepoints": [311], "characters": "\u0137" }, "к": { "codepoints": [1082], "characters": "\u043A" }, "𝔨": { "codepoints": [120104], "characters": "\uD835\uDD28" }, "ĸ": { "codepoints": [312], "characters": "\u0138" }, "х": { "codepoints": [1093], "characters": "\u0445" }, "ќ": { "codepoints": [1116], "characters": "\u045C" }, "𝕜": { "codepoints": [120156], "characters": "\uD835\uDD5C" }, "𝓀": { "codepoints": [120000], "characters": "\uD835\uDCC0" }, "⇚": { "codepoints": [8666], "characters": "\u21DA" }, "⇐": { "codepoints": [8656], "characters": "\u21D0" }, "⤛": { "codepoints": [10523], "characters": "\u291B" }, "⤎": { "codepoints": [10510], "characters": "\u290E" }, "≦": { "codepoints": [8806], "characters": "\u2266" }, "⪋": { "codepoints": [10891], "characters": "\u2A8B" }, "⥢": { "codepoints": [10594], "characters": "\u2962" }, "ĺ": { "codepoints": [314], "characters": "\u013A" }, "⦴": { "codepoints": [10676], "characters": "\u29B4" }, "ℒ": { "codepoints": [8466], "characters": "\u2112" }, "λ": { "codepoints": [955], "characters": "\u03BB" }, "⟨": { "codepoints": [10216], "characters": "\u27E8" }, "⦑": { "codepoints": [10641], "characters": "\u2991" }, "⟨": { "codepoints": [10216], "characters": "\u27E8" }, "⪅": { "codepoints": [10885], "characters": "\u2A85" }, "«": { "codepoints": [171], "characters": "\u00AB" }, "«": { "codepoints": [171], "characters": "\u00AB" }, "←": { "codepoints": [8592], "characters": "\u2190" }, "⇤": { "codepoints": [8676], "characters": "\u21E4" }, "⤟": { "codepoints": [10527], "characters": "\u291F" }, "⤝": { "codepoints": [10525], "characters": "\u291D" }, "↩": { "codepoints": [8617], "characters": "\u21A9" }, "↫": { "codepoints": [8619], "characters": "\u21AB" }, "⤹": { "codepoints": [10553], "characters": "\u2939" }, "⥳": { "codepoints": [10611], "characters": "\u2973" }, "↢": { "codepoints": [8610], "characters": "\u21A2" }, "⪫": { "codepoints": [10923], "characters": "\u2AAB" }, "⤙": { "codepoints": [10521], "characters": "\u2919" }, "⪭": { "codepoints": [10925], "characters": "\u2AAD" }, "⪭︀": { "codepoints": [10925, 65024], "characters": "\u2AAD\uFE00" }, "⤌": { "codepoints": [10508], "characters": "\u290C" }, "❲": { "codepoints": [10098], "characters": "\u2772" }, "{": { "codepoints": [123], "characters": "\u007B" }, "[": { "codepoints": [91], "characters": "\u005B" }, "⦋": { "codepoints": [10635], "characters": "\u298B" }, "⦏": { "codepoints": [10639], "characters": "\u298F" }, "⦍": { "codepoints": [10637], "characters": "\u298D" }, "ľ": { "codepoints": [318], "characters": "\u013E" }, "ļ": { "codepoints": [316], "characters": "\u013C" }, "⌈": { "codepoints": [8968], "characters": "\u2308" }, "{": { "codepoints": [123], "characters": "\u007B" }, "л": { "codepoints": [1083], "characters": "\u043B" }, "⤶": { "codepoints": [10550], "characters": "\u2936" }, "“": { "codepoints": [8220], "characters": "\u201C" }, "„": { "codepoints": [8222], "characters": "\u201E" }, "⥧": { "codepoints": [10599], "characters": "\u2967" }, "⥋": { "codepoints": [10571], "characters": "\u294B" }, "↲": { "codepoints": [8626], "characters": "\u21B2" }, "≤": { "codepoints": [8804], "characters": "\u2264" }, "←": { "codepoints": [8592], "characters": "\u2190" }, "↢": { "codepoints": [8610], "characters": "\u21A2" }, "↽": { "codepoints": [8637], "characters": "\u21BD" }, "↼": { "codepoints": [8636], "characters": "\u21BC" }, "⇇": { "codepoints": [8647], "characters": "\u21C7" }, "↔": { "codepoints": [8596], "characters": "\u2194" }, "⇆": { "codepoints": [8646], "characters": "\u21C6" }, "⇋": { "codepoints": [8651], "characters": "\u21CB" }, "↭": { "codepoints": [8621], "characters": "\u21AD" }, "⋋": { "codepoints": [8907], "characters": "\u22CB" }, "⋚": { "codepoints": [8922], "characters": "\u22DA" }, "≤": { "codepoints": [8804], "characters": "\u2264" }, "≦": { "codepoints": [8806], "characters": "\u2266" }, "⩽": { "codepoints": [10877], "characters": "\u2A7D" }, "⩽": { "codepoints": [10877], "characters": "\u2A7D" }, "⪨": { "codepoints": [10920], "characters": "\u2AA8" }, "⩿": { "codepoints": [10879], "characters": "\u2A7F" }, "⪁": { "codepoints": [10881], "characters": "\u2A81" }, "⪃": { "codepoints": [10883], "characters": "\u2A83" }, "⋚︀": { "codepoints": [8922, 65024], "characters": "\u22DA\uFE00" }, "⪓": { "codepoints": [10899], "characters": "\u2A93" }, "⪅": { "codepoints": [10885], "characters": "\u2A85" }, "⋖": { "codepoints": [8918], "characters": "\u22D6" }, "⋚": { "codepoints": [8922], "characters": "\u22DA" }, "⪋": { "codepoints": [10891], "characters": "\u2A8B" }, "≶": { "codepoints": [8822], "characters": "\u2276" }, "≲": { "codepoints": [8818], "characters": "\u2272" }, "⥼": { "codepoints": [10620], "characters": "\u297C" }, "⌊": { "codepoints": [8970], "characters": "\u230A" }, "𝔩": { "codepoints": [120105], "characters": "\uD835\uDD29" }, "≶": { "codepoints": [8822], "characters": "\u2276" }, "⪑": { "codepoints": [10897], "characters": "\u2A91" }, "↽": { "codepoints": [8637], "characters": "\u21BD" }, "↼": { "codepoints": [8636], "characters": "\u21BC" }, "⥪": { "codepoints": [10602], "characters": "\u296A" }, "▄": { "codepoints": [9604], "characters": "\u2584" }, "љ": { "codepoints": [1113], "characters": "\u0459" }, "≪": { "codepoints": [8810], "characters": "\u226A" }, "⇇": { "codepoints": [8647], "characters": "\u21C7" }, "⌞": { "codepoints": [8990], "characters": "\u231E" }, "⥫": { "codepoints": [10603], "characters": "\u296B" }, "◺": { "codepoints": [9722], "characters": "\u25FA" }, "ŀ": { "codepoints": [320], "characters": "\u0140" }, "⎰": { "codepoints": [9136], "characters": "\u23B0" }, "⎰": { "codepoints": [9136], "characters": "\u23B0" }, "≨": { "codepoints": [8808], "characters": "\u2268" }, "⪉": { "codepoints": [10889], "characters": "\u2A89" }, "⪉": { "codepoints": [10889], "characters": "\u2A89" }, "⪇": { "codepoints": [10887], "characters": "\u2A87" }, "⪇": { "codepoints": [10887], "characters": "\u2A87" }, "≨": { "codepoints": [8808], "characters": "\u2268" }, "⋦": { "codepoints": [8934], "characters": "\u22E6" }, "⟬": { "codepoints": [10220], "characters": "\u27EC" }, "⇽": { "codepoints": [8701], "characters": "\u21FD" }, "⟦": { "codepoints": [10214], "characters": "\u27E6" }, "⟵": { "codepoints": [10229], "characters": "\u27F5" }, "⟷": { "codepoints": [10231], "characters": "\u27F7" }, "⟼": { "codepoints": [10236], "characters": "\u27FC" }, "⟶": { "codepoints": [10230], "characters": "\u27F6" }, "↫": { "codepoints": [8619], "characters": "\u21AB" }, "↬": { "codepoints": [8620], "characters": "\u21AC" }, "⦅": { "codepoints": [10629], "characters": "\u2985" }, "𝕝": { "codepoints": [120157], "characters": "\uD835\uDD5D" }, "⨭": { "codepoints": [10797], "characters": "\u2A2D" }, "⨴": { "codepoints": [10804], "characters": "\u2A34" }, "∗": { "codepoints": [8727], "characters": "\u2217" }, "_": { "codepoints": [95], "characters": "\u005F" }, "◊": { "codepoints": [9674], "characters": "\u25CA" }, "◊": { "codepoints": [9674], "characters": "\u25CA" }, "⧫": { "codepoints": [10731], "characters": "\u29EB" }, "(": { "codepoints": [40], "characters": "\u0028" }, "⦓": { "codepoints": [10643], "characters": "\u2993" }, "⇆": { "codepoints": [8646], "characters": "\u21C6" }, "⌟": { "codepoints": [8991], "characters": "\u231F" }, "⇋": { "codepoints": [8651], "characters": "\u21CB" }, "⥭": { "codepoints": [10605], "characters": "\u296D" }, "‎": { "codepoints": [8206], "characters": "\u200E" }, "⊿": { "codepoints": [8895], "characters": "\u22BF" }, "‹": { "codepoints": [8249], "characters": "\u2039" }, "𝓁": { "codepoints": [120001], "characters": "\uD835\uDCC1" }, "↰": { "codepoints": [8624], "characters": "\u21B0" }, "≲": { "codepoints": [8818], "characters": "\u2272" }, "⪍": { "codepoints": [10893], "characters": "\u2A8D" }, "⪏": { "codepoints": [10895], "characters": "\u2A8F" }, "[": { "codepoints": [91], "characters": "\u005B" }, "‘": { "codepoints": [8216], "characters": "\u2018" }, "‚": { "codepoints": [8218], "characters": "\u201A" }, "ł": { "codepoints": [322], "characters": "\u0142" }, "<": { "codepoints": [60], "characters": "\u003C" }, "<": { "codepoints": [60], "characters": "\u003C" }, "⪦": { "codepoints": [10918], "characters": "\u2AA6" }, "⩹": { "codepoints": [10873], "characters": "\u2A79" }, "⋖": { "codepoints": [8918], "characters": "\u22D6" }, "⋋": { "codepoints": [8907], "characters": "\u22CB" }, "⋉": { "codepoints": [8905], "characters": "\u22C9" }, "⥶": { "codepoints": [10614], "characters": "\u2976" }, "⩻": { "codepoints": [10875], "characters": "\u2A7B" }, "⦖": { "codepoints": [10646], "characters": "\u2996" }, "◃": { "codepoints": [9667], "characters": "\u25C3" }, "⊴": { "codepoints": [8884], "characters": "\u22B4" }, "◂": { "codepoints": [9666], "characters": "\u25C2" }, "⥊": { "codepoints": [10570], "characters": "\u294A" }, "⥦": { "codepoints": [10598], "characters": "\u2966" }, "≨︀": { "codepoints": [8808, 65024], "characters": "\u2268\uFE00" }, "≨︀": { "codepoints": [8808, 65024], "characters": "\u2268\uFE00" }, "∺": { "codepoints": [8762], "characters": "\u223A" }, "¯": { "codepoints": [175], "characters": "\u00AF" }, "¯": { "codepoints": [175], "characters": "\u00AF" }, "♂": { "codepoints": [9794], "characters": "\u2642" }, "✠": { "codepoints": [10016], "characters": "\u2720" }, "✠": { "codepoints": [10016], "characters": "\u2720" }, "↦": { "codepoints": [8614], "characters": "\u21A6" }, "↦": { "codepoints": [8614], "characters": "\u21A6" }, "↧": { "codepoints": [8615], "characters": "\u21A7" }, "↤": { "codepoints": [8612], "characters": "\u21A4" }, "↥": { "codepoints": [8613], "characters": "\u21A5" }, "▮": { "codepoints": [9646], "characters": "\u25AE" }, "⨩": { "codepoints": [10793], "characters": "\u2A29" }, "м": { "codepoints": [1084], "characters": "\u043C" }, "—": { "codepoints": [8212], "characters": "\u2014" }, "∡": { "codepoints": [8737], "characters": "\u2221" }, "𝔪": { "codepoints": [120106], "characters": "\uD835\uDD2A" }, "℧": { "codepoints": [8487], "characters": "\u2127" }, "µ": { "codepoints": [181], "characters": "\u00B5" }, "µ": { "codepoints": [181], "characters": "\u00B5" }, "∣": { "codepoints": [8739], "characters": "\u2223" }, "*": { "codepoints": [42], "characters": "\u002A" }, "⫰": { "codepoints": [10992], "characters": "\u2AF0" }, "·": { "codepoints": [183], "characters": "\u00B7" }, "·": { "codepoints": [183], "characters": "\u00B7" }, "−": { "codepoints": [8722], "characters": "\u2212" }, "⊟": { "codepoints": [8863], "characters": "\u229F" }, "∸": { "codepoints": [8760], "characters": "\u2238" }, "⨪": { "codepoints": [10794], "characters": "\u2A2A" }, "⫛": { "codepoints": [10971], "characters": "\u2ADB" }, "…": { "codepoints": [8230], "characters": "\u2026" }, "∓": { "codepoints": [8723], "characters": "\u2213" }, "⊧": { "codepoints": [8871], "characters": "\u22A7" }, "𝕞": { "codepoints": [120158], "characters": "\uD835\uDD5E" }, "∓": { "codepoints": [8723], "characters": "\u2213" }, "𝓂": { "codepoints": [120002], "characters": "\uD835\uDCC2" }, "∾": { "codepoints": [8766], "characters": "\u223E" }, "μ": { "codepoints": [956], "characters": "\u03BC" }, "⊸": { "codepoints": [8888], "characters": "\u22B8" }, "⊸": { "codepoints": [8888], "characters": "\u22B8" }, "⋙̸": { "codepoints": [8921, 824], "characters": "\u22D9\u0338" }, "≫⃒": { "codepoints": [8811, 8402], "characters": "\u226B\u20D2" }, "≫̸": { "codepoints": [8811, 824], "characters": "\u226B\u0338" }, "⇍": { "codepoints": [8653], "characters": "\u21CD" }, "⇎": { "codepoints": [8654], "characters": "\u21CE" }, "⋘̸": { "codepoints": [8920, 824], "characters": "\u22D8\u0338" }, "≪⃒": { "codepoints": [8810, 8402], "characters": "\u226A\u20D2" }, "≪̸": { "codepoints": [8810, 824], "characters": "\u226A\u0338" }, "⇏": { "codepoints": [8655], "characters": "\u21CF" }, "⊯": { "codepoints": [8879], "characters": "\u22AF" }, "⊮": { "codepoints": [8878], "characters": "\u22AE" }, "∇": { "codepoints": [8711], "characters": "\u2207" }, "ń": { "codepoints": [324], "characters": "\u0144" }, "∠⃒": { "codepoints": [8736, 8402], "characters": "\u2220\u20D2" }, "≉": { "codepoints": [8777], "characters": "\u2249" }, "⩰̸": { "codepoints": [10864, 824], "characters": "\u2A70\u0338" }, "≋̸": { "codepoints": [8779, 824], "characters": "\u224B\u0338" }, "ʼn": { "codepoints": [329], "characters": "\u0149" }, "≉": { "codepoints": [8777], "characters": "\u2249" }, "♮": { "codepoints": [9838], "characters": "\u266E" }, "♮": { "codepoints": [9838], "characters": "\u266E" }, "ℕ": { "codepoints": [8469], "characters": "\u2115" }, " ": { "codepoints": [160], "characters": "\u00A0" }, " ": { "codepoints": [160], "characters": "\u00A0" }, "≎̸": { "codepoints": [8782, 824], "characters": "\u224E\u0338" }, "≏̸": { "codepoints": [8783, 824], "characters": "\u224F\u0338" }, "⩃": { "codepoints": [10819], "characters": "\u2A43" }, "ň": { "codepoints": [328], "characters": "\u0148" }, "ņ": { "codepoints": [326], "characters": "\u0146" }, "≇": { "codepoints": [8775], "characters": "\u2247" }, "⩭̸": { "codepoints": [10861, 824], "characters": "\u2A6D\u0338" }, "⩂": { "codepoints": [10818], "characters": "\u2A42" }, "н": { "codepoints": [1085], "characters": "\u043D" }, "–": { "codepoints": [8211], "characters": "\u2013" }, "≠": { "codepoints": [8800], "characters": "\u2260" }, "⇗": { "codepoints": [8663], "characters": "\u21D7" }, "⤤": { "codepoints": [10532], "characters": "\u2924" }, "↗": { "codepoints": [8599], "characters": "\u2197" }, "↗": { "codepoints": [8599], "characters": "\u2197" }, "≐̸": { "codepoints": [8784, 824], "characters": "\u2250\u0338" }, "≢": { "codepoints": [8802], "characters": "\u2262" }, "⤨": { "codepoints": [10536], "characters": "\u2928" }, "≂̸": { "codepoints": [8770, 824], "characters": "\u2242\u0338" }, "∄": { "codepoints": [8708], "characters": "\u2204" }, "∄": { "codepoints": [8708], "characters": "\u2204" }, "𝔫": { "codepoints": [120107], "characters": "\uD835\uDD2B" }, "≧̸": { "codepoints": [8807, 824], "characters": "\u2267\u0338" }, "≱": { "codepoints": [8817], "characters": "\u2271" }, "≱": { "codepoints": [8817], "characters": "\u2271" }, "≧̸": { "codepoints": [8807, 824], "characters": "\u2267\u0338" }, "⩾̸": { "codepoints": [10878, 824], "characters": "\u2A7E\u0338" }, "⩾̸": { "codepoints": [10878, 824], "characters": "\u2A7E\u0338" }, "≵": { "codepoints": [8821], "characters": "\u2275" }, "≯": { "codepoints": [8815], "characters": "\u226F" }, "≯": { "codepoints": [8815], "characters": "\u226F" }, "⇎": { "codepoints": [8654], "characters": "\u21CE" }, "↮": { "codepoints": [8622], "characters": "\u21AE" }, "⫲": { "codepoints": [10994], "characters": "\u2AF2" }, "∋": { "codepoints": [8715], "characters": "\u220B" }, "⋼": { "codepoints": [8956], "characters": "\u22FC" }, "⋺": { "codepoints": [8954], "characters": "\u22FA" }, "∋": { "codepoints": [8715], "characters": "\u220B" }, "њ": { "codepoints": [1114], "characters": "\u045A" }, "⇍": { "codepoints": [8653], "characters": "\u21CD" }, "≦̸": { "codepoints": [8806, 824], "characters": "\u2266\u0338" }, "↚": { "codepoints": [8602], "characters": "\u219A" }, "‥": { "codepoints": [8229], "characters": "\u2025" }, "≰": { "codepoints": [8816], "characters": "\u2270" }, "↚": { "codepoints": [8602], "characters": "\u219A" }, "↮": { "codepoints": [8622], "characters": "\u21AE" }, "≰": { "codepoints": [8816], "characters": "\u2270" }, "≦̸": { "codepoints": [8806, 824], "characters": "\u2266\u0338" }, "⩽̸": { "codepoints": [10877, 824], "characters": "\u2A7D\u0338" }, "⩽̸": { "codepoints": [10877, 824], "characters": "\u2A7D\u0338" }, "≮": { "codepoints": [8814], "characters": "\u226E" }, "≴": { "codepoints": [8820], "characters": "\u2274" }, "≮": { "codepoints": [8814], "characters": "\u226E" }, "⋪": { "codepoints": [8938], "characters": "\u22EA" }, "⋬": { "codepoints": [8940], "characters": "\u22EC" }, "∤": { "codepoints": [8740], "characters": "\u2224" }, "𝕟": { "codepoints": [120159], "characters": "\uD835\uDD5F" }, "¬": { "codepoints": [172], "characters": "\u00AC" }, "¬": { "codepoints": [172], "characters": "\u00AC" }, "∉": { "codepoints": [8713], "characters": "\u2209" }, "⋹̸": { "codepoints": [8953, 824], "characters": "\u22F9\u0338" }, "⋵̸": { "codepoints": [8949, 824], "characters": "\u22F5\u0338" }, "∉": { "codepoints": [8713], "characters": "\u2209" }, "⋷": { "codepoints": [8951], "characters": "\u22F7" }, "⋶": { "codepoints": [8950], "characters": "\u22F6" }, "∌": { "codepoints": [8716], "characters": "\u220C" }, "∌": { "codepoints": [8716], "characters": "\u220C" }, "⋾": { "codepoints": [8958], "characters": "\u22FE" }, "⋽": { "codepoints": [8957], "characters": "\u22FD" }, "∦": { "codepoints": [8742], "characters": "\u2226" }, "∦": { "codepoints": [8742], "characters": "\u2226" }, "⫽⃥": { "codepoints": [11005, 8421], "characters": "\u2AFD\u20E5" }, "∂̸": { "codepoints": [8706, 824], "characters": "\u2202\u0338" }, "⨔": { "codepoints": [10772], "characters": "\u2A14" }, "⊀": { "codepoints": [8832], "characters": "\u2280" }, "⋠": { "codepoints": [8928], "characters": "\u22E0" }, "⪯̸": { "codepoints": [10927, 824], "characters": "\u2AAF\u0338" }, "⊀": { "codepoints": [8832], "characters": "\u2280" }, "⪯̸": { "codepoints": [10927, 824], "characters": "\u2AAF\u0338" }, "⇏": { "codepoints": [8655], "characters": "\u21CF" }, "↛": { "codepoints": [8603], "characters": "\u219B" }, "⤳̸": { "codepoints": [10547, 824], "characters": "\u2933\u0338" }, "↝̸": { "codepoints": [8605, 824], "characters": "\u219D\u0338" }, "↛": { "codepoints": [8603], "characters": "\u219B" }, "⋫": { "codepoints": [8939], "characters": "\u22EB" }, "⋭": { "codepoints": [8941], "characters": "\u22ED" }, "⊁": { "codepoints": [8833], "characters": "\u2281" }, "⋡": { "codepoints": [8929], "characters": "\u22E1" }, "⪰̸": { "codepoints": [10928, 824], "characters": "\u2AB0\u0338" }, "𝓃": { "codepoints": [120003], "characters": "\uD835\uDCC3" }, "∤": { "codepoints": [8740], "characters": "\u2224" }, "∦": { "codepoints": [8742], "characters": "\u2226" }, "≁": { "codepoints": [8769], "characters": "\u2241" }, "≄": { "codepoints": [8772], "characters": "\u2244" }, "≄": { "codepoints": [8772], "characters": "\u2244" }, "∤": { "codepoints": [8740], "characters": "\u2224" }, "∦": { "codepoints": [8742], "characters": "\u2226" }, "⋢": { "codepoints": [8930], "characters": "\u22E2" }, "⋣": { "codepoints": [8931], "characters": "\u22E3" }, "⊄": { "codepoints": [8836], "characters": "\u2284" }, "⫅̸": { "codepoints": [10949, 824], "characters": "\u2AC5\u0338" }, "⊈": { "codepoints": [8840], "characters": "\u2288" }, "⊂⃒": { "codepoints": [8834, 8402], "characters": "\u2282\u20D2" }, "⊈": { "codepoints": [8840], "characters": "\u2288" }, "⫅̸": { "codepoints": [10949, 824], "characters": "\u2AC5\u0338" }, "⊁": { "codepoints": [8833], "characters": "\u2281" }, "⪰̸": { "codepoints": [10928, 824], "characters": "\u2AB0\u0338" }, "⊅": { "codepoints": [8837], "characters": "\u2285" }, "⫆̸": { "codepoints": [10950, 824], "characters": "\u2AC6\u0338" }, "⊉": { "codepoints": [8841], "characters": "\u2289" }, "⊃⃒": { "codepoints": [8835, 8402], "characters": "\u2283\u20D2" }, "⊉": { "codepoints": [8841], "characters": "\u2289" }, "⫆̸": { "codepoints": [10950, 824], "characters": "\u2AC6\u0338" }, "≹": { "codepoints": [8825], "characters": "\u2279" }, "ñ": { "codepoints": [241], "characters": "\u00F1" }, "ñ": { "codepoints": [241], "characters": "\u00F1" }, "≸": { "codepoints": [8824], "characters": "\u2278" }, "⋪": { "codepoints": [8938], "characters": "\u22EA" }, "⋬": { "codepoints": [8940], "characters": "\u22EC" }, "⋫": { "codepoints": [8939], "characters": "\u22EB" }, "⋭": { "codepoints": [8941], "characters": "\u22ED" }, "ν": { "codepoints": [957], "characters": "\u03BD" }, "#": { "codepoints": [35], "characters": "\u0023" }, "№": { "codepoints": [8470], "characters": "\u2116" }, " ": { "codepoints": [8199], "characters": "\u2007" }, "⊭": { "codepoints": [8877], "characters": "\u22AD" }, "⤄": { "codepoints": [10500], "characters": "\u2904" }, "≍⃒": { "codepoints": [8781, 8402], "characters": "\u224D\u20D2" }, "⊬": { "codepoints": [8876], "characters": "\u22AC" }, "≥⃒": { "codepoints": [8805, 8402], "characters": "\u2265\u20D2" }, ">⃒": { "codepoints": [62, 8402], "characters": "\u003E\u20D2" }, "⧞": { "codepoints": [10718], "characters": "\u29DE" }, "⤂": { "codepoints": [10498], "characters": "\u2902" }, "≤⃒": { "codepoints": [8804, 8402], "characters": "\u2264\u20D2" }, "<⃒": { "codepoints": [60, 8402], "characters": "\u003C\u20D2" }, "⊴⃒": { "codepoints": [8884, 8402], "characters": "\u22B4\u20D2" }, "⤃": { "codepoints": [10499], "characters": "\u2903" }, "⊵⃒": { "codepoints": [8885, 8402], "characters": "\u22B5\u20D2" }, "∼⃒": { "codepoints": [8764, 8402], "characters": "\u223C\u20D2" }, "⇖": { "codepoints": [8662], "characters": "\u21D6" }, "⤣": { "codepoints": [10531], "characters": "\u2923" }, "↖": { "codepoints": [8598], "characters": "\u2196" }, "↖": { "codepoints": [8598], "characters": "\u2196" }, "⤧": { "codepoints": [10535], "characters": "\u2927" }, "Ⓢ": { "codepoints": [9416], "characters": "\u24C8" }, "ó": { "codepoints": [243], "characters": "\u00F3" }, "ó": { "codepoints": [243], "characters": "\u00F3" }, "⊛": { "codepoints": [8859], "characters": "\u229B" }, "⊚": { "codepoints": [8858], "characters": "\u229A" }, "ô": { "codepoints": [244], "characters": "\u00F4" }, "ô": { "codepoints": [244], "characters": "\u00F4" }, "о": { "codepoints": [1086], "characters": "\u043E" }, "⊝": { "codepoints": [8861], "characters": "\u229D" }, "ő": { "codepoints": [337], "characters": "\u0151" }, "⨸": { "codepoints": [10808], "characters": "\u2A38" }, "⊙": { "codepoints": [8857], "characters": "\u2299" }, "⦼": { "codepoints": [10684], "characters": "\u29BC" }, "œ": { "codepoints": [339], "characters": "\u0153" }, "⦿": { "codepoints": [10687], "characters": "\u29BF" }, "𝔬": { "codepoints": [120108], "characters": "\uD835\uDD2C" }, "˛": { "codepoints": [731], "characters": "\u02DB" }, "ò": { "codepoints": [242], "characters": "\u00F2" }, "ò": { "codepoints": [242], "characters": "\u00F2" }, "⧁": { "codepoints": [10689], "characters": "\u29C1" }, "⦵": { "codepoints": [10677], "characters": "\u29B5" }, "Ω": { "codepoints": [937], "characters": "\u03A9" }, "∮": { "codepoints": [8750], "characters": "\u222E" }, "↺": { "codepoints": [8634], "characters": "\u21BA" }, "⦾": { "codepoints": [10686], "characters": "\u29BE" }, "⦻": { "codepoints": [10683], "characters": "\u29BB" }, "‾": { "codepoints": [8254], "characters": "\u203E" }, "⧀": { "codepoints": [10688], "characters": "\u29C0" }, "ō": { "codepoints": [333], "characters": "\u014D" }, "ω": { "codepoints": [969], "characters": "\u03C9" }, "ο": { "codepoints": [959], "characters": "\u03BF" }, "⦶": { "codepoints": [10678], "characters": "\u29B6" }, "⊖": { "codepoints": [8854], "characters": "\u2296" }, "𝕠": { "codepoints": [120160], "characters": "\uD835\uDD60" }, "⦷": { "codepoints": [10679], "characters": "\u29B7" }, "⦹": { "codepoints": [10681], "characters": "\u29B9" }, "⊕": { "codepoints": [8853], "characters": "\u2295" }, "∨": { "codepoints": [8744], "characters": "\u2228" }, "↻": { "codepoints": [8635], "characters": "\u21BB" }, "⩝": { "codepoints": [10845], "characters": "\u2A5D" }, "ℴ": { "codepoints": [8500], "characters": "\u2134" }, "ℴ": { "codepoints": [8500], "characters": "\u2134" }, "ª": { "codepoints": [170], "characters": "\u00AA" }, "ª": { "codepoints": [170], "characters": "\u00AA" }, "º": { "codepoints": [186], "characters": "\u00BA" }, "º": { "codepoints": [186], "characters": "\u00BA" }, "⊶": { "codepoints": [8886], "characters": "\u22B6" }, "⩖": { "codepoints": [10838], "characters": "\u2A56" }, "⩗": { "codepoints": [10839], "characters": "\u2A57" }, "⩛": { "codepoints": [10843], "characters": "\u2A5B" }, "ℴ": { "codepoints": [8500], "characters": "\u2134" }, "ø": { "codepoints": [248], "characters": "\u00F8" }, "ø": { "codepoints": [248], "characters": "\u00F8" }, "⊘": { "codepoints": [8856], "characters": "\u2298" }, "õ": { "codepoints": [245], "characters": "\u00F5" }, "õ": { "codepoints": [245], "characters": "\u00F5" }, "⊗": { "codepoints": [8855], "characters": "\u2297" }, "⨶": { "codepoints": [10806], "characters": "\u2A36" }, "ö": { "codepoints": [246], "characters": "\u00F6" }, "ö": { "codepoints": [246], "characters": "\u00F6" }, "⌽": { "codepoints": [9021], "characters": "\u233D" }, "∥": { "codepoints": [8741], "characters": "\u2225" }, "¶": { "codepoints": [182], "characters": "\u00B6" }, "¶": { "codepoints": [182], "characters": "\u00B6" }, "∥": { "codepoints": [8741], "characters": "\u2225" }, "⫳": { "codepoints": [10995], "characters": "\u2AF3" }, "⫽": { "codepoints": [11005], "characters": "\u2AFD" }, "∂": { "codepoints": [8706], "characters": "\u2202" }, "п": { "codepoints": [1087], "characters": "\u043F" }, "%": { "codepoints": [37], "characters": "\u0025" }, ".": { "codepoints": [46], "characters": "\u002E" }, "‰": { "codepoints": [8240], "characters": "\u2030" }, "⊥": { "codepoints": [8869], "characters": "\u22A5" }, "‱": { "codepoints": [8241], "characters": "\u2031" }, "𝔭": { "codepoints": [120109], "characters": "\uD835\uDD2D" }, "φ": { "codepoints": [966], "characters": "\u03C6" }, "ϕ": { "codepoints": [981], "characters": "\u03D5" }, "ℳ": { "codepoints": [8499], "characters": "\u2133" }, "☎": { "codepoints": [9742], "characters": "\u260E" }, "π": { "codepoints": [960], "characters": "\u03C0" }, "⋔": { "codepoints": [8916], "characters": "\u22D4" }, "ϖ": { "codepoints": [982], "characters": "\u03D6" }, "ℏ": { "codepoints": [8463], "characters": "\u210F" }, "ℎ": { "codepoints": [8462], "characters": "\u210E" }, "ℏ": { "codepoints": [8463], "characters": "\u210F" }, "+": { "codepoints": [43], "characters": "\u002B" }, "⨣": { "codepoints": [10787], "characters": "\u2A23" }, "⊞": { "codepoints": [8862], "characters": "\u229E" }, "⨢": { "codepoints": [10786], "characters": "\u2A22" }, "∔": { "codepoints": [8724], "characters": "\u2214" }, "⨥": { "codepoints": [10789], "characters": "\u2A25" }, "⩲": { "codepoints": [10866], "characters": "\u2A72" }, "±": { "codepoints": [177], "characters": "\u00B1" }, "±": { "codepoints": [177], "characters": "\u00B1" }, "⨦": { "codepoints": [10790], "characters": "\u2A26" }, "⨧": { "codepoints": [10791], "characters": "\u2A27" }, "±": { "codepoints": [177], "characters": "\u00B1" }, "⨕": { "codepoints": [10773], "characters": "\u2A15" }, "𝕡": { "codepoints": [120161], "characters": "\uD835\uDD61" }, "£": { "codepoints": [163], "characters": "\u00A3" }, "£": { "codepoints": [163], "characters": "\u00A3" }, "≺": { "codepoints": [8826], "characters": "\u227A" }, "⪳": { "codepoints": [10931], "characters": "\u2AB3" }, "⪷": { "codepoints": [10935], "characters": "\u2AB7" }, "≼": { "codepoints": [8828], "characters": "\u227C" }, "⪯": { "codepoints": [10927], "characters": "\u2AAF" }, "≺": { "codepoints": [8826], "characters": "\u227A" }, "⪷": { "codepoints": [10935], "characters": "\u2AB7" }, "≼": { "codepoints": [8828], "characters": "\u227C" }, "⪯": { "codepoints": [10927], "characters": "\u2AAF" }, "⪹": { "codepoints": [10937], "characters": "\u2AB9" }, "⪵": { "codepoints": [10933], "characters": "\u2AB5" }, "⋨": { "codepoints": [8936], "characters": "\u22E8" }, "≾": { "codepoints": [8830], "characters": "\u227E" }, "′": { "codepoints": [8242], "characters": "\u2032" }, "ℙ": { "codepoints": [8473], "characters": "\u2119" }, "⪵": { "codepoints": [10933], "characters": "\u2AB5" }, "⪹": { "codepoints": [10937], "characters": "\u2AB9" }, "⋨": { "codepoints": [8936], "characters": "\u22E8" }, "∏": { "codepoints": [8719], "characters": "\u220F" }, "⌮": { "codepoints": [9006], "characters": "\u232E" }, "⌒": { "codepoints": [8978], "characters": "\u2312" }, "⌓": { "codepoints": [8979], "characters": "\u2313" }, "∝": { "codepoints": [8733], "characters": "\u221D" }, "∝": { "codepoints": [8733], "characters": "\u221D" }, "≾": { "codepoints": [8830], "characters": "\u227E" }, "⊰": { "codepoints": [8880], "characters": "\u22B0" }, "𝓅": { "codepoints": [120005], "characters": "\uD835\uDCC5" }, "ψ": { "codepoints": [968], "characters": "\u03C8" }, " ": { "codepoints": [8200], "characters": "\u2008" }, "𝔮": { "codepoints": [120110], "characters": "\uD835\uDD2E" }, "⨌": { "codepoints": [10764], "characters": "\u2A0C" }, "𝕢": { "codepoints": [120162], "characters": "\uD835\uDD62" }, "⁗": { "codepoints": [8279], "characters": "\u2057" }, "𝓆": { "codepoints": [120006], "characters": "\uD835\uDCC6" }, "ℍ": { "codepoints": [8461], "characters": "\u210D" }, "⨖": { "codepoints": [10774], "characters": "\u2A16" }, "?": { "codepoints": [63], "characters": "\u003F" }, "≟": { "codepoints": [8799], "characters": "\u225F" }, """: { "codepoints": [34], "characters": "\u0022" }, """: { "codepoints": [34], "characters": "\u0022" }, "⇛": { "codepoints": [8667], "characters": "\u21DB" }, "⇒": { "codepoints": [8658], "characters": "\u21D2" }, "⤜": { "codepoints": [10524], "characters": "\u291C" }, "⤏": { "codepoints": [10511], "characters": "\u290F" }, "⥤": { "codepoints": [10596], "characters": "\u2964" }, "∽̱": { "codepoints": [8765, 817], "characters": "\u223D\u0331" }, "ŕ": { "codepoints": [341], "characters": "\u0155" }, "√": { "codepoints": [8730], "characters": "\u221A" }, "⦳": { "codepoints": [10675], "characters": "\u29B3" }, "⟩": { "codepoints": [10217], "characters": "\u27E9" }, "⦒": { "codepoints": [10642], "characters": "\u2992" }, "⦥": { "codepoints": [10661], "characters": "\u29A5" }, "⟩": { "codepoints": [10217], "characters": "\u27E9" }, "»": { "codepoints": [187], "characters": "\u00BB" }, "»": { "codepoints": [187], "characters": "\u00BB" }, "→": { "codepoints": [8594], "characters": "\u2192" }, "⥵": { "codepoints": [10613], "characters": "\u2975" }, "⇥": { "codepoints": [8677], "characters": "\u21E5" }, "⤠": { "codepoints": [10528], "characters": "\u2920" }, "⤳": { "codepoints": [10547], "characters": "\u2933" }, "⤞": { "codepoints": [10526], "characters": "\u291E" }, "↪": { "codepoints": [8618], "characters": "\u21AA" }, "↬": { "codepoints": [8620], "characters": "\u21AC" }, "⥅": { "codepoints": [10565], "characters": "\u2945" }, "⥴": { "codepoints": [10612], "characters": "\u2974" }, "↣": { "codepoints": [8611], "characters": "\u21A3" }, "↝": { "codepoints": [8605], "characters": "\u219D" }, "⤚": { "codepoints": [10522], "characters": "\u291A" }, "∶": { "codepoints": [8758], "characters": "\u2236" }, "ℚ": { "codepoints": [8474], "characters": "\u211A" }, "⤍": { "codepoints": [10509], "characters": "\u290D" }, "❳": { "codepoints": [10099], "characters": "\u2773" }, "}": { "codepoints": [125], "characters": "\u007D" }, "]": { "codepoints": [93], "characters": "\u005D" }, "⦌": { "codepoints": [10636], "characters": "\u298C" }, "⦎": { "codepoints": [10638], "characters": "\u298E" }, "⦐": { "codepoints": [10640], "characters": "\u2990" }, "ř": { "codepoints": [345], "characters": "\u0159" }, "ŗ": { "codepoints": [343], "characters": "\u0157" }, "⌉": { "codepoints": [8969], "characters": "\u2309" }, "}": { "codepoints": [125], "characters": "\u007D" }, "р": { "codepoints": [1088], "characters": "\u0440" }, "⤷": { "codepoints": [10551], "characters": "\u2937" }, "⥩": { "codepoints": [10601], "characters": "\u2969" }, "”": { "codepoints": [8221], "characters": "\u201D" }, "”": { "codepoints": [8221], "characters": "\u201D" }, "↳": { "codepoints": [8627], "characters": "\u21B3" }, "ℜ": { "codepoints": [8476], "characters": "\u211C" }, "ℛ": { "codepoints": [8475], "characters": "\u211B" }, "ℜ": { "codepoints": [8476], "characters": "\u211C" }, "ℝ": { "codepoints": [8477], "characters": "\u211D" }, "▭": { "codepoints": [9645], "characters": "\u25AD" }, "®": { "codepoints": [174], "characters": "\u00AE" }, "®": { "codepoints": [174], "characters": "\u00AE" }, "⥽": { "codepoints": [10621], "characters": "\u297D" }, "⌋": { "codepoints": [8971], "characters": "\u230B" }, "𝔯": { "codepoints": [120111], "characters": "\uD835\uDD2F" }, "⇁": { "codepoints": [8641], "characters": "\u21C1" }, "⇀": { "codepoints": [8640], "characters": "\u21C0" }, "⥬": { "codepoints": [10604], "characters": "\u296C" }, "ρ": { "codepoints": [961], "characters": "\u03C1" }, "ϱ": { "codepoints": [1009], "characters": "\u03F1" }, "→": { "codepoints": [8594], "characters": "\u2192" }, "↣": { "codepoints": [8611], "characters": "\u21A3" }, "⇁": { "codepoints": [8641], "characters": "\u21C1" }, "⇀": { "codepoints": [8640], "characters": "\u21C0" }, "⇄": { "codepoints": [8644], "characters": "\u21C4" }, "⇌": { "codepoints": [8652], "characters": "\u21CC" }, "⇉": { "codepoints": [8649], "characters": "\u21C9" }, "↝": { "codepoints": [8605], "characters": "\u219D" }, "⋌": { "codepoints": [8908], "characters": "\u22CC" }, "˚": { "codepoints": [730], "characters": "\u02DA" }, "≓": { "codepoints": [8787], "characters": "\u2253" }, "⇄": { "codepoints": [8644], "characters": "\u21C4" }, "⇌": { "codepoints": [8652], "characters": "\u21CC" }, "‏": { "codepoints": [8207], "characters": "\u200F" }, "⎱": { "codepoints": [9137], "characters": "\u23B1" }, "⎱": { "codepoints": [9137], "characters": "\u23B1" }, "⫮": { "codepoints": [10990], "characters": "\u2AEE" }, "⟭": { "codepoints": [10221], "characters": "\u27ED" }, "⇾": { "codepoints": [8702], "characters": "\u21FE" }, "⟧": { "codepoints": [10215], "characters": "\u27E7" }, "⦆": { "codepoints": [10630], "characters": "\u2986" }, "𝕣": { "codepoints": [120163], "characters": "\uD835\uDD63" }, "⨮": { "codepoints": [10798], "characters": "\u2A2E" }, "⨵": { "codepoints": [10805], "characters": "\u2A35" }, ")": { "codepoints": [41], "characters": "\u0029" }, "⦔": { "codepoints": [10644], "characters": "\u2994" }, "⨒": { "codepoints": [10770], "characters": "\u2A12" }, "⇉": { "codepoints": [8649], "characters": "\u21C9" }, "›": { "codepoints": [8250], "characters": "\u203A" }, "𝓇": { "codepoints": [120007], "characters": "\uD835\uDCC7" }, "↱": { "codepoints": [8625], "characters": "\u21B1" }, "]": { "codepoints": [93], "characters": "\u005D" }, "’": { "codepoints": [8217], "characters": "\u2019" }, "’": { "codepoints": [8217], "characters": "\u2019" }, "⋌": { "codepoints": [8908], "characters": "\u22CC" }, "⋊": { "codepoints": [8906], "characters": "\u22CA" }, "▹": { "codepoints": [9657], "characters": "\u25B9" }, "⊵": { "codepoints": [8885], "characters": "\u22B5" }, "▸": { "codepoints": [9656], "characters": "\u25B8" }, "⧎": { "codepoints": [10702], "characters": "\u29CE" }, "⥨": { "codepoints": [10600], "characters": "\u2968" }, "℞": { "codepoints": [8478], "characters": "\u211E" }, "ś": { "codepoints": [347], "characters": "\u015B" }, "‚": { "codepoints": [8218], "characters": "\u201A" }, "≻": { "codepoints": [8827], "characters": "\u227B" }, "⪴": { "codepoints": [10932], "characters": "\u2AB4" }, "⪸": { "codepoints": [10936], "characters": "\u2AB8" }, "š": { "codepoints": [353], "characters": "\u0161" }, "≽": { "codepoints": [8829], "characters": "\u227D" }, "⪰": { "codepoints": [10928], "characters": "\u2AB0" }, "ş": { "codepoints": [351], "characters": "\u015F" }, "ŝ": { "codepoints": [349], "characters": "\u015D" }, "⪶": { "codepoints": [10934], "characters": "\u2AB6" }, "⪺": { "codepoints": [10938], "characters": "\u2ABA" }, "⋩": { "codepoints": [8937], "characters": "\u22E9" }, "⨓": { "codepoints": [10771], "characters": "\u2A13" }, "≿": { "codepoints": [8831], "characters": "\u227F" }, "с": { "codepoints": [1089], "characters": "\u0441" }, "⋅": { "codepoints": [8901], "characters": "\u22C5" }, "⊡": { "codepoints": [8865], "characters": "\u22A1" }, "⩦": { "codepoints": [10854], "characters": "\u2A66" }, "⇘": { "codepoints": [8664], "characters": "\u21D8" }, "⤥": { "codepoints": [10533], "characters": "\u2925" }, "↘": { "codepoints": [8600], "characters": "\u2198" }, "↘": { "codepoints": [8600], "characters": "\u2198" }, "§": { "codepoints": [167], "characters": "\u00A7" }, "§": { "codepoints": [167], "characters": "\u00A7" }, ";": { "codepoints": [59], "characters": "\u003B" }, "⤩": { "codepoints": [10537], "characters": "\u2929" }, "∖": { "codepoints": [8726], "characters": "\u2216" }, "∖": { "codepoints": [8726], "characters": "\u2216" }, "✶": { "codepoints": [10038], "characters": "\u2736" }, "𝔰": { "codepoints": [120112], "characters": "\uD835\uDD30" }, "⌢": { "codepoints": [8994], "characters": "\u2322" }, "♯": { "codepoints": [9839], "characters": "\u266F" }, "щ": { "codepoints": [1097], "characters": "\u0449" }, "ш": { "codepoints": [1096], "characters": "\u0448" }, "∣": { "codepoints": [8739], "characters": "\u2223" }, "∥": { "codepoints": [8741], "characters": "\u2225" }, "­": { "codepoints": [173], "characters": "\u00AD" }, "­": { "codepoints": [173], "characters": "\u00AD" }, "σ": { "codepoints": [963], "characters": "\u03C3" }, "ς": { "codepoints": [962], "characters": "\u03C2" }, "ς": { "codepoints": [962], "characters": "\u03C2" }, "∼": { "codepoints": [8764], "characters": "\u223C" }, "⩪": { "codepoints": [10858], "characters": "\u2A6A" }, "≃": { "codepoints": [8771], "characters": "\u2243" }, "≃": { "codepoints": [8771], "characters": "\u2243" }, "⪞": { "codepoints": [10910], "characters": "\u2A9E" }, "⪠": { "codepoints": [10912], "characters": "\u2AA0" }, "⪝": { "codepoints": [10909], "characters": "\u2A9D" }, "⪟": { "codepoints": [10911], "characters": "\u2A9F" }, "≆": { "codepoints": [8774], "characters": "\u2246" }, "⨤": { "codepoints": [10788], "characters": "\u2A24" }, "⥲": { "codepoints": [10610], "characters": "\u2972" }, "←": { "codepoints": [8592], "characters": "\u2190" }, "∖": { "codepoints": [8726], "characters": "\u2216" }, "⨳": { "codepoints": [10803], "characters": "\u2A33" }, "⧤": { "codepoints": [10724], "characters": "\u29E4" }, "∣": { "codepoints": [8739], "characters": "\u2223" }, "⌣": { "codepoints": [8995], "characters": "\u2323" }, "⪪": { "codepoints": [10922], "characters": "\u2AAA" }, "⪬": { "codepoints": [10924], "characters": "\u2AAC" }, "⪬︀": { "codepoints": [10924, 65024], "characters": "\u2AAC\uFE00" }, "ь": { "codepoints": [1100], "characters": "\u044C" }, "/": { "codepoints": [47], "characters": "\u002F" }, "⧄": { "codepoints": [10692], "characters": "\u29C4" }, "⌿": { "codepoints": [9023], "characters": "\u233F" }, "𝕤": { "codepoints": [120164], "characters": "\uD835\uDD64" }, "♠": { "codepoints": [9824], "characters": "\u2660" }, "♠": { "codepoints": [9824], "characters": "\u2660" }, "∥": { "codepoints": [8741], "characters": "\u2225" }, "⊓": { "codepoints": [8851], "characters": "\u2293" }, "⊓︀": { "codepoints": [8851, 65024], "characters": "\u2293\uFE00" }, "⊔": { "codepoints": [8852], "characters": "\u2294" }, "⊔︀": { "codepoints": [8852, 65024], "characters": "\u2294\uFE00" }, "⊏": { "codepoints": [8847], "characters": "\u228F" }, "⊑": { "codepoints": [8849], "characters": "\u2291" }, "⊏": { "codepoints": [8847], "characters": "\u228F" }, "⊑": { "codepoints": [8849], "characters": "\u2291" }, "⊐": { "codepoints": [8848], "characters": "\u2290" }, "⊒": { "codepoints": [8850], "characters": "\u2292" }, "⊐": { "codepoints": [8848], "characters": "\u2290" }, "⊒": { "codepoints": [8850], "characters": "\u2292" }, "□": { "codepoints": [9633], "characters": "\u25A1" }, "□": { "codepoints": [9633], "characters": "\u25A1" }, "▪": { "codepoints": [9642], "characters": "\u25AA" }, "▪": { "codepoints": [9642], "characters": "\u25AA" }, "→": { "codepoints": [8594], "characters": "\u2192" }, "𝓈": { "codepoints": [120008], "characters": "\uD835\uDCC8" }, "∖": { "codepoints": [8726], "characters": "\u2216" }, "⌣": { "codepoints": [8995], "characters": "\u2323" }, "⋆": { "codepoints": [8902], "characters": "\u22C6" }, "☆": { "codepoints": [9734], "characters": "\u2606" }, "★": { "codepoints": [9733], "characters": "\u2605" }, "ϵ": { "codepoints": [1013], "characters": "\u03F5" }, "ϕ": { "codepoints": [981], "characters": "\u03D5" }, "¯": { "codepoints": [175], "characters": "\u00AF" }, "⊂": { "codepoints": [8834], "characters": "\u2282" }, "⫅": { "codepoints": [10949], "characters": "\u2AC5" }, "⪽": { "codepoints": [10941], "characters": "\u2ABD" }, "⊆": { "codepoints": [8838], "characters": "\u2286" }, "⫃": { "codepoints": [10947], "characters": "\u2AC3" }, "⫁": { "codepoints": [10945], "characters": "\u2AC1" }, "⫋": { "codepoints": [10955], "characters": "\u2ACB" }, "⊊": { "codepoints": [8842], "characters": "\u228A" }, "⪿": { "codepoints": [10943], "characters": "\u2ABF" }, "⥹": { "codepoints": [10617], "characters": "\u2979" }, "⊂": { "codepoints": [8834], "characters": "\u2282" }, "⊆": { "codepoints": [8838], "characters": "\u2286" }, "⫅": { "codepoints": [10949], "characters": "\u2AC5" }, "⊊": { "codepoints": [8842], "characters": "\u228A" }, "⫋": { "codepoints": [10955], "characters": "\u2ACB" }, "⫇": { "codepoints": [10951], "characters": "\u2AC7" }, "⫕": { "codepoints": [10965], "characters": "\u2AD5" }, "⫓": { "codepoints": [10963], "characters": "\u2AD3" }, "≻": { "codepoints": [8827], "characters": "\u227B" }, "⪸": { "codepoints": [10936], "characters": "\u2AB8" }, "≽": { "codepoints": [8829], "characters": "\u227D" }, "⪰": { "codepoints": [10928], "characters": "\u2AB0" }, "⪺": { "codepoints": [10938], "characters": "\u2ABA" }, "⪶": { "codepoints": [10934], "characters": "\u2AB6" }, "⋩": { "codepoints": [8937], "characters": "\u22E9" }, "≿": { "codepoints": [8831], "characters": "\u227F" }, "∑": { "codepoints": [8721], "characters": "\u2211" }, "♪": { "codepoints": [9834], "characters": "\u266A" }, "¹": { "codepoints": [185], "characters": "\u00B9" }, "¹": { "codepoints": [185], "characters": "\u00B9" }, "²": { "codepoints": [178], "characters": "\u00B2" }, "²": { "codepoints": [178], "characters": "\u00B2" }, "³": { "codepoints": [179], "characters": "\u00B3" }, "³": { "codepoints": [179], "characters": "\u00B3" }, "⊃": { "codepoints": [8835], "characters": "\u2283" }, "⫆": { "codepoints": [10950], "characters": "\u2AC6" }, "⪾": { "codepoints": [10942], "characters": "\u2ABE" }, "⫘": { "codepoints": [10968], "characters": "\u2AD8" }, "⊇": { "codepoints": [8839], "characters": "\u2287" }, "⫄": { "codepoints": [10948], "characters": "\u2AC4" }, "⟉": { "codepoints": [10185], "characters": "\u27C9" }, "⫗": { "codepoints": [10967], "characters": "\u2AD7" }, "⥻": { "codepoints": [10619], "characters": "\u297B" }, "⫂": { "codepoints": [10946], "characters": "\u2AC2" }, "⫌": { "codepoints": [10956], "characters": "\u2ACC" }, "⊋": { "codepoints": [8843], "characters": "\u228B" }, "⫀": { "codepoints": [10944], "characters": "\u2AC0" }, "⊃": { "codepoints": [8835], "characters": "\u2283" }, "⊇": { "codepoints": [8839], "characters": "\u2287" }, "⫆": { "codepoints": [10950], "characters": "\u2AC6" }, "⊋": { "codepoints": [8843], "characters": "\u228B" }, "⫌": { "codepoints": [10956], "characters": "\u2ACC" }, "⫈": { "codepoints": [10952], "characters": "\u2AC8" }, "⫔": { "codepoints": [10964], "characters": "\u2AD4" }, "⫖": { "codepoints": [10966], "characters": "\u2AD6" }, "⇙": { "codepoints": [8665], "characters": "\u21D9" }, "⤦": { "codepoints": [10534], "characters": "\u2926" }, "↙": { "codepoints": [8601], "characters": "\u2199" }, "↙": { "codepoints": [8601], "characters": "\u2199" }, "⤪": { "codepoints": [10538], "characters": "\u292A" }, "ß": { "codepoints": [223], "characters": "\u00DF" }, "ß": { "codepoints": [223], "characters": "\u00DF" }, "⌖": { "codepoints": [8982], "characters": "\u2316" }, "τ": { "codepoints": [964], "characters": "\u03C4" }, "⎴": { "codepoints": [9140], "characters": "\u23B4" }, "ť": { "codepoints": [357], "characters": "\u0165" }, "ţ": { "codepoints": [355], "characters": "\u0163" }, "т": { "codepoints": [1090], "characters": "\u0442" }, "⃛": { "codepoints": [8411], "characters": "\u20DB" }, "⌕": { "codepoints": [8981], "characters": "\u2315" }, "𝔱": { "codepoints": [120113], "characters": "\uD835\uDD31" }, "∴": { "codepoints": [8756], "characters": "\u2234" }, "∴": { "codepoints": [8756], "characters": "\u2234" }, "θ": { "codepoints": [952], "characters": "\u03B8" }, "ϑ": { "codepoints": [977], "characters": "\u03D1" }, "ϑ": { "codepoints": [977], "characters": "\u03D1" }, "≈": { "codepoints": [8776], "characters": "\u2248" }, "∼": { "codepoints": [8764], "characters": "\u223C" }, " ": { "codepoints": [8201], "characters": "\u2009" }, "≈": { "codepoints": [8776], "characters": "\u2248" }, "∼": { "codepoints": [8764], "characters": "\u223C" }, "þ": { "codepoints": [254], "characters": "\u00FE" }, "þ": { "codepoints": [254], "characters": "\u00FE" }, "˜": { "codepoints": [732], "characters": "\u02DC" }, "×": { "codepoints": [215], "characters": "\u00D7" }, "×": { "codepoints": [215], "characters": "\u00D7" }, "⊠": { "codepoints": [8864], "characters": "\u22A0" }, "⨱": { "codepoints": [10801], "characters": "\u2A31" }, "⨰": { "codepoints": [10800], "characters": "\u2A30" }, "∭": { "codepoints": [8749], "characters": "\u222D" }, "⤨": { "codepoints": [10536], "characters": "\u2928" }, "⊤": { "codepoints": [8868], "characters": "\u22A4" }, "⌶": { "codepoints": [9014], "characters": "\u2336" }, "⫱": { "codepoints": [10993], "characters": "\u2AF1" }, "𝕥": { "codepoints": [120165], "characters": "\uD835\uDD65" }, "⫚": { "codepoints": [10970], "characters": "\u2ADA" }, "⤩": { "codepoints": [10537], "characters": "\u2929" }, "‴": { "codepoints": [8244], "characters": "\u2034" }, "™": { "codepoints": [8482], "characters": "\u2122" }, "▵": { "codepoints": [9653], "characters": "\u25B5" }, "▿": { "codepoints": [9663], "characters": "\u25BF" }, "◃": { "codepoints": [9667], "characters": "\u25C3" }, "⊴": { "codepoints": [8884], "characters": "\u22B4" }, "≜": { "codepoints": [8796], "characters": "\u225C" }, "▹": { "codepoints": [9657], "characters": "\u25B9" }, "⊵": { "codepoints": [8885], "characters": "\u22B5" }, "◬": { "codepoints": [9708], "characters": "\u25EC" }, "≜": { "codepoints": [8796], "characters": "\u225C" }, "⨺": { "codepoints": [10810], "characters": "\u2A3A" }, "⨹": { "codepoints": [10809], "characters": "\u2A39" }, "⧍": { "codepoints": [10701], "characters": "\u29CD" }, "⨻": { "codepoints": [10811], "characters": "\u2A3B" }, "⏢": { "codepoints": [9186], "characters": "\u23E2" }, "𝓉": { "codepoints": [120009], "characters": "\uD835\uDCC9" }, "ц": { "codepoints": [1094], "characters": "\u0446" }, "ћ": { "codepoints": [1115], "characters": "\u045B" }, "ŧ": { "codepoints": [359], "characters": "\u0167" }, "≬": { "codepoints": [8812], "characters": "\u226C" }, "↞": { "codepoints": [8606], "characters": "\u219E" }, "↠": { "codepoints": [8608], "characters": "\u21A0" }, "⇑": { "codepoints": [8657], "characters": "\u21D1" }, "⥣": { "codepoints": [10595], "characters": "\u2963" }, "ú": { "codepoints": [250], "characters": "\u00FA" }, "ú": { "codepoints": [250], "characters": "\u00FA" }, "↑": { "codepoints": [8593], "characters": "\u2191" }, "ў": { "codepoints": [1118], "characters": "\u045E" }, "ŭ": { "codepoints": [365], "characters": "\u016D" }, "û": { "codepoints": [251], "characters": "\u00FB" }, "û": { "codepoints": [251], "characters": "\u00FB" }, "у": { "codepoints": [1091], "characters": "\u0443" }, "⇅": { "codepoints": [8645], "characters": "\u21C5" }, "ű": { "codepoints": [369], "characters": "\u0171" }, "⥮": { "codepoints": [10606], "characters": "\u296E" }, "⥾": { "codepoints": [10622], "characters": "\u297E" }, "𝔲": { "codepoints": [120114], "characters": "\uD835\uDD32" }, "ù": { "codepoints": [249], "characters": "\u00F9" }, "ù": { "codepoints": [249], "characters": "\u00F9" }, "↿": { "codepoints": [8639], "characters": "\u21BF" }, "↾": { "codepoints": [8638], "characters": "\u21BE" }, "▀": { "codepoints": [9600], "characters": "\u2580" }, "⌜": { "codepoints": [8988], "characters": "\u231C" }, "⌜": { "codepoints": [8988], "characters": "\u231C" }, "⌏": { "codepoints": [8975], "characters": "\u230F" }, "◸": { "codepoints": [9720], "characters": "\u25F8" }, "ū": { "codepoints": [363], "characters": "\u016B" }, "¨": { "codepoints": [168], "characters": "\u00A8" }, "¨": { "codepoints": [168], "characters": "\u00A8" }, "ų": { "codepoints": [371], "characters": "\u0173" }, "𝕦": { "codepoints": [120166], "characters": "\uD835\uDD66" }, "↑": { "codepoints": [8593], "characters": "\u2191" }, "↕": { "codepoints": [8597], "characters": "\u2195" }, "↿": { "codepoints": [8639], "characters": "\u21BF" }, "↾": { "codepoints": [8638], "characters": "\u21BE" }, "⊎": { "codepoints": [8846], "characters": "\u228E" }, "υ": { "codepoints": [965], "characters": "\u03C5" }, "ϒ": { "codepoints": [978], "characters": "\u03D2" }, "υ": { "codepoints": [965], "characters": "\u03C5" }, "⇈": { "codepoints": [8648], "characters": "\u21C8" }, "⌝": { "codepoints": [8989], "characters": "\u231D" }, "⌝": { "codepoints": [8989], "characters": "\u231D" }, "⌎": { "codepoints": [8974], "characters": "\u230E" }, "ů": { "codepoints": [367], "characters": "\u016F" }, "◹": { "codepoints": [9721], "characters": "\u25F9" }, "𝓊": { "codepoints": [120010], "characters": "\uD835\uDCCA" }, "⋰": { "codepoints": [8944], "characters": "\u22F0" }, "ũ": { "codepoints": [361], "characters": "\u0169" }, "▵": { "codepoints": [9653], "characters": "\u25B5" }, "▴": { "codepoints": [9652], "characters": "\u25B4" }, "⇈": { "codepoints": [8648], "characters": "\u21C8" }, "ü": { "codepoints": [252], "characters": "\u00FC" }, "ü": { "codepoints": [252], "characters": "\u00FC" }, "⦧": { "codepoints": [10663], "characters": "\u29A7" }, "⇕": { "codepoints": [8661], "characters": "\u21D5" }, "⫨": { "codepoints": [10984], "characters": "\u2AE8" }, "⫩": { "codepoints": [10985], "characters": "\u2AE9" }, "⊨": { "codepoints": [8872], "characters": "\u22A8" }, "⦜": { "codepoints": [10652], "characters": "\u299C" }, "ϵ": { "codepoints": [1013], "characters": "\u03F5" }, "ϰ": { "codepoints": [1008], "characters": "\u03F0" }, "∅": { "codepoints": [8709], "characters": "\u2205" }, "ϕ": { "codepoints": [981], "characters": "\u03D5" }, "ϖ": { "codepoints": [982], "characters": "\u03D6" }, "∝": { "codepoints": [8733], "characters": "\u221D" }, "↕": { "codepoints": [8597], "characters": "\u2195" }, "ϱ": { "codepoints": [1009], "characters": "\u03F1" }, "ς": { "codepoints": [962], "characters": "\u03C2" }, "⊊︀": { "codepoints": [8842, 65024], "characters": "\u228A\uFE00" }, "⫋︀": { "codepoints": [10955, 65024], "characters": "\u2ACB\uFE00" }, "⊋︀": { "codepoints": [8843, 65024], "characters": "\u228B\uFE00" }, "⫌︀": { "codepoints": [10956, 65024], "characters": "\u2ACC\uFE00" }, "ϑ": { "codepoints": [977], "characters": "\u03D1" }, "⊲": { "codepoints": [8882], "characters": "\u22B2" }, "⊳": { "codepoints": [8883], "characters": "\u22B3" }, "в": { "codepoints": [1074], "characters": "\u0432" }, "⊢": { "codepoints": [8866], "characters": "\u22A2" }, "∨": { "codepoints": [8744], "characters": "\u2228" }, "⊻": { "codepoints": [8891], "characters": "\u22BB" }, "≚": { "codepoints": [8794], "characters": "\u225A" }, "⋮": { "codepoints": [8942], "characters": "\u22EE" }, "|": { "codepoints": [124], "characters": "\u007C" }, "|": { "codepoints": [124], "characters": "\u007C" }, "𝔳": { "codepoints": [120115], "characters": "\uD835\uDD33" }, "⊲": { "codepoints": [8882], "characters": "\u22B2" }, "⊂⃒": { "codepoints": [8834, 8402], "characters": "\u2282\u20D2" }, "⊃⃒": { "codepoints": [8835, 8402], "characters": "\u2283\u20D2" }, "𝕧": { "codepoints": [120167], "characters": "\uD835\uDD67" }, "∝": { "codepoints": [8733], "characters": "\u221D" }, "⊳": { "codepoints": [8883], "characters": "\u22B3" }, "𝓋": { "codepoints": [120011], "characters": "\uD835\uDCCB" }, "⫋︀": { "codepoints": [10955, 65024], "characters": "\u2ACB\uFE00" }, "⊊︀": { "codepoints": [8842, 65024], "characters": "\u228A\uFE00" }, "⫌︀": { "codepoints": [10956, 65024], "characters": "\u2ACC\uFE00" }, "⊋︀": { "codepoints": [8843, 65024], "characters": "\u228B\uFE00" }, "⦚": { "codepoints": [10650], "characters": "\u299A" }, "ŵ": { "codepoints": [373], "characters": "\u0175" }, "⩟": { "codepoints": [10847], "characters": "\u2A5F" }, "∧": { "codepoints": [8743], "characters": "\u2227" }, "≙": { "codepoints": [8793], "characters": "\u2259" }, "℘": { "codepoints": [8472], "characters": "\u2118" }, "𝔴": { "codepoints": [120116], "characters": "\uD835\uDD34" }, "𝕨": { "codepoints": [120168], "characters": "\uD835\uDD68" }, "℘": { "codepoints": [8472], "characters": "\u2118" }, "≀": { "codepoints": [8768], "characters": "\u2240" }, "≀": { "codepoints": [8768], "characters": "\u2240" }, "𝓌": { "codepoints": [120012], "characters": "\uD835\uDCCC" }, "⋂": { "codepoints": [8898], "characters": "\u22C2" }, "◯": { "codepoints": [9711], "characters": "\u25EF" }, "⋃": { "codepoints": [8899], "characters": "\u22C3" }, "▽": { "codepoints": [9661], "characters": "\u25BD" }, "𝔵": { "codepoints": [120117], "characters": "\uD835\uDD35" }, "⟺": { "codepoints": [10234], "characters": "\u27FA" }, "⟷": { "codepoints": [10231], "characters": "\u27F7" }, "ξ": { "codepoints": [958], "characters": "\u03BE" }, "⟸": { "codepoints": [10232], "characters": "\u27F8" }, "⟵": { "codepoints": [10229], "characters": "\u27F5" }, "⟼": { "codepoints": [10236], "characters": "\u27FC" }, "⋻": { "codepoints": [8955], "characters": "\u22FB" }, "⨀": { "codepoints": [10752], "characters": "\u2A00" }, "𝕩": { "codepoints": [120169], "characters": "\uD835\uDD69" }, "⨁": { "codepoints": [10753], "characters": "\u2A01" }, "⨂": { "codepoints": [10754], "characters": "\u2A02" }, "⟹": { "codepoints": [10233], "characters": "\u27F9" }, "⟶": { "codepoints": [10230], "characters": "\u27F6" }, "𝓍": { "codepoints": [120013], "characters": "\uD835\uDCCD" }, "⨆": { "codepoints": [10758], "characters": "\u2A06" }, "⨄": { "codepoints": [10756], "characters": "\u2A04" }, "△": { "codepoints": [9651], "characters": "\u25B3" }, "⋁": { "codepoints": [8897], "characters": "\u22C1" }, "⋀": { "codepoints": [8896], "characters": "\u22C0" }, "ý": { "codepoints": [253], "characters": "\u00FD" }, "ý": { "codepoints": [253], "characters": "\u00FD" }, "я": { "codepoints": [1103], "characters": "\u044F" }, "ŷ": { "codepoints": [375], "characters": "\u0177" }, "ы": { "codepoints": [1099], "characters": "\u044B" }, "¥": { "codepoints": [165], "characters": "\u00A5" }, "¥": { "codepoints": [165], "characters": "\u00A5" }, "𝔶": { "codepoints": [120118], "characters": "\uD835\uDD36" }, "ї": { "codepoints": [1111], "characters": "\u0457" }, "𝕪": { "codepoints": [120170], "characters": "\uD835\uDD6A" }, "𝓎": { "codepoints": [120014], "characters": "\uD835\uDCCE" }, "ю": { "codepoints": [1102], "characters": "\u044E" }, "ÿ": { "codepoints": [255], "characters": "\u00FF" }, "ÿ": { "codepoints": [255], "characters": "\u00FF" }, "ź": { "codepoints": [378], "characters": "\u017A" }, "ž": { "codepoints": [382], "characters": "\u017E" }, "з": { "codepoints": [1079], "characters": "\u0437" }, "ż": { "codepoints": [380], "characters": "\u017C" }, "ℨ": { "codepoints": [8488], "characters": "\u2128" }, "ζ": { "codepoints": [950], "characters": "\u03B6" }, "𝔷": { "codepoints": [120119], "characters": "\uD835\uDD37" }, "ж": { "codepoints": [1078], "characters": "\u0436" }, "⇝": { "codepoints": [8669], "characters": "\u21DD" }, "𝕫": { "codepoints": [120171], "characters": "\uD835\uDD6B" }, "𝓏": { "codepoints": [120015], "characters": "\uD835\uDCCF" }, "‍": { "codepoints": [8205], "characters": "\u200D" }, "‌": { "codepoints": [8204], "characters": "\u200C" } } libzeep-7.3.2/src/0000775000175000017500000000000015150027072013605 5ustar maartenmaartenlibzeep-7.3.2/src/access-control.cpp0000664000175000017500000000142515150027072017232 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2025 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/access-control.hpp" #include "zeep/http/reply.hpp" #include "zeep/unicode-support.hpp" #include #include namespace zeep::http { void access_control::get_access_control_headers(reply &rep) const { if (not m_allow_origin.empty()) rep.set_header("Access-Control-Allow-Origin", m_allow_origin); if (m_allow_credentials) rep.set_header("Access-Control-Allow-Credentials", "true"); if (not m_allowed_headers.empty()) rep.set_header("Access-Control-Allow-Headers", join(m_allowed_headers, ",")); } } // namespace zeep::httplibzeep-7.3.2/src/connection.cpp0000664000175000017500000001221515150027072016451 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/connection.hpp" #include "zeep/http/asio.hpp" #include "zeep/http/message-parser.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/http/server.hpp" #include "zeep/uri.hpp" #include #include #include #include #include #include #include namespace zeep::http { std::vector get_buffers(reply &rep) { std::vector result; for (auto &buffer : rep.to_buffers()) result.emplace_back(buffer.data(), buffer.size()); return result; } std::vector get_data_buffers(reply &rep) { std::vector result; for (auto &buffer : rep.data_to_buffers()) result.emplace_back(buffer.data(), buffer.size()); return result; } // Needed for CLang/libc++ on FreeBSD 10 connection *get_pointer(const std::shared_ptr &p) { return p.get(); } connection::connection(asio_ns::io_context &service, basic_server &handler) : m_socket(service) , m_server(handler) , m_bufs(m_buffer.prepare(4096)) { } void connection::start() { m_bufs = m_buffer.prepare(4096); m_socket.async_read_some(m_bufs, [self = shared_from_this()](asio_system_ns::error_code ec, size_t bytes_transferred) { self->handle_read(ec, bytes_transferred); }); } void connection::handle_read(asio_system_ns::error_code ec, size_t bytes_transferred) { if (not ec) { if (bytes_transferred > 0) m_buffer.commit(bytes_transferred); try { auto result = m_request_parser.parse(m_buffer); if (result) { auto req = m_request_parser.get_request(); req.set_local_endpoint( m_socket.local_endpoint().address().to_string(), m_socket.local_endpoint().port()); m_request_parser.reset(); m_server.handle_request(m_socket, req, m_reply); // by now, a client might have taken over our socket, in that case, simply drop out if (not m_socket.is_open()) return; m_reply.set_version(req.get_version()); if (req.keep_alive()) { m_reply.set_header("Connection", "Keep-Alive"); m_reply.set_header("Keep-Alive", "timeout=5, max=100"); m_keep_alive = true; } if (req.get_version() == std::make_tuple(0, 9)) { auto buffers = asio_ns::buffer(m_reply.get_content()); asio_ns::async_write(m_socket, buffers, [self = shared_from_this()](asio_system_ns::error_code ec, size_t bytes_transferred) { self->handle_write(ec, bytes_transferred); }); } else { auto buffers = get_buffers(m_reply); asio_ns::async_write(m_socket, buffers, [self = shared_from_this()](asio_system_ns::error_code ec, size_t bytes_transferred) { self->handle_write(ec, bytes_transferred); }); } } else if (not result) { m_reply = reply::stock_reply(status_type::bad_request); auto buffers = get_buffers(m_reply); asio_ns::async_write(m_socket, buffers, [self = shared_from_this()](asio_system_ns::error_code ec, size_t bytes_transferred) { self->handle_write(ec, bytes_transferred); }); } else { m_bufs = m_buffer.prepare(4096); m_socket.async_read_some(m_bufs, [self = shared_from_this()](asio_system_ns::error_code ec, size_t bytes_transferred) { self->handle_read(ec, bytes_transferred); }); } } catch (const uri_parse_error &ex) { std::clog << "Invalid URI requested\n"; m_reply = reply::stock_reply(status_type::bad_request); auto buffers = get_buffers(m_reply); asio_ns::async_write(m_socket, buffers, [self = shared_from_this()](asio_system_ns::error_code ec, size_t bytes_transferred) { self->handle_write(ec, bytes_transferred); }); } catch (const std::exception &ex) { std::clog << "Internal server error: " << std::quoted(ex.what()) << '\n'; m_reply = reply::stock_reply(status_type::internal_server_error); auto buffers = get_buffers(m_reply); asio_ns::async_write(m_socket, buffers, [self = shared_from_this()](asio_system_ns::error_code ec, size_t bytes_transferred) { self->handle_write(ec, bytes_transferred); }); } catch (...) // NOLINT(bugprone-empty-catch) { } } } void connection::handle_write(asio_system_ns::error_code ec, size_t /*bytes_transferred*/) { if (not ec) { auto buffers = get_data_buffers(m_reply); if (not buffers.empty()) { asio_ns::async_write(m_socket, buffers, [self = shared_from_this()](asio_system_ns::error_code ec, size_t bytes_transferred) { self->handle_write(ec, bytes_transferred); }); } else if (m_keep_alive) { m_request_parser.reset(); m_reply = {}; if (m_buffer.in_avail()) handle_read({}, 0); // special case else { m_bufs = m_buffer.prepare(4096); m_socket.async_read_some(m_bufs, [self = shared_from_this()](asio_system_ns::error_code ec, size_t bytes_transferred) { self->handle_read(ec, bytes_transferred); }); } } else m_socket.close(); } } } // namespace zeep::http libzeep-7.3.2/src/controller-rsrc.cpp0000664000175000017500000002677515150027072017464 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/template-processor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; // -------------------------------------------------------------------- // We have a special, private version of mrsrc here. To be able to create // shared libraries and still be able to link when there's no mrc used. namespace mrsrc { /// \brief Internal data structure as generated by mrc struct rsrc_imp { unsigned int m_next; unsigned int m_child; unsigned int m_name; unsigned int m_size; unsigned int m_data; }; } // namespace mrsrc #if _MSC_VER extern "C" const mrsrc::rsrc_imp *gResourceIndexDefault[1] = {}; extern "C" const char *gResourceDataDefault[1] = {}; extern "C" const char *gResourceNameDefault[1] = {}; extern "C" const mrsrc::rsrc_imp gResourceIndex[]; extern "C" const char gResourceData[]; extern "C" const char gResourceName[]; # pragma comment(linker, "/alternatename:gResourceIndex=gResourceIndexDefault") # pragma comment(linker, "/alternatename:gResourceData=gResourceDataDefault") # pragma comment(linker, "/alternatename:gResourceName=gResourceNameDefault") #else extern "C" __attribute__((weak, alias("gResourceIndexDefault"))) const mrsrc::rsrc_imp gResourceIndex[1]; extern "C" __attribute__((weak, alias("gResourceDataDefault"))) const char gResourceData[1]; extern "C" __attribute__((weak, alias("gResourceNameDefault"))) const char gResourceName[1]; const mrsrc::rsrc_imp *gResourceIndexDefault[1] = {}; const char *gResourceDataDefault[1] = {}; const char *gResourceNameDefault[1] = {}; #endif namespace mrsrc { class rsrc_data { public: static rsrc_data &instance() { static rsrc_data s_instance; return s_instance; } [[nodiscard]] const rsrc_imp *index() const { return m_index; } [[nodiscard]] const char *data(unsigned int offset) const { return m_data + offset; } [[nodiscard]] const char *name(unsigned int offset) const { return m_name + offset; } private: rsrc_data() { m_index = gResourceIndex; m_data = gResourceData; m_name = gResourceName; } rsrc_imp m_dummy = {}; const rsrc_imp *m_index = &m_dummy; const char *m_data = ""; const char *m_name = ""; }; /// \brief Class mrsrc::rsrc contains a pointer to the data in the /// resource, as well as offering an iterator interface to its /// children. class rsrc { public: rsrc() : m_impl(rsrc_data::instance().index()) { } rsrc(const rsrc &other) = default; rsrc &operator=(const rsrc &other) = default; rsrc(const std::filesystem::path& path); // NOLINTNEXTLINE(clang-analyzer-security.ArrayBound) [[nodiscard]] std::string name() const { return m_impl ? rsrc_data::instance().name(m_impl->m_name) : ""; } [[nodiscard]] const char *data() const { return m_impl ? rsrc_data::instance().data(m_impl->m_data) : nullptr; } [[nodiscard]] size_t size() const { return m_impl ? m_impl->m_size : 0; } explicit operator bool() const { return m_impl != nullptr and m_impl->m_size > 0; } template class iterator_t { public: using iterator_category = std::input_iterator_tag; using value_type = RSRC; using difference_type = std::ptrdiff_t; using pointer = value_type *; using reference = value_type &; iterator_t(const rsrc_imp *cur) : m_cur(cur) { } iterator_t(const iterator_t &i) = default; iterator_t &operator=(const iterator_t &i) = default; reference operator*() { return m_cur; } pointer operator->() { return &m_cur; } iterator_t &operator++() { if (m_cur.m_impl->m_next) m_cur.m_impl = rsrc_data::instance().index() + m_cur.m_impl->m_next; else m_cur.m_impl = nullptr; return *this; } iterator_t operator++(int) { auto tmp(*this); this->operator++(); return tmp; } bool operator==(const iterator_t &rhs) const { return m_cur.m_impl == rhs.m_cur.m_impl; } bool operator!=(const iterator_t &rhs) const { return m_cur.m_impl != rhs.m_cur.m_impl; } private: value_type m_cur; }; using iterator = iterator_t; [[nodiscard]] iterator begin() const { const rsrc_imp *impl = nullptr; if (m_impl and m_impl->m_child) impl = rsrc_data::instance().index() + m_impl->m_child; return { impl }; } [[nodiscard]] iterator end() const { return { nullptr }; } private: rsrc(const rsrc_imp *imp) : m_impl(imp) { } const rsrc_imp *m_impl; }; inline rsrc::rsrc(const std::filesystem::path& p) { m_impl = rsrc_data::instance().index(); // using std::filesytem::path would have been natural here of course... auto pb = p.begin(); auto pe = p.end(); while (m_impl != nullptr and pb != pe) { auto name = *pb++; const rsrc_imp *impl = nullptr; for (const rsrc &child : *this) { if (child.name() == name) { impl = child.m_impl; break; } } m_impl = impl; } if (pb != pe) // not found m_impl = nullptr; } // -------------------------------------------------------------------- template class basic_streambuf : public std::basic_streambuf { public: using char_type = CharT; using traits_type = Traits; using int_type = typename traits_type::int_type; using pos_type = typename traits_type::pos_type; using off_type = typename traits_type::off_type; /// \brief constructor taking a \a path to the resource in memory basic_streambuf(std::string path) : m_rsrc(std::move(path)) { init(); } /// \brief constructor taking a \a rsrc basic_streambuf(const rsrc &rsrc) : m_rsrc(rsrc) { init(); } basic_streambuf(const basic_streambuf &) = delete; basic_streambuf(basic_streambuf &&rhs) noexcept : basic_streambuf(rhs.m_rsrc) { } basic_streambuf &operator=(const basic_streambuf &) = delete; basic_streambuf &operator=(basic_streambuf &&rhs) noexcept { swap(rhs); return *this; } void swap(basic_streambuf &rhs) noexcept { std::swap(m_begin, rhs.m_begin); std::swap(m_end, rhs.m_end); std::swap(m_current, rhs.m_current); } /// \brief Analogous to is_open of an ifstream_buffer, return true if the resource is valid [[nodiscard]] bool is_valid() const { return static_cast(m_rsrc); } private: void init() { if (m_rsrc) { m_begin = reinterpret_cast(m_rsrc.data()); m_end = reinterpret_cast(m_rsrc.data() + m_rsrc.size()); m_current = m_begin; } } protected: int_type underflow() override { if (m_current == m_end) return traits_type::eof(); return traits_type::to_int_type(*m_current); } int_type uflow() override { if (m_current == m_end) return traits_type::eof(); return traits_type::to_int_type(*m_current++); } int_type pbackfail(int_type ch) override { if (m_current == m_begin or (ch != traits_type::eof() and ch != m_current[-1])) return traits_type::eof(); return traits_type::to_int_type(*--m_current); } std::streamsize showmanyc() override { assert(std::less_equal()(m_current, m_end)); return m_end - m_current; } pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode /*which*/) override { switch (dir) { case std::ios_base::beg: m_current = m_begin + off; break; case std::ios_base::end: m_current = m_end + off; break; case std::ios_base::cur: m_current += off; break; default: break; } if (m_current < m_begin) m_current = m_begin; if (m_current > m_end) m_current = m_end; return m_current - m_begin; } pos_type seekpos(pos_type pos, std::ios_base::openmode /*which*/) override { m_current = m_begin + pos; if (m_current < m_begin) m_current = m_begin; if (m_current > m_end) m_current = m_end; return m_current - m_begin; } private: rsrc m_rsrc; const char_type *m_begin = nullptr; const char_type *m_end = nullptr; const char_type *m_current = nullptr; }; using streambuf = basic_streambuf>; // -------------------------------------------------------------------- // class mrsrc::istream template class basic_istream : public std::basic_istream { public: using char_type = CharT; using traits_type = Traits; using int_type = typename traits_type::int_type; using pos_type = typename traits_type::pos_type; using off_type = typename traits_type::off_type; private: using rsrc_streambuf_type = basic_streambuf; using rsrc_istream_type = std::basic_istream; rsrc_streambuf_type m_buffer; public: basic_istream(std::string path) : basic_istream(rsrc(std::move(path))) { } basic_istream(const rsrc &resource) : rsrc_istream_type(&m_buffer) , m_buffer(resource) { if (resource) this->init(&m_buffer); else rsrc_istream_type::setstate(std::ios_base::badbit); } basic_istream(const basic_istream &) = delete; basic_istream(basic_istream &&rhs) noexcept : rsrc_istream_type(std::move(rhs)) , m_buffer(std::move(rhs.m_buffer)) { rsrc_istream_type::set_rdbuf(&m_buffer); } basic_istream &operator=(const basic_istream &) = delete; basic_istream &operator=(basic_istream &&rhs) noexcept { rsrc_istream_type::operator=(std::move(rhs)); m_buffer = std::move(rhs.m_buffer); return *this; } void swap(basic_istream &rhs) noexcept { rsrc_istream_type::swap(rhs); m_buffer.swap(rhs.m_buffer); } rsrc_streambuf_type *rdbuf() const { return const_cast(&m_buffer); } }; using istream = basic_istream>; } // namespace mrsrc // -------------------------------------------------------------------- namespace zeep::http { // ----------------------------------------------------------------------- #if _WIN32 and not defined(WIN32_LEAN_AND_MEAN) # define WIN32_LEAN_AND_MEAN # include #endif rsrc_loader::rsrc_loader(const std::filesystem::path &/*unused*/) { #if _WIN32 char exePath[MAX_PATH] = {}; if (::GetModuleFileNameA(NULL, exePath, MAX_PATH) > 0) mRsrcWriteTime = fs::last_write_time(exePath); #else char exePath[PATH_MAX + 1]; auto r = readlink("/proc/self/exe", exePath, PATH_MAX); if (r > 0) { exePath[r] = 0; std::error_code ec; mRsrcWriteTime = fs::last_write_time(exePath, ec); if (ec) mRsrcWriteTime = fs::file_time_type::clock::now(); } else mRsrcWriteTime = fs::file_time_type::clock::now(); #endif } /// return last_write_time of \a file fs::file_time_type rsrc_loader::file_time(std::filesystem::path file, std::error_code &ec) noexcept { fs::file_time_type result = {}; ec = {}; mrsrc::rsrc rsrc(file); if (rsrc) result = mRsrcWriteTime; else ec = std::make_error_code(std::errc::no_such_file_or_directory); return result; } // basic loader, returns error in ec if file was not found std::istream *rsrc_loader::load_file(std::string file, std::error_code &ec) noexcept { mrsrc::rsrc resource(std::move(file)); std::istream *result = nullptr; ec = {}; if (resource) { try { result = new mrsrc::istream(resource); } catch (const std::bad_alloc &) { ec = std::make_error_code(std::errc::not_enough_memory); } } else ec = std::make_error_code(std::errc::no_such_file_or_directory); return result; } } // namespace zeep::http libzeep-7.3.2/src/controller.cpp0000664000175000017500000000622515150027072016501 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/controller.hpp" #include "zeep/exception.hpp" #include "zeep/http/asio.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/http/scope.hpp" #include "zeep/http/server.hpp" #include "zeep/uri.hpp" #include "glob.hpp" #include #include #include #include #include namespace zeep::http { controller::controller(const std::string &prefix_path) : m_prefix_path(prefix_path) { } controller::~controller() { for (auto mp : m_mountpoints) delete mp; } bool controller::dispatch_request(asio_ns::ip::tcp::socket & /*socket*/, request &req, reply &rep) { return handle_request(req, rep); } bool controller::path_matches_prefix(const uri &path) const { bool result = m_prefix_path.empty(); if (not result) { auto ab = m_prefix_path.get_segments().begin(), ae = m_prefix_path.get_segments().end(); auto bb = path.get_segments().begin(), be = path.get_segments().end(); do { if (ab->empty() and ab + 1 == ae) { result = true; break; } result = ab != ae and bb != be and *ab == *bb; ++ab; ++bb; } while (result and ab != ae); } return result; } uri controller::get_prefixless_path(const request &req) const { auto path = req.get_uri().get_path(); auto ab = m_prefix_path.get_segments().begin(), ae = m_prefix_path.get_segments().end(); auto bb = path.get_segments().begin(), be = path.get_segments().end(); while (ab != ae and bb != be) { if (ab->empty() and ab + 1 == ae) break; if (*ab != *bb) throw zeep::exception("Controller does not have the same prefix as the request"); ++ab; ++bb; } return { bb, be }; } void controller::get_options(const request &req, reply &rep) { if (m_server) m_server->get_options_for_request(req, rep); } // -------------------------------------------------------------------- void controller::init_scope(scope & /*unused*/) { } bool controller::handle_request(http::request &req, http::reply &rep) { auto uri = get_prefixless_path(req); auto path = get_prefixless_path(req).string(); bool result = false; for (auto &mp : m_mountpoints) { if (req.get_method() != mp->m_method) continue; scope scope(get_server(), req); if (mp->m_path_params.empty()) { if (not glob_match(std::filesystem::path(path), mp->m_path)) continue; } else { std::smatch m; if (not std::regex_match(path, m, mp->m_rx)) continue; for (size_t i = 0; i < mp->m_path_params.size(); ++i) scope.add_path_param(mp->m_path_params[i], decode_url(m[i + 1].str())); } scope.put("baseuri", path); init_scope(scope); if (req.get_method() == "OPTIONS") get_options(req, rep); else rep = call_mount_point(mp, scope); result = true; break; } return result; } reply controller::call_mount_point(mount_point_base *mp, const scope &scope) { return mp->call(scope); } } // namespace zeep::http libzeep-7.3.2/src/crypto.cpp0000664000175000017500000007731315150027072015644 0ustar maartenmaarten// Copyright Maarten L. Hekkelman. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/crypto.hpp" #include #include #include #include #include #include #include #include #include namespace zeep { // -------------------------------------------------------------------- // encoding/decoding const char kBase64CharTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const uint8_t kBase64IndexTable[128] = { 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 62, // + 128, // not used 128, // not used 128, // not used 63, // / 52, // 0 53, // 1 54, // 2 55, // 3 56, // 4 57, // 5 58, // 6 59, // 7 60, // 8 61, // 9 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 0, // A 1, // B 2, // C 3, // D 4, // E 5, // F 6, // G 7, // H 8, // I 9, // J 10, // K 11, // L 12, // M 13, // N 14, // O 15, // P 16, // Q 17, // R 18, // S 19, // T 20, // U 21, // V 22, // W 23, // X 24, // Y 25, // Z 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used 26, // a 27, // b 28, // c 29, // d 30, // e 31, // f 32, // g 33, // h 34, // i 35, // j 36, // k 37, // l 38, // m 39, // n 40, // o 41, // p 42, // q 43, // r 44, // s 45, // t 46, // u 47, // v 48, // w 49, // x 50, // y 51, // z 128, // not used 128, // not used 128, // not used 128, // not used 128, // not used }; inline uint8_t sextet(char ch) { if (ch < '+' or ch > 'z' or kBase64IndexTable[static_cast(ch)] >= 128) throw invalid_base64(); return kBase64IndexTable[static_cast(ch)]; } std::string encode_base64(std::string_view data, size_t wrap_width) { std::string::size_type n = data.length(); std::string::size_type m = 4 * (n / 3); if (n % 3) m += 4; if (wrap_width != 0) m += (m / wrap_width) + 1; std::string result; result.reserve(m); auto ch = data.begin(); size_t l = 0; while (n > 0) { char s[4] = { '=', '=', '=', '=' }; switch (n) { case 1: { uint8_t i = *ch++; s[0] = kBase64CharTable[i >> 2]; s[1] = kBase64CharTable[(i << 4) bitand 0x03f]; n -= 1; break; } case 2: { uint8_t i1 = *ch++; uint8_t i2 = *ch++; s[0] = kBase64CharTable[i1 >> 2]; s[1] = kBase64CharTable[(i1 << 4 bitor i2 >> 4) bitand 0x03f]; s[2] = kBase64CharTable[(i2 << 2) bitand 0x03f]; n -= 2; break; } default: { uint8_t i1 = *ch++; uint8_t i2 = *ch++; uint8_t i3 = *ch++; s[0] = kBase64CharTable[i1 >> 2]; s[1] = kBase64CharTable[(i1 << 4 bitor i2 >> 4) bitand 0x03f]; s[2] = kBase64CharTable[(i2 << 2 bitor i3 >> 6) bitand 0x03f]; s[3] = kBase64CharTable[i3 bitand 0x03f]; n -= 3; break; } } if (wrap_width == 0) result.append(s, s + 4); else { for (char i : s) { if (l == wrap_width) { result.append(1, '\n'); l = 0; } result.append(1, i); ++l; } } } if (wrap_width != 0) result.append(1, '\n'); assert(result.length() == m); return result; } std::string decode_base64(std::string_view data) { size_t n = data.length(); size_t m = 3 * (n / 4); std::string result; result.reserve(m); auto i = data.begin(); while (i != data.end()) { uint8_t sxt[4] = {}; int b = 0, c = 3; while (b < 4) { if (i == data.end()) break; char ch = *i++; switch (ch) { case ' ': case '\t': case '\n': case '\r': break; case '=': if (b == 2 and *i++ == '=') { c = 1; b = 4; } else if (b == 3) { c = 2; b = 4; } else throw invalid_base64(); break; default: sxt[b] = sextet(ch); ++b; break; } } if (b == 4) { result.append(1, static_cast(sxt[0] << 2 bitor sxt[1] >> 4)); if (c >= 2) result.append(1, static_cast(sxt[1] << 4 bitor sxt[2] >> 2)); if (c == 3) result.append(1, static_cast(sxt[2] << 6 bitor sxt[3])); } else if (b != 0) throw invalid_base64(); } return result; } std::string encode_base64url(std::string_view data) { std::string result = encode_base64(data); while (not result.empty() and result.back() == '=') result.pop_back(); for (auto p = result.find('+'); p != std::string::npos; p = result.find('+', p + 1)) result.replace(p, 1, 1, '-'); for (auto p = result.find('/'); p != std::string::npos; p = result.find('/', p + 1)) result.replace(p, 1, 1, '_'); return result; } std::string decode_base64url(std::string data) { for (auto p = data.find('-'); p != std::string::npos; p = data.find('-', p + 1)) data.replace(p, 1, 1, '+'); for (auto p = data.find('_'); p != std::string::npos; p = data.find('_', p + 1)) data.replace(p, 1, 1, '/'); switch (data.length() % 4) { case 0: break; case 2: data.append(2, '='); break; case 3: data.append(1, '='); break; default: throw invalid_base64(); } return decode_base64(data); } // -------------------------------------------------------------------- const char kBase32CharTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; const uint8_t kBase32IndexTable[128] = { 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 26, // 2 27, // 3 28, // 4 29, // 5 30, // 6 31, // 7 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 0, // A 1, // B 2, // C 3, // D 4, // E 5, // F 6, // G 7, // H 8, // I 9, // J 10, // K 11, // L 12, // M 13, // N 14, // O 15, // P 16, // Q 17, // R 18, // S 19, // T 20, // U 21, // V 22, // W 23, // X 24, // Y 25, // Z 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used 0, // a 1, // b 2, // c 3, // d 4, // e 5, // f 6, // g 7, // h 8, // i 9, // j 10, // k 11, // l 12, // m 13, // n 14, // o 15, // p 16, // q 17, // r 18, // s 19, // t 20, // u 21, // v 22, // w 23, // x 24, // y 25, // z 32, // not used 32, // not used 32, // not used 32, // not used 32, // not used }; inline size_t quintet(char ch) { if (ch < '2' or ch > 'z' or kBase32IndexTable[static_cast(ch)] >= 32) throw invalid_base32(); return kBase32IndexTable[static_cast(ch)]; } std::string encode_base32(std::string_view data, size_t wrap_width) { std::string::size_type n = data.length(); std::string::size_type m = 8 * (n / 5); if (n % 5) m += 8; if (wrap_width != 0) m += (m / wrap_width) + 1; std::string result; result.reserve(m); auto ch = data.begin(); size_t l = 0; while (n > 0) { char s[8] = { '=', '=', '=', '=', '=', '=', '=', '=' }; switch (n) { case 1: { uint8_t i1 = *ch++; s[0] = kBase32CharTable[(i1 >> 3) bitand 0x01f]; s[1] = kBase32CharTable[(i1 << 2) bitand 0x01f]; n -= 1; break; } case 2: { uint8_t i1 = *ch++; uint8_t i2 = *ch++; // i1 i2 s[0] = kBase32CharTable[(i1 >> 3) bitand 0x01f]; // 5 s[1] = kBase32CharTable[(i1 << 2 bitor i2 >> 6) bitand 0x01f]; // 3 2 s[2] = kBase32CharTable[(i2 >> 1) bitand 0x01f]; // 5 s[3] = kBase32CharTable[(i2 << 4) bitand 0x01f]; // 1 n -= 2; break; } case 3: { uint8_t i1 = *ch++; uint8_t i2 = *ch++; uint8_t i3 = *ch++; // i1 i2 i3 s[0] = kBase32CharTable[(i1 >> 3) bitand 0x01f]; // 5 s[1] = kBase32CharTable[(i1 << 2 bitor i2 >> 6) bitand 0x01f]; // 3 2 s[2] = kBase32CharTable[(i2 >> 1) bitand 0x01f]; // 5 s[3] = kBase32CharTable[(i2 << 4 bitor i3 >> 4) bitand 0x01f]; // 1 4 s[4] = kBase32CharTable[(i3 << 1) bitand 0x01f]; // 4 n -= 3; break; } case 4: { uint8_t i1 = *ch++; uint8_t i2 = *ch++; uint8_t i3 = *ch++; uint8_t i4 = *ch++; // i1 i2 i3 i4 s[0] = kBase32CharTable[(i1 >> 3) bitand 0x01f]; // 5 s[1] = kBase32CharTable[(i1 << 2 bitor i2 >> 6) bitand 0x01f]; // 3 2 s[2] = kBase32CharTable[(i2 >> 1) bitand 0x01f]; // 5 s[3] = kBase32CharTable[(i2 << 4 bitor i3 >> 4) bitand 0x01f]; // 1 4 s[4] = kBase32CharTable[(i3 << 1) bitand 0x01f]; // 4 1 s[5] = kBase32CharTable[(i4 >> 2) bitand 0x01f]; // 5 s[6] = kBase32CharTable[(i4 << 3) bitand 0x01f]; // 2 n -= 4; break; } default: { uint8_t i1 = *ch++; uint8_t i2 = *ch++; uint8_t i3 = *ch++; uint8_t i4 = *ch++; uint8_t i5 = *ch++; // i1 i2 i3 i4 i5 s[0] = kBase32CharTable[(i1 >> 3) bitand 0x01f]; // 5 s[1] = kBase32CharTable[(i1 << 2 bitor i2 >> 6) bitand 0x01f]; // 3 2 s[2] = kBase32CharTable[(i2 >> 1) bitand 0x01f]; // 5 s[3] = kBase32CharTable[(i2 << 4 bitor i3 >> 4) bitand 0x01f]; // 1 4 s[4] = kBase32CharTable[(i3 << 1 bitor i4 >> 7) bitand 0x01f]; // 4 1 s[5] = kBase32CharTable[(i4 >> 2) bitand 0x01f]; // 5 s[6] = kBase32CharTable[(i4 << 3 bitor i5 >> 5) bitand 0x01f]; // 2 3 s[7] = kBase32CharTable[(i5) bitand 0x01f]; // 5 n -= 5; break; } } if (wrap_width == 0) result.append(s, s + 8); else { for (char i : s) { if (l == wrap_width) { result.append(1, '\n'); l = 0; } result.append(1, i); ++l; } } } if (wrap_width != 0) result.append(1, '\n'); assert(result.length() == m); return result; } std::string decode_base32(std::string_view data) { size_t n = data.length(); size_t m = 8 * (n / 5); std::string result; result.reserve(m); auto i = data.begin(); int padding = 0; while (i != data.end() and not padding) { uint8_t qnt[5] = {}; int c = 0; while (c + padding != 8 and i != data.end()) { auto ch = *i++; if (std::isspace(ch)) continue; if (padding) { if (ch != '=') throw invalid_base32(); ++padding; continue; } if (ch == '=') { ++padding; continue; } if (ch < '2' or ch > 'z' or kBase32IndexTable[static_cast(ch)] >= 32) throw invalid_base32(); auto b = kBase32IndexTable[static_cast(ch)]; switch (c++) { case 0: qnt[0] |= b << 3; break; case 1: qnt[0] |= b >> 2; qnt[1] = b << 6; break; case 2: qnt[1] |= b << 1; break; case 3: qnt[1] |= b >> 4; qnt[2] = b << 4; break; case 4: qnt[2] |= b >> 1; qnt[3] = b << 7; break; case 5: qnt[3] |= b << 2; break; case 6: qnt[3] |= b >> 3; qnt[4] = b << 5; break; case 7: qnt[4] |= b; break; default:; } } if (c + padding != 8) throw invalid_base32(); switch (c) { case 2: result.append(qnt, qnt + 1); break; case 4: result.append(qnt, qnt + 2); break; case 5: result.append(qnt, qnt + 3); break; case 7: result.append(qnt, qnt + 4); break; case 8: result.append(qnt, qnt + 5); break; default: throw invalid_base32(); } } while (i != data.end()) { if (not std::isspace(*i++)) throw invalid_base32(); } return result; } // -------------------------------------------------------------------- // hex std::string encode_hex(std::string_view data) { // convert to hex const char kHexChars[] = "0123456789abcdef"; std::string result; result.reserve(data.length() * 2); for (uint8_t b : data) { result += kHexChars[b >> 4]; result += kHexChars[b bitand 0x0f]; } return result; } std::string decode_hex(std::string_view data) { if (data.length() % 2 == 1) throw invalid_hex(); std::string result; result.reserve(data.length() / 2); for (std::string::size_type i = 0; i < data.length(); i += 2) { uint8_t n[2] = {}; for (int j = 0; j < 2; ++j) { auto ch = data[i + j]; if (ch >= '0' and ch <= '9') n[j] = ch - '0'; else if (ch >= 'a' and ch <= 'f') n[j] = ch - 'a' + 10; else if (ch >= 'A' and ch <= 'F') n[j] = ch - 'A' + 10; else throw invalid_hex(); } result.push_back(static_cast(n[0] << 4 bitor n[1])); } return result; } // -------------------------------------------------------------------- // random std::string random_hash() { std::random_device rng; union { uint32_t data[4]; char s[4 * 4]; } v = { { rng(), rng(), rng(), rng() } }; return { v.s, v.s + sizeof(v) }; } // -------------------------------------------------------------------- // hashes // -------------------------------------------------------------------- #if _WIN32 # pragma warning(disable : 4146) // unary minus operator applied to unsigned type, result still unsigned #endif constexpr inline uint32_t rotl32(uint32_t n, unsigned int c) { const unsigned int mask = (CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2. // assert ( (c<=mask) &&"rotate by type width or more"); c &= mask; return (n << c) bitor (n >> ((-c) & mask)); } constexpr inline uint32_t rotr32(uint32_t n, unsigned int c) { const unsigned int mask = (CHAR_BIT * sizeof(n) - 1); // assert ( (c<=mask) &&"rotate by type width or more"); c &= mask; return (n >> c) bitor (n << ((-c) & mask)); } // -------------------------------------------------------------------- struct hash_impl { virtual ~hash_impl() = default; virtual void write_bit_length(uint64_t l, uint8_t *b) = 0; virtual void transform(const uint8_t *data) = 0; virtual std::string final() = 0; }; // -------------------------------------------------------------------- constexpr auto F1(uint32_t x, uint32_t y, uint32_t z) { return (z xor (x bitand (y xor z))); } constexpr auto F2(uint32_t x, uint32_t y, uint32_t z) { return F1(z, x, y); } constexpr auto F3(uint32_t x, uint32_t y, uint32_t z) { return (x xor y xor z); } constexpr auto F4(uint32_t x, uint32_t y, uint32_t z) { return (y xor (x bitor compl z)); } template constexpr void STEP(F f, uint32_t &w, uint32_t x, uint32_t y, uint32_t z, uint32_t data, uint32_t s) { w += f(x, y, z) + data; w = rotl32(w, s); w += x; } struct md5_hash_impl : public hash_impl // NOLINT(hicpp-member-init) { using word_type = uint32_t; static const size_t word_count = 4; static const size_t block_size = 64; static const size_t digest_size = word_count * sizeof(word_type); word_type m_h[word_count]; virtual void init() { m_h[0] = 0x67452301; m_h[1] = 0xefcdab89; m_h[2] = 0x98badcfe; m_h[3] = 0x10325476; } void write_bit_length(uint64_t l, uint8_t *p) override { for (int i = 0; i < 8; ++i) *p++ = static_cast((l >> (i * 8))); } void transform(const uint8_t *data) override { uint32_t a = m_h[0], b = m_h[1], c = m_h[2], d = m_h[3]; uint32_t in[16]; for (unsigned int &i : in) { i = static_cast(data[0]) << 0 | static_cast(data[1]) << 8 | static_cast(data[2]) << 16 | static_cast(data[3]) << 24; data += 4; } STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); m_h[0] += a; m_h[1] += b; m_h[2] += c; m_h[3] += d; } std::string final() override { std::string result; result.reserve(digest_size); for (unsigned int i : m_h) { for (size_t j = 0; j < sizeof(word_type); ++j) { auto b = static_cast(i >> (j * 8)); result.push_back(b); } } return result; } }; // -------------------------------------------------------------------- struct sha1_hash_impl : public hash_impl // NOLINT(hicpp-member-init) { using word_type = uint32_t; static const size_t word_count = 5; static const size_t block_size = 64; static const size_t digest_size = word_count * sizeof(word_type); word_type m_h[word_count]; virtual void init() { m_h[0] = 0x67452301; m_h[1] = 0xEFCDAB89; m_h[2] = 0x98BADCFE; m_h[3] = 0x10325476; m_h[4] = 0xC3D2E1F0; } void write_bit_length(uint64_t l, uint8_t *b) override { if constexpr (std::endian::native == std::endian::big) memcpy(b, &l, sizeof(l)); else { b[0] = static_cast(l >> 56); b[1] = static_cast(l >> 48); b[2] = static_cast(l >> 40); b[3] = static_cast(l >> 32); b[4] = static_cast(l >> 24); b[5] = static_cast(l >> 16); b[6] = static_cast(l >> 8); b[7] = static_cast(l >> 0); } } void transform(const uint8_t *data) override { union // NOLINT(hicpp-member-init) { uint8_t s[64]; uint32_t w[80]; } w; if constexpr (std::endian::native == std::endian::big) memcpy(w.s, data, 64); else { auto p = data; for (size_t i = 0; i < 16; ++i) { w.s[i * 4 + 3] = *p++; w.s[i * 4 + 2] = *p++; w.s[i * 4 + 1] = *p++; w.s[i * 4 + 0] = *p++; } } for (size_t i = 16; i < 80; ++i) w.w[i] = rotl32(w.w[i - 3] xor w.w[i - 8] xor w.w[i - 14] xor w.w[i - 16], 1); word_type wv[word_count]; for (size_t i = 0; i < word_count; ++i) wv[i] = m_h[i]; for (size_t i = 0; i < 80; ++i) { uint32_t f, k; if (i < 20) { f = (wv[1] bitand wv[2]) bitor ((compl wv[1]) bitand wv[3]); k = 0x5A827999; } else if (i < 40) { f = wv[1] xor wv[2] xor wv[3]; k = 0x6ED9EBA1; } else if (i < 60) { f = (wv[1] bitand wv[2]) bitor (wv[1] bitand wv[3]) bitor (wv[2] bitand wv[3]); k = 0x8F1BBCDC; } else { f = wv[1] xor wv[2] xor wv[3]; k = 0xCA62C1D6; } uint32_t t = rotl32(wv[0], 5) + f + wv[4] + k + w.w[i]; wv[4] = wv[3]; wv[3] = wv[2]; wv[2] = rotl32(wv[1], 30); wv[1] = wv[0]; wv[0] = t; } for (size_t i = 0; i < word_count; ++i) m_h[i] += wv[i]; } std::string final() override { std::string result(digest_size, '\0'); if constexpr (std::endian::native == std::endian::big) memcpy(const_cast(result.data()), &m_h, digest_size); else { auto s = result.begin(); for (unsigned int i : m_h) { *s++ = static_cast(i >> 24); *s++ = static_cast(i >> 16); *s++ = static_cast(i >> 8); *s++ = static_cast(i >> 0); } } return result; } }; // -------------------------------------------------------------------- struct sha256_hash_impl : public hash_impl // NOLINT(hicpp-member-init) { using word_type = uint32_t; static const size_t word_count = 8; static const size_t block_size = 64; static const size_t digest_size = word_count * sizeof(word_type); word_type m_h[word_count]; virtual void init() { m_h[0] = 0x6a09e667; m_h[1] = 0xbb67ae85; m_h[2] = 0x3c6ef372; m_h[3] = 0xa54ff53a; m_h[4] = 0x510e527f; m_h[5] = 0x9b05688c; m_h[6] = 0x1f83d9ab; m_h[7] = 0x5be0cd19; } void write_bit_length(uint64_t l, uint8_t *b) override { if constexpr (std::endian::native == std::endian::big) memcpy(b, &l, sizeof(l)); else { b[0] = static_cast(l >> 56); b[1] = static_cast(l >> 48); b[2] = static_cast(l >> 40); b[3] = static_cast(l >> 32); b[4] = static_cast(l >> 24); b[5] = static_cast(l >> 16); b[6] = static_cast(l >> 8); b[7] = static_cast(l >> 0); } } void transform(const uint8_t *data) override { static const uint32_t k[] = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; word_type wv[word_count]; for (size_t i = 0; i < word_count; ++i) wv[i] = m_h[i]; union // NOLINT(hicpp-member-init) { uint8_t s[64]; uint32_t w[64]; } w; if constexpr (std::endian::native == std::endian::big) memcpy(w.w, data, 64); else { auto p = data; for (size_t i = 0; i < 16; ++i) { w.s[i * 4 + 3] = *p++; w.s[i * 4 + 2] = *p++; w.s[i * 4 + 1] = *p++; w.s[i * 4 + 0] = *p++; } } for (size_t i = 16; i < 64; ++i) { auto s0 = rotr32(w.w[i - 15], 7) xor rotr32(w.w[i - 15], 18) xor (w.w[i - 15] >> 3); auto s1 = rotr32(w.w[i - 2], 17) xor rotr32(w.w[i - 2], 19) xor (w.w[i - 2] >> 10); w.w[i] = w.w[i - 16] + s0 + w.w[i - 7] + s1; } for (size_t i = 0; i < 64; ++i) { uint32_t S1 = rotr32(wv[4], 6) xor rotr32(wv[4], 11) xor rotr32(wv[4], 25); uint32_t ch = (wv[4] bitand wv[5]) xor (compl wv[4] bitand wv[6]); uint32_t t1 = wv[7] + S1 + ch + k[i] + w.w[i]; uint32_t S0 = rotr32(wv[0], 2) xor rotr32(wv[0], 13) xor rotr32(wv[0], 22); uint32_t maj = (wv[0] bitand wv[1]) xor (wv[0] bitand wv[2]) xor (wv[1] bitand wv[2]); uint32_t t2 = S0 + maj; wv[7] = wv[6]; wv[6] = wv[5]; wv[5] = wv[4]; wv[4] = wv[3] + t1; wv[3] = wv[2]; wv[2] = wv[1]; wv[1] = wv[0]; wv[0] = t1 + t2; } for (size_t i = 0; i < word_count; ++i) m_h[i] += wv[i]; } std::string final() override { std::string result(digest_size, '\0'); if constexpr (std::endian::native == std::endian::big) memcpy(const_cast(result.data()), &m_h, digest_size); else { auto s = result.begin(); for (unsigned int i : m_h) { *s++ = static_cast(i >> 24); *s++ = static_cast(i >> 16); *s++ = static_cast(i >> 8); *s++ = static_cast(i >> 0); } } return result; } }; // -------------------------------------------------------------------- template class hash_base : public I { public: using word_type = typename I::word_type; static const size_t word_count = I::word_count; static const size_t block_size = I::block_size; static const size_t digest_size = I::digest_size; hash_base() // NOLINT(hicpp-member-init) { I::init(); } void init() override { I::init(); m_data_length = 0; m_bit_length = 0; } void update(std::string_view data); void update(const uint8_t *data, size_t n); using I::transform; std::string final() override; private: uint8_t m_data[block_size]; uint32_t m_data_length = 0; int64_t m_bit_length = 0; }; template void hash_base::update(std::string_view data) { update(reinterpret_cast(data.data()), data.size()); } template void hash_base::update(const uint8_t *p, size_t length) { m_bit_length += length * 8; if (m_data_length > 0) { uint32_t n = block_size - m_data_length; if (n > length) n = static_cast(length); memcpy(m_data + m_data_length, p, n); m_data_length += n; if (m_data_length == block_size) { transform(m_data); m_data_length = 0; } p += n; length -= n; } while (length >= block_size) { transform(p); p += block_size; length -= block_size; } if (length > 0) { memcpy(m_data, p, length); m_data_length += static_cast(length); } } template std::string hash_base::final() { m_data[m_data_length] = 0x80; ++m_data_length; std::fill(m_data + m_data_length, m_data + block_size, static_cast(0)); if (block_size - m_data_length < 8) { transform(m_data); std::fill(m_data, m_data + block_size - 8, static_cast(0)); } I::write_bit_length(m_bit_length, m_data + block_size - 8); transform(m_data); std::fill(m_data, m_data + block_size, static_cast(0)); auto result = I::final(); init(); return result; } using MD5 = hash_base; using SHA1 = hash_base; using SHA256 = hash_base; // -------------------------------------------------------------------- std::string sha1(std::string_view data) { SHA1 h; h.init(); h.update(data); return h.final(); } std::string sha1(std::streambuf &data) { SHA1 h; h.init(); while (data.in_avail() > 0) { uint8_t buffer[256]; auto n = data.sgetn(reinterpret_cast(buffer), sizeof(buffer)); h.update(buffer, n); } return h.final(); } std::string sha256(std::string_view data) { SHA256 h; h.init(); h.update(data); return h.final(); } std::string md5(std::string_view data) { MD5 h; h.init(); h.update(data); return h.final(); } // -------------------------------------------------------------------- // hmac template class HMAC { public: static const size_t block_size = H::block_size; static const size_t digest_size = H::digest_size; explicit HMAC(std::string_view key) : m_ipad(block_size, '\x36') , m_opad(block_size, '\x5c') { std::string key_data{ key }; if (key.length() > block_size) { H keyHash; keyHash.update(key); key_data = keyHash.final(); } assert(key_data.length() < block_size); for (size_t i = 0; i < key_data.length(); ++i) { m_opad[i] ^= key_data[i]; m_ipad[i] ^= key_data[i]; } } HMAC &update(std::string_view data) { if (not m_inner_updated) { m_inner.update(m_ipad); m_inner_updated = true; } m_inner.update(data); return *this; } std::string final() { H outer; outer.update(m_opad); outer.update(m_inner.final()); m_inner_updated = false; return outer.final(); } private: std::string m_ipad, m_opad; bool m_inner_updated = false; H m_inner; }; std::string hmac_sha1(std::string_view message, std::string_view key) { return HMAC(key).update(message).final(); } std::string hmac_sha256(std::string_view message, std::string_view key) { return HMAC(key).update(message).final(); } std::string hmac_md5(std::string_view message, std::string_view key) { return HMAC(key).update(message).final(); } // -------------------------------------------------------------------- // password/key derivation template std::string pbkdf2(std::string_view salt, std::string_view password, unsigned iterations, unsigned keyLength) { std::string result; HMAC hmac(password); uint32_t i = 1; while (result.length() < keyLength) { hmac.update(salt); for (int j = 0; j < 4; ++j) { char b = static_cast(i >> ((3 - j) * CHAR_BIT)); hmac.update(std::string{ b }); } auto derived = hmac.final(); auto buffer = derived; for (unsigned c = 1; c < iterations; ++c) { buffer = hmac.update(buffer).final(); for (size_t ix = 0; ix < buffer.length(); ++ix) derived[ix] ^= buffer[ix]; } result.append(derived); ++i; } if (result.length() > keyLength) result.erase(result.begin() + keyLength, result.end()); return result; } /// create password hash according to PBKDF2 with HmacSHA1 std::string pbkdf2_hmac_sha1(std::string_view salt, std::string_view password, unsigned iterations, unsigned keyLength) { return pbkdf2>(salt, password, iterations, keyLength); } /// create password hash according to PBKDF2 with HmacSHA256 std::string pbkdf2_hmac_sha256(std::string_view salt, std::string_view password, unsigned iterations, unsigned keyLength) { return pbkdf2>(salt, password, iterations, keyLength); } } // namespace zeeplibzeep-7.3.2/src/daemon.cpp0000664000175000017500000004304015150027072015555 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // Source code specifically for Unix/Linux // Utility routines to build daemon processes #include "zeep/http/daemon.hpp" #include "signals.hpp" #include "zeep/config.hpp" #include "zeep/exception.hpp" #include "zeep/http/asio.hpp" #include "zeep/http/preforked-server.hpp" #include "zeep/http/server.hpp" #include "zeep/unicode-support.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _WIN32 # include # include # include # include #endif namespace fs = std::filesystem; namespace zeep::http { #if __APPLE__ int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups) { return ::getgrouplist(user, (int)group, (int *)groups, ngroups); } #endif daemon::daemon(server_factory_type &&factory, std::string pid_file, std::string stdout_log_file, std::string stderr_log_file) : m_factory(std::move(factory)) , m_pid_file(std::move(pid_file)) , m_stdout_log_file(std::move(stdout_log_file)) , m_stderr_log_file(std::move(stderr_log_file)) { } daemon::daemon(server_factory_type &&factory, const std::string &name) : daemon(std::move(factory), "/var/run/" + name, "/var/log/" + name + "/access.log", "/var/log/" + name + "/error.log") { } int daemon::run_foreground(std::string_view address, uint16_t port) { asio_ns::io_context io_context; asio_ns::ip::tcp::endpoint endpoint; asio_system_ns::error_code ec; auto addr = asio_ns::ip::make_address(address, ec); if (not ec) endpoint = asio_ns::ip::tcp::endpoint(addr, port); else { asio_ns::ip::tcp::resolver resolver(io_context); for (auto &ep : resolver.resolve(address, std::to_string(port))) { endpoint = ep; break; } } asio_ns::ip::tcp::acceptor acceptor(io_context); acceptor.open(endpoint.protocol()); acceptor.set_option(asio_ns::ip::tcp::acceptor::reuse_address(true)); if (acceptor.bind(endpoint, ec)) throw std::runtime_error(std::string("Is server running already? ") + ec.message()); acceptor.listen(); acceptor.close(); signal_catcher sc; sc.block(); std::unique_ptr s(m_factory()); s->bind(address, port); std::thread t([s = s.get()] { s->run(1); }); sc.unblock(); sc.wait(); s->stop(); if (t.joinable()) t.join(); return 0; } #if HTTP_HAS_UNIX_DAEMON int daemon::start(std::string_view address, uint16_t port, int nr_of_procs, int nr_of_threads, const std::string &run_as_user) { int result = 0; if (pid_is_for_executable()) { std::clog << "Server is already running.\n"; result = 1; } else { std::error_code ec; if (fs::exists(m_pid_file, ec)) fs::remove(m_pid_file, ec); fs::path pidDir = fs::path(m_pid_file).parent_path(); if (not fs::is_directory(pidDir, ec)) fs::create_directories(pidDir, ec); if (ec) std::clog << "Creating directory for pid file failed: " << ec.message() << '\n'; fs::path outLogDir = fs::path(m_stdout_log_file).parent_path(); if (not fs::is_directory(outLogDir, ec)) fs::create_directories(outLogDir, ec); if (ec) std::clog << "Creating directory " << outLogDir << " for log files failed: " << ec.message() << '\n'; fs::path errLogDir = fs::path(m_stderr_log_file).parent_path(); if (not fs::is_directory(errLogDir, ec)) fs::create_directories(errLogDir, ec); if (ec) std::clog << "Creating directory " << outLogDir << " for log files failed: " << ec.message() << '\n'; try { asio_ns::io_context io_context; asio_ns::ip::tcp::endpoint endpoint; try { endpoint = asio_ns::ip::tcp::endpoint(asio_ns::ip::make_address(address), port); } catch (const std::exception &e) { asio_ns::ip::tcp::resolver resolver(io_context); for (auto &ep : resolver.resolve(address, std::to_string(port))) { endpoint = ep; break; } } asio_ns::ip::tcp::acceptor acceptor(io_context); acceptor.open(endpoint.protocol()); acceptor.set_option(asio_ns::ip::tcp::acceptor::reuse_address(true)); acceptor.bind(endpoint); acceptor.listen(); acceptor.close(); } catch (exception &e) { throw std::runtime_error(std::string("Is server running already? ") + e.what()); } int pid = daemonize(); if (pid == 0) { for (;;) { bool hupped = run_main_loop(address, port, nr_of_procs, nr_of_threads, run_as_user); if (hupped) { std::clog << "Server was interrupted, will attempt to resume\n"; continue; } break; } if (fs::exists(m_pid_file, ec)) fs::remove(m_pid_file, ec); if (ec) std::clog << "Removing pid file failed: " << ec.message() << '\n'; _exit(0); } // Forking is done twice, so mop up the zombie here int status, pid_c; pid_c = waitpid(-1, &status, WUNTRACED); if (pid_c != -1) { if (WIFSIGNALED(status) and WTERMSIG(status) != SIGKILL) std::clog << "child " << pid_c << " terminated by signal " << WTERMSIG(status) << '\n'; // else // std::clog << "child terminated normally\n"; } } return result; } int daemon::start(std::string_view address, uint16_t port, int nr_of_threads, const std::string &run_as_user) { using namespace std::literals; int result = 0; if (pid_is_for_executable()) { std::clog << "Server is already running.\n"; result = 1; } else { std::error_code ec; if (fs::exists(m_pid_file, ec)) fs::remove(m_pid_file, ec); fs::path pidDir = fs::path(m_pid_file).parent_path(); if (not fs::is_directory(pidDir, ec)) fs::create_directories(pidDir, ec); if (ec) std::clog << "Creating directory for pid file failed: " << ec.message() << '\n'; fs::path outLogDir = fs::path(m_stdout_log_file).parent_path(); if (not fs::is_directory(outLogDir, ec)) fs::create_directories(outLogDir, ec); if (ec) std::clog << "Creating directory " << outLogDir << " for log files failed: " << ec.message() << '\n'; fs::path errLogDir = fs::path(m_stderr_log_file).parent_path(); if (not fs::is_directory(errLogDir, ec)) fs::create_directories(errLogDir, ec); if (ec) std::clog << "Creating directory " << outLogDir << " for log files failed: " << ec.message() << '\n'; try { asio_ns::io_context io_context; asio_ns::ip::tcp::endpoint endpoint; try { endpoint = asio_ns::ip::tcp::endpoint(asio_ns::ip::make_address(address), port); } catch (const std::exception &e) { asio_ns::ip::tcp::resolver resolver(io_context); for (auto &ep : resolver.resolve(address, std::to_string(port))) { endpoint = ep; break; } } asio_ns::ip::tcp::acceptor acceptor(io_context); acceptor.open(endpoint.protocol()); acceptor.set_option(asio_ns::ip::tcp::acceptor::reuse_address(true)); acceptor.bind(endpoint); acceptor.listen(); acceptor.close(); } catch (exception &e) { throw std::runtime_error(std::string("Is server running already? ") + e.what()); } int pid = daemonize(); if (pid == 0) // Child process { open_log_file(); std::clog << "starting server\n" << "Listening to " << address << ':' << port << '\n'; signal_catcher sc; sc.block(); // Drop privileges if (not run_as_user.empty()) { struct passwd *pw = getpwnam(run_as_user.c_str()); if (pw == nullptr) { std::clog << "Failed to set uid to " << run_as_user << ": " << strerror(errno) << '\n'; exit(1); } if (pw->pw_uid != getuid()) { int ngroups = 0; if (getgrouplist(pw->pw_name, pw->pw_gid, nullptr, &ngroups) == -1 and ngroups > 0) { std::vector groups(ngroups); if (getgrouplist(pw->pw_name, pw->pw_gid, groups.data(), &ngroups) != -1 and setgroups(ngroups, groups.data()) == -1) { std::clog << "Failed to set groups for " << run_as_user << ": " << strerror(errno) << '\n'; exit(1); } } if (setgid(pw->pw_gid) < 0) { std::clog << "Failed to set gid for " << run_as_user << ": " << strerror(errno) << '\n'; exit(1); } if (setuid(pw->pw_uid) < 0) { std::clog << "Failed to set uid to " << run_as_user << ": " << strerror(errno) << '\n'; exit(1); } } } for (;;) { sc.block(); std::unique_ptr server; try { server.reset(m_factory()); server->bind(address, port); } catch (const exception &e) { std::clog << "Failed to launch server: " << e.what() << '\n'; exit(1); } std::thread t([nr_of_threads, &server]() { server->run(nr_of_threads); }); sc.unblock(); int sig = sc.wait(); std::clog << "Process " << getpid() << " received signal " << sig << "\n"; server->stop(); if (t.joinable()) t.join(); if (sig == SIGHUP) { // re-open log files open_log_file(); std::clog << "re-starting server\n" << "Listening to " << address << ':' << port << '\n'; continue; } break; } if (fs::exists(m_pid_file, ec)) fs::remove(m_pid_file, ec); if (ec) std::clog << "Removing pid file failed: " << ec.message() << '\n'; // We're done. Exit _exit(0); } // avoid zombies int status, pid_c; pid_c = waitpid(-1, &status, WUNTRACED); if (pid_c != -1) { if (WIFSIGNALED(status) and WTERMSIG(status) != SIGKILL) std::clog << "child " << pid_c << " terminated by signal " << WTERMSIG(status) << '\n'; // else // std::clog << "child terminated normally\n"; } } return result; } int daemon::stop() { int result = 1; if (pid_is_for_executable()) { std::ifstream file(m_pid_file); if (not file.is_open()) throw std::runtime_error("Failed to open pid file"); int pid; file >> pid; file.close(); result = ::kill(pid, SIGINT); if (result != 0) std::clog << "Failed to stop process " << pid << ": " << strerror(errno) << '\n'; // avoid zombies int status, pid_c; pid_c = waitpid(pid, &status, WUNTRACED); if (pid_c != -1) { if (WIFSIGNALED(status) and WTERMSIG(status) != SIGKILL) std::clog << "child " << pid_c << " terminated by signal " << WTERMSIG(status) << '\n'; // else // std::clog << "child terminated normally\n"; } std::error_code ec; if (fs::exists(m_pid_file, ec)) fs::remove(m_pid_file, ec); if (ec) std::clog << "Could not remove pid file: " << ec.message() << '\n'; } else throw std::runtime_error("Not my pid file: " + m_pid_file); return result; } int daemon::status() { int result; if (pid_is_for_executable()) { std::clog << "server is running\n"; result = 0; } else { std::clog << "server is not running\n"; result = 1; } return result; } int daemon::reload() { int result; if (pid_is_for_executable()) { std::ifstream file(m_pid_file); if (not file.is_open()) throw std::runtime_error("Failed to open pid file"); int pid; file >> pid; result = ::kill(pid, SIGHUP); } else { std::clog << "server is not running\n"; result = 1; } return result; } int daemon::daemonize() { int pid = fork(); if (pid == -1) { std::clog << "Fork failed\n"; exit(1); } // exit the parent (=calling) process if (pid != 0) return pid; if (setsid() < 0) { std::clog << "Failed to create process group: " << strerror(errno) << '\n'; exit(1); } // This in-between process should not catch SIGHUP (void)signal(SIGHUP, SIG_IGN); // fork again, to avoid being able to attach to a terminal device pid = fork(); if (pid == -1) std::clog << "Fork failed\n"; if (pid != 0) _exit(0); // write our pid to the pid file std::ofstream pidFile(m_pid_file); if (not pidFile.is_open()) { std::clog << "Failed to write to " << m_pid_file << ": " << strerror(errno) << '\n'; exit(1); } pidFile << getpid() << '\n'; pidFile.close(); if (chdir("/") != 0) { std::clog << "Cannot chdir to /: " << strerror(errno) << '\n'; exit(1); } // close stdin close(STDIN_FILENO); (void)open("/dev/null", O_RDONLY); // NOLINT(hicpp-vararg) // The final process should however catch SIGHUP (void)signal(SIGHUP, SIG_DFL); return 0; } void daemon::open_log_file() { // Flush the IO first std::cout.flush(); std::cerr.flush(); std::clog.flush(); // open the log file int fd_out = open(m_stdout_log_file.c_str(), O_CREAT | O_APPEND | O_RDWR, 0644); // NOLINT(hicpp-vararg) if (fd_out < 0) { std::clog << "Opening log file " << m_stdout_log_file << " failed\n"; exit(1); } int fd_err; if (m_stderr_log_file == m_stdout_log_file) fd_err = fd_out; else { fd_err = open(m_stderr_log_file.c_str(), O_CREAT | O_APPEND | O_RDWR, 0644); // NOLINT(hicpp-vararg) if (fd_err < 0) { std::clog << "Opening log file " << m_stderr_log_file << " failed\n"; exit(1); } } // redirect stdout and stderr to the log file dup2(fd_out, STDOUT_FILENO); dup2(fd_err, STDERR_FILENO); // close the actual file descriptors to avoid leaks close(fd_out); if (fd_err != fd_out) close(fd_err); } bool daemon::run_main_loop(std::string_view address, uint16_t port, int nr_of_procs, int nr_of_threads, const std::string &run_as_user) { int sig = 0; int restarts = 0; for (;;) { auto start = time(nullptr); open_log_file(); if (sig == 0) std::clog << "starting server\n"; else std::clog << "restarting server\n"; std::clog << "Listening to " << address << ':' << port << '\n'; sigset_t new_mask, old_mask; sigfillset(&new_mask); pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask); preforked_server server([=, this]() { try { if (not run_as_user.empty()) { struct passwd* pw = getpwnam(run_as_user.c_str()); if (pw == nullptr) { std::clog << "Failed to set uid to " << run_as_user << ": " << strerror(errno) << '\n'; exit(1); } if (pw->pw_uid != getuid()) { int ngroups = 0; if (getgrouplist(pw->pw_name, pw->pw_gid, nullptr, &ngroups) == -1 and ngroups > 0) { std::vector groups(ngroups); if (getgrouplist(pw->pw_name, pw->pw_gid, groups.data(), &ngroups) != -1 and setgroups(ngroups, groups.data()) == -1) { std::clog << "Failed to set groups for " << run_as_user << ": " << strerror(errno) << '\n'; exit(1); } } if (setgid(pw->pw_gid) < 0) { std::clog << "Failed to set gid for " << run_as_user << ": " << strerror(errno) << '\n'; exit(1); } if (setuid(pw->pw_uid) < 0) { std::clog << "Failed to set uid to " << run_as_user << ": " << strerror(errno) << '\n'; exit(1); } } } return m_factory(); } catch (const exception& e) { std::clog << "Failed to launch server: " << e.what() << '\n'; exit(1); } }); std::thread t([s = &server, address, port, nr_of_procs, nr_of_threads] { s->run(address, port, nr_of_procs, nr_of_threads); }); try { server.start(); } catch (const exception &ex) { std::clog << '\n' << "Exception running server: \n" << ex.what() << '\n' << '\n'; exit(1); } pthread_sigmask(SIG_SETMASK, &old_mask, nullptr); // Wait for signal indicating time to shut down. sigset_t wait_mask; sigemptyset(&wait_mask); sigaddset(&wait_mask, SIGINT); sigaddset(&wait_mask, SIGHUP); sigaddset(&wait_mask, SIGQUIT); sigaddset(&wait_mask, SIGTERM); sigaddset(&wait_mask, SIGCHLD); pthread_sigmask(SIG_BLOCK, &wait_mask, nullptr); sigwait(&wait_mask, &sig); pthread_sigmask(SIG_SETMASK, &old_mask, nullptr); server.stop(); t.join(); if (sig != SIGCHLD) break; int status, pid; pid = waitpid(-1, &status, WUNTRACED); if (pid != -1) { if (WIFSIGNALED(status) and WTERMSIG(status) != SIGKILL) std::clog << "child " << pid << " terminated by signal " << WTERMSIG(status) << '\n'; // else // std::clog << "child terminated normally\n"; } // did the client crash within the time window? if (time(nullptr) - start > m_restart_time_window) { restarts = 0; // no, it was outside, reset counter continue; } if (++restarts >= m_max_restarts) { std::clog << "aborting due to excessive restarts\n"; break; } } return sig == SIGHUP; } bool daemon::pid_is_for_executable() { using namespace std::literals; bool result = false; if (fs::exists(m_pid_file)) { std::ifstream pidfile(m_pid_file); if (not pidfile.is_open()) throw std::runtime_error("Failed to open pid file " + m_pid_file + ": " + strerror(errno)); int pid; pidfile >> pid; // if /proc/PID/exe points to our executable, this means we're already running char path[PATH_MAX] = ""; if (readlink(("/proc/" + std::to_string(pid) + "/exe").c_str(), path, sizeof(path)) > 0) { char exe[PATH_MAX] = ""; if (readlink("/proc/self/exe", exe, sizeof(exe)) == -1) throw std::runtime_error("could not get exe path ("s + strerror(errno) + ")"); result = strcmp(exe, path) == 0 or (ends_with(path, " (deleted)") and starts_with(path, exe)); } else if (errno == ENOENT) // link file doesn't exist (can happen on e.g. macOS) result = kill(pid, 0) == 0; // simply test using kill with signal 0. else throw std::runtime_error("Failed to read executable link : "s + strerror(errno)); } return result; } #endif } // namespace zeep::http libzeep-7.3.2/src/el-object.cpp0000664000175000017500000006705215150027072016167 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2025 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/el/object.hpp" #include "zeep/exception.hpp" #include "zeep/unicode-support.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace zeep::el { object operator+(const object &lhs, const object &rhs) { using value_type = object::value_type; auto lhs_type = lhs.type(); auto rhs_type = rhs.type(); object result; if (lhs_type == rhs_type) { switch (lhs_type) { case value_type::boolean: case value_type::number_int: result = lhs.m_data.m_value.m_int + rhs.m_data.m_value.m_int; break; case value_type::number_float: result = lhs.m_data.m_value.m_float + rhs.m_data.m_value.m_float; break; case value_type::string: result = *lhs.m_data.m_value.m_string + *rhs.m_data.m_value.m_string; break; case value_type::null: break; default: throw std::runtime_error("Invalid types for operator +"); } } else if (lhs_type == value_type::number_float and rhs.is_number()) result = lhs.m_data.m_value.m_float + rhs.get(); else if (lhs_type == value_type::number_int and rhs.is_number()) result = lhs.m_data.m_value.m_int + rhs.get(); else if (lhs_type == value_type::null) result = rhs; else if (rhs_type == value_type::null) result = lhs; else if (lhs_type == value_type::string or rhs_type == value_type::string) result = lhs.get() + rhs.get(); else throw std::runtime_error("Invalid types for operator +"); return result; } object operator-(const object &lhs, const object &rhs) { using value_type = object::value_type; auto lhs_type = lhs.type(); auto rhs_type = rhs.type(); object result; if (lhs_type == rhs_type) { switch (lhs_type) { case value_type::boolean: case value_type::number_int: result = lhs.m_data.m_value.m_int - rhs.m_data.m_value.m_int; break; case value_type::number_float: result = lhs.m_data.m_value.m_float - rhs.m_data.m_value.m_float; break; default: throw std::runtime_error("Invalid types for operator -"); } } else if (lhs_type == value_type::number_float and rhs.is_number()) result = lhs.m_data.m_value.m_float - rhs.get(); else if (lhs_type == value_type::number_int and rhs.is_number()) result = lhs.m_data.m_value.m_int - rhs.get(); else throw std::runtime_error("Invalid types for operator -"); return result; } object operator*(const object &lhs, const object &rhs) { using value_type = object::value_type; auto lhs_type = lhs.type(); auto rhs_type = rhs.type(); object result; if (lhs_type == rhs_type) { switch (lhs_type) { case value_type::boolean: case value_type::number_int: result = lhs.m_data.m_value.m_int * rhs.m_data.m_value.m_int; break; case value_type::number_float: result = lhs.m_data.m_value.m_float * rhs.m_data.m_value.m_float; break; default: throw std::runtime_error("Invalid types for operator *"); } } else if (lhs_type == value_type::number_float and rhs.is_number()) result = lhs.m_data.m_value.m_float * rhs.get(); else if (lhs_type == value_type::number_int and rhs.is_number()) result = lhs.m_data.m_value.m_int * rhs.get(); else throw std::runtime_error("Invalid types for operator *"); return result; } object operator/(const object &lhs, const object &rhs) { using value_type = object::value_type; auto lhs_type = lhs.type(); auto rhs_type = rhs.type(); object result; if (lhs_type == rhs_type) { switch (lhs_type) { case value_type::boolean: case value_type::number_int: result = lhs.m_data.m_value.m_int / rhs.m_data.m_value.m_int; break; case value_type::number_float: result = lhs.m_data.m_value.m_float / rhs.m_data.m_value.m_float; break; default: throw std::runtime_error("Invalid types for operator /"); } } else if (lhs_type == value_type::number_float and rhs.is_number()) result = lhs.m_data.m_value.m_float / rhs.get(); else if (lhs_type == value_type::number_int and rhs.is_number()) result = lhs.m_data.m_value.m_int / rhs.get(); else throw std::runtime_error("Invalid types for operator /"); return result; } object operator%(const object &lhs, const object &rhs) { using value_type = object::value_type; auto lhs_type = lhs.type(); auto rhs_type = rhs.type(); object result; if (lhs_type == rhs_type) { switch (lhs_type) { case value_type::boolean: case value_type::number_int: result = lhs.m_data.m_value.m_int % rhs.m_data.m_value.m_int; break; default: throw std::runtime_error("Invalid types for operator %"); } } else if (lhs_type == value_type::number_int and rhs.is_number()) result = lhs.m_data.m_value.m_int % rhs.get(); else throw std::runtime_error("Invalid types for operator %"); return result; } bool operator==(const object &lhs, const object &rhs) noexcept { using value_type = object::value_type; auto lhs_type = lhs.type(); auto rhs_type = rhs.type(); if (lhs_type == rhs_type) { switch (lhs_type) { case value_type::array: return *lhs.m_data.m_value.m_array == *rhs.m_data.m_value.m_array; case value_type::object: return *lhs.m_data.m_value.m_object == *rhs.m_data.m_value.m_object; case value_type::string: return *lhs.m_data.m_value.m_string == *rhs.m_data.m_value.m_string; case value_type::number_int: return lhs.m_data.m_value.m_int == rhs.m_data.m_value.m_int; case value_type::number_float: return lhs.m_data.m_value.m_float == rhs.m_data.m_value.m_float; case value_type::boolean: return lhs.m_data.m_value.m_boolean == rhs.m_data.m_value.m_boolean; case value_type::null: return true; } } else if (lhs_type == value_type::number_float and rhs_type == value_type::number_int) return lhs.m_data.m_value.m_float == static_cast(rhs.m_data.m_value.m_int); else if (lhs_type == value_type::number_int and rhs_type == value_type::number_float) return static_cast(lhs.m_data.m_value.m_int) == rhs.m_data.m_value.m_float; return false; } std::partial_ordering operator<=>(const object &lhs, const object &rhs) noexcept { using value_type = object::value_type; auto lhs_type = lhs.type(); auto rhs_type = rhs.type(); if (lhs_type == rhs_type) { switch (lhs_type) { case value_type::array: return *lhs.m_data.m_value.m_array <=> *rhs.m_data.m_value.m_array; case value_type::object: return *lhs.m_data.m_value.m_object <=> *rhs.m_data.m_value.m_object; case value_type::string: return *lhs.m_data.m_value.m_string <=> *rhs.m_data.m_value.m_string; case value_type::number_int: return lhs.m_data.m_value.m_int <=> rhs.m_data.m_value.m_int; case value_type::number_float: return lhs.m_data.m_value.m_float <=> rhs.m_data.m_value.m_float; case value_type::boolean: return lhs.m_data.m_value.m_boolean <=> rhs.m_data.m_value.m_boolean; default: break; } } else if (lhs_type == value_type::number_float and rhs_type == value_type::number_int) return lhs.m_data.m_value.m_float <=> static_cast(rhs.m_data.m_value.m_int); else if (lhs_type == value_type::number_int and rhs_type == value_type::number_float) return static_cast(lhs.m_data.m_value.m_int) <=> rhs.m_data.m_value.m_float; return lhs_type <=> rhs_type; } // -------------------------------------------------------------------- size_t object::size() const noexcept { switch (m_data.m_type) { case value_type::null: return 0; case value_type::array: return m_data.m_value.m_array->size(); case value_type::object: return m_data.m_value.m_object->size(); default: return 1; } } size_t object::max_size() const noexcept { switch (m_data.m_type) { case value_type::array: return m_data.m_value.m_array->max_size(); case value_type::object: return m_data.m_value.m_object->max_size(); default: return size(); } } void object::push_back(object &&val) { if (not(is_null() or is_array())) throw std::runtime_error("Invalid type for push_back"); if (is_null()) { m_data.m_type = value_type::array; m_data.m_value = value_type::array; } m_data.m_value.m_array->push_back(std::move(val)); } void object::push_back(const object &val) { if (not(is_null() or is_array())) throw std::runtime_error("Invalid type for push_back"); if (is_null()) { m_data.m_type = value_type::array; m_data.m_value = value_type::array; } m_data.m_value.m_array->push_back(val); } object::reference object::at(size_t index) { if (not is_array()) throw std::runtime_error("Type should have been array to use at()"); return m_data.m_value.m_array->at(index); } object::const_reference object::at(size_t index) const { if (not is_array()) throw std::runtime_error("Type should have been array to use at()"); return m_data.m_value.m_array->at(index); } bool object::contains(const object &test) const { bool result = false; if (is_object()) result = m_data.m_value.m_object->count(test.get()) > 0; else if (is_array()) result = std::ranges::find(*m_data.m_value.m_array, test) != m_data.m_value.m_array->end(); return result; } object::reference object::operator[](size_t index) { if (is_null()) { m_data.m_type = value_type::array; m_data.m_value.m_array = create(); } else if (not is_array()) throw std::runtime_error("Type should have been array to use operator[]"); if (index + 1 > m_data.m_value.m_array->size()) m_data.m_value.m_array->resize(index + 1); return m_data.m_value.m_array->operator[](index); } object::const_reference object::operator[](size_t index) const { if (not is_array()) throw std::runtime_error("Type should have been array to use operator[]"); return m_data.m_value.m_array->operator[](index); } // object member access object::reference object::at(const typename object_type::key_type &key) { if (not is_object()) throw std::runtime_error("Type should have been object to use at()"); return m_data.m_value.m_object->at(key); } object::const_reference object::at(const typename object_type::key_type &key) const { if (not is_object()) throw std::runtime_error("Type should have been object to use at()"); return m_data.m_value.m_object->at(key); } object::reference object::operator[](const typename object_type::key_type &key) { if (is_null()) { m_data.m_type = value_type::object; m_data.m_value.m_object = create(); } else if (not is_object()) throw std::runtime_error("Type should have been object to use operator[]"); return m_data.m_value.m_object->operator[](key); } object::const_reference object::operator[](const typename object_type::key_type &key) const { if (not is_object()) throw std::runtime_error("Type should have been object to use operator[]"); return m_data.m_value.m_object->operator[](key); } bool object::empty() const noexcept { switch (m_data.m_type) { case value_type::null: return true; case value_type::array: return m_data.m_value.m_array->empty(); case value_type::object: return m_data.m_value.m_object->empty(); case value_type::string: return m_data.m_value.m_string->empty(); default: return false; } } // -------------------------------------------------------------------- void serialize(std::ostream &os, const object &v) { switch (v.m_data.m_type) { case object::value_type::array: { auto &a = *v.m_data.m_value.m_array; os << '['; for (size_t i = 0; i < a.size(); ++i) { serialize(os, a[i]); if (i + 1 < a.size()) os << ','; } os << ']'; break; } case object::value_type::boolean: os << std::boolalpha << v.m_data.m_value.m_boolean; break; case object::value_type::null: os << "null"; break; case object::value_type::number_float: if (v.m_data.m_value.m_float == 0 or std::isnormal(v.m_data.m_value.m_float)) os << v.m_data.m_value.m_float; else // os << "\"NaN\""; os << "null"; break; case object::value_type::number_int: os << v.m_data.m_value.m_int; break; case object::value_type::object: { os << '{'; bool first = true; for (auto &kv : *v.m_data.m_value.m_object) { if (not first) os << ','; os << '"' << kv.first << "\":"; serialize(os, kv.second); first = false; } os << '}'; break; } case object::value_type::string: os << '"'; for (uint8_t c : *v.m_data.m_value.m_string) { switch (c) { case '\"': os << "\\\""; break; case '\\': os << "\\\\"; break; case '/': os << "\\/"; break; case '\b': os << "\\b"; break; case '\n': os << "\\n"; break; case '\r': os << "\\r"; break; case '\t': os << "\\t"; break; default: if (c < 0x0020) { static const char kHex[17] = "0123456789abcdef"; os << "\\u00" << kHex[(c >> 4) & 0x0f] << kHex[c & 0x0f]; } else os << static_cast(c); break; } } os << '"'; break; } } // -------------------------------------------------------------------- // deserialize is in fact a JSON parser :-) class json_parser { public: json_parser(std::istream &is) : m_is(is) { } void parse(object &object); private: enum class token_t : uint8_t { Eof, LeftBrace, RightBrace, LeftBracket, RightBracket, Comma, Colon, String, Integer, Number, True, False, Null, Undef }; [[nodiscard]] std::string describe_token(token_t t) const { switch (t) { case token_t::Eof: return "end of data"; case token_t::LeftBrace: return "left brace ('{')"; case token_t::RightBrace: return "richt brace ('}')"; case token_t::LeftBracket: return "left bracket ('[')"; case token_t::RightBracket: return "right bracket (']')"; case token_t::Comma: return "comma"; case token_t::Colon: return "colon"; case token_t::String: return "string"; case token_t::Integer: return "integer"; case token_t::Number: return "number"; case token_t::True: return "true"; case token_t::False: return "false"; case token_t::Null: return "null"; case token_t::Undef: return "undefined token"; default: assert(false); return "???"; } } void match(token_t expected); void parse_value(object &e); void parse_object(object &e); void parse_array(object &e); [[nodiscard]] uint8_t get_next_byte(); [[nodiscard]] char32_t get_next_unicode(); [[nodiscard]] char32_t get_next_char(); void retract(); [[nodiscard]] token_t get_next_token(); std::istream &m_is; // a minimal stack for ungetc like operations char32_t m_buffer[2]{}; char32_t *m_buffer_ptr = m_buffer; std::string m_token; double m_token_float{}; int64_t m_token_int{}; token_t m_lookahead{ token_t::Eof }; }; uint8_t json_parser::get_next_byte() { int result = m_is.rdbuf()->sbumpc(); if (result == std::streambuf::traits_type::eof()) result = 0; return static_cast(result); } char32_t json_parser::get_next_unicode() { char32_t result = get_next_byte(); if (result & 0x080) { unsigned char ch[3]; if ((result & 0x0E0) == 0x0C0) { ch[0] = get_next_byte(); if ((ch[0] & 0x0c0) != 0x080) throw std::runtime_error("Invalid utf-8"); result = ((result & 0x01F) << 6) | (ch[0] & 0x03F); } else if ((result & 0x0F0) == 0x0E0) { ch[0] = get_next_byte(); ch[1] = get_next_byte(); if ((ch[0] & 0x0c0) != 0x080 or (ch[1] & 0x0c0) != 0x080) throw std::runtime_error("Invalid utf-8"); result = ((result & 0x00F) << 12) | ((ch[0] & 0x03F) << 6) | (ch[1] & 0x03F); } else if ((result & 0x0F8) == 0x0F0) { ch[0] = get_next_byte(); ch[1] = get_next_byte(); ch[2] = get_next_byte(); if ((ch[0] & 0x0c0) != 0x080 or (ch[1] & 0x0c0) != 0x080 or (ch[2] & 0x0c0) != 0x080) throw std::runtime_error("Invalid utf-8"); result = ((result & 0x007) << 18) | ((ch[0] & 0x03F) << 12) | ((ch[1] & 0x03F) << 6) | (ch[2] & 0x03F); if (result > 0x10ffff) throw std::runtime_error("invalid utf-8 character (out of range)"); } } return result; } char32_t json_parser::get_next_char() { char32_t result = 0; if (m_buffer_ptr > m_buffer) // if buffer is not empty we already did all the validity checks result = *--m_buffer_ptr; else { result = get_next_unicode(); if (result >= 0x080) { if (result == 0x0ffff or result == 0x0fffe) { using namespace std::literals; char s[32] = {}; if (auto r = std::to_chars(s, s + sizeof(s), result, 16); r.ec == std::errc{}) throw std::runtime_error("character 0x"s + s + " is not allowed"); else throw std::runtime_error("character "s + std::to_string(result) + " is not allowed"); } // surrogate support else if (result >= 0x0D800 and result <= 0x0DBFF) { char32_t uc2 = get_next_char(); if (uc2 >= 0x0DC00 and uc2 <= 0x0DFFF) result = (result - 0x0D800) * 0x400 + (uc2 - 0x0DC00) + 0x010000; else throw std::runtime_error("leading surrogate character without trailing surrogate character"); } else if (result >= 0x0DC00 and result <= 0x0DFFF) throw std::runtime_error("trailing surrogate character without a leading surrogate"); } } // append(m_token, result); // somehow, append refuses to inline, so we have to do it ourselves if (result < 0x080) m_token += (static_cast(result)); else if (result < 0x0800) { char ch[2] = { static_cast(0x0c0 | (result >> 6)), static_cast(0x080 | (result & 0x3f)) }; m_token.append(ch, 2); } else if (result < 0x00010000) { char ch[3] = { static_cast(0x0e0 | (result >> 12)), static_cast(0x080 | ((result >> 6) & 0x3f)), static_cast(0x080 | (result & 0x3f)) }; m_token.append(ch, 3); } else { char ch[4] = { static_cast(0x0f0 | (result >> 18)), static_cast(0x080 | ((result >> 12) & 0x3f)), static_cast(0x080 | ((result >> 6) & 0x3f)), static_cast(0x080 | (result & 0x3f)) }; m_token.append(ch, 4); } return result; } void json_parser::retract() { assert(not m_token.empty()); *m_buffer_ptr++ = pop_last_char(m_token); } auto json_parser::get_next_token() -> token_t { enum class state_t { Start, Negative, Zero, Number, NumberFraction, NumberExpSign, NumberExpDigit1, NumberExpDigit2, Literal, String, Escape, EscapeHex1, EscapeHex2, EscapeHex3, EscapeHex4 } state = state_t::Start; token_t token = token_t::Undef; double fraction = 1.0, exponent = 1; bool negative = false, negativeExp = false; char32_t hx = {}; m_token.clear(); while (token == token_t::Undef) { char32_t ch = get_next_char(); switch (state) { case state_t::Start: switch (ch) { case 0: token = token_t::Eof; break; case '{': token = token_t::LeftBrace; break; case '}': token = token_t::RightBrace; break; case '[': token = token_t::LeftBracket; break; case ']': token = token_t::RightBracket; break; case ',': token = token_t::Comma; break; case ':': token = token_t::Colon; break; case ' ': case '\n': case '\r': case '\t': m_token.clear(); break; case '"': m_token.pop_back(); state = state_t::String; break; case '-': state = state_t::Negative; break; default: if (ch == '0') { state = state_t::Zero; m_token_int = 0; } else if (ch >= '1' and ch <= '9') { m_token_int = ch - '0'; state = state_t::Number; } else if (ch < 128 and std::isalpha(static_cast(ch))) state = state_t::Literal; else throw zeep::exception( std::format("Invalid character '{}' in json", std::isprint(static_cast(ch)) ? std::string{ static_cast(ch) } : to_hex(ch))); } break; case state_t::Negative: if (ch == '0') { state = state_t::Zero; negative = true; } else if (ch >= '1' and ch <= '9') { state = state_t::Number; m_token_int = ch - '0'; negative = true; } else throw zeep::exception("invalid character '-' in json"); break; case state_t::Zero: #if DISALLOW_LEADING_ZERO if ((ch >= '0' and ch <= '9') or ch == '.') throw zeep::exception("invalid number in json, should not start with zero"); #else if (ch >= '0' and ch <= '9') throw zeep::exception("invalid number in json, should not start with zero"); else if (ch == '.') { m_token_float = 0; m_token_int = 0; fraction = 0.1; state = state_t::NumberFraction; } #endif else { retract(); m_token_int = 0; token = token_t::Integer; } break; case state_t::Number: if (ch >= '0' and ch <= '9') m_token_int = 10 * m_token_int + (ch - '0'); else if (ch == '.') { m_token_float = static_cast(m_token_int); fraction = 0.1; state = state_t::NumberFraction; } else if (ch == 'e' or ch == 'E') { m_token_float = static_cast(m_token_int); state = state_t::NumberExpSign; } else { retract(); token = token_t::Integer; if (negative) m_token_int = -m_token_int; } break; case state_t::NumberFraction: if (ch >= '0' and ch <= '9') { m_token_float += fraction * (ch - '0'); fraction /= 10; } else if (ch == 'e' or ch == 'E') state = state_t::NumberExpSign; else { retract(); token = token_t::Number; if (negative) m_token_float = -m_token_float; } break; case state_t::NumberExpSign: if (ch == '+') state = state_t::NumberExpDigit1; else if (ch == '-') { negativeExp = true; state = state_t::NumberExpDigit1; } else if (ch >= '0' and ch <= '9') { exponent = (ch - '0'); state = state_t::NumberExpDigit2; } break; case state_t::NumberExpDigit1: if (ch >= '0' and ch <= '9') { exponent = (ch - '0'); state = state_t::NumberExpDigit2; } else throw zeep::exception("invalid floating point format in json"); break; case state_t::NumberExpDigit2: if (ch >= '0' and ch <= '9') exponent = 10 * exponent + (ch - '0'); else { retract(); m_token_float *= std::pow(10, (negativeExp ? -1 : 1) * exponent); if (negative) m_token_float = -m_token_float; token = token_t::Number; } break; case state_t::Literal: if (ch > 128 or not std::isalpha(static_cast(ch))) { retract(); if (m_token == "true") token = token_t::True; else if (m_token == "false") token = token_t::False; else if (m_token == "null") token = token_t::Null; else throw zeep::exception("Invalid literal found in json: " + m_token); } break; case state_t::String: if (ch == '\"') { token = token_t::String; m_token.pop_back(); } else if (ch == 0) throw zeep::exception("Invalid unterminated string in json"); else if (ch == '\\') { state = state_t::Escape; m_token.pop_back(); } break; case state_t::Escape: switch (ch) { case '"': case '\\': case '/': break; case 'n': m_token.back() = '\n'; break; case 't': m_token.back() = '\t'; break; case 'r': m_token.back() = '\r'; break; case 'f': m_token.back() = '\f'; break; case 'b': m_token.back() = '\b'; break; case 'u': state = state_t::EscapeHex1; m_token.pop_back(); break; default: throw zeep::exception("Invalid escape sequence in json (\\" + std::string{ static_cast(ch) } + ')'); } if (state == state_t::Escape) state = state_t::String; break; case state_t::EscapeHex1: if (ch >= '0' and ch <= '9') hx = ch - '0'; else if (ch >= 'a' and ch <= 'f') hx = 10 + ch - 'a'; else if (ch >= 'A' and ch <= 'F') hx = 10 + ch - 'A'; else throw zeep::exception("Invalid hex sequence in json"); m_token.pop_back(); state = state_t::EscapeHex2; break; case state_t::EscapeHex2: if (ch >= '0' and ch <= '9') hx = 16 * hx + ch - '0'; else if (ch >= 'a' and ch <= 'f') hx = 16 * hx + 10 + ch - 'a'; else if (ch >= 'A' and ch <= 'F') hx = 16 * hx + 10 + ch - 'A'; else throw zeep::exception("Invalid hex sequence in json"); m_token.pop_back(); state = state_t::EscapeHex3; break; case state_t::EscapeHex3: if (ch >= '0' and ch <= '9') hx = 16 * hx + ch - '0'; else if (ch >= 'a' and ch <= 'f') hx = 16 * hx + 10 + ch - 'a'; else if (ch >= 'A' and ch <= 'F') hx = 16 * hx + 10 + ch - 'A'; else throw zeep::exception("Invalid hex sequence in json"); m_token.pop_back(); state = state_t::EscapeHex4; break; case state_t::EscapeHex4: if (ch >= '0' and ch <= '9') hx = 16 * hx + ch - '0'; else if (ch >= 'a' and ch <= 'f') hx = 16 * hx + 10 + ch - 'a'; else if (ch >= 'A' and ch <= 'F') hx = 16 * hx + 10 + ch - 'A'; else throw zeep::exception("Invalid hex sequence in json"); m_token.pop_back(); append(m_token, hx); state = state_t::String; break; } } #if __cpp_lib_to_chars >= 201611L if (token == token_t::Number) { double vf; if (auto r = std::from_chars(m_token.data(), m_token.data() + m_token.length(), vf); r.ec == std::errc{}) m_token_float = vf; } #endif return token; } void json_parser::match(token_t expected) { if (m_lookahead != expected) throw zeep::exception("Syntax error in json, expected " + describe_token(expected) + " but found " + describe_token(m_lookahead)); m_lookahead = get_next_token(); } void json_parser::parse_value(object &e) { switch (m_lookahead) { case token_t::Eof: break; case token_t::Null: match(m_lookahead); break; case token_t::False: match(m_lookahead); e = false; break; case token_t::True: match(m_lookahead); e = true; break; case token_t::Integer: match(m_lookahead); e = m_token_int; break; case token_t::Number: match(m_lookahead); e = m_token_float; break; case token_t::LeftBrace: match(m_lookahead); parse_object(e); match(token_t::RightBrace); break; case token_t::LeftBracket: match(m_lookahead); parse_array(e); match(token_t::RightBracket); break; case token_t::String: e = m_token; match(m_lookahead); break; default: throw std::runtime_error("Syntax error in json, unexpected token " + describe_token(m_lookahead)); } } void json_parser::parse_object(object &e) { for (;;) { if (m_lookahead == token_t::RightBrace or m_lookahead == token_t::Eof) break; auto name = m_token; match(token_t::String); match(token_t::Colon); object v; parse_value(v); e.emplace(name, v); if (m_lookahead != token_t::Comma) break; match(m_lookahead); } } void json_parser::parse_array(object &e) { for (;;) { if (m_lookahead == token_t::RightBracket or m_lookahead == token_t::Eof) break; object v; parse_value(v); e.emplace_back(v); if (m_lookahead != token_t::Comma) break; match(m_lookahead); } } void json_parser::parse(object &obj) { m_lookahead = get_next_token(); parse_value(obj); if (m_lookahead != token_t::Eof) throw zeep::exception("Extraneaous data after parsing json"); } // -------------------------------------------------------------------- void deserialize(std::istream &is, object &o) { json_parser p(is); p.parse(o); } } // namespace zeep::el libzeep-7.3.2/src/el-processing.cpp0000664000175000017500000011053515150027072017070 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // // webapp::json is a set of classes used to implement an 'expression language' // A script language used in the XHTML templates used by the zeep webapp. // #include "format.hpp" #include "zeep/el/object.hpp" #include "zeep/el/processing.hpp" #include "zeep/exception.hpp" #include "zeep/http/asio.hpp" #include "zeep/http/request.hpp" #include "zeep/http/scope.hpp" #include "zeep/uri.hpp" #include "zeep/unicode-support.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace zeep::http { bool is_name_start_char(unicode uc) { return uc == L':' or (uc >= L'A' and uc <= L'Z') or uc == L'_' or (uc >= L'a' and uc <= L'z') or (uc >= 0x0C0 and uc <= 0x0D6) or (uc >= 0x0D8 and uc <= 0x0F6) or (uc >= 0x0F8 and uc <= 0x02FF) or (uc >= 0x0370 and uc <= 0x037D) or (uc >= 0x037F and uc <= 0x01FFF) or (uc >= 0x0200C and uc <= 0x0200D) or (uc >= 0x02070 and uc <= 0x0218F) or (uc >= 0x02C00 and uc <= 0x02FEF) or (uc >= 0x03001 and uc <= 0x0D7FF) or (uc >= 0x0F900 and uc <= 0x0FDCF) or (uc >= 0x0FDF0 and uc <= 0x0FFFD) or (uc >= 0x010000 and uc <= 0x0EFFFF); } bool is_name_char(unicode uc) { return uc == '-' or uc == '.' or (uc >= '0' and uc <= '9') or uc == 0x0B7 or is_name_start_char(uc) or (uc >= 0x00300 and uc <= 0x0036F) or (uc >= 0x0203F and uc <= 0x02040); } using request = http::request; // -------------------------------------------------------------------- enum class token_type { undef, eof, number_int, number_float, string, object, assign, and_, or_, not_, eq, ne, lt, le, ge, gt, plus, minus, div, mod, mult, lparen, rparen, lbracket, rbracket, lbrace, rbrace, if_, elvis, else_, dot, hash, bar, true_, false_, in, comma, whitespace, fragment_separator, variable_template, selection_template, message_template, link_template, fragment_template, error }; // -------------------------------------------------------------------- // interpreter for expression language struct interpreter { using unicode = uint32_t; interpreter(const scope &scope) : m_scope(scope) { } // template // OutputIterator operator()(Match &m, OutputIterator out, std::regex::match_flag_type); object evaluate(const std::string &s); std::vector> evaluate_attr_expr(const std::string &s); bool evaluate_assert(const std::string &s); void evaluate_with(scope &scope, const std::string &s); object evaluate_link(const std::string &s); bool process(std::string &s); void match(token_type t); unsigned char next_byte(); unicode get_next_char(); void retract(); void get_next_token(); object parse_expr(); // or_expr ( '?' expr ':' expr )? object parse_or_expr(); // and_expr ( 'or' and_expr)* object parse_and_expr(); // equality_expr ( 'and' equality_expr)* object parse_equality_expr(); // relational_expr ( ('=='|'!=') relational_expr )? object parse_relational_expr(); // additive_expr ( ('<'|'<='|'>='|'>') additive_expr )* object parse_additive_expr(); // multiplicative_expr ( ('+'|'-') multiplicative_expr)* object parse_multiplicative_expr(); // unary_expr (('%'|'/') unary_expr)* object parse_unary_expr(); // ('-')? primary_expr object parse_primary_expr(); // '(' expr ')' | number | string object parse_literal_substitution(); // '|xxx ${var}|' object parse_template_expr(); object parse_link_template_expr(); object parse_fragment_expr(); object parse_selector(); object parse_utility_expr(); object call_method(const std::string &className, const std::string &method, std::vector ¶ms); const scope &m_scope; token_type m_lookahead = token_type::eof; std::string m_token_string; int64_t m_token_number_int{}; double m_token_number_float{}; std::string::const_iterator m_ptr, m_end; bool m_return_whitespace = false; bool m_expect_fragment_spec = false; }; // template // inline OutputIterator interpreter::operator()(Match &m, OutputIterator out, std::regex_constants::match_flag_type) // { // string s(m[1]); // process(s); // copy(s.begin(), s.end(), out); // return out; // } object interpreter::evaluate(const std::string &s) { object result; try { m_ptr = s.begin(); m_end = s.end(); get_next_token(); if (m_lookahead != token_type::eof) result = parse_expr(); match(token_type::eof); } catch (const std::exception &e) { using namespace std::literals; result = "Error parsing expression: "s + e.what(); } return result; } std::vector> interpreter::evaluate_attr_expr(const std::string &s) { std::vector> result; m_ptr = s.begin(); m_end = s.end(); get_next_token(); for (;;) { std::string var = m_token_string; match(token_type::object); match(token_type::assign); auto value = parse_expr(); result.emplace_back(var, value.get()); if (m_lookahead != token_type::comma) break; match(token_type::comma); } match(token_type::eof); return result; } bool interpreter::evaluate_assert(const std::string &s) { bool result = true; m_ptr = s.begin(); m_end = s.end(); get_next_token(); for (;;) { auto value = parse_expr(); if (not value) { result = false; break; } if (m_lookahead != token_type::comma) break; match(token_type::comma); } // match(token_type::eof); return result; } void interpreter::evaluate_with(scope &scope, const std::string &s) { m_ptr = s.begin(); m_end = s.end(); get_next_token(); while (m_lookahead == token_type::object) { auto name = m_token_string; match(token_type::object); match(token_type::assign); auto value = parse_expr(); scope.put(name, value); if (m_lookahead == token_type::comma) { match(token_type::comma); continue; } } match(token_type::eof); } object interpreter::evaluate_link(const std::string &s) { object result; try { m_ptr = s.begin(); m_end = s.end(); get_next_token(); switch (m_lookahead) { case token_type::object: case token_type::fragment_separator: result = parse_fragment_expr(); break; case token_type::link_template: match(token_type::link_template); result = parse_fragment_expr(); match(token_type::rbrace); break; default: m_expect_fragment_spec = true; result = parse_primary_expr(); break; } match(token_type::eof); } catch (const std::exception &e) { using namespace std::literals; result = "Error parsing expression: "s + e.what(); } return result; } bool interpreter::process(std::string &s) { bool result = false; try { m_ptr = s.begin(); m_end = s.end(); get_next_token(); object obj; if (m_lookahead != token_type::eof) obj = parse_expr(); match(token_type::eof); if (obj.is_null()) s.clear(); else s = obj.get(); result = true; } catch (const std::exception &e) { result = false; } return result; } void interpreter::match(token_type t) { if (t != m_lookahead) throw zeep::exception("syntax error"); get_next_token(); } unsigned char interpreter::next_byte() { char result = 0; if (m_ptr < m_end) { result = *m_ptr; ++m_ptr; } m_token_string += result; return static_cast(result); } // We assume all paths are in valid UTF-8 encoding interpreter::unicode interpreter::get_next_char() { unicode result = 0; unsigned char ch[5]; ch[0] = next_byte(); if ((ch[0] & 0x080) == 0) result = ch[0]; else if ((ch[0] & 0x0E0) == 0x0C0) { ch[1] = next_byte(); if ((ch[1] & 0x0c0) != 0x080) throw zeep::exception("Invalid utf-8"); result = ((ch[0] & 0x01F) << 6) | (ch[1] & 0x03F); } else if ((ch[0] & 0x0F0) == 0x0E0) { ch[1] = next_byte(); ch[2] = next_byte(); if ((ch[1] & 0x0c0) != 0x080 or (ch[2] & 0x0c0) != 0x080) throw zeep::exception("Invalid utf-8"); result = ((ch[0] & 0x00F) << 12) | ((ch[1] & 0x03F) << 6) | (ch[2] & 0x03F); } else if ((ch[0] & 0x0F8) == 0x0F0) { ch[1] = next_byte(); ch[2] = next_byte(); ch[3] = next_byte(); if ((ch[1] & 0x0c0) != 0x080 or (ch[2] & 0x0c0) != 0x080 or (ch[3] & 0x0c0) != 0x080) throw zeep::exception("Invalid utf-8"); result = ((ch[0] & 0x007) << 18) | ((ch[1] & 0x03F) << 12) | ((ch[2] & 0x03F) << 6) | (ch[3] & 0x03F); } if (result > 0x10ffff) throw zeep::exception("invalid utf-8 character (out of range)"); return result; } void interpreter::retract() { std::string::iterator c = m_token_string.end(); // skip one valid character back in the input buffer // since we've arrived here, we can safely assume input // is valid UTF-8 do --c; while ((*c & 0x0c0) == 0x080); if (m_ptr != m_end or *c != 0) m_ptr -= m_token_string.end() - c; m_token_string.erase(c, m_token_string.end()); } void interpreter::get_next_token() { enum class State { Start, Equals, ExclamationMark, LessThan, GreaterThan, Question, Number, NumberFraction, Name, Literal, Colon, Hash, TemplateStart } state = State::Start; token_type token = token_type::undef; double fraction = 1.0; m_token_string.clear(); while (token == token_type::undef) { unicode ch = get_next_char(); switch (state) { case State::Start: switch (ch) { case 0: token = token_type::eof; break; case '(': token = token_type::lparen; break; case ')': token = token_type::rparen; break; case '[': token = token_type::lbracket; break; case ']': token = token_type::rbracket; break; case '{': token = token_type::lbrace; break; case '}': token = token_type::rbrace; break; case '?': state = State::Question; break; case '/': token = token_type::div; break; case '+': token = token_type::plus; break; case '-': token = token_type::minus; break; case '.': token = token_type::dot; break; case ',': token = token_type::comma; break; case '|': token = token_type::bar; break; case '=': state = State::Equals; break; case '!': state = State::ExclamationMark; break; case '<': state = State::LessThan; break; case '>': state = State::GreaterThan; break; case ':': state = State::Colon; break; case '*': case '$': case '#': case '@': case '~': state = State::TemplateStart; break; case ' ': case '\n': case '\r': case '\t': if (m_return_whitespace) token = token_type::whitespace; else m_token_string.clear(); break; case '\'': state = State::Literal; break; default: if (ch >= '0' and ch <= '9') { m_token_number_int = ch - '0'; state = State::Number; } else if (is_name_start_char(ch)) state = State::Name; else token = token_type::error; // throw zeep::exception("invalid character (" + to_hex(ch) + ") in expression"); } break; case State::TemplateStart: if (ch == '{') { switch (m_token_string[0]) { case '$': token = token_type::variable_template; break; case '*': token = token_type::selection_template; break; case '#': token = token_type::message_template; break; case '@': token = token_type::link_template; break; case '~': token = token_type::fragment_template; break; default: assert(false); } } else { retract(); if (m_token_string[0] == '*') token = token_type::mult; else if (m_token_string[0] == '#') state = State::Hash; else token = token_type::error; // else // throw zeep::exception("invalid character (" + std::string{static_cast(isprint(ch) ? ch : ' ')} + '/' + to_hex(ch) + ") in expression"); } break; case State::Equals: if (ch != '=') { retract(); token = token_type::assign; } else token = token_type::eq; break; case State::Question: if (ch == ':') token = token_type::elvis; else { retract(); token = token_type::if_; } break; case State::ExclamationMark: if (ch != '=') { retract(); token = token_type::error; // throw zeep::exception("unexpected character ('!') in expression"); } else token = token_type::ne; break; case State::LessThan: if (ch == '=') token = token_type::le; else { retract(); token = token_type::lt; } break; case State::GreaterThan: if (ch == '=') token = token_type::ge; else { retract(); token = token_type::gt; } break; case State::Number: if (ch >= '0' and ch <= '9') m_token_number_int = 10 * m_token_number_int + (ch - '0'); else if (ch == '.') { m_token_number_float = static_cast(m_token_number_int); fraction = 0.1; state = State::NumberFraction; } else { retract(); token = token_type::number_int; } break; case State::NumberFraction: if (ch >= '0' and ch <= '9') { m_token_number_float += fraction * (ch - '0'); fraction /= 10; } else { retract(); token = token_type::number_float; } break; case State::Name: if (ch == '.' or ch == ':' or not is_name_char(ch)) { retract(); if (m_token_string == "div") token = token_type::div; else if (m_token_string == "mod") token = token_type::mod; else if (m_token_string == "and") token = token_type::and_; else if (m_token_string == "or") token = token_type::or_; else if (m_token_string == "not") token = token_type::not_; else if (m_token_string == "lt") token = token_type::lt; else if (m_token_string == "le") token = token_type::le; else if (m_token_string == "ge") token = token_type::ge; else if (m_token_string == "gt") token = token_type::gt; else if (m_token_string == "ne") token = token_type::ne; else if (m_token_string == "eq") token = token_type::eq; else if (m_token_string == "true") token = token_type::true_; else if (m_token_string == "false") token = token_type::false_; else if (m_token_string == "in") token = token_type::in; else token = token_type::object; } break; case State::Literal: if (ch == 0) token = token_type::error; // throw zeep::exception("run-away string, missing quote character?"); else if (ch == '\'') { token = token_type::string; m_token_string = m_token_string.substr(1, m_token_string.length() - 2); } break; case State::Hash: if (ch == '.' or not is_name_char(ch)) { retract(); token = token_type::hash; } break; case State::Colon: if (ch == ':') token = token_type::fragment_separator; else { retract(); token = token_type::else_; } break; } } m_lookahead = token; #if __cpp_lib_to_chars >= 201611L if (token == token_type::number_float) { double vf; if (auto r = std::from_chars(m_token_string.data(), m_token_string.data() + m_token_string.length(), vf); r.ec == std::errc{}) m_token_number_float = vf; } #endif } object interpreter::parse_expr() { object result; result = parse_or_expr(); if (m_lookahead == token_type::if_) { match(m_lookahead); object a = parse_expr(); if (m_lookahead == token_type::else_) { match(token_type::else_); object b = parse_expr(); if (result) result = a; else result = b; } else if (result) result = a; else result = {}; } else if (m_lookahead == token_type::elvis) { match(m_lookahead); object a = parse_expr(); if (not result) result = a; } return result; } object interpreter::parse_or_expr() { object result = parse_and_expr(); while (m_lookahead == token_type::or_) { match(m_lookahead); bool b1 = result.get(); bool b2 = parse_and_expr().get(); result = b1 or b2; } return result; } object interpreter::parse_and_expr() { object result = parse_equality_expr(); while (m_lookahead == token_type::and_) { match(m_lookahead); bool b1 = result.get(); bool b2 = parse_equality_expr().get(); result = b1 and b2; } return result; } object interpreter::parse_equality_expr() { object result = parse_relational_expr(); if (m_lookahead == token_type::eq) { match(m_lookahead); result = (result == parse_relational_expr()); } else if (m_lookahead == token_type::ne) { match(m_lookahead); result = not(result == parse_relational_expr()); } return result; } object interpreter::parse_relational_expr() { object result = parse_additive_expr(); switch (m_lookahead) { case token_type::lt: match(m_lookahead); result = (result < parse_additive_expr()); break; case token_type::le: match(m_lookahead); result = (result <= parse_additive_expr()); break; case token_type::ge: match(m_lookahead); result = (result >= parse_additive_expr()); break; case token_type::gt: match(m_lookahead); result = (result > parse_additive_expr()); break; case token_type::not_: { match(token_type::not_); match(token_type::in); object list = parse_additive_expr(); result = not list.contains(result); break; } case token_type::in: { match(m_lookahead); object list = parse_additive_expr(); result = list.contains(result); break; } default: break; } return result; } object interpreter::parse_additive_expr() { object result = parse_multiplicative_expr(); while (m_lookahead == token_type::plus or m_lookahead == token_type::minus) { if (m_lookahead == token_type::plus) { match(m_lookahead); result = (result + parse_multiplicative_expr()); } else { match(m_lookahead); result = (result - parse_multiplicative_expr()); } } return result; } object interpreter::parse_multiplicative_expr() { object result = parse_unary_expr(); while (m_lookahead == token_type::div or m_lookahead == token_type::mod or m_lookahead == token_type::mult) { if (m_lookahead == token_type::mult) { match(m_lookahead); result = (result * parse_unary_expr()); } else if (m_lookahead == token_type::div) { match(m_lookahead); result = (result / parse_unary_expr()); } else { match(m_lookahead); result = (result % parse_unary_expr()); } } return result; } object interpreter::parse_unary_expr() { object result; if (m_lookahead == token_type::minus) { match(m_lookahead); result = -(parse_primary_expr()); } else if (m_lookahead == token_type::not_) { match(m_lookahead); result = not parse_primary_expr().get(); } else result = parse_primary_expr(); return result; } object interpreter::parse_template_expr() { object result; bool save_return_white_space = m_return_whitespace; m_return_whitespace = false; switch (m_lookahead) { case token_type::variable_template: match(token_type::variable_template); result = parse_expr(); m_return_whitespace = save_return_white_space; match(token_type::rbrace); break; case token_type::link_template: match(token_type::link_template); result = parse_link_template_expr(); m_return_whitespace = save_return_white_space; match(token_type::rbrace); break; case token_type::selection_template: match(token_type::selection_template); if (m_lookahead == token_type::object) { result = m_scope.lookup(m_token_string, true); match(token_type::object); for (;;) { if (m_lookahead == token_type::dot) { match(m_lookahead); if (result.type() == object::value_type::array and (m_token_string == "count" or m_token_string == "length")) result = result.size(); else if (m_token_string == "empty") result = result.empty(); else if (result.type() == object::value_type::object) result = const_cast(result)[m_token_string]; else result = object::value_type::null; match(token_type::object); continue; } if (m_lookahead == token_type::lbracket) { match(m_lookahead); object index = parse_expr(); match(token_type::rbracket); if (index.empty() or (result.type() != object::value_type::array and result.type() != object::value_type::object)) result = object(); else if (result.type() == object::value_type::array) result = result[index.get()]; else if (result.type() == object::value_type::object) result = result[index.get()]; else result = object::value_type::null; continue; } break; } } else result = parse_expr(); m_return_whitespace = save_return_white_space; match(token_type::rbrace); break; default: throw zeep::exception("syntax error, unexpected token: " + m_token_string); } return result; } object interpreter::parse_primary_expr() { object result; switch (m_lookahead) { case token_type::true_: result = true; match(m_lookahead); break; case token_type::false_: result = false; match(m_lookahead); break; case token_type::number_int: result = m_token_number_int; match(m_lookahead); break; case token_type::number_float: result = m_token_number_float; match(m_lookahead); break; case token_type::string: result = m_token_string; match(m_lookahead); break; case token_type::lparen: match(m_lookahead); result = parse_expr(); match(token_type::rparen); break; case token_type::hash: result = parse_utility_expr(); break; // a list case token_type::lbrace: match(m_lookahead); for (;;) { result.push_back(parse_expr()); if (m_lookahead == token_type::comma) { match(m_lookahead); continue; } break; } match(token_type::rbrace); break; case token_type::bar: match(token_type::bar); result = parse_literal_substitution(); match(token_type::bar); break; case token_type::object: result = m_scope.lookup(m_token_string); match(token_type::object); for (;;) { if (m_lookahead == token_type::dot) { match(m_lookahead); if (not result.is_null()) { if (result.type() == object::value_type::array and (m_token_string == "count" or m_token_string == "length")) result = object(static_cast(result.size())); else if (m_token_string == "empty") result = result.empty(); else if (result.type() == object::value_type::object) result = const_cast(result)[m_token_string]; else result = object::value_type::null; } match(token_type::object); continue; } if (m_lookahead == token_type::lbracket) { match(m_lookahead); object index = parse_expr(); match(token_type::rbracket); if (not result.is_null()) { if (result.type() == object::value_type::array and not(result.empty() or index.empty())) result = result[index.get()]; else if (result.type() == object::value_type::object and not(result.empty() or index.empty())) result = result[index.get()]; else result = object::value_type::null; } continue; } break; } break; case token_type::variable_template: case token_type::link_template: case token_type::selection_template: result = parse_template_expr(); break; case token_type::fragment_template: match(token_type::fragment_template); result = parse_fragment_expr(); match(token_type::rbrace); break; default: throw zeep::exception("syntax error, expected number, string or object"); } return result; } object interpreter::parse_literal_substitution() { std::string result; m_return_whitespace = true; while (m_lookahead != token_type::bar and m_lookahead != token_type::eof) { switch (m_lookahead) { case token_type::variable_template: case token_type::selection_template: case token_type::message_template: result += parse_template_expr().get(); break; default: result += m_token_string; match(m_lookahead); break; } } m_return_whitespace = false; return result; } // -------------------------------------------------------------------- object interpreter::parse_link_template_expr() { std::string path; int braces = 0; auto context = m_scope.get_context_name(); // in case of a relative URL starting with a forward slash, we prefix the URL with the context_name of the server if (m_lookahead == token_type::div) { match(token_type::div); path = context; if (path.back() != '/') path += '/'; } while (m_lookahead != token_type::lparen and m_lookahead != token_type::eof) { if (m_lookahead == token_type::rbrace) { if (braces-- == 0) break; path += m_token_string; match(token_type::rbrace); continue; } switch (m_lookahead) { case token_type::bar: match(m_lookahead); path += parse_literal_substitution().get(); match(token_type::bar); break; case token_type::variable_template: case token_type::selection_template: path += parse_template_expr().get(); break; case token_type::lbrace: path += m_token_string; match(token_type::lbrace); ++braces; break; default: { path += m_token_string; match(m_lookahead); break; } } } if (m_lookahead == token_type::lparen) { match(token_type::lparen); std::map parameters; for (;;) { std::string name = m_token_string; match(token_type::object); if (m_lookahead == token_type::assign) { match(token_type::assign); std::string value = parse_primary_expr().get(); // put into path directly, if found std::string::size_type p = path.find('{' + name + '}'); if (p == std::string::npos) parameters[name] = value; else { do { path.replace(p, name.length() + 2, value); p += value.length(); } while ((p = path.find('{' + name + '}', p)) != std::string::npos); } } else parameters[name] = ""; if (m_lookahead == token_type::comma) { match(token_type::comma); continue; } break; } match(token_type::rparen); if (not parameters.empty()) { path += '?'; auto n = parameters.size(); for (const auto &p : parameters) { path += encode_url(p.first); if (not p.second.empty()) path += '=' + encode_url(p.second); if (--n > 0) path += '&'; } } } path = uri(path, context).string(); return path; } // -------------------------------------------------------------------- object interpreter::parse_fragment_expr() { object result{ { "fragment-spec", true } }; if (m_lookahead == token_type::fragment_separator) result["template"] = "this"; else if (m_lookahead == token_type::object) { result["template"] = m_token_string; match(m_lookahead); } else if (m_lookahead == token_type::rbrace) { result["template"] = "this"; result["selector"] = { { "xpath", "" } }; } else result["template"] = parse_expr(); if (m_lookahead == token_type::fragment_separator) { match(token_type::fragment_separator); result["selector"] = parse_selector(); } return result; } // -------------------------------------------------------------------- object interpreter::parse_selector() { object result; std::string xpath; while (m_lookahead == token_type::div or m_lookahead == token_type::object or m_lookahead == token_type::lbracket or m_lookahead == token_type::dot or m_lookahead == token_type::hash) { bool divided = false; if (m_lookahead == token_type::div) { divided = true; match(m_lookahead); if (m_lookahead == token_type::div) { match(m_lookahead); xpath += "//"; } else xpath += "/"; } else xpath += "//"; if (m_lookahead == token_type::object) { std::string name = m_token_string; match(m_lookahead); if (m_lookahead == token_type::lparen and (name == "text" or name == "comment" or name == "processing-instruction" or name == "node")) { match(m_lookahead); match(token_type::rparen); xpath += name + "()"; } else { if (divided) xpath += name; else xpath += std::format( R"(*[name()='{}' or attribute::*[namespace-uri() = $ns and (local-name() = 'ref' or local-name() = 'fragment') and starts-with(string(), '{}')]])", name, name); if (m_lookahead == token_type::lparen) { match(token_type::lparen); while (m_lookahead != token_type::rparen and m_lookahead != token_type::eof) { result["params"].push_back(parse_expr()); if (m_lookahead == token_type::comma) { match(m_lookahead); continue; } break; } match(token_type::rparen); // break; // what else? } } } else xpath += "*"; while (m_lookahead == token_type::lbracket or m_lookahead == token_type::dot or m_lookahead == token_type::hash) { switch (m_lookahead) { case token_type::lbracket: do { xpath += m_token_string; match(m_lookahead); } while (m_lookahead != token_type::rbracket and m_lookahead != token_type::eof); xpath += m_token_string; match(token_type::rbracket); break; case token_type::dot: match(m_lookahead); xpath += "[@class='" + m_token_string + "']"; match(token_type::object); break; case token_type::hash: xpath += "[@id='" + m_token_string.substr(1) + "']"; result["by-id"] = true; match(m_lookahead); break; default: break; } } } result["xpath"] = xpath; return result; } // -------------------------------------------------------------------- object interpreter::parse_utility_expr() { auto className = m_token_string; match(token_type::hash); match(token_type::dot); auto method = m_token_string; match(token_type::object); std::vector params; if (m_lookahead == token_type::lparen) { match(token_type::lparen); while (m_lookahead != token_type::rparen) { params.push_back(parse_expr()); if (m_lookahead == token_type::comma) { match(token_type::comma); continue; } break; } match(token_type::rparen); } return call_method(className, method, params); } object interpreter::call_method(const std::string &className, const std::string &method, std::vector ¶ms) { object result; if (className[0] == '#') result = expression_utility_object_base::evaluate(m_scope, className.substr(1), method, params); // if (result.is_null()) // throw runtime_error("Undefined class for utility object call: " + className); return result; } // -------------------------------------------------------------------- // some expression utility objects expression_utility_object_base::instance *expression_utility_object_base::s_head; class date_expr_util_object : public expression_utility_object { public: static constexpr const char *name() { return "dates"; } protected: [[nodiscard]] object evaluate(const scope &scope, const std::string &method, const std::vector ¶ms) const override { object result; if (method == "format") { if (params.size() == 2 and params[0].is_string()) { auto t = params[0].get(); auto f = params[1].get(); auto st = zeem::value_serializer::from_string(t); std::wostringstream os; os.imbue(scope.get_locale()); auto tt = std::chrono::system_clock::to_time_t(st); auto wf = convert_s2w(f); os << std::put_time(std::gmtime(&tt), wf.c_str()); result = convert_w2s(os.str()); } } return result; } } s_date_instance; class number_expr_util_object : public expression_utility_object { public: static constexpr const char *name() { return "numbers"; } protected: [[nodiscard]] object evaluate(const scope &scope, const std::string &method, const std::vector ¶ms) const override { object result; if (method == "formatDecimal") { if (params.size() >= 1 and params[0].is_number()) { int intDigits = 1; if (params.size() >= 2 and params[1].is_number_int()) intDigits = params[1].get(); int decimals = 0; if (params.size() >= 3 and params[2].is_number_int()) decimals = params[2].get(); double d; if (params[0].is_number_int()) d = static_cast(params[0].get()); else d = params[0].get(); return format_decimal(d, intDigits, decimals, scope.get_locale()); } } else if (method == "formatDiskSize") { if (params.size() >= 1 and params[0].is_number()) { double nr = params[0].get(); const char kBase[] = { 'B', 'K', 'M', 'G', 'T', 'P', 'E' }; // whatever int base = 0; while (nr > 1024) { nr /= 1024; ++base; } int decimals = 0; if (params.size() >= 2 and params[1].is_number_int()) decimals = params[1].get(); return format_decimal(nr, 1, decimals, scope.get_locale()) + ' ' + kBase[base]; } } return {}; } } s_number_instance; class request_expr_util_object : public expression_utility_object { public: static constexpr const char *name() { return "request"; } protected: [[nodiscard]] object evaluate(const scope &scope, const std::string &method, const std::vector ¶ms) const override { object result; if (method == "getRequestURI" or method == "getRequestURL") result = scope.get_request().get_uri().string(); else if ((method == "getParameter") and params.size() == 1) { auto p = scope.get_parameter(params[0].get()); if (p.has_value()) result = *p; } return result; } } s_request_instance; class security_expr_util_object : public expression_utility_object { public: static constexpr const char *name() { return "security"; } protected: [[nodiscard]] object evaluate(const scope &scope, const std::string &method, const std::vector ¶ms) const override { object result; if (method == "hasRole") { if (params.size() == 1 and params[0].is_string()) { auto role = params[0].get(); auto roles = scope.get_credentials()["role"]; result = roles.is_array() and roles.contains(role); } } else if (method == "authorized") { result = scope.get_credentials()["username"].is_string(); } else if (method == "username") { result = scope.get_credentials()["username"]; } return result; } } s_security_instance; // -------------------------------------------------------------------- // interpreter calls bool process_el(const scope &scope, std::string &text) { interpreter interpreter(scope); return interpreter.process(text); } std::string process_el_2(const scope &scope, const std::string &text) { std::string s = text; interpreter interpreter(scope); return interpreter.process(s) ? s : text; } object evaluate_el(const scope &scope, const std::string &text) { interpreter interpreter(scope); return interpreter.evaluate(text); } std::vector> evaluate_el_attr(const scope &scope, const std::string &text) { interpreter interpreter(scope); return interpreter.evaluate_attr_expr(text); } bool evaluate_el_assert(const scope &scope, const std::string &text) { interpreter interpreter(scope); return interpreter.evaluate_assert(text); } void evaluate_el_with(scope &scope, const std::string &text) { interpreter interpreter(scope); interpreter.evaluate_with(scope, text); } object evaluate_el_link(const scope &scope, const std::string &text) { interpreter interpreter(scope); return interpreter.evaluate_link(text); } } // namespace zeep::http libzeep-7.3.2/src/error-handler.cpp0000664000175000017500000001156015150027072017060 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/error-handler.hpp" #include "zeep/el/object.hpp" #include "zeep/el/processing.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/http/scope.hpp" #include "zeep/http/security.hpp" #include "zeep/http/server.hpp" #include "zeep/http/status.hpp" #include "zeep/http/template-processor.hpp" #include "zeep/uri.hpp" #include #include #include #include namespace zeep::http { error_handler::error_handler(std::string error_template) : m_error_template(std::move(error_template)) { } bool error_handler::create_error_reply(const request & /*req*/, const std::exception_ptr & /*eptr*/, reply & /*reply*/) { return false; } bool error_handler::create_unauth_reply(const request &req, reply &rep) { return create_error_reply(req, status_type::unauthorized, "You don't have access to this page", rep); } bool error_handler::create_error_reply(const request &req, status_type status, reply &rep) { return create_error_reply(req, status, get_status_description(status), rep); } bool error_handler::create_error_reply(const request &req, status_type status, std::string message, reply &rep) { bool handled = false; if (not m_error_template.empty() and m_server->has_template_processor()) { auto &template_processor = m_server->get_template_processor(); scope scope(*get_server(), req); object error{ { "nr", static_cast(status) }, { "head", make_error_code(status).message() }, { "description", get_status_description(status) }, { "message", message }, { "request", { { "method", req.get_method() }, { "uri", req.get_uri().string() } } } }; auto credentials = req.get_credentials(); if (credentials.is_object()) error["request"]["username"] = credentials["user"].get(); scope.put("error", error); try { template_processor.create_reply_from_template(m_error_template, scope, rep); handled = true; } catch (const std::exception &) { using namespace zeem::literals; auto doc = R"( Error

Additional information:

)"_xml; template_processor.process_tags(doc.child(), scope); rep.set_content(doc); handled = true; } } if (not handled) { rep = reply::stock_reply(status, message); handled = true; } else rep.set_status(status); return true; } // -------------------------------------------------------------------- bool default_error_handler::create_error_reply(const request &req, const std::exception_ptr &eptr, reply &reply) { bool result = false; try { if (eptr) std::rethrow_exception(eptr); } catch (const status_type &s) { result = error_handler::create_error_reply(req, s, get_status_description(s), reply); } catch (const unauthorized_exception &) { result = error_handler::create_unauth_reply(req, reply); } catch (const http_status_exception &ex) { result = error_handler::create_error_reply(req, ex.status(), get_status_description(ex.status()), reply); } catch (const std::exception &ex) { result = error_handler::create_error_reply(req, status_type::internal_server_error, ex.what(), reply); } catch (...) { result = error_handler::create_error_reply(req, status_type::internal_server_error, "unhandled exception", reply); } return result; } } // namespace zeep::http libzeep-7.3.2/src/format.cpp0000664000175000017500000001274715150027072015614 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2019-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #if __cpp_lib_format # include std::string FormatDecimal(double d, int integerDigits, int decimalDigits, std::locale loc) { return std::vformat(loc, "{:{}.{}f}", std::make_format_args(integerDigits + decimalDigits, decimalDigits, d)); } #else # include "format.hpp" # include "zeep/unicode-support.hpp" # include # include # include # include # include # include namespace zeep { std::string decimal_point(const std::locale &loc) { std::string result; if (std::has_facet>(loc)) zeep::append(result, std::use_facet>(loc).decimal_point()); else if (std::has_facet>(loc)) result = std::use_facet>(loc).decimal_point(); else result = { '.' }; return result; } std::string thousands_sep(const std::locale &loc) { std::string result; if (std::has_facet>(loc)) zeep::append(result, std::use_facet>(loc).thousands_sep()); else if (std::has_facet>(loc)) result = std::use_facet>(loc).thousands_sep(); else result = { ',' }; return result; } std::string grouping(const std::locale &loc) { std::string result; if (std::has_facet>(loc)) result = std::use_facet>(loc).grouping(); else if (std::has_facet>(loc)) result = std::use_facet>(loc).grouping(); return result; } struct thousand_grouping { public: thousand_grouping(const std::locale &loc) : m_sep(thousands_sep(loc)) , m_grouping(grouping(loc)) { } bool operator()(int exp10) const { std::deque gs(m_grouping.begin(), m_grouping.end()); bool result = false; if (not gs.empty()) { int g = gs.front(); for (;;) { if (exp10 < g) break; if (exp10 == g) { result = true; break; } if (gs.size() > 1) gs.pop_front(); g += gs.front(); } } return result; } [[nodiscard]] std::string separator() const { return m_sep; } private: std::string m_sep, m_grouping; }; template class Decimal { public: Decimal(T x); std::string formatFixed(int minIntDigits, int decimals, const std::locale &loc); friend std::ostream &operator<<(std::ostream &os, Decimal d) { os << "crunched: " << d.m_crunched << " exp10: " << d.m_exp10 << " dec: " << d.m_dec; return os; } private: std::tuple roundDecimal(int n); T m_crunched; int m_exp10; std::string m_dec; }; template Decimal::Decimal(T x) { // CrunchDouble int exp = (x == 0) ? 0 : static_cast(1 + std::logb(x)); int n = m_exp10 = (exp * 301029) / 1000000; auto p = 10.0; if (n < 0) { for (n = -n; n > 0; n >>= 1) { if (n & 1) x *= p; p *= p; } } else { T f = 1.0; for (; n > 0; n >>= 1) { if (n & 1) f *= p; p *= p; } x /= f; } if (x != 0) { while (std::fabs(x) >= 1.0) { x *= 0.1; ++m_exp10; } while (std::fabs(x) < 0.1) { x *= 10.0; --m_exp10; } } m_crunched = x; // Num2Dec int digits = std::numeric_limits::digits10 + 1; int ix = 0; if (x < 0) x = -x; const double kDigitValues[8] = { 1.0E+01, 1.0E+02, 1.0E+03, 1.0E+04, 1.0E+05, 1.0E+06, 1.0E+07, 1.0E+08 }; while (digits > 0) { n = digits; if (n > 8) n = 8; digits -= n; m_dec.insert(m_dec.end(), n, ' '); x *= kDigitValues[n - 1]; auto lx = lrint(trunc(x)); x -= lx; for (int i = n - 1; i >= 0; --i) { m_dec[ix + i] = lx % 10 + '0'; lx /= 10; } ix += 8; } } template std::string Decimal::formatFixed(int intDigits, int decimals, const std::locale &loc) { int digits = decimals + intDigits; if (m_exp10 > intDigits) digits += m_exp10 - intDigits; int exp10; std::string dec; std::tie(dec, exp10) = roundDecimal(decimals + m_exp10); std::string s; thousand_grouping tg(loc); auto dp = dec.begin(), de = dec.end(); int exp = intDigits; if (exp < exp10) exp = exp10; for (int i = 0; i < digits; ++i) { if (i > 0) { if (tg(exp)) s += tg.separator(); else if (exp == 0) s += decimal_point(loc); } if (exp <= exp10 and dp != de) s += *dp++; else s += '0'; --exp; } return s; } template std::tuple Decimal::roundDecimal(int newLength) { std::string dec = m_dec; int exp10 = m_exp10; int l = static_cast(dec.length()); if (newLength < 0) dec = "0"; else if (newLength < l) { l = newLength + 1; int carry = dec[l - 1] >= '5'; dec.resize(l - 1); while (newLength > 0) { --l; int c = dec[l - 1] - '0' + carry; carry = c > 9; if (carry == 1) { --newLength; dec.resize(newLength); } else { dec[l - 1] = static_cast(c + '0'); dec.resize(l); break; } } if (carry == 1) { ++exp10; dec += '1'; } else if (newLength == 0) dec = "0"; } return std::make_tuple(dec, exp10); } std::string format_decimal(double d, int integerDigits, int decimalDigits, const std::locale &loc) { Decimal dec(d); std::string result; if (d < 0) result = "-"; result += dec.formatFixed(integerDigits, decimalDigits, loc); return result; } // #endif } // namespace zeep #endif libzeep-7.3.2/src/format.hpp0000664000175000017500000000123315150027072015605 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2019-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// This file contains definitions of various utility routines #include #include namespace zeep { /// \brief A locale dependent formatting of a decimal number /// /// Returns a formatted number with the specified number of digits /// using separators taken from std::locale \a loc std::string format_decimal(double d, int integerDigits, int decimalDigits, const std::locale &loc); } // namespace zeep libzeep-7.3.2/src/glob.cpp0000664000175000017500000000725615150027072015246 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2019-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "glob.hpp" #include "zeep/unicode-support.hpp" #include #include #include #include #include // -------------------------------------------------------------------- namespace zeep::http { namespace { bool Match(const char *pattern, const char *name) { for (;;) { char op = *pattern; switch (op) { case 0: return *name == 0; case '*': { // separate shortcut if (pattern[1] == '*' and pattern[2] == '/' and Match(pattern + 3, name)) return true; // ends with ** if (pattern[1] == '*' and pattern[2] == 0) return true; // ends with * if (pattern[1] == 0) { while (*name) { if (*name == '/' or *name == '\\') return false; ++name; } return true; } // contains ** if (pattern[1] == '*') { while (*name) { if (Match(pattern + 2, name)) return true; ++name; } return false; } // contains just * while (*name) { if (Match(pattern + 1, name)) return true; if (*name == '/') return false; ++name; } return false; } case '?': if (*name) return Match(pattern + 1, name + 1); else return false; default: if ((*name == '/' and op == '\\') or (*name == '\\' and op == '/') or tolower(*name) == tolower(op)) { ++name; ++pattern; } else return false; break; } } } void expand_group(std::string pattern, std::vector &expanded) { static std::regex rx(R"(\{([^{},]*,[^{}]*)\})"); std::smatch m; if (std::regex_search(pattern, m, rx)) { std::vector options; std::string group = m[1].str(); split(options, group, ",", false); for (std::string &option : options) expand_group(m.prefix().str() + option + m.suffix().str(), expanded); } else expanded.push_back(std::move(pattern)); } } // namespace // -------------------------------------------------------------------- bool glob_match(const std::filesystem::path &path, std::string glob_pattern) { bool result = path.empty() and glob_pattern.empty(); if (not glob_pattern.empty() and glob_pattern.back() == '/') glob_pattern += "**"; std::vector patterns; split(patterns, glob_pattern, ";"); std::vector expandedpatterns; std::ranges::for_each(patterns, [&expandedpatterns](std::string &pattern) { expand_group(pattern, expandedpatterns); }); for (std::string &pat : expandedpatterns) { if ((path.empty() and pat.empty()) or Match(pat.c_str(), path.generic_string().c_str())) { result = true; break; } } return result; } // -------------------------------------------------------------------- bool glob_match(const uri &path, std::string glob_pattern) { bool result = path.empty() and glob_pattern.empty(); if (not glob_pattern.empty() and glob_pattern.back() == '/') glob_pattern += "**"; std::vector patterns; split(patterns, glob_pattern, ";"); std::vector expandedpatterns; std::ranges::for_each(patterns, [&expandedpatterns](std::string &pattern) { expand_group(pattern, expandedpatterns); }); for (std::string &pat : expandedpatterns) { if ((path.empty() and pat.empty()) or Match(pat.c_str(), path.get_path().string().c_str())) { result = true; break; } } return result; } } // namespace zeep::http libzeep-7.3.2/src/glob.hpp0000664000175000017500000000277215150027072015251 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2019 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once /// \file /// This file contains definitions of various utility routines #include "zeep/uri.hpp" #include #include namespace zeep::http { /// \brief compare an fs::path with a glob pattern /// /// Returns true if the path \a p matches \a pattern /// Matching is done using shell like glob patterns: /// /// construct | Matches /// --------------|-------- /// ? | single character /// * | zero or multiple characters /// {a,b} | matching either pattern a or b /// /// \param p The path to match /// \param pattern The pattern to match against /// \return True in case of a match bool glob_match(const std::filesystem::path &p, std::string pattern); /// \brief compare the path part of a uri with a glob pattern /// /// Returns true if the path \a p matches \a pattern /// Matching is done using shell like glob patterns: /// /// construct | Matches /// --------------|-------- /// ? | single character /// * | zero or multiple characters /// {a,b} | matching either pattern a or b /// /// \param u The uri whose path to match /// \param pattern The pattern to match against /// \return True in case of a match bool glob_match(const uri &u, std::string pattern); } // namespace zeep::httplibzeep-7.3.2/src/html-controller.cpp0000664000175000017500000000504715150027072017444 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/html-controller.hpp" #include "zeep/http/controller.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/http/scope.hpp" #include "zeep/http/server.hpp" #include "zeep/http/template-processor.hpp" #include "zeep/uri.hpp" #include "glob.hpp" #include #include #include #include namespace zeep::http { basic_template_processor &html_controller::get_template_processor() { return m_server->get_template_processor(); } const basic_template_processor &html_controller::get_template_processor() const { return m_server->get_template_processor(); } // -------------------------------------------------------------------- reply html_controller::html_mount_point_simple::call(const scope &scope) { return m_controller.get_template_processor().create_reply_from_template(m_template, scope); } // -------------------------------------------------------------------- reply html_controller::handle_file(const scope &scope) { assert(scope.get_request().get_method() == "GET" or scope.get_request().get_method() == "HEAD"); return get_template_processor().create_reply_for_get_file(scope); } void html_controller::init_scope(scope &/* scope */) { } // -------------------------------------------------------------------- // bool html_controller_v1::handle_request(request &req, reply &rep) { bool result = controller::handle_request(req, rep); if (not result) { auto uri = get_prefixless_path(req); auto path = uri.string(); // set up the scope by putting some globals in it scope scope(get_server(), req); scope.put("baseuri", path); init_scope(scope); auto handler = std::ranges::find_if(m_dispatch_table, [&uri, method = req.get_method()](const mount_point_v1 &m) { // return m.path == path and return glob_match(uri, m.path) and (method == "HEAD" or method == "OPTIONS" or m.method == method or m.method == "UNDEFINED"); }); if (handler != m_dispatch_table.end()) { if (req.get_method() == "OPTIONS") get_options(req, rep); else handler->handler(req, scope, rep); result = true; } } if (not result) rep = reply::stock_reply(status_type::not_found); return result; } } // namespace zeep::http libzeep-7.3.2/src/login-controller.cpp0000664000175000017500000001722215150027072017606 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/login-controller.hpp" #include "zeep/crypto.hpp" #include "zeep/http/controller.hpp" #include "zeep/http/error-handler.hpp" #include "zeep/http/html-controller.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/http/scope.hpp" #include "zeep/http/security.hpp" #include "zeep/http/server.hpp" #include "zeep/http/status.hpp" #include "zeep/http/template-processor.hpp" #include "zeep/uri.hpp" #include #include #include #include #include #include #include #include #include namespace zeep::http { class login_error_handler : public error_handler { public: login_error_handler(login_controller *c) : m_login_controller(c) { } bool create_unauth_reply(const request &req, reply &reply) override { m_login_controller->create_unauth_reply(req, reply); return true; } bool create_error_reply(const request &req, const std::exception_ptr &eptr, reply &reply) override { bool result = false; try { if (eptr) std::rethrow_exception(eptr); } catch (const std::system_error &ex) { if (ex.code() == std::error_code(unauthorized, status_type_category())) result = create_unauth_reply(req, reply); } catch (const unauthorized_exception &) { result = create_unauth_reply(req, reply); } catch (...) { result = false; } return result; } private: login_controller *m_login_controller; }; login_controller::login_controller(const std::string &prefix_path) : html_controller(prefix_path) { map_get("login", &login_controller::handle_get_login); map_post("login", &login_controller::handle_post_login, "username", "password"); map_get("logout", &login_controller::handle_logout); map_post("logout", &login_controller::handle_logout); } void login_controller::set_server(basic_server *server) { controller::set_server(server); assert(server->has_security_context()); if (not server->has_security_context()) throw std::runtime_error("The HTTP server has no security context"); auto &sc = server->get_security_context(); sc.add_rule("/login", {}); server->add_error_handler(new login_error_handler(this)); } zeem::document login_controller::load_login_form(const request &req) const { if (m_server->has_template_processor()) { try { auto &tp = m_server->get_template_processor(); zeem::document doc; doc.set_preserve_cdata(true); tp.load_template("login", doc); scope sc{ get_server(), req }; sc.put("uri", req.get_uri().string()); tp.process_tags(doc.child(), sc); return doc; } catch (const std::exception &ex) { std::clog << ex.what() << '\n'; } } using namespace zeem::literals; auto doc = R"( Please sign in
)"_xml; for (auto form : doc.find("//form")) { uri url = get_context_name(); url /= form->get_attribute("action"); form->set_attribute("action", url.string()); } return doc; } void login_controller::create_unauth_reply(const request &req, reply &reply) { auto doc = load_login_form(req); auto csrf_cookie = req.get_cookie("csrf-token"); if (csrf_cookie.empty()) { csrf_cookie = encode_base64url(random_hash()); reply.set_cookie("csrf-token", csrf_cookie, { { "HttpOnly", "" }, { "SameSite", "Lax" }, { "Path", "/" } }); } for (auto csrf : doc.find("//input[@name='_csrf']")) csrf->set_attribute("value", csrf_cookie); for (auto uri : doc.find("//input[@name='uri']")) { auto req_uri = req.get_uri().string(); if (req_uri == "/login") { auto p = req.get_parameter("uri"); if (p.has_value()) req_uri = *p; } uri->set_attribute("value", req_uri); } reply.set_content(doc); reply.set_status(status_type::unauthorized); } reply login_controller::handle_get_login(const scope &scope) { auto &req = scope.get_request(); auto doc = load_login_form(req); reply rep = reply::stock_reply(status_type::ok); auto csrf_cookie = req.get_cookie("csrf-token"); if (csrf_cookie.empty()) { csrf_cookie = encode_base64url(random_hash()); rep.set_cookie("csrf-token", csrf_cookie, { { "HttpOnly", "" }, { "SameSite", "Lax" }, { "Path", "/" } }); } for (auto csrf : doc.find("//input[@name='_csrf']")) csrf->set_attribute("value", csrf_cookie); rep.set_content(doc); return rep; } reply login_controller::handle_post_login(const scope &scope, const std::string &username, const std::string &password) { auto &req = scope.get_request(); auto csrf = req.get_parameter("_csrf").value_or(""); if (csrf != req.get_cookie("csrf-token")) throw http_status_exception(status_type::forbidden); uri uri(req.get_parameter("uri").value_or("")); auto rep = create_redirect_for_request(req); try { get_server()->get_security_context().verify_username_password(username, password, rep); } catch (const authentication_exception &e) { auto doc = load_login_form(req); for (auto csrf_attr : doc.find("//input[@name='_csrf']")) csrf_attr->set_attribute("value", req.get_cookie("csrf-token")); auto user = doc.find_first("//input[@name='username']"); user->set_attribute("value", username); auto pw = doc.find_first("//input[@name='password']"); pw->set_attribute("class", pw->get_attribute("class") + " is-invalid"); for (auto i_uri : doc.find("//input[@name='uri']")) i_uri->set_attribute("value", uri.string()); rep = reply::stock_reply(unauthorized); rep.set_content(doc); std::clog << e.what() << '\n'; } return rep; } reply login_controller::handle_logout(const scope &scope) { auto &req = scope.get_request(); auto rep = create_redirect_for_request(req); rep.set_delete_cookie("access_token"); return rep; } reply login_controller::create_redirect_for_request(const request &req) const { uri url = get_context_name(); if (auto p = req.get_parameter("uri"); p.has_value()) { uri requested_uri(*p); if (not requested_uri.has_authority()) url /= requested_uri; } if (url.empty()) url = "/"; return reply::redirect(url, status_type::see_other); } } // namespace zeep::http libzeep-7.3.2/src/message-parser.cpp0000664000175000017500000002674515150027072017245 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/message-parser.hpp" #include "zeep/exception.hpp" #include "zeep/http/header.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/uri.hpp" #include "zeep/unicode-support.hpp" #include #include #include #include #include #include #include #include #include #include namespace zeep::http { namespace { bool is_tspecial_or_cntrl(int c) { switch (c) { case '(': case ')': case '<': case '>': case '@': case ',': case ';': case ':': case '\\': case '"': case '/': case '[': case ']': case '?': case '=': case '{': case '}': case ' ': case 0x7f: return true; default: return c <= 0x1f; } } } // namespace void parser::reset() { m_parser = nullptr; m_state = 0; m_chunk_size = 0; m_data.clear(); m_uri.clear(); m_method.clear(); m_parsing_content = false; m_collect_payload = true; m_http_version_major = 1; m_http_version_minor = 0; } parse_result parser::parse_header_lines(char ch) { parse_result result = indeterminate; // parse the header lines, consisting of // NAME: VALUE // optionally followed by more VALUE prefixed by white space on the next lines switch (m_state) { case 0: // If the header starts with \r, it is the start of an empty line // which indicates the end of the header section if (ch == '\r') m_state = 20; else if ((ch == ' ' or ch == '\t') and not m_headers.empty()) m_state = 10; else if (is_tspecial_or_cntrl(ch)) result = false; else { m_headers.emplace_back(); m_headers.back().name += ch; m_state = 1; } break; case 1: if (ch == ':') ++m_state; else if (is_tspecial_or_cntrl(ch)) result = false; else m_headers.back().name += ch; break; case 2: if (ch == ' ') ++m_state; else result = false; break; case 3: if (ch == '\r') m_state += 2; else if (ch != ' ') { m_headers.back().value += ch; ++m_state; } break; case 4: if (ch == '\r') ++m_state; else m_headers.back().value += ch; break; case 5: if (ch == '\n') m_state = 0; else result = false; break; case 10: if (ch == '\r') m_state = 4; else if (iscntrl(ch)) result = false; else if (not(ch == ' ' or ch == '\t')) { m_headers.back().value += ch; m_state = 3; } break; case 20: if (ch == '\n') result = post_process_headers(); else result = false; break; default:; } return result; } bool parser::find_last_token(const header &h, std::string_view t) const { bool result = false; if (h.value.length() >= t.length()) { auto ix = h.value.length() - t.length(); result = iequals(h.value.substr(ix), t); if (result) result = ix == 0 or h.value[ix] == ' ' or h.value[ix] == ','; } return result; } parse_result parser::post_process_headers() { using namespace std::literals; parse_result result = true; auto i = std::ranges::find_if(m_headers, [](const header &h) { return iequals(h.name, "transfer-encoding"sv); }); if (i != m_headers.end()) { if (find_last_token(*i, "chunked")) { m_parser = &parser::parse_chunk; m_state = 0; m_parsing_content = true; } else result = false; } else { i = std::ranges::find_if(m_headers, [](const header &h) { return iequals(h.name, "content-length"sv); }); if (i != m_headers.end()) { auto r = std::from_chars(i->value.data(), i->value.data() + i->value.length(), m_chunk_size); if (r.ec != std::errc()) result = false; else if (m_chunk_size) { m_parser = &parser::parse_content; m_parsing_content = true; m_payload.reserve(m_chunk_size); } else m_parsing_content = false; } } return result; } parse_result parser::parse_chunk(char ch) { parse_result result = indeterminate; switch (m_state) { // Transfer-Encoding: Chunked // lines starting with hex encoded length, optionally followed by text // then a newline (\r\n) and the actual length bytes. // This repeats until length is zero // new chunk, starts with hex encoded length case 0: if (isxdigit(ch)) { m_data = ch; ++m_state; } else result = false; break; case 1: if (isxdigit(ch)) m_data += ch; else if (ch == ';') ++m_state; else if (ch == '\r') m_state = 3; else result = false; break; case 2: if (ch == '\r') ++m_state; else if (is_tspecial_or_cntrl(ch)) result = false; break; case 3: if (ch == '\n') { auto r = std::from_chars(m_data.data(), m_data.data() + m_data.length(), m_chunk_size, 16); if (r.ec == std::errc{}) result = false; else if (m_chunk_size > 0) { m_payload.reserve(m_payload.size() + m_chunk_size); ++m_state; } else m_state = 10; } else result = false; break; case 4: if (m_collect_payload) m_payload += ch; if (--m_chunk_size == 0) m_state = 5; // parse trailing \r\n break; case 5: if (ch == '\r') ++m_state; else result = false; break; case 6: if (ch == '\n') m_state = 0; else result = false; break; // trailing \r\n case 10: if (ch == '\r') ++m_state; else result = false; break; case 11: if (ch == '\n') result = true; else result = false; break; default:; } return result; } parse_result parser::parse_content(char ch) { parse_result result = indeterminate; // here we simply read m_chunk_size of bytes and finish if (m_collect_payload) m_payload += ch; if (m_payload.length() == m_chunk_size) { result = true; m_parsing_content = false; } return result; } // -------------------------------------------------------------------- // parse_result request_parser::parse(std::streambuf &text) { if (m_parser == nullptr) { m_parser = static_cast(&request_parser::parse_initial_line); m_parsing_content = false; } parse_result result = indeterminate; if (m_http_version_major == 0 and m_http_version_minor == 9) result = true; else { bool is_parsing_content = m_parsing_content; while (text.in_avail() > 0 and result == indeterminate) { result = (this->*m_parser)(static_cast(text.sbumpc())); if (result and is_parsing_content == false and m_parsing_content == true) { is_parsing_content = true; result = indeterminate; } } } return result; } request request_parser::get_request() { if (iequals(m_method, "CONNECT")) { // Special case, uri should be of the form HOST:PORT if (not is_valid_connect_host(m_uri)) throw zeep::exception("Invalid host for CONNECT"); m_uri = "http://" + m_uri; } return { m_method, m_uri, { m_http_version_major, m_http_version_minor }, std::move(m_headers), std::move(m_payload) }; } parse_result request_parser::parse_initial_line(char ch) { parse_result result = indeterminate; // a state machine to parse the initial request line // which consists of: // METHOD URI HTTP/1.0 (or 1.1) switch (m_state) { // we're parsing the method here case 0: if (isalpha(ch)) m_method += ch; else if (ch == ' ') ++m_state; else result = false; break; // we're parsing the URI here case 1: if (ch == ' ') ++m_state; else if (ch == '\r' or ch == '\n') { m_http_version_major = 0; m_http_version_minor = 9; result = true; } else if (iscntrl(ch)) result = false; else m_uri += ch; break; // we're parsing the trailing HTTP/1.x here case 2: if (ch == 'H') ++m_state; else result = false; break; case 3: case 4: if (ch == 'T') ++m_state; else result = false; break; case 5: if (ch == 'P') ++m_state; else result = false; break; case 6: if (ch == '/') ++m_state; else result = false; break; case 7: if (ch == '1') ++m_state; else result = false; break; case 8: if (ch == '.') ++m_state; else if (ch == '\r') m_state = 11; else result = false; break; case 9: if (ch == '1' or ch == '0') { if (ch == '1') m_http_version_minor = 1; ++m_state; } else result = false; break; case 10: if (ch == '\r') ++m_state; else result = false; break; case 11: if (ch == '\n') { m_state = 0; m_parser = &parser::parse_header_lines; } else result = false; break; default:; } return result; } // -------------------------------------------------------------------- // void reply_parser::reset() { parser::reset(); m_status = 0; m_status_line.clear(); } parse_result reply_parser::parse(std::streambuf &text) { if (m_parser == nullptr) { m_parser = static_cast(&reply_parser::parse_initial_line); m_parsing_content = false; } parse_result result = indeterminate; bool is_parsing_content = m_parsing_content; while (text.in_avail() and result == indeterminate) { result = (this->*m_parser)(static_cast(text.sbumpc())); if (result and is_parsing_content == false and m_parsing_content == true) { is_parsing_content = true; result = indeterminate; } } return result; } reply reply_parser::get_reply() { return { static_cast(m_status), { m_http_version_major, m_http_version_minor }, std::move(m_headers), std::move(m_payload) }; } parse_result reply_parser::parse_initial_line(char ch) { parse_result result = indeterminate; // a state machine to parse the initial reply line // which consists of: // HTTP/1.{0,1} XXX status-message switch (m_state) { // we're parsing the initial HTTP/1.x here case 0: if (ch == 'H') ++m_state; else result = false; break; case 1: case 2: if (ch == 'T') ++m_state; else result = false; break; case 3: if (ch == 'P') ++m_state; else result = false; break; case 4: if (ch == '/') ++m_state; else result = false; break; case 5: if (ch == '1') ++m_state; else result = false; break; case 6: if (ch == '.') ++m_state; else if (ch == '\r') m_state = 11; else result = false; break; case 7: if (ch == '1' or ch == '0') { if (ch == '1') m_http_version_minor = 1; ++m_state; } else result = false; break; case 8: if (isspace(ch)) ++m_state; else result = false; break; // we're parsing the result code here (three digits) case 9: if (isdigit(ch)) { m_status = 100 * (ch - '0'); ++m_state; } else result = false; break; case 10: if (isdigit(ch)) { m_status += 10 * (ch - '0'); ++m_state; } else result = false; break; case 11: if (isdigit(ch)) { m_status += 1 * (ch - '0'); ++m_state; } else result = false; break; case 12: if (isspace(ch)) ++m_state; else result = false; break; // we're parsing the status message here case 13: if (ch == '\r') ++m_state; else m_status_line += ch; break; case 14: if (ch == '\n') { m_state = 0; m_parser = &parser::parse_header_lines; } else result = false; break; default:; } return result; } } // namespace zeep::http libzeep-7.3.2/src/preforked-server.cpp0000664000175000017500000002270315150027072017602 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/preforked-server.hpp" #include "zeep/config.hpp" #include "zeep/exception.hpp" #include "zeep/http/asio.hpp" #include "zeep/http/connection.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/server.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HTTP_SERVER_HAS_PREFORK # include # include # include // for Hurd # if not defined(WCONTINUED) # define WCONTINUED 0 # endif namespace zeep::http { bool read_socket_from_parent(int fd_socket, asio_ns::ip::tcp::socket &socket) { using native_handle_type = asio_ns::ip::tcp::socket::native_handle_type; # if __APPLE__ // macos is special... assert(CMSG_SPACE(sizeof(int)) == 16); # endif union { struct cmsghdr cm; # if __APPLE__ char control[16]; # else char control[CMSG_SPACE(sizeof(int))]; # endif } control_un{}; struct msghdr msg{}; msg.msg_control = control_un.control; msg.msg_controllen = sizeof(control_un.control); asio_ns::ip::tcp::socket::endpoint_type peer_endpoint; struct iovec iov[1]; iov[0].iov_base = peer_endpoint.data(); iov[0].iov_len = peer_endpoint.capacity(); msg.msg_iov = iov; msg.msg_iovlen = 1; bool result = false; auto n = recvmsg(fd_socket, &msg, 0); if (n >= 0) { peer_endpoint.resize(n); struct cmsghdr *cmptr CMSG_FIRSTHDR(&msg); if (cmptr != nullptr and cmptr->cmsg_len == CMSG_LEN(sizeof(int))) { if (cmptr->cmsg_level != SOL_SOCKET) std::clog << "control level != SOL_SOCKET\n"; else if (cmptr->cmsg_type != SCM_RIGHTS) std::clog << "control type != SCM_RIGHTS"; else { /* Produces warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing] int fd = *(reinterpret_cast(CMSG_DATA(cmptr))); */ auto *fdptr = reinterpret_cast(CMSG_DATA(cmptr)); int fd = *fdptr; if (fd >= 0) { socket.assign(peer_endpoint.protocol(), fd); result = true; } } } } return result; } class child_process { public: child_process(std::function constructor, asio_ns::io_context &io_context, asio_ns::ip::tcp::acceptor &acceptor, int nr_of_threads) : m_constructor(std::move(constructor)) , m_acceptor(acceptor) , m_socket(io_context) , m_nr_of_threads(nr_of_threads) { m_acceptor.async_accept(m_socket, [this](auto ec) { handle_accept(ec); }); } ~child_process() { if (m_pid > 0) // should never happen { kill(m_pid, SIGKILL); const std::time_t now_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); int status; if (waitpid(m_pid, &status, WUNTRACED | WCONTINUED) != -1) { if (WIFSIGNALED(status) and WTERMSIG(status) != SIGKILL) std::clog << std::put_time(std::localtime(&now_t), "%F %T") << " child " << m_pid << " terminated by signal " << WTERMSIG(status) << '\n'; // else // std::clog << std::put_time(std::localtime(&now_t), "%F %T") << " child terminated normally\n"; } else std::clog << std::put_time(std::localtime(&now_t), "%F %T") << "error in waitpid: " << strerror(errno) << '\n'; } } void stop(); private: void handle_accept(const asio_system_ns::error_code &ec); void start(); void run(); std::function m_constructor; asio_ns::ip::tcp::acceptor &m_acceptor; asio_ns::ip::tcp::socket m_socket; int m_nr_of_threads; int m_pid = -1; int m_fd = -1; }; void child_process::start() { using namespace std::literals; // create a socket pair to pass the file descriptors through int sockfd[2]; int err = socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd); if (err < 0) throw exception("Error creating socket pair: "s + strerror(errno)); // fork m_pid = fork(); if (m_pid < 0) throw exception("Error forking worker application: "s + strerror(errno)); if (m_pid == 0) // child process { close(sockfd[0]); // remove the blocks on the signal handlers sigset_t wait_mask; sigemptyset(&wait_mask); pthread_sigmask(SIG_SETMASK, &wait_mask, nullptr); // Time to construct the Server object std::unique_ptr srvr(m_constructor()); // run the server as a worker std::thread t([server = srvr.get(), this] { server->run(m_nr_of_threads); }); // now start the processing loop passing on file descriptors read // from the parent process try { for (;;) { auto conn = std::make_shared(srvr->get_io_context(), *srvr); if (not read_socket_from_parent(sockfd[1], conn->get_socket())) break; conn->start(); } } catch (std::exception &e) { std::clog << "Exception caught: " << e.what() << '\n'; exit(1); } srvr->stop(); t.join(); exit(0); } // close one end of the pipe, save the other m_fd = sockfd[0]; close(sockfd[1]); } void child_process::stop() { using namespace std::chrono_literals; if (m_fd > 0) { // close the socket to the worker, this should terminate the child if it is still alive close(m_fd); m_fd = -1; } if (m_pid != -1) { // however, sometimes it doesn't, so we have to take some serious action // Anyway, we'll wait until child dies to avoid zombies // and to make sure the client really stops... int count = 5; // wait five seconds before killing client int status; while (count-- > 0) { if (waitpid(m_pid, &status, WUNTRACED | WCONTINUED | WNOHANG) == -1) break; if (WIFEXITED(status)) break; std::this_thread::sleep_for(100ms); } if (not WIFEXITED(status)) kill(m_pid, SIGKILL); m_pid = -1; } } void child_process::handle_accept(const asio_system_ns::error_code &ec) { if (ec) { std::clog << "Accept failed: " << ec << '\n'; return; } try { if (m_pid == -1 or m_fd == -1) start(); using native_handle_type = asio_ns::ip::tcp::socket::native_handle_type; union { struct cmsghdr cm; # if __APPLE__ char control[16]; # else char control[CMSG_SPACE(sizeof(native_handle_type))]; # endif } control_un{}; struct msghdr msg{}; msg.msg_control = control_un.control; msg.msg_controllen = sizeof(control_un.control); struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg); cmptr->cmsg_len = CMSG_LEN(sizeof(int)); cmptr->cmsg_level = SOL_SOCKET; cmptr->cmsg_type = SCM_RIGHTS; /* Procudes warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing] *(reinterpret_cast(CMSG_DATA(cmptr))) = socket.native(); */ auto *fdptr = reinterpret_cast(CMSG_DATA(cmptr)); *fdptr = m_socket.native_handle(); msg.msg_name = nullptr; msg.msg_namelen = 0; struct iovec iov[1]; iov[0].iov_base = m_socket.remote_endpoint().data(); iov[0].iov_len = m_socket.remote_endpoint().size(); msg.msg_iov = iov; msg.msg_iovlen = 1; auto err = sendmsg(m_fd, &msg, 0); if (err < 0) { std::clog << "Error passing file descriptor: " << strerror(errno) << '\n'; // ec = std::make_error_code(errno); stop(); } } catch (const std::exception &e) { std::clog << "error writing socket to client: " << e.what() << '\n'; reply r = reply::stock_reply(status_type::service_unavailable); std::vector buffers; for (auto &buffer : r.to_buffers()) buffers.emplace_back(buffer.data(), buffer.size()); try { asio_ns::write(m_socket, buffers); } catch (const std::exception &e) { std::clog << e.what() << '\n'; } } m_socket.close(); m_acceptor.async_accept(m_socket, [this](auto ec) { handle_accept(ec); }); } // -------------------------------------------------------------------- preforked_server::preforked_server(std::function constructor) : m_constructor(std::move(constructor)) { m_lock.lock(); } void preforked_server::run(std::string_view address, uint16_t port, int nr_of_processes, int nr_of_threads) { // first wait until we are allowed to start listening std::unique_lock lock(m_lock); asio_ns::ip::tcp::acceptor acceptor(m_io_context); // then bind the address here asio_ns::ip::tcp::endpoint endpoint; try { endpoint = asio_ns::ip::tcp::endpoint(asio_ns::ip::make_address(address), port); } catch (const std::exception &e) { asio_ns::ip::tcp::resolver resolver(m_io_context); for (auto &ep : resolver.resolve(address, std::to_string(port))) { endpoint = ep; break; } } acceptor.open(endpoint.protocol()); acceptor.set_option(asio_ns::ip::tcp::acceptor::reuse_address(true)); acceptor.bind(endpoint); acceptor.listen(); std::vector threads; threads.reserve(nr_of_processes); for (int i = 0; i < nr_of_processes; ++i) { threads.emplace_back([this, nr_of_threads, &acceptor]() { auto work = asio_ns::make_work_guard(m_io_context); child_process p(m_constructor, m_io_context, acceptor, nr_of_threads); m_io_context.run(); }); } for (auto &t : threads) t.join(); } void preforked_server::start() { m_lock.unlock(); } void preforked_server::stop() { m_io_context.stop(); } } // namespace zeep::http #endif libzeep-7.3.2/src/reply.cpp0000664000175000017500000002710515150027072015451 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "revision.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/status.hpp" #include "zeep/uri.hpp" #include #include #include #include namespace zeep::http { std::string get_status_description(status_type status) { switch (status) { case status_type::moved_permanently: return "The document requested was moved permanently to a new location"; case status_type::moved_temporarily: return "The document requested was moved temporarily to a new location"; case status_type::see_other: return "The document can be found at another location"; case status_type::not_modified: return "The requested document was not modified"; case status_type::bad_request: return "There was an error in the request, e.g. an incorrect method or a malformed URI"; case status_type::unauthorized: return "You are not authorized to access this location"; case status_type::proxy_authentication_required: return "You are not authorized to use this proxy"; case status_type::forbidden: return "Access to this location is forbidden"; case status_type::not_found: return "The requested web page was not found on this server."; case status_type::unprocessable_entity: return "Your request could not be handled since a parameter contained an invalid value"; case status_type::internal_server_error: return "An internal error prevented the server from processing your request"; case status_type::not_implemented: return "Your request could not be handled since the required code is not implemented"; case status_type::bad_gateway: return "The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request. "; case status_type::service_unavailable: return "The service is unavailable at this moment, try again later"; default: return "An internal error prevented the server from processing your request"; } } // ---------------------------------------------------------------------------- namespace { const std::string kNameValueSeparator{ ':', ' ' }, kCRLF{ '\r', '\n' }, kZERO{ '0' }; } reply::reply(status_type status, std::tuple version) : m_status(status) , m_version_major(std::get<0>(version)) , m_version_minor(std::get<1>(version)) { using namespace std::literals; auto now = std::chrono::ceil(std::chrono::system_clock::now()); set_header("Date", std::format("{0:%a}, {0:%d} {0:%b} {0:%Y} {0:%H}:{0:%M}:{0:%S} GMT", now)); set_header("Server", "libzeep/"s + klibzeepVersionNumber); set_header("Content-Length", "0"); } reply::reply(status_type status, std::tuple version, std::vector
&&headers, std::string &&payload) : reply(status, version) { m_headers = std::move(headers); m_content = std::move(payload); } reply::reply(const reply &rhs) : m_status(rhs.m_status) , m_version_major(rhs.m_version_major) , m_version_minor(rhs.m_version_minor) , m_headers(rhs.m_headers) , m_data(rhs.m_data) , m_content(rhs.m_content) { } void reply::set_version(int version_major, int version_minor) { const std::streambuf::pos_type kNoPos = -1; m_version_major = version_major; m_version_minor = version_minor; // for HTTP/1.0 replies we need to calculate the data length if (m_version_major == 1 and m_version_minor == 0 and m_data) { m_chunked = false; std::streamsize length = 0; std::streamsize pos = m_data->rdbuf()->pubseekoff(0, std::ios_base::cur); if (pos == kNoPos) { // no other option than copying over the data to our buffer char buffer[10240]; for (;;) { std::streamsize n = m_data->rdbuf()->sgetn(buffer, sizeof(buffer)); if (n == 0) break; length += n; m_content.insert(m_content.end(), buffer, buffer + n); } m_data.reset(); } else { length = m_data->rdbuf()->pubseekoff(0, std::ios_base::end); length -= pos; m_data->rdbuf()->pubseekoff(pos, std::ios_base::beg); } set_header("Content-Length", std::to_string(length)); remove_header("Transfer-Encoding"); } } void reply::set_header(std::string name, std::string value) { bool updated = false; for (header &h : m_headers) { if (iequals(h.name, name)) { h.value = value; updated = true; break; } } if (not updated) { header nh = { std::move(name), std::move(value) }; m_headers.push_back(nh); } } std::string reply::get_header(std::string_view name) const { std::string result; for (const header &h : m_headers) { if (iequals(h.name, name)) { result = h.value; break; } } return result; } void reply::remove_header(std::string_view name) { std::erase_if(m_headers, [name](header &h) { return iequals(h.name, name); }); } void reply::set_cookie(std::string_view name, const std::string &value, std::initializer_list directives) { std::ostringstream vs; vs << name << '=' << value; for (auto &directive : directives) vs << "; " << directive.name << (directive.value.empty() ? "" : "=" + directive.value); m_headers.emplace_back("Set-Cookie", vs.str()); } void reply::set_delete_cookie(std::string_view name) { using namespace std::literals; std::stringstream s; const std::time_t now_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now() - 24h); s << std::put_time(std::localtime(&now_t), "%a, %d %b %Y %H:%M:%S GMT"); set_cookie(name, "", { { "Expires", '"' + s.str() + '"' } }); } std::string reply::get_cookie(std::string_view name) const { std::string result; for (const header &h : m_headers) { if (iequals(h.name, "Set-Cookie")) { result = h.value; auto ns = result.find('='); if (ns == std::string::npos) continue; if (result.compare(0, ns, name) != 0) continue; auto ds = result.find(';', ns + 1); result = result.substr(ns + 1, ds - ns - 1); break; } } return result; } void reply::set_content(const el::object &json) { std::ostringstream s; s << json; set_content(s.str(), "application/json"); } void reply::set_content(const zeem::element &data) { std::stringstream s; s << data; set_content(s.str(), "text/xml; charset=utf-8"); } void reply::set_content(zeem::document &doc) { std::stringstream s; if (doc.front().name() == "html") doc.set_write_html(true); else doc.set_write_doctype(false); if (doc.is_html5()) { doc.set_write_doctype(true); doc.set_escape_double_quote(false); } else if (doc.child()->get_ns() == "http://www.w3.org/1999/xhtml") { doc.set_escape_double_quote(false); doc.set_collapse_empty_tags(true); } s << doc; std::string contentType; if (doc.is_html5()) contentType = "text/html; charset=utf-8"; else if (doc.child()->get_ns() == "http://www.w3.org/1999/xhtml") contentType = "application/xhtml+xml; charset=utf-8"; else contentType = "text/xml; charset=utf-8"; set_content(s.str(), contentType); } void reply::set_content(std::string data, std::string contentType) { m_data.reset(); m_content = std::move(data); m_chunked = false; set_header("Content-Length", std::to_string(m_content.length())); remove_header("Transfer-Encoding"); set_header("Content-Type", std::move(contentType)); } void reply::set_content(const char *data, size_t size, std::string contentType) { m_data.reset(); m_content = std::string(data, size); m_chunked = false; set_header("Content-Length", std::to_string(m_content.length())); remove_header("Transfer-Encoding"); set_header("Content-Type", std::move(contentType)); } void reply::set_content(std::istream *idata, std::string contentType) { m_data.reset(idata); m_content.clear(); m_chunked = true; set_header("Content-Type", std::move(contentType)); set_header("Transfer-Encoding", "chunked"); remove_header("Content-Length"); } std::vector reply::to_buffers() const { // A global, thread local storage for the status line text thread_local static std::string s_status_line; std::vector result; s_status_line = std::format("HTTP/{}.{} {} {}\r\n", m_version_major, m_version_minor, static_cast(m_status), make_error_code(m_status).message()); result.emplace_back(s_status_line); for (const header &h : m_headers) { result.push_back(h.name); result.push_back(kNameValueSeparator); result.push_back(h.value); result.push_back(kCRLF); } result.push_back(kCRLF); result.push_back(m_content); return result; } std::vector reply::data_to_buffers() { std::vector result; if (m_data) { const unsigned int kMaxChunkSize = 10240; m_buffer.resize(kMaxChunkSize); std::streamsize n = 0; try { n = m_data->rdbuf()->sgetn(m_buffer.data(), static_cast(m_buffer.size())); } catch (...) { std::clog << "Exception in reading from file\n"; } // chunked encoding? if (m_chunked) { if (n == 0) { result.push_back(kZERO); result.push_back(kCRLF); result.push_back(kCRLF); m_data.reset(); } else { thread_local static std::array s_size_buffer; ///< to store the string with the size for chunked encoding const char kHex[] = "0123456789abcdef"; char *e = s_size_buffer.data() + s_size_buffer.size(); char *p = e; auto l = n; while (n != 0) { *--p = kHex[n & 0x0f]; n >>= 4; } result.emplace_back(p, e - p); result.push_back(kCRLF); result.emplace_back(&m_buffer[0], l); result.push_back(kCRLF); } } else { if (n > 0) result.emplace_back(&m_buffer[0], n); else m_data.reset(); } } return result; } reply reply::stock_reply(status_type status, const std::string &info) { reply result; if (status != status_type::not_modified) { std::stringstream text; text << "\n" << " \n" << "

" << make_error_code(status).message() << "

\n"; if (not info.empty()) { text << "

"; for (char c : info) { switch (c) { case '&': text << "&"; break; case '<': text << "<"; break; case '>': text << ">"; break; case 0: break; // silently ignore default: if ((c >= 1 and c <= 8) or (c >= 0x0b and c <= 0x0c) or (c >= 0x0e and c <= 0x1f) or c == 0x7f) text << "&#" << std::hex << c << ';'; else text << c; break; } } text << "

\n"; } text << " \n" << ""; result.set_content(text.str(), "text/html; charset=utf-8"); } result.m_status = status; return result; } reply reply::stock_reply(status_type status) { return stock_reply(status, ""); } reply reply::redirect(const uri &location, status_type status) { reply result; result.m_status = status; std::string text = make_error_code(status).message(); result.m_content = "" + text + "

" + std::to_string(static_cast(status)) + ' ' + text + "

"; result.set_header("Location", location.string()); result.set_header("Content-Length", std::to_string(result.m_content.length())); result.set_header("Content-Type", "text/html; charset=utf-8"); return result; } reply reply::redirect(const uri &location) { return redirect(location, status_type::moved_temporarily); } size_t reply::size() const { size_t result = 0; for (auto &b : to_buffers()) result += b.size(); return result; } std::ostream &operator<<(std::ostream &lhs, const reply &rhs) { for (auto &b : rhs.to_buffers()) lhs << b; return lhs; } } // namespace zeep::http libzeep-7.3.2/src/request.cpp0000664000175000017500000004361715150027072016014 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/request.hpp" #include "zeep/el/object.hpp" #include "zeep/streambuf.hpp" #include "zeep/unicode-support.hpp" #include #include #include #include #include #include #include #include namespace zeep::http { request::request(std::string method, uri uri, std::tuple version, std::vector
&&headers, std::string &&payload) : m_method(std::move(method)) , m_uri(std::move(uri)) , m_version({ static_cast('0' + std::get<0>(version)), '.', static_cast('0' + std::get<1>(version)) }) , m_headers(std::move(headers)) , m_payload(std::move(payload)) { } void swap(request &lhs, request &rhs) noexcept { if (&lhs != &rhs) { std::swap(lhs.m_local_address, rhs.m_local_address); std::swap(lhs.m_local_port, rhs.m_local_port); std::swap(lhs.m_method, rhs.m_method); std::swap(lhs.m_uri, rhs.m_uri); std::swap(lhs.m_version, rhs.m_version); std::swap(lhs.m_headers, rhs.m_headers); std::swap(lhs.m_payload, rhs.m_payload); std::swap(lhs.m_close, rhs.m_close); std::swap(lhs.m_timestamp, rhs.m_timestamp); std::swap(lhs.m_credentials, rhs.m_credentials); std::swap(lhs.m_remote_address, rhs.m_remote_address); } } float request::get_accept(std::string_view type) const { float result = 1.0f; #define IDENT "[-+.a-z0-9]+" #define TYPE "\\*|" IDENT #define MEDIARANGE "\\s*(" TYPE ")/(" TYPE ").*?(?:;\\s*q=(\\d(?:\\.\\d?)?))?" static std::regex rx(MEDIARANGE); if (type.empty()) return 1.0; std::string t1(type), t2; std::string::size_type s = t1.find('/'); if (s != std::string::npos) { t2 = t1.substr(s + 1); t1.erase(s, t1.length() - s); } for (const header &h : m_headers) { if (not iequals(h.name, "Accept")) continue; result = 0; std::string::size_type b = 0, e = h.value.find(','); for (;;) { if (e == std::string::npos) e = h.value.length(); std::string mediarange = h.value.substr(b, e - b); std::smatch m; if (std::regex_search(mediarange, m, rx)) { std::string type1 = m[1].str(); std::string type2 = m[2].str(); float value = 1.0f; if (m[3].matched) value = std::stof(m[3].str()); if (type1 == t1 and type2 == t2) { result = value; break; } if ((type1 == t1 and type2 == "*") or (type1 == "*" and type2 == "*")) { if (result < value) result = value; } } if (e == h.value.length()) break; b = e + 1; while (b < h.value.length() and isspace(h.value[b])) ++b; e = h.value.find(',', b); } break; } return result; } // m_request.http_version_minor >= 1 and not m_request.close bool request::keep_alive() const { return get_version() >= std::make_tuple(1, 1) and iequals(get_header("Connection"), "keep-alive"); } void request::set_header(std::string name, std::string value) { bool replaced = false; for (header &h : m_headers) { if (not iequals(h.name, name)) continue; h.value = value; replaced = true; break; } if (not replaced) m_headers.emplace_back(std::move(name), std::move(value)); } std::string request::get_header(std::string_view name) const { std::string result; for (const header &h : m_headers) { if (not iequals(h.name, name)) continue; result = h.value; break; } return result; } void request::remove_header(std::string_view name) { std::erase_if(m_headers, [name](const header &h) -> bool { return h.name == name; }); } std::pair get_urldecoded_parameter(std::string_view s, std::string_view name) { std::string::size_type b = 0; std::string result; bool found = false; size_t nlen = name.length(); while (b != std::string::npos) { std::string::size_type e = s.find_first_of("&;", b); std::string::size_type n = (e == std::string::npos) ? s.length() - b : e - b; if ((n == nlen or (n > nlen + 1 and s[b + nlen] == '=')) and name == s.substr(b, nlen)) { found = true; if (n == nlen) result = name; // what else? else { b += nlen + 1; result = s.substr(b, e - b); result = decode_url(result); } break; } b = e == std::string::npos ? e : e + 1; } return std::make_pair(result, found); } std::optional request::get_parameter(std::string_view name) const { std::string result, contentType = get_header("Content-Type"); bool found = false; if (starts_with(contentType, "application/x-www-form-urlencoded")) { tie(result, found) = get_urldecoded_parameter(m_payload, name); if (found) return result; } auto query = m_uri.get_query(false); if (not query.empty()) { tie(result, found) = get_urldecoded_parameter(query, name); if (found) return result; } if (starts_with(contentType, "application/json")) { try { char_streambuf buf(m_payload.data(), m_payload.length()); std::istream is(&buf); el::object e; deserialize(is, e); if (e.is_object() and e.contains(name)) { result = e.at(std::string{ name }).get(); found = true; } } catch (const std::exception &) { found = false; } } else if (starts_with(contentType, "multipart/form-data")) { std::string::size_type b = contentType.find("boundary="); if (b != std::string::npos) { std::string boundary = contentType.substr(b + strlen("boundary=")); enum { START, HEADER, CONTENT, SKIP } state = SKIP; std::string contentName; std::regex rx("content-disposition:\\s*form-data;.*?\\bname=\"([^\"]+)\".*", std::regex::icase); std::smatch m; std::string::difference_type i = 0, r = 0, l = 0; for (i = 0; i <= static_cast(m_payload.length()); ++i) { if (m_payload[i] != '\r' and m_payload[i] != '\n') continue; // we have found a 'line' at [l, i) if (m_payload.compare(l, 2, "--") == 0 and m_payload.compare(l + 2, boundary.length(), boundary) == 0) { // if we're in the content state or if this is the last line if (state == CONTENT or m_payload.compare(l + 2 + boundary.length(), 2, "--") == 0) { if (r > 0) { auto n = l - r; if (n >= 1 and m_payload[r + n - 1] == '\n') --n; if (n >= 1 and m_payload[r + n - 1] == '\r') --n; result.assign(m_payload, r, n); } break; } // Not the last, so it must be a separator and we're now in the Header part state = HEADER; } else if (state == HEADER) { if (l == i) // empty line { if (contentName == name) { found = true; state = CONTENT; r = i + 1; if (m_payload[i] == '\r' and m_payload[i + 1] == '\n') r = i + 2; } else state = SKIP; } else if (std::regex_match(m_payload.begin() + l, m_payload.begin() + i, m, rx)) contentName = m[1].str(); } if (m_payload[i] == '\r' and m_payload[i + 1] == '\n') ++i; l = i + 1; } } } if (found) return result; return {}; } std::multimap request::get_parameters() const { std::string ps; if (m_method == "POST") { std::string contentType = get_header("Content-Type"); if (starts_with(contentType, "application/x-www-form-urlencoded")) ps = m_payload; } else if (m_method == "GET" or m_method == "PUT") ps = m_uri.get_query(false); std::multimap parameters; while (not ps.empty()) { std::string::size_type e = ps.find_first_of("&;"); std::string param; if (e != std::string::npos) { param = ps.substr(0, e); ps.erase(0, e + 1); } else std::swap(param, ps); if (not param.empty()) { std::string name, value; std::string::size_type d = param.find('='); if (d != std::string::npos) { name = param.substr(0, d); value = param.substr(d + 1); } parameters.emplace(decode_url(name), decode_url(value)); } } return parameters; } struct file_param_parser { file_param_parser(const request &req, const std::string &payload, std::string name); file_param next(); const request &m_req; const std::string m_name; const std::string &m_payload; std::string m_boundary; static const std::regex k_rx_disp, k_rx_cont; enum { START, HEADER, CONTENT, SKIP } m_state = SKIP; std::string::difference_type m_i = 0; }; const std::regex file_param_parser::k_rx_disp(R"x(content-disposition:\s*form-data(;.+))x", std::regex::icase); const std::regex file_param_parser::k_rx_cont(R"x(content-type:\s*(\S+/[^;]+)(;.*)?)x", std::regex::icase); file_param_parser::file_param_parser(const request &req, const std::string &payload, std::string name) : m_req(req) , m_name(std::move(name)) , m_payload(payload) { std::string contentType = m_req.get_header("Content-Type"); if (starts_with(contentType, "multipart/form-data")) { std::string::size_type b = contentType.find("boundary="); if (b != std::string::npos) m_boundary = contentType.substr(b + strlen("boundary=")); } } file_param file_param_parser::next() { if (m_boundary.empty()) return {}; std::string contentName; std::smatch m; std::string::difference_type r = 0, l = 0; file_param result = {}; bool found = false; for (; m_i <= static_cast(m_payload.length()); ++m_i) { if (m_payload[m_i] != '\r' and m_payload[m_i] != '\n') continue; // we have found a 'line' at [l, i) if (m_payload.compare(l, 2, "--") == 0 and m_payload.compare(l + 2, m_boundary.length(), m_boundary) == 0) { // if we're in the content state or if this is the last line if (m_state == CONTENT or m_payload.compare(l + 2 + m_boundary.length(), 2, "--") == 0) { if (r > 0) { auto n = l - r; if (n >= 1 and m_payload[r + n - 1] == '\n') --n; if (n >= 1 and m_payload[r + n - 1] == '\r') --n; result.data = m_payload.data() + r; result.length = n; } m_state = HEADER; break; } // Not the last, so it must be a separator and we're now in the Header part m_state = HEADER; } else if (m_state == HEADER) { if (l == m_i) // empty line { if (contentName == m_name) { m_state = CONTENT; found = true; r = m_i + 1; if (m_payload[m_i] == '\r' and m_payload[m_i + 1] == '\n') r = m_i + 2; } else { result = {}; m_state = SKIP; } } else if (std::regex_match(m_payload.begin() + l, m_payload.begin() + m_i, m, k_rx_disp)) { auto p = m[1].str(); std::regex re(R"rx(;\s*(\w+)=("[^"]*"|'[^']*'|\w+))rx"); auto b = p.begin(); auto e = p.end(); std::match_results m2; while (b < e and std::regex_search(b, e, m2, re)) { auto key = m2[1].str(); auto value = m2[2].str(); if (value.length() > 1 and ((value.front() == '"' and value.back() == '"') or (value.front() == '\'' and value.back() == '\''))) value = value.substr(1, value.length() - 2); if (key == "name") contentName = value; else if (key == "filename") result.filename = value; b = m2[0].second; } } else if (std::regex_match(m_payload.begin() + l, m_payload.begin() + m_i, m, k_rx_cont)) { result.mimetype = m[1].str(); if (starts_with(result.mimetype, "multipart/")) throw std::runtime_error("multipart file uploads are not supported"); } } if (m_payload[m_i] == '\r' and m_payload[m_i + 1] == '\n') ++m_i; l = m_i + 1; } if (not found) result = {}; return result; } file_param request::get_file_parameter(std::string name) const { file_param_parser fpp(*this, m_payload, std::move(name)); return fpp.next(); } std::vector request::get_file_parameters(std::string name) const { file_param_parser fpp(*this, m_payload, std::move(name)); std::vector result; for (;;) { auto fp = fpp.next(); if (not fp) break; result.push_back(fp); } return result; } std::string request::get_cookie(std::string_view name) const { for (const header &h : m_headers) { if (not iequals(h.name, "Cookie")) continue; std::vector rawCookies; split(rawCookies, h.value, ";"); for (std::string &cookie : rawCookies) { trim(cookie); auto d = cookie.find('='); if (d == std::string::npos) continue; if (cookie.compare(0, d, name) == 0) return cookie.substr(d + 1); } } return ""; } void request::set_cookie(const std::string &name, std::string value) { std::map cookies; for (auto &h : m_headers) { if (not iequals(h.name, "Cookie")) continue; std::vector rawCookies; split(rawCookies, h.value, ";"); for (std::string &cookie : rawCookies) { trim(cookie); auto d = cookie.find('='); if (d == std::string::npos) continue; cookies[cookie.substr(0, d)] = cookie.substr(d + 1); } } std::erase_if(m_headers, [](header &h) { return iequals(h.name, "Cookie"); }); cookies[name] = std::move(value); std::ostringstream cs; bool first = true; for (auto &cookie : cookies) { if (first) first = false; else cs << "; "; cs << cookie.first << '=' << cookie.second; } set_header("Cookie", cs.str()); } // -------------------------------------------------------------------- // Locale support class locale_table { public: static locale_table &instance() { static locale_table s_instance; return s_instance; } std::locale get(const std::string &accept_language); private: locale_table() = default; static const std::map> kLocalesPerLang; static const std::regex kAcceptsRX; }; const std::map> locale_table::kLocalesPerLang{ { "ar", { "AE", "BH", "DZ", "EG", "IQ", "JO", "KW", "LB", "LY", "MA", "OM", "QA", "SA", "SD", "SY", "TN", "YE" } }, { "be", { "BY" } }, { "bg", { "BG" } }, { "ca", { "ES" } }, { "cs", { "CZ" } }, { "da", { "DK" } }, { "de", { "AT", "CH", "DE", "LU" } }, { "el", { "GR" } }, { "en", { "US", "AU", "CA", "GB", "IE", "IN", "NZ", "ZA" } }, { "es", { "AR", "BO", "CL", "CO", "CR", "DO", "EC", "ES", "GT", "HN", "MX", "NI", "PA", "PE", "PR", "PY", "SV", "UY", "VE" } }, { "et", { "EE" } }, { "fi", { "FI" } }, { "fr", { "BE", "CA", "CH", "FR", "LU" } }, { "hi", { "IN" } }, { "hr", { "HR" } }, { "hu", { "HU" } }, { "is", { "IS" } }, { "it", { "CH", "IT" } }, { "iw", { "IL" } }, { "ja", { "JP" } }, { "ko", { "KR" } }, { "lt", { "LT" } }, { "lv", { "LV" } }, { "mk", { "MK" } }, { "nl", { "NL", "BE" } }, { "no", { "NO", "NO_NY" } }, { "pl", { "PL" } }, { "pt", { "BR", "PT" } }, { "ro", { "RO" } }, { "ru", { "RU" } }, { "sk", { "SK" } }, { "sl", { "SI" } }, { "sq", { "AL" } }, { "sr", { "BA", "CS" } }, { "sv", { "SE" } }, { "th", { "TH", "TH_TH" } }, { "tr", { "TR" } }, { "uk", { "UA" } }, { "vi", { "VN" } }, { "zh", { "CN", "HK", "TW" } } }; const std::regex locale_table::kAcceptsRX(R"(([[:alpha:]]{1,8})(?:-([[:alnum:]]{1,8}))?(?:;q=([01](?:\.\d{1,3})))?)"); std::locale locale_table::get(const std::string &acceptedLanguage) { std::vector accepted; split(accepted, acceptedLanguage, ","); struct lang_score { std::string lang, region; float score; std::locale loc; bool operator<(const lang_score &rhs) const { return score > rhs.score; } }; std::vector scores; auto tryLangRegion = [&scores](std::string lang, std::string region, float score) { try { auto name = lang + '_' + region + ".UTF-8"; std::locale loc(name); if (iequals(loc.name(), name)) scores.emplace_back(std::move(lang), std::move(region), score, loc); } catch (const std::exception &) // NOLINT(bugprone-empty-catch) { } }; for (auto &l : accepted) { std::smatch m; if (std::regex_search(l, m, kAcceptsRX)) { float score = 1; if (m[3].matched) score = std::stof(m.str(3)); auto lang = m.str(1); if (m[2].matched) tryLangRegion(lang, m[2], score); else if (kLocalesPerLang.count(lang)) { for (auto region : kLocalesPerLang.at(lang)) tryLangRegion(lang, std::string{ region }, score); } } } return scores.empty() ? std::locale("C") : std::locale(scores.front().loc); } std::locale request::get_locale() const { return locale_table::instance().get(get_header("Accept-Language")); } namespace { const std::string_view kNameValueSeparator{ ": " }, kCRLF{ "\r\n" }; } std::vector request::to_buffers() const { thread_local static std::string s_request_line; std::vector result; s_request_line = get_request_line(); result.emplace_back(s_request_line); result.emplace_back(kCRLF); for (const header &h : m_headers) { result.emplace_back(h.name); result.emplace_back(kNameValueSeparator); result.emplace_back(h.value); result.emplace_back(kCRLF); } result.emplace_back(kCRLF); result.emplace_back(m_payload); return result; } // std::vector request::to_buffers() const // { // thread_local static std::string s_request_line; // std::vector result; // s_request_line = get_request_line(); // result.emplace_back(asio_ns::buffer(s_request_line)); // result.push_back(asio_ns::buffer(kCRLF)); // for (const header &h : m_headers) // { // result.push_back(asio_ns::buffer(h.name)); // result.push_back(asio_ns::buffer(kNameValueSeparator)); // result.push_back(asio_ns::buffer(h.value)); // result.push_back(asio_ns::buffer(kCRLF)); // } // result.push_back(asio_ns::buffer(kCRLF)); // result.push_back(asio_ns::buffer(m_payload)); // return result; // } std::ostream &operator<<(std::ostream &io, const request &req) { io << req.get_request_line() << "\r\n"; for (const header &h : req.m_headers) io << h.name << ": " << h.value << "\r\n"; io << "\r\n" << req.m_payload; return io; } } // namespace zeep::http libzeep-7.3.2/src/scope.cpp0000664000175000017500000000706415150027072015431 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2025-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/scope.hpp" #include "zeep/exception.hpp" #include "zeep/http/server.hpp" #include #include namespace zeep::http { // -------------------------------------------------------------------- // scope std::ostream &operator<<(std::ostream &lhs, const scope &rhs) { const scope *s = &rhs; while (s != nullptr) { for (const scope::data_map::value_type& e : s->m_data) lhs << e.first << " = " << e.second << '\n'; s = s->m_next; } return lhs; } scope::scope() : m_next(nullptr) , m_depth(0) , m_req(nullptr) , m_server(nullptr) { } scope::scope(const scope &next) : m_next(const_cast(&next)) , m_depth(next.m_depth + 1) , m_req(next.m_req) , m_server(next.m_server) { if (m_depth > 1000) throw std::runtime_error("scope stack overflow"); } scope::scope(const request &req) : m_next(nullptr) , m_depth(0) , m_req(&req) , m_server(nullptr) { } scope::scope(const basic_server *server, const request &req) : m_next(nullptr) , m_depth(0) , m_req(&req) , m_server(server) { } void scope::add_path_param(std::string name, std::string value) { m_path_parameters.emplace_back(std::move(name), std::move(value)); } el::object &scope::operator[](const std::string &name) { return lookup(name); } const el::object &scope::lookup(const std::string &name, bool includeSelected) const { const el::object *result = nullptr; auto i = m_data.find(name); if (i != m_data.end()) result = &i->second; else if (includeSelected and m_selected.contains(name)) result = &m_selected.at(name); else if (m_next != nullptr) result = &m_next->lookup(name, includeSelected); if (result == nullptr) { static el::object s_null; result = &s_null; } return *result; } const el::object &scope::operator[](const std::string &name) const { return lookup(name); } el::object &scope::lookup(const std::string &name) { el::object *result = nullptr; auto i = m_data.find(name); if (i != m_data.end()) result = &i->second; else if (m_next != nullptr) result = &m_next->lookup(name); if (result == nullptr) { m_data[name] = el::object(); result = &m_data[name]; } return *result; } const request &scope::get_request() const { // if (m_next) // return m_next->get_request(); if (m_req == nullptr) throw zeep::exception("Invalid scope, no request"); return *m_req; } std::string scope::get_context_name() const { return m_server ? m_server->get_context_name() : ""; } el::object scope::get_credentials() const { if (m_req == nullptr or m_server == nullptr) throw zeep::exception("Invalid scope, no request, no server"); return m_req->get_credentials(); } bool scope::has_role(std::string_view role) const { auto credentials = get_credentials(); return credentials.is_object() and credentials["role"].is_array() and credentials["role"].contains(role); } void scope::select_object(const el::object &o) { m_selected = o; } auto scope::get_nodeset(const std::string &name) const -> node_set_type { auto i = m_nodesets.find(name); if (i != m_nodesets.end()) return i->second; if (m_next == nullptr) return {}; return m_next->get_nodeset(name); } void scope::set_nodeset(const std::string &name, node_set_type &&nodes) { m_nodesets.emplace(std::make_pair(name, std::move(nodes))); } std::string scope::get_csrf_token() const { return get_request().get_cookie("csrf-token"); } } // namespace zeep::http libzeep-7.3.2/src/security.cpp0000664000175000017500000001446715150027072016174 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/security.hpp" #include "zeep/crypto.hpp" #include "zeep/el/object.hpp" #include "zeep/el/processing.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/uri.hpp" #include "glob.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace zeep::http { namespace { #define BASE64URL "(?:[-_A-Za-z0-9]{4})*(?:[-_A-Za-z0-9]{2,3})?" std::regex kJWTRx("^(" BASE64URL R"()\.()" BASE64URL R"()\.()" BASE64URL ")$"); } // namespace // -------------------------------------------------------------------- bool user_service::user_is_valid(const object &credentials) const { return user_is_valid(credentials["username"].get()); } bool user_service::user_is_valid(const std::string &username) const { bool result = false; try { auto user = load_user(username); result = user.username == username; } catch (...) { result = false; } return result; } // -------------------------------------------------------------------- security_context::security_context(std::string secret, user_service &users, bool defaultAccessAllowed) : m_secret(std::move(secret)) , m_users(users) , m_default_allow(defaultAccessAllowed) , m_default_jwt_exp(std::chrono::years{ 1 }) { register_password_encoder(); } void security_context::validate_request(request &req) const { bool allow = m_default_allow; for (;;) { auto path = req.get_uri(); std::set roles; auto access_token = req.get_cookie("access_token"); for (;;) { if (access_token.empty()) break; std::smatch m; if (not std::regex_match(access_token, m, kJWTRx)) break; auto JOSEHeader = object::parse_JSON(decode_base64url(m[1].str())); const object kJOSEHeader{ { "typ", "JWT" }, { "alg", "HS256" } }; if (JOSEHeader != kJOSEHeader) break; // check signature auto sig = encode_base64url(hmac_sha256(m[1].str() + '.' + m[2].str(), m_secret)); if (sig != m[3].str()) break; auto credentials = object::parse_JSON(decode_base64url(m[2].str())); // check exp using namespace std::chrono; auto exp = credentials["exp"].get(); auto exp_t = time_point() + seconds{ exp }; if (system_clock::now() > exp_t) break; // expired if (not credentials.is_object() or not credentials["role"].is_array()) break; // make sure user still exists. if (not m_users.user_is_valid(credentials)) break; for (const auto& role : credentials["role"]) roles.insert(role.get()); req.set_credentials(std::move(credentials)); break; } // first check if this page is allowed without any credentials // that means, the first rule that matches this uri should allow // access. for (auto &rule : m_rules) { if (not glob_match(path, rule.m_pattern)) continue; if (rule.m_roles.empty()) allow = true; else { std::set common; std::ranges::set_intersection(roles, rule.m_roles, std::inserter(common, common.begin())); allow = not common.empty(); } break; } break; } if (allow and m_validate_csrf) { if (auto p = req.get_parameter("_csrf"); p.has_value()) { const auto& req_csrf_param = *p; if (auto req_csrf_cookie = req.get_cookie("csrf-token"); req_csrf_cookie != req_csrf_param) { allow = false; std::cerr << "CSRF validation failed\n"; } } } if (not allow) throw unauthorized_exception(); } // -------------------------------------------------------------------- void security_context::add_authorization_headers(reply &rep, const user_details user, std::chrono::system_clock::duration exp) { using namespace std::chrono; object JOSEHeader{ { "typ", "JWT" }, { "alg", "HS256" } }; auto exp_t = duration_cast(system_clock::now() + exp - system_clock::time_point()).count(); object credentials{ { "username", user.username }, { "exp", exp_t } }; for (auto &role : user.roles) credentials["role"].push_back(role); auto h1 = encode_base64url(JOSEHeader.get_JSON()); auto h2 = encode_base64url(credentials.get_JSON()); auto h3 = encode_base64url(hmac_sha256(h1 + '.' + h2, m_secret)); std::stringstream s; const std::time_t now_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now() + exp); s << std::put_time(std::localtime(&now_t), "%a, %d %b %Y %H:%M:%S GMT"); rep.set_cookie("access_token", h1 + '.' + h2 + '.' + h3, // clang-format off { { "HttpOnly", "" }, { "SameSite", "Lax" }, { "Expires", '"' + s.str() + '"' } } // clang-format on ); } void security_context::add_authorization_headers(reply &rep, const user_details &user) { add_authorization_headers(rep, user, m_default_jwt_exp); } // -------------------------------------------------------------------- bool security_context::verify_username_password(const std::string &username, const std::string &raw_password) { bool result = false; auto user = m_users.load_user(username); for (auto const &[name, pwenc] : m_known_password_encoders) { if (!user.password.starts_with(name)) continue; result = pwenc->matches(raw_password, user.password); break; } return result; } void security_context::verify_username_password(const std::string &username, const std::string &raw_password, reply &rep) { if (not verify_username_password(username, raw_password)) throw invalid_password_exception(); add_authorization_headers(rep, m_users.load_user(username)); } // -------------------------------------------------------------------- std::pair security_context::get_csrf_token(request &req) { // See if we need to add a new csrf token bool csrf_is_new = false; std::string csrf = req.get_cookie("csrf-token"); if (csrf.empty()) { csrf_is_new = true; csrf = encode_base64url(random_hash()); req.set_cookie("csrf-token", csrf); } return { csrf, csrf_is_new }; } } // namespace zeep::http libzeep-7.3.2/src/server.cpp0000664000175000017500000002400515150027072015620 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2014-2026 // Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/server.hpp" #include "zeep/el/object.hpp" #include "zeep/el/processing.hpp" #include "zeep/http/access-control.hpp" #include "zeep/http/connection.hpp" #include "zeep/http/controller.hpp" #include "zeep/http/error-handler.hpp" #include "zeep/http/header.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/http/security.hpp" #include "zeep/http/status.hpp" #include "zeep/http/template-processor.hpp" #include "zeep/unicode-support.hpp" #include "zeep/uri.hpp" #if USE_DATE_H # include # include #endif #include #include #include #include #include #include // for list #include #include #include // for set #include #include #include #include #include // for tie #include namespace zeep::http { // -------------------------------------------------------------------- // http::basic_server basic_server::basic_server() : m_log_forwarded(true) , m_security_context(nullptr) , m_allowed_methods{ "GET", "POST", "PUT", "OPTIONS", "HEAD", "DELETE" } { // add a default error handler add_error_handler(new default_error_handler()); } basic_server::basic_server(security_context *s_cntxt) : basic_server() { m_security_context.reset(s_cntxt); } basic_server::~basic_server() { try { basic_server::stop(); } catch (const std::exception &ex) { std::clog << "error stopping server: " << ex.what() << '\n'; } for (auto c : m_controllers) delete c; for (auto eh : m_error_handlers) delete eh; } void basic_server::set_template_processor(basic_template_processor *template_processor) { m_template_processor.reset(template_processor); } void basic_server::bind(std::string_view address, unsigned short port) { m_address = address; m_port = port; m_acceptor = std::make_shared(get_io_context()); m_new_connection = std::make_shared(get_io_context(), *this); // then bind the address here asio_ns::ip::tcp::endpoint endpoint; asio_system_ns::error_code ec; auto addr = asio_ns::ip::make_address(address, ec); if (not ec) endpoint = asio_ns::ip::tcp::endpoint(addr, port); else { asio_ns::ip::tcp::resolver resolver(get_io_context()); for (auto &ep : resolver.resolve(address, std::to_string(port))) { endpoint = ep; break; } } m_acceptor->open(endpoint.protocol()); m_acceptor->set_option(asio_ns::ip::tcp::acceptor::reuse_address(true)); m_acceptor->bind(endpoint); m_acceptor->listen(); m_acceptor->async_accept(m_new_connection->get_socket(), [this](asio_system_ns::error_code ec) { this->handle_accept(ec); }); } void basic_server::get_options_for_request(const request &req, reply &rep) { rep = reply::stock_reply(status_type::no_content); rep.set_header("Allow", join(m_allowed_methods, ",")); rep.set_header("Cache-Control", "max-age=86400"); set_access_control_headers(req, rep); } void basic_server::set_access_control_headers([[maybe_unused]] const request &req, reply &rep) { if (m_access_control) m_access_control->get_access_control_headers(rep); } void basic_server::add_controller(controller *c) { m_controllers.push_back(c); c->set_server(this); } void basic_server::add_error_handler(error_handler *eh) { m_error_handlers.push_front(eh); eh->set_server(this); } void basic_server::run(int nr_of_threads) { // keep the server at work until we call stop auto work = asio_ns::make_work_guard(get_io_context()); for (int i = 0; i < nr_of_threads; ++i) m_threads.emplace_back([this]() { get_io_context().run(); }); for (auto &t : m_threads) { if (t.joinable()) t.join(); } } void basic_server::stop() { m_new_connection.reset(); if (m_acceptor and m_acceptor->is_open()) m_acceptor->close(); m_acceptor.reset(); } void basic_server::handle_accept(asio_system_ns::error_code ec) { if (not ec) { m_new_connection->start(); m_new_connection = std::make_shared(get_io_context(), *this); m_acceptor->async_accept(m_new_connection->get_socket(), [this](asio_system_ns::error_code ec) { this->handle_accept(ec); }); } } void basic_server::handle_request(asio_ns::ip::tcp::socket &socket, request &req, reply &rep) { // we're pessimistic rep = reply::stock_reply(status_type::not_found); auto start = std::chrono::system_clock::now(); std::string referer("-"), userAgent("-"), accept, client; for (const header &h : req.get_headers()) { if (m_log_forwarded and iequals(h.name, "X-Forwarded-For")) { client = h.value; std::string::size_type comma = client.rfind(','); if (comma != std::string::npos) { if (comma < client.length() - 1 and client[comma + 1] == ' ') ++comma; client = client.substr(comma + 1, std::string::npos); } } else if (iequals(h.name, "Referer")) referer = h.value; else if (iequals(h.name, "User-Agent")) userAgent = h.value; else if (iequals(h.name, "Accept")) accept = h.value; } try { // asking for the remote endpoint address failed sometimes // causing aborting exceptions, so I moved it here. if (client.empty()) { asio_ns::ip::address addr = socket.remote_endpoint().address(); client = addr.to_string(); } req.set_remote_address(client); // shortcut, check for supported method auto method = req.get_method(); if (not(m_allowed_methods.empty() or m_allowed_methods.count(method))) throw http_status_exception(status_type::bad_request); std::string csrf; bool csrf_is_new = false; if (m_security_context) { m_security_context->validate_request(req); std::tie(csrf, csrf_is_new) = m_security_context->get_csrf_token(req); } // do the actual work. bool processed = false; for (auto c : m_controllers) { if (not c->path_matches_prefix(req.get_uri())) continue; if (c->dispatch_request(socket, req, rep)) { processed = true; break; } } if (not processed) { for (auto eh : m_error_handlers) { try { if (eh->create_error_reply(req, status_type::not_found, rep)) break; } catch (...) { continue; } } } if (method == "HEAD" or method == "OPTIONS") rep.set_content("", rep.get_content_type()); else if (csrf_is_new) rep.set_cookie("csrf-token", csrf, { { "HttpOnly", "" }, { "SameSite", "Lax" }, { "Path", "/" } }); if (not m_context_name.empty() and (rep.get_status() == status_type::moved_permanently or rep.get_status() == status_type::moved_temporarily)) { auto location = rep.get_header("location"); if (location.front() == '/') rep.set_header("location", m_context_name + location); } // work around buggy IE... also, using req.accept() doesn't work since it contains */* ... duh if (starts_with(rep.get_content_type(), "application/xhtml+xml") and not contains(accept, "application/xhtml+xml") and contains(userAgent, "MSIE")) { rep.set_content_type("text/html; charset=utf-8"); } set_access_control_headers(req, rep); } catch (...) { auto eptr = std::current_exception(); // special case, caller expects a JSON reply if (req.get_accept("application/json") == 1.0f) { try { if (eptr) std::rethrow_exception(eptr); } catch (const http_status_exception &ex) { rep = http::reply::stock_reply(ex.status()); object error({ { "error", get_status_description(ex.status()) } }); rep.set_content(error); rep.set_status(ex.status()); } catch (status_type s) { rep = http::reply::stock_reply(s); object error({ { "error", get_status_description(s) } }); rep.set_content(error); rep.set_status(s); } catch (const std::exception &e) { rep = http::reply::stock_reply(status_type::internal_server_error); object error({ { "error", e.what() } }); rep.set_content(error); rep.set_status(status_type::internal_server_error); } catch (...) { rep = http::reply::stock_reply(status_type::internal_server_error); object error({ { "error", "unknown error" } }); rep.set_content(error); rep.set_status(status_type::internal_server_error); } } else { for (auto eh : m_error_handlers) { try { if (eh->create_error_reply(req, eptr, rep)) break; } catch (...) { continue; } } } } log_request(client, req, rep, start, referer, userAgent, {}); } void basic_server::log_request(std::string_view client, const request &req, const reply &rep, std::chrono::system_clock::time_point start, std::string_view referer, std::string_view userAgent, std::string_view entry) noexcept { try { auto credentials = req.get_credentials(); std::string username = credentials.is_object() ? credentials["username"].get() : ""; if (username.empty()) username = "-"; const auto &[major, minor] = req.get_version(); std::ostringstream ts; // macOS still has no zoned time... #if USE_DATE_H auto t = date::make_zoned(date::current_zone(), date::floor(start)); date::to_stream(ts, "%d/%b/%Y:%H:%M:%S %Ez", t); #else auto t = std::chrono::zoned_time{ std::chrono::current_zone(), std::chrono::floor(start) }; ts << std::format("{:%d/%b/%Y:%H:%M:%S %Ez}", t); #endif std::cout << std::format(R"({} - {} [{}] "{} {} HTTP/{}.{}" {} {} "{}" "{}"{})", client, username, ts.str(), req.get_method(), req.get_uri().string(), major, minor, static_cast(rep.get_status()), rep.size(), referer, userAgent, entry.empty() ? std::string{} : ((std::ostringstream() << ' ' << std::quoted(entry)).str())) << '\n' << std::flush; } catch (const std::exception &ex) { std::cerr << "error writing to log: " << ex.what() << '\n'; } } } // namespace zeep::http libzeep-7.3.2/src/signals.cpp0000664000175000017500000000640515150027072015756 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "signals.hpp" // -------------------------------------------------------------------- // // Signal handling // #if _WIN32 #include #include #include #include #include #ifndef SIGQUIT #define SIGQUIT SIGTERM #endif #ifndef SIGHUP #define SIGHUP SIGBREAK #endif namespace zeep { struct signal_catcher_impl { static BOOL __stdcall CtrlHandler(DWORD inCntrlType); static void signal(int inSignal); static int sSignal; static std::condition_variable sCondition; static std::mutex sMutex; }; int signal_catcher_impl::sSignal; std::condition_variable signal_catcher_impl::sCondition; std::mutex signal_catcher_impl::sMutex; BOOL signal_catcher_impl::CtrlHandler(DWORD inCntrlType) { BOOL result = true; switch (inCntrlType) { // Handle the CTRL-C signal. case CTRL_C_EVENT: sSignal = SIGINT; break; // CTRL-CLOSE: confirm that the user wants to exit. case CTRL_CLOSE_EVENT: sSignal = SIGQUIT; break; // Pass other signals to the next handler. case CTRL_BREAK_EVENT: sSignal = SIGHUP; break; case CTRL_SHUTDOWN_EVENT: case CTRL_LOGOFF_EVENT: sSignal = SIGTERM; break; default: result = FALSE; } if (result) sCondition.notify_one(); return result; } signal_catcher::signal_catcher() : mImpl(nullptr) { if (not SetConsoleCtrlHandler(&signal_catcher_impl::CtrlHandler, true)) throw std::runtime_error("Could not install control handler"); } signal_catcher::~signal_catcher() { } void signal_catcher::block() { } void signal_catcher::unblock() { } int signal_catcher::wait() { std::unique_lock lock(signal_catcher_impl::sMutex); signal_catcher_impl::sCondition.wait(lock); return signal_catcher_impl::sSignal; } void signal_catcher::signal_hangup(std::thread &t) { signal_catcher_impl::CtrlHandler(CTRL_BREAK_EVENT); } } // namespace zeep #else #include #include #include namespace zeep { // -------------------------------------------------------------------- // // signal // struct signal_catcher_impl { sigset_t new_mask, old_mask; }; signal_catcher::signal_catcher() : mImpl(new signal_catcher_impl) { sigfillset(&mImpl->new_mask); } signal_catcher::~signal_catcher() { delete mImpl; } void signal_catcher::block() { pthread_sigmask(SIG_BLOCK, &mImpl->new_mask, &mImpl->old_mask); } void signal_catcher::unblock() { pthread_sigmask(SIG_SETMASK, &mImpl->old_mask, nullptr); } int signal_catcher::wait() { // Wait for signal indicating time to shut down. sigset_t wait_mask; sigemptyset(&wait_mask); sigaddset(&wait_mask, SIGINT); sigaddset(&wait_mask, SIGHUP); // sigaddset(&wait_mask, SIGCHLD); sigaddset(&wait_mask, SIGQUIT); sigaddset(&wait_mask, SIGTERM); pthread_sigmask(SIG_BLOCK, &wait_mask, nullptr); int sig = 0; sigwait(&wait_mask, &sig); return sig; } void signal_catcher::signal_hangup(std::thread &t) { // kill(getpid(), SIGHUP); pthread_kill(t.native_handle(), SIGHUP); } } // namespace zeep #endif libzeep-7.3.2/src/signals.hpp0000664000175000017500000000123515150027072015757 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2021. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once #include namespace zeep { class signal_catcher { public: signal_catcher(const signal_catcher &) = delete; signal_catcher &operator=(const signal_catcher &) = delete; signal_catcher(); ~signal_catcher(); void block(); void unblock(); int wait(); static void signal_hangup(std::thread &t); private: struct signal_catcher_impl *mImpl; }; } // namespace zeep libzeep-7.3.2/src/soap-controller.cpp0000664000175000017500000001126515150027072017441 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/exception.hpp" #include "zeep/http/soap-controller.hpp" #include "zeep/uri.hpp" namespace zeep::http { soap_envelope::soap_envelope() : m_request(nullptr) { } // envelope::envelope(zeem::document& data) // : m_request(nullptr) // { // const zeem::xpath // sRequestPath("/Envelope[namespace-uri()='http://schemas.xmlsoap.org/soap/envelope/']/Body[position()=1]/*[position()=1]"); // std::list l = sRequestPath.evaluate(*data.root()); // if (l.empty()) // throw zeep::exception("Empty or invalid SOAP envelope passed"); // m_request = l.front(); // } zeem::element make_envelope(zeem::element&& data) { zeem::element env("soap:Envelope", { { "xmlns:soap", "http://schemas.xmlsoap.org/soap/envelope/" }, { "soap:encodingStyle", "http://www.w3.org/2003/05/soap-encoding" } }); auto& body = env.emplace_back("soap:Body"); body.emplace_back(std::move(data)); return env; } zeem::element make_fault(std::string what) { zeem::element fault("soap:Fault"); auto faultCode = fault.emplace_back("faultcode"); faultCode.set_content("soap:Server"); auto faultString(fault.emplace_back("faultstring")); faultString.set_content(what); return make_envelope(std::move(fault)); } zeem::element make_fault(const std::exception& ex) { return make_fault(std::string(ex.what())); } // -------------------------------------------------------------------- bool soap_controller::handle_request(request& req, reply& reply) { bool result = false; auto p = get_prefixless_path(req); if (req.get_method() == "POST" and p.empty()) { result = true; try { zeem::document envelope(req.get_payload()); auto request = envelope.find_first( "/Envelope[namespace-uri()='http://schemas.xmlsoap.org/soap/envelope/']/Body[position()=1]/*[position()=1]"); if (request == envelope.cend()) throw zeep::exception("Empty or invalid SOAP envelope passed"); if (request->get_ns() != m_ns) throw zeep::exception("Invalid namespace for request"); std::string action = request->name(); // log() << action << ' '; for (auto& mp: m_mountpoints) { if (mp->m_action != action) continue; mp->call(*request, reply, m_ns); break; } } catch (const std::exception& e) { reply.set_content(make_fault(e)); reply.set_status(status_type::internal_server_error); } catch (status_type& s) { reply.set_content(make_fault(get_status_description(s))); reply.set_status(s); } } else if (req.get_method() == "GET" and p == "wsdl") { reply.set_content(make_wsdl()); reply.set_status(status_type::ok); } return result; } /// \brief Create a WSDL based on the registered actions zeem::element soap_controller::make_wsdl() { // start by making the root node: wsdl:definitions zeem::element wsdl("wsdl:definitions", { { "targetNamespace", m_ns }, { "xmlns:ns", m_ns }, { "xmlns:wsdl", "http://schemas.xmlsoap.org/wsdl/" }, { "xmlns:soap", "http://schemas.xmlsoap.org/wsdl/soap/" } }); // add wsdl:types auto& types = wsdl.emplace_back("wsdl:types"); // add xsd:schema auto& schema = types.emplace_back("xsd:schema", { { "targetNamespace", m_ns }, { "elementFormDefault", "qualified" }, { "attributeFormDefault", "unqualified" }, { "xmlns:xsd", "http://www.w3.org/2001/XMLSchema" } }); using namespace std::literals; // add wsdl:binding auto& binding = wsdl.emplace_back("wsdl:binding", { { "name", m_service }, { "type", "ns:" + m_service + "PortType" } }); // add soap:binding binding.emplace_back("soap:binding", { { "style", "document" }, { "transport", "http://schemas.xmlsoap.org/soap/http" } }); // add wsdl:portType auto& portType = wsdl.emplace_back("wsdl:portType", { { "name", m_service + "PortType" } }); // and the types zeem::type_map typeMap; message_map messageMap; for (auto& mp: m_mountpoints) mp->describe(typeMap, messageMap, portType, binding); for (auto &m : messageMap) wsdl.emplace_back(std::move(m.second)); for (auto &t : typeMap) schema.emplace_back(std::move(t.second)); // finish with the wsdl:service auto& service = wsdl.emplace_back("wsdl:service", { { "name", m_service } }); auto& port = service.emplace_back("wsdl:port", { { "name", m_service }, { "binding", "ns:" + m_service } }); std::string location = get_context_name() + "/" + m_location; port.emplace_back("soap:address", { { "location", location } }); return wsdl; } } libzeep-7.3.2/src/tag-processor.cpp0000664000175000017500000006753415150027072017120 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2019-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/tag-processor.hpp" #include "zeep/http/html-controller.hpp" #include "zeep/http/template-processor.hpp" #include #include #include #include namespace fs = std::filesystem; namespace zeep::http { std::unordered_set kFixedValueBooleanAttributes{ "async", "autofocus", "autoplay", "checked", "controls", "declare", "default", "defer", "disabled", "formnovalidate", "hidden", "ismap", "loop", "multiple", "novalidate", "nowrap", "open", "pubdate", "readonly", "required", "reversed", "scoped", "seamless", "selected" }; // -------------------------------------------------------------------- int attribute_precedence(const zeem::attribute &attr) { if (attr.name() == "insert" or attr.name() == "replace") return -10; else if (attr.name() == "each") return -9; else if (attr.name() == "if" or attr.name() == "unless" or attr.name() == "switch" or attr.name() == "case") return -8; else if (attr.name() == "object" or attr.name() == "with") return -7; else if (attr.name() == "attr" or attr.name() == "attrappend" or attr.name() == "attrprepend" or attr.name() == "classappend" or attr.name() == "styleappend") return -6; else if (attr.name() == "text" or attr.name() == "utext") return 1; else if (attr.name() == "fragment") return 2; else if (attr.name() == "remove") return 3; else return 0; }; // -------------------------------------------------------------------- tag_processor::tag_processor(const char *ns) : tag_processor_base(ns) { using namespace std::placeholders; register_attr_handler("assert", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_assert(element, attr, scope, dir, loader); }); register_attr_handler("attr", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_attr(element, attr, scope, dir, loader); }); register_attr_handler("classappend", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_classappend(element, attr, scope, dir, loader); }); register_attr_handler("each", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_each(element, attr, scope, dir, loader); }); register_attr_handler("if", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_if(element, attr, scope, dir, loader, false); }); register_attr_handler("include", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_include(element, attr, scope, dir, loader, TemplateIncludeAction::include); }); register_attr_handler("inline", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_inline(element, attr, scope, dir, loader); }); register_attr_handler("insert", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_include(element, attr, scope, dir, loader, TemplateIncludeAction::insert); }); register_attr_handler("replace", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_include(element, attr, scope, dir, loader, TemplateIncludeAction::replace); }); register_attr_handler("styleappend", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_styleappend(element, attr, scope, dir, loader); }); register_attr_handler("switch", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_switch(element, attr, scope, dir, loader); }); register_attr_handler("text", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_text(element, attr, scope, dir, loader, true); }); register_attr_handler("unless", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_if(element, attr, scope, dir, loader, true); }); register_attr_handler("utext", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_text(element, attr, scope, dir, loader, false); }); register_attr_handler("with", [this](zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path &dir, basic_template_processor &loader) { return process_attr_with(element, attr, scope, dir, loader); }); } void tag_processor::process_xml(zeem::node *node, const scope &parentScope, const fs::path &dir, basic_template_processor &loader) { m_template.clear(); m_template.emplace_back(*static_cast(node)); process_node(node, parentScope, dir, loader); auto e = dynamic_cast(node); if (e != nullptr) post_process(e, parentScope, dir, loader); } // -------------------------------------------------------------------- // post processing: remove blocks, remove attributes with ns = ns(), process remove void tag_processor::post_process(zeem::element *e, const scope &parentScope, const fs::path &dir, basic_template_processor &loader) { auto parent = e->parent(); for (auto ai = e->attributes().begin(); ai != e->attributes().end();) { if (ai->get_ns() == m_ns and ai->name() == "remove" and parent != nullptr) { scope sub(parentScope); auto action = process_attr_remove(e, *ai, sub, dir, loader); if (action == AttributeAction::remove) { parent->erase(e); return; } } if (ai->get_ns() == m_ns) ai = e->attributes().erase(ai); else ++ai; } if (e->get_ns() == m_ns and e->name() == "block" and parent != nullptr) { for (auto &ci : e->nodes()) parent->nodes().insert(e, std::move(ci)); parent->erase(e); return; } // take a copy since iterators might get invalid std::vector children; std::ranges::transform(*e, std::back_inserter(children), [](auto &c) { return &c; }); for (auto &c : children) post_process(c, parentScope, dir, loader); // postpone removing namespaces until all children have been processed for (auto ai = e->attributes().begin(); ai != e->attributes().end();) { if (ai->is_namespace() and ai->value() == m_ns) ai = e->attributes().erase(ai); else ++ai; } } // ----------------------------------------------------------------------- void tag_processor::process_text(zeem::node_with_text &text, const scope &scope) { auto parent = text.parent(); auto next = std::find_if(parent->nodes().begin(), parent->nodes().end(), [&text](auto &n) { return &n == &text; }); assert(next != parent->nodes().end()); ++next; std::string s = text.get_text(); size_t b = 0; while (b < s.length()) { auto i = s.find('[', b); if (i == std::string::npos) break; char c2 = s[i + 1]; if (c2 != '[' and c2 != '(') { b = i + 1; continue; } i += 2; auto j = s.find(c2 == '(' ? ")]" : "]]", i); if (j == std::string::npos) break; auto m = s.substr(i, j - i); if (not process_el(scope, m)) m.insert(0, "Error processing "); if (c2 == '(' and m.find('<') != std::string::npos) // 'unescaped' text, but since we're an xml library reverse this by parsing the result and putting the { zeem::document subDoc("" + m + ""); auto foo = subDoc.front(); for (auto &n : foo.nodes()) parent->nodes().emplace(next, std::move(n)); text.set_text(s.substr(0, i - 2)); s = s.substr(j + 2); b = 0; parent->nodes().emplace(next, zeem::text(s)); } else { s.replace(i - 2, j - i + 4, m); b = i - 2 + m.length(); } } text.set_text(s); } // -------------------------------------------------------------------- zeem::element tag_processor::resolve_fragment_spec( zeem::element *node, const fs::path &dir, basic_template_processor &loader, const object &spec, const scope &scope) { if (spec.contains("is-node-set") and spec["is-node-set"]) return scope.get_nodeset(spec["node-set-name"].get()); if (spec.is_object() and spec["template"].is_string() and spec["selector"].is_object() and spec["selector"]["xpath"].is_string()) { auto file = spec["template"].get(); auto selector = spec["selector"]["xpath"].get(); if (not(spec.is_null() or selector.empty())) return resolve_fragment_spec(node, dir, loader, file, selector, true); } else if (spec.is_string()) { const std::regex kTemplateRx(R"(^\s*(\S*)\s*::\s*(#?[-_[:alnum:]]+)$)"); std::smatch m; std::string s = spec.get(); if (not std::regex_match(s, m, kTemplateRx)) throw std::runtime_error("Invalid attribute value for :include/insert/replace"); std::string file = m[1]; std::string id = m[2]; bool byID = false; std::string selector; if (id[0] == '#') // by ID { byID = true; selector = "//*[@id='" + id.substr(1) + "']"; } else selector = "//*[name()='" + id + "' or attribute::*[namespace-uri() = $ns and (local-name() = 'ref' or local-name() = 'fragment') and starts-with(string(), '" + id + "')]]"; return resolve_fragment_spec(node, dir, loader, file, selector, byID); } return {}; } zeem::element tag_processor::resolve_fragment_spec( zeem::element *node, const fs::path &dir, basic_template_processor &loader, const std::string &file, std::string_view selector, bool byID) { zeem::context ctx; ctx.set("ns", ns()); zeem::xpath xp(selector); // xp.dump(); zeem::document doc; zeem::element_container *root = nullptr; if (file.empty() or file == "this") root = m_template.root(); else { doc.set_preserve_cdata(true); bool loaded = false; for (std::string ext : { "", ".xhtml", ".html", ".xml" }) { std::error_code ec; fs::path template_file = dir / (file + ext); (void)loader.file_time(template_file.string(), ec); if (ec) continue; loader.load_template(template_file.string(), doc); loaded = true; break; } if (not loaded) throw std::runtime_error("Could not locate template file " + file); root = doc.root(); } zeem::element result; for (auto n : xp.evaluate(*root, ctx)) { auto ni = result.nodes().emplace_back(*n); if (ni->type() != zeem::node_type::element) continue; auto e = static_cast(&*ni); if (root != node->root()) fix_namespaces(*e, *static_cast(n), *node); auto &attr = e->attributes(); if (byID) // remove the copied ID attr.erase("id"); attr.erase(e->prefix_tag("ref", ns())); attr.erase(e->prefix_tag("fragment", ns())); } return result; } // ----------------------------------------------------------------------- void tag_processor::process_node(zeem::node *node, const scope &parentScope, const std::filesystem::path &dir, basic_template_processor &loader) { for (;;) { if (node->type() == zeem::node_type::cdata) { if (node->root()->type() == zeem::node_type::document and static_cast(node->root())->is_html5()) { // HTML5 does not support CDATA sections, replace it zeem::element_container *parent = node->parent(); auto parent_nodes = parent->nodes(); auto ni = std::ranges::find_if(parent_nodes, [node](auto &n) { return &n == node; }); [[maybe_unused]] auto ti = parent_nodes.emplace(ni, zeem::text( static_cast(node)->get_text())); // assert(std::next(ti) == ni); // < this fails when building with MSVC parent_nodes.erase(ni); break; } } if (node->type() == zeem::node_type::text or node->type() == zeem::node_type::cdata) { process_text(*static_cast(node), parentScope); break; } auto *e = dynamic_cast(node); if (e == nullptr) break; zeem::element_container *parent = e->parent(); scope scope(parentScope); bool inlined = false; try { auto &attributes = e->attributes(); attributes.sort([](auto &a, auto &b) { return attribute_precedence(a) < attribute_precedence(b); }); auto attr = attributes.begin(); while (attr != attributes.end()) { if (attr->get_ns() != m_ns or attr->name() == "remove" or attr->name() == "ref" or attr->name() == "fragment") { ++attr; continue; } AttributeAction action = AttributeAction::none; if (attr->name() == "object") scope.select_object(evaluate_el(scope, attr->value())); else if (attr->name() == "inline") { action = process_attr_inline(e, *attr, scope, dir, loader); inlined = true; } else { auto h = m_attr_handlers.find(attr->name()); if (h != m_attr_handlers.end()) action = h->second(e, *attr, scope, dir, loader); else if (kFixedValueBooleanAttributes.count(attr->name())) action = process_attr_boolean_value(e, *attr, scope, dir, loader); else action = process_attr_generic(e, *attr, scope, dir, loader); } if (action == AttributeAction::remove) { parent->erase(node); node = nullptr; break; } attr = e->attributes().erase(attr); } } catch (const std::exception &ex) { parent->nodes().insert(e, zeem::text("Error processing element '" + e->get_qname() + "': " + ex.what())); // parent->erase(e); } if (node != nullptr) { auto i = e->nodes().begin(); while (i != e->nodes().end()) { auto &n = *i; ++i; if (inlined and dynamic_cast(&n) != nullptr) continue; process_node(&n, scope, dir, loader); } } break; } } // ----------------------------------------------------------------------- auto tag_processor::process_attr_if(zeem::element * /*element*/, zeem::attribute &attr, scope &scope, const fs::path & /*dir*/, basic_template_processor & /*loader*/, bool unless) -> AttributeAction { return ((not evaluate_el(scope, attr.value()) == unless)) ? AttributeAction::none : AttributeAction::remove; } // ----------------------------------------------------------------------- auto tag_processor::process_attr_assert(zeem::element * /*element*/, zeem::attribute &attr, scope &scope, const fs::path & /*dir*/, basic_template_processor & /*loader*/) -> AttributeAction { if (not evaluate_el_assert(scope, attr.value())) throw zeep::exception("Assertion failed for '" + attr.value() + "'"); return AttributeAction::none; } // ----------------------------------------------------------------------- auto tag_processor::process_attr_text(zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path & /*dir*/, basic_template_processor & /*loader*/, bool escaped) -> AttributeAction { object obj = evaluate_el(scope, attr.value()); if (not obj.is_null()) { std::string text; if (obj.is_object() and obj.contains("is-node-set") and obj["is-node-set"]) { auto s = scope.get_nodeset(obj["node-set-name"].get()); text = s.str(); } else text = obj.get(); if (escaped) element->set_text(text); else { element->set_text(""); zeem::document subDoc("" + text + ""); auto foo = subDoc.front(); for (auto &n : foo.nodes()) element->nodes().emplace(element->end(), std::move(n)); } } return AttributeAction::none; } // -------------------------------------------------------------------- auto tag_processor::process_attr_switch(zeem::element *element, zeem::attribute &attr, scope &scope, const fs::path & /*dir*/, basic_template_processor & /*loader*/) -> AttributeAction { auto vo = evaluate_el(scope, attr.value()); std::string v; if (not vo.is_null()) v = vo.get(); zeem::element e2(*element); element->nodes().clear(); auto cases = e2.find(".//*[@case]"); zeem::element *selected = nullptr; zeem::element *wildcard = nullptr; for (auto c : cases) { auto ca = c->get_attribute(element->prefix_tag("case", ns())); if (ca == "*") wildcard = c; else if (v == ca or (process_el(scope, ca) and v == ca)) { selected = c; break; } } if (selected == nullptr) selected = wildcard; if (selected != nullptr) { selected->attributes().erase(element->prefix_tag("case", ns())); element->emplace_back(std::move(*selected)); } return AttributeAction::none; } // ----------------------------------------------------------------------- auto tag_processor::process_attr_with(zeem::element * /*element*/, zeem::attribute &attr, scope &scope, const fs::path & /*dir*/, basic_template_processor & /*loader*/) -> AttributeAction { evaluate_el_with(scope, attr.value()); return AttributeAction::none; } // -------------------------------------------------------------------- tag_processor::AttributeAction tag_processor::process_attr_each(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path &dir, basic_template_processor &loader) { std::regex kEachRx(R"(^\s*(\w+)(?:\s*,\s*(\w+))?\s*:\s*(.+)$)"); std::smatch m; auto s = attr.value(); if (not std::regex_match(s, m, kEachRx)) throw std::runtime_error("Invalid attribute value for :each"); std::string var = m[1]; std::string stat = m[2]; object collection = evaluate_el(scope, m[3]); if (collection.is_array()) { auto *parent = node->parent(); assert(parent); size_t collectionSize = collection.size(); size_t ix = 0; for (auto v : collection) { auto subscope(scope); subscope.put(var, v); if (not stat.empty()) subscope.put(stat, object{ { "index", ix }, { "count", ix + 1 }, { "size", collectionSize }, { "current", v }, { "even", ix % 2 == 1 }, { "odd", ix % 2 == 0 }, { "first", ix == 0 }, { "last", ix + 1 == collectionSize } }); zeem::element clone(*node); clone.attributes().erase(attr.get_qname()); auto i = parent->emplace(node, std::move(clone)); // insert before processing, to assign namespaces process_node(i.operator->(), subscope, dir, loader); ++ix; } } return AttributeAction::remove; } // -------------------------------------------------------------------- tag_processor::AttributeAction tag_processor::process_attr_attr(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path & /*dir*/, basic_template_processor & /*loader*/) { auto v = evaluate_el_attr(scope, attr.value()); for (const auto &vi : v) node->set_attribute(vi.first, vi.second); return AttributeAction::none; } // -------------------------------------------------------------------- tag_processor::AttributeAction tag_processor::process_attr_generic(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path & /*dir*/, basic_template_processor & /*loader*/) { auto s = attr.value(); process_el(scope, s); node->set_attribute(attr.name(), s); return AttributeAction::none; } // -------------------------------------------------------------------- tag_processor::AttributeAction tag_processor::process_attr_boolean_value( zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path & /*dir*/, basic_template_processor & /*loader*/) { auto s = attr.value(); if (evaluate_el(scope, s)) node->set_attribute(attr.name(), attr.name()); else node->attributes().erase(attr.name()); return AttributeAction::none; } // -------------------------------------------------------------------- tag_processor::AttributeAction tag_processor::process_attr_inline(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path & /*dir*/, basic_template_processor & /*loader*/) { auto type = attr.value(); if (type == "javascript" or type == "css") { std::regex r = std::regex(R"(/\*\[\[(.+?)\]\]\*/\s*('([^'\\]|\\.)*'|"([^"\\]|\\.)*"|[^;\n])*|\[\[(.+?)\]\])"); for (auto &n : node->nodes()) { if (n.type() != zeem::node_type::text and n.type() != zeem::node_type::cdata) continue; auto *text = static_cast(&n); std::string s = text->get_text(); std::string t; auto b = std::sregex_iterator(s.begin(), s.end(), r); auto e = std::sregex_iterator(); auto i = s.begin(); for (auto ri = b; ri != e; ++ri) { const auto &m = *ri; t.append(i, s.begin() + m.position()); i = s.begin() + m.position() + m.length(); auto v = m[1].matched ? m.str(1) : m.str(5); object obj = evaluate_el(scope, v); std::stringstream ss; ss << obj; v = ss.str(); t.append(v.begin(), v.end()); } t.append(i, s.end()); text->set_text(t); } } else if (type != "none") { for (auto &n : node->nodes()) { if (n.type() != zeem::node_type::text and n.type() != zeem::node_type::cdata) continue; auto *text_p = static_cast(&n); auto &text = *text_p; std::string s = text.get_text(); auto next = text.next(); size_t b = 0; while (b < s.length()) { auto i = s.find('[', b); if (i == std::string::npos) break; char c2 = s[i + 1]; if (c2 != '[' and c2 != '(') { b = i + 1; continue; } i += 2; auto j = s.find(c2 == '(' ? ")]" : "]]", i); if (j == std::string::npos) break; auto m = s.substr(i, j - i); if (not process_el(scope, m)) m.insert(0, "Error processing "); if (c2 == '(' and m.find('<') != std::string::npos) // 'unescaped' text, but since we're an xml library reverse this by parsing the result and putting the { zeem::document subDoc("" + m + ""); for (auto &subnode : subDoc.front().nodes()) node->nodes().emplace(next, std::move(subnode)); text.set_text(s.substr(0, i - 2)); s = s.substr(j + 2); b = 0; node->nodes().insert(next, zeem::text(s)); } else { s.replace(i - 2, j - i + 4, m); b = i + m.length() - 2; } } text.set_text(s); } } return AttributeAction::none; } // -------------------------------------------------------------------- tag_processor::AttributeAction tag_processor::process_attr_include(zeem::element *node, zeem::attribute &attr, scope &parentScope, const std::filesystem::path &dir, basic_template_processor &loader, TemplateIncludeAction tia) { AttributeAction result = AttributeAction::none; auto av = attr.value(); auto o = evaluate_el_link(parentScope, av); object params; if (o.is_object()) params = o["selector"]["params"]; auto templates = resolve_fragment_spec(node, dir, loader, o, parentScope); for (auto &templ : templates.nodes()) { auto *el = dynamic_cast(&templ); if (el == nullptr) { if (tia == TemplateIncludeAction::include) { auto i = node->nodes().emplace(node->end(), templ); process_node(i.operator->(), parentScope, dir, loader); } else { if (tia == TemplateIncludeAction::insert) { auto i = node->nodes().emplace(node->end(), templ); process_node(i.operator->(), parentScope, dir, loader); } else { auto i = node->parent()->nodes().emplace(node, templ); process_node(i.operator->(), parentScope, dir, loader); result = AttributeAction::remove; } } continue; } // take a full copy, and fix up the prefixes for the namespaces, if required zeem::element &replacement(*el); scope scope(parentScope); for (auto &f : el->attributes()) { // the copy lost its namespace info if (node->namespace_for_prefix(f.get_prefix()) != ns() or f.name() != "fragment") continue; auto v = f.value(); auto s = v.find('('); auto p = params.begin(); while (s != std::string::npos and p != params.end()) { s += 1; auto e = v.find_first_of(",)", s); if (e == std::string::npos) break; auto argname = v.substr(s, e - s); auto &po = *p; if (po.is_object()) { object pe{ { "is-node-set", true }, { "node-set-name", argname } }; scope.put(argname, pe); auto ns = resolve_fragment_spec(node, dir, loader, po, parentScope); if (ns.nodes().empty()) scope.put(argname, po); else scope.set_nodeset(argname, std::move(ns)); } else scope.put(argname, po); ++p; s = e; } break; } if (tia == TemplateIncludeAction::include) { for (auto &child : replacement.nodes()) { auto i = node->nodes().emplace(node->end(), std::move(child)); process_node(i.operator->(), scope, dir, loader); } } else { zeem::element::iterator i; if (tia == TemplateIncludeAction::insert) i = node->emplace(node->end(), std::move(replacement)); else { i = node->parent()->emplace(node, std::move(replacement)); result = AttributeAction::remove; } auto e2 = dynamic_cast(&*i); if (e2 != nullptr) { auto &attrs = e2->attributes(); // if (templateID[0] == '#') // remove the copied ID // attr.erase("id"); attrs.erase(i->prefix_tag("ref", ns())); attrs.erase(i->prefix_tag("fragment", ns())); } process_node(i.operator->(), scope, dir, loader); } } if (result == AttributeAction::remove) static_cast(node->parent())->flatten_text(); else node->flatten_text(); return result; } // -------------------------------------------------------------------- tag_processor::AttributeAction tag_processor::process_attr_remove(zeem::element *node, zeem::attribute &attr, scope & /*scope*/, const std::filesystem::path & /*dir*/, basic_template_processor & /*loader*/) { auto mode = attr.value(); AttributeAction result = AttributeAction::none; if (mode == "all") result = AttributeAction::remove; else if (mode == "body") node->erase(node->begin(), node->end()); else if (mode == "all-but-first") { if (node->size() > 1) node->erase(std::next(node->begin()), node->end()); } else if (mode == "tag") { auto i = zeem::element::iterator(node); for (auto &c : *node) { i = node->parent()->emplace(i, std::move(c)); // process_node(i, scope, dir, loader); ++i; } result = AttributeAction::remove; } return result; } // -------------------------------------------------------------------- tag_processor::AttributeAction tag_processor::process_attr_classappend(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path & /*dir*/, basic_template_processor & /*loader*/) { for (;;) { auto s = attr.value(); s = process_el_2(scope, s); trim(s); if (s.empty()) break; auto c = node->attributes().find("class"); if (c == node->attributes().end()) { node->attributes().emplace({ "class", s }); break; } auto cs = c->value(); trim(cs); if (not cs.empty()) s.insert(0, cs + ' '); c->set_value(s); break; } return AttributeAction::none; } // -------------------------------------------------------------------- tag_processor::AttributeAction tag_processor::process_attr_styleappend(zeem::element *node, zeem::attribute &attr, scope &scope, const std::filesystem::path & /*dir*/, basic_template_processor & /*loader*/) { for (;;) { auto s = attr.value(); s = process_el_2(scope, s); trim(s); if (s.empty()) break; if (s.back() != ';') s += ';'; auto c = node->attributes().find("style"); if (c == node->attributes().end()) { node->attributes().emplace({ "style", s }); break; } auto cs = c->value(); trim(cs); if (not cs.empty()) { if (cs.back() == ';') s.insert(0, cs); else s.insert(0, cs + ';'); } c->set_value(s); break; } return AttributeAction::none; } } // namespace zeep::http libzeep-7.3.2/src/template-processor.cpp0000664000175000017500000002330415150027072020143 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2014-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/template-processor.hpp" #include "zeep/el/object.hpp" #include "zeep/el/processing.hpp" #include "zeep/exception.hpp" #include "zeep/http/header.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/scope.hpp" #include "zeep/http/tag-processor.hpp" #include "zeep/unicode-support.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; namespace zeep::http { // -------------------------------------------------------------------- // file_loader::file_loader(std::filesystem::path docroot) : resource_loader() , m_docroot(std::move(docroot)) { if (not m_docroot.empty() and not std::filesystem::exists(m_docroot)) throw std::runtime_error("Docroot '" + m_docroot.string() + "' does not seem to exist"); } /// return last_write_time of \a file std::filesystem::file_time_type file_loader::file_time(std::filesystem::path file, std::error_code &ec) noexcept { if (file.has_root_path()) file = fs::relative(file, file.root_path()); return fs::last_write_time(m_docroot / file, ec); } /// return last_write_time of \a file std::istream *file_loader::load_file(std::string file, std::error_code &ec) noexcept { fs::path path(file); if (path.has_root_path()) path = fs::relative(path, path.root_path()); std::ifstream *result = nullptr; if (not fs::is_regular_file(m_docroot / path)) ec = std::make_error_code(std::errc::no_such_file_or_directory); else { try { result = new std::ifstream(m_docroot / path, std::ios::binary); if (not result->is_open()) { delete result; result = nullptr; ec = std::make_error_code(std::errc::no_such_file_or_directory); } } catch (const std::bad_alloc &) { ec = std::make_error_code(std::errc::not_enough_memory); result = nullptr; } } return result; } // -------------------------------------------------------------------- // reply basic_template_processor::create_reply_for_get_file(const scope &scope) { // TODO: maarten - The time used here is local, not GMT. Needs fix? std::error_code ec; auto ft = file_time(scope["baseuri"].get(), ec); if (ec) return reply::stock_reply(status_type::not_found); using namespace std::chrono; auto fileDate = floor(time_point_cast(ft - decltype(ft)::clock::now() + system_clock::now())); for (const header &h : scope.get_headers()) { if (iequals(h.name, "If-Modified-Since")) { std::istringstream ss{ h.value }; std::tm tm; ss >> std::get_time(&tm, "%a, %d %b %Y %H:%M:%S GMT"); auto modifiedSince = system_clock::from_time_t(std::mktime(&tm)); if (fileDate <= modifiedSince) return reply::stock_reply(status_type::not_modified); break; } } fs::path file = scope["baseuri"].get(); std::unique_ptr in(load_file(file.string(), ec)); if (ec) return reply::stock_reply(status_type::not_found); std::string mimetype = "text/plain"; if (file.extension() == ".css") mimetype = "text/css"; else if (file.extension() == ".js") mimetype = "text/javascript"; else if (file.extension() == ".png") mimetype = "image/png"; else if (file.extension() == ".svg") mimetype = "image/svg+xml"; else if (file.extension() == ".html" or file.extension() == ".htm") mimetype = "text/html"; else if (file.extension() == ".xml" or file.extension() == ".xsl" or file.extension() == ".xslt") mimetype = "text/xml"; else if (file.extension() == ".xhtml") mimetype = "application/xhtml+xml"; else if (file.extension() == ".ico") mimetype = "image/x-icon"; else if (file.extension() == ".json" or file.extension() == ".schema") mimetype = "application/json"; else if (file.extension() == ".bz2") mimetype = "application/x-bzip2"; else if (file.extension() == ".gz") mimetype = "application/gzip"; reply result(status_type::ok); result.set_content(in.release(), mimetype); using namespace std::chrono; result.set_header("Last-Modified", std::format("{0:%a}, {0:%d} {0:%b} {0:%Y} {0:%H}:{0:%M}:{0:%S} GMT", fileDate)); return result; } void basic_template_processor::set_docroot(fs::path path) { m_docroot = std::move(path); } std::optional basic_template_processor::get_template_file(const std::string &file) { std::optional result; for (const char *ext : { "", ".xhtml", ".html", ".xml" }) { std::error_code ec; fs::path p = file + ext; (void)file_time(p.string(), ec); if (ec) continue; result = p; break; } return result; } void basic_template_processor::load_template(const std::string &file, zeem::document &doc) { std::string templateSelector; object spec; std::unique_ptr data; std::error_code ec; auto templateFile = get_template_file(file); if (templateFile.has_value()) data.reset(load_file(templateFile->string(), ec)); else { auto espec = evaluate_el_link({}, file); if (espec.is_object()) // reset the content, saves having to add another method { templateFile = get_template_file(espec["template"].get()); if (templateFile.has_value()) data.reset(load_file(templateFile->string(), ec)); templateSelector = espec["selector"]["xpath"].get(); } } if (not data) { #if defined(_WIN32) char msg[1024] = ""; DWORD dw = ::GetLastError(); if (dw != NO_ERROR) { char *lpMsgBuf = nullptr; int m = ::FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&lpMsgBuf, 0, NULL); if (lpMsgBuf != nullptr) { // strip off the trailing whitespace characters while (m > 0 and isspace(lpMsgBuf[m - 1])) --m; lpMsgBuf[m] = 0; strncpy(msg, lpMsgBuf, sizeof(msg)); ::LocalFree(lpMsgBuf); } } throw exception("error opening: " + (m_docroot / file).string() + " (" + msg + ")"); #else throw exception("error opening: " + (m_docroot / file).string() + " (" + strerror(errno) + ")"); #endif } doc.set_preserve_cdata(true); try { *data >> doc; } catch (const std::exception &ex) { std::clog << "Error parsing template: " << ex.what() << '\n'; throw; } if (not templateSelector.empty()) { // tricky? Find first matching fragment and make it the root node of the document zeem::context ctx; // this is problematic, take the first processor namespace for now. // TODO: maarten - fix this std::string ns; for (auto &tp : m_tag_processor_creators) { std::unique_ptr ptp(tp.second(tp.first)); if (dynamic_cast(ptp.get()) == nullptr) continue; ns = tp.first; ctx.set("ns", ns); break; } zeem::xpath xp(templateSelector); std::vector> result; for (auto n : xp.evaluate(doc, ctx)) { auto e = dynamic_cast(n); if (e == nullptr) continue; zeem::document dest; auto &attr = e->attributes(); if (spec["selector"]["by-id"]) attr.erase("id"); attr.erase(e->prefix_tag("ref", ns)); attr.erase(e->prefix_tag("fragment", ns)); auto parent = e->parent(); dest.push_back(std::move(*e)); if (parent->type() == zeem::node_type::element) zeem::fix_namespaces(dest.front(), static_cast(*parent), dest.front()); std::swap(doc, dest); break; } } } void basic_template_processor::create_reply_from_template(const std::string &file, const scope &scope, reply &reply) { zeem::document doc; doc.set_preserve_cdata(true); load_template(file, doc); process_tags(doc.child(), scope); doc.set_write_html(true); reply.set_content(doc); } void basic_template_processor::init_scope(request &/* req */, scope & /*scope*/) { } void basic_template_processor::process_tags(zeem::node *node, const scope &scope) { // only process elements if (dynamic_cast(node) == nullptr) return; std::set registeredNamespaces; for (auto &tpc : m_tag_processor_creators) registeredNamespaces.insert(tpc.first); if (not registeredNamespaces.empty()) process_tags(static_cast(node), scope, registeredNamespaces); // decorate all forms with a hidden input with name _csrf auto csrf = scope.get_csrf_token(); if (not csrf.empty()) { auto forms = zeem::xpath(R"(//form[not(input[@name='_csrf'])])"); zeem::context ctx; for (auto &form : forms.evaluate(*node, ctx)) form->emplace_back(zeem::element("input", { { "name", "_csrf" }, { "value", csrf }, { "type", "hidden" } })); } } void basic_template_processor::process_tags(zeem::element *node, const scope &scope, std::set registeredNamespaces) { std::set nss; for (auto &ns : node->attributes()) { if (not ns.is_namespace()) continue; if (registeredNamespaces.count(ns.value())) nss.insert(ns.value()); } for (auto &ns : nss) { std::unique_ptr processor(create_tag_processor(ns)); processor->process_xml(node, scope, "", *this); registeredNamespaces.erase(ns); } if (not registeredNamespaces.empty()) { for (auto &e : *node) process_tags(&e, scope, registeredNamespaces); } } } // namespace zeep::http libzeep-7.3.2/src/uri.cpp0000664000175000017500000003332515150027072015116 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2025-2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/uri.hpp" #include "zeep/unicode-support.hpp" #include #include #include #include #include #include namespace zeep { namespace { const char kHex[] = "0123456789ABCDEF"; } // ah, the beauty of regular expressions! // .... // Unfortunately, the implementation of many regular expression // libraries is sub-optimal. And thus we don't use this magic // anymore, apart from matching the IP_LITERAL part for a host. #define GEN_DELIMS R"([][]:/?#@])" #define SUB_DELIMS R"([!$&'()*+,;=])" // #define RESERVED GEN_DELIMS | SUB_DELIMS #define UNRESERVED R"([-._~A-Za-z0-9])" #define SCHEME R"([a-zA-Z][-+.a-zA-Z0-9]*)" #define PCT_ENCODED "%[[:xdigit:]]{2}" #define USERINFO "(?:" UNRESERVED "|" PCT_ENCODED "|" SUB_DELIMS "|" \ ":" \ ")*" #define REG_NAME "(?:" UNRESERVED "|" PCT_ENCODED "|" SUB_DELIMS ")*" #define PORT "[[:digit:]]*" #define DEC_OCTET "(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])" #define IPv4_ADDRESS DEC_OCTET R"(\.)" DEC_OCTET R"(\.)" DEC_OCTET R"(\.)" DEC_OCTET #define h16 "[[:xdigit:]]{1,4}" #define ls32 "(?:" h16 ":" h16 ")|" IPv4_ADDRESS #define IPv6_ADDRESS "(?:" \ "(?:" h16 ":){6}" ls32 "|" \ "::" \ "(?:" h16 ":){5}" ls32 "|" \ "(?:" h16 ")?" \ "::" \ "(?:" h16 ":){4}" ls32 "|" \ "(?:(?:" h16 ":){1}" h16 ")?" \ "::" \ "(?:" h16 ":){3}" ls32 "|" \ "(?:(?:" h16 ":){2}" h16 ")?" \ "::" \ "(?:" h16 ":){2}" ls32 "|" \ "(?:(?:" h16 ":){3}" h16 ")?" \ "::" \ "(?:" h16 ":){1}" ls32 "|" \ "(?:(?:" h16 ":){4}" h16 ")?" \ "::" ls32 "|" \ "(?:(?:" h16 ":){5}" h16 ")?" \ "::" h16 "|" \ "(?:(?:" h16 ":){6}" h16 ")?" \ "::" \ "|" \ ")" #define IPvFUTURE R"(v[[:xdigit:]]\.(?:)" UNRESERVED "|" SUB_DELIMS "|" \ ":" \ ")+" #define IP_LITERAL R"(\[(?:)" IPv6_ADDRESS "|" IPvFUTURE R"()\])" #define HOST IP_LITERAL "|" IPv4_ADDRESS "|" REG_NAME #define AUTHORITY "(" USERINFO "\\@" \ ")?(" HOST ")(:" PORT ")?" #define PCHAR UNRESERVED "|" PCT_ENCODED "|" SUB_DELIMS "|" \ ":" \ "|" \ "@" #define SEGMENT "(?:" PCHAR ")*" #define SEGMENT_NZ "(?:" PCHAR "){1,}" #define SEGMENT_NZ_NC "(?:" UNRESERVED "|" PCT_ENCODED "|" SUB_DELIMS "){1,}" #define PATH_ABEMPTY "(?:" \ "/" \ "(" SEGMENT "(?:/" SEGMENT ")*" \ "))?" #define PATH_ABSOLUTE "/" \ "(?:" SEGMENT_NZ "(?:" \ "/" SEGMENT ")*" \ ")?" #define PATH_ROOTLESS SEGMENT_NZ "(?:" \ "/" SEGMENT ")*" #define PATH_EMPTY "" #define HIER_PART "//" AUTHORITY PATH_ABEMPTY "|" \ "(" PATH_ABSOLUTE ")|" \ "(" PATH_ROOTLESS ")|" PATH_EMPTY #define QUERY "(?:\\?|/|" PCHAR ")*" #define FRAGMENT "(?:\\?|/|" PCHAR ")*" #define URI "^(?:(" SCHEME "):)?(?:" HIER_PART ")(?:\\?(" QUERY "))?(?:#(" FRAGMENT "))?$" // -------------------------------------------------------------------- // const std::regex kURIRx(URI); // -------------------------------------------------------------------- uri::uri(const std::string &url) { parse(url.c_str()); remove_dot_segments(); } uri::uri(const char *url) { parse(url); remove_dot_segments(); } uri::uri(const std::string &url, const uri &base) { parse(url.c_str()); transform(base); remove_dot_segments(); } void swap(uri &lhs, uri &rhs) noexcept { std::swap(lhs.m_scheme, rhs.m_scheme); std::swap(lhs.m_userinfo, rhs.m_userinfo); std::swap(lhs.m_host, rhs.m_host); std::swap(lhs.m_port, rhs.m_port); std::swap(lhs.m_path, rhs.m_path); std::swap(lhs.m_query, rhs.m_query); std::swap(lhs.m_fragment, rhs.m_fragment); std::swap(lhs.m_absolutePath, rhs.m_absolutePath); } std::string uri::string() const { std::ostringstream os; write(os, true); return os.str(); } std::string uri::unencoded_string() const { std::ostringstream os; write(os, false); return os.str(); } uri uri::get_path() const { uri result; result.m_absolutePath = m_absolutePath; result.m_path = m_path; return result; } void uri::set_path(const std::string &path) { m_path.clear(); m_absolutePath = false; auto cp = path.c_str(); if (*cp == '/') { m_absolutePath = true; ++cp; } cp = parse_segment(cp); while (*cp == '/') { ++cp; cp = parse_segment(cp); } remove_dot_segments(); } void uri::set_query(std::string query, bool encode) { if (encode) { std::ostringstream os; for (auto c : query) { if (is_unreserved(c) or is_sub_delim(c) or c == ':' or c == '@' or c == '/' or c == '?') os << c; else os << '%' << kHex[c >> 4] << kHex[c & 15]; } m_query = os.str(); } else m_query = std::move(query); } void uri::set_fragment(std::string fragment, bool encode) { if (encode) { std::ostringstream os; for (auto c : fragment) { if (is_unreserved(c) or is_sub_delim(c) or c == ':' or c == '@' or c == '/' or c == '?') os << c; else os << '%' << kHex[c >> 4] << kHex[c & 15]; } m_fragment = os.str(); } else m_fragment = std::move(fragment); } uri &uri::operator/=(const uri &rhs) { if (m_path.empty()) { m_absolutePath = rhs.m_absolutePath; m_path = rhs.m_path; } else { if (m_path.back().empty()) m_path.pop_back(); m_path.insert(m_path.end(), rhs.m_path.begin(), rhs.m_path.end()); } remove_dot_segments(); return *this; } uri uri::relative(const uri &base) const { uri result; if (m_scheme == base.m_scheme and m_userinfo == base.m_userinfo and m_host == base.m_host and m_port == base.m_port) { auto ab = m_path.begin(), ae = m_path.end(); auto bb = base.m_path.begin(), be = base.m_path.end(); result.m_absolutePath = true; while (ab != ae and bb != be and *ab == *bb) { result.m_absolutePath = false; ++ab; ++bb; } if (ab == ae and bb == be) result.m_path.emplace_back(""); else if (not result.m_absolutePath) { while (bb != be and bb + 1 != be) { result.m_path.emplace_back(".."); ++bb; } } if (ab != ae) { result.m_path.insert(result.m_path.end(), ab, ae); if (result.m_path.back().empty()) { if (result.m_path.size() == 1) result.m_path.back() = "."; else if (result.m_path[result.m_path.size() - 2] == "..") result.m_path.pop_back(); } } if (m_query != base.m_query) result.m_query = m_query; result.m_fragment = m_fragment; } else { if (m_scheme != base.m_scheme) result.m_scheme = m_scheme; if (m_userinfo != base.m_userinfo or m_host != base.m_host or m_port != base.m_port) { result.m_userinfo = m_userinfo; result.m_host = m_host; result.m_port = m_port; } result.m_absolutePath = m_absolutePath; result.m_path = m_path; result.m_query = m_query; result.m_fragment = m_fragment; } return result; } // -------------------------------------------------------------------- const char *uri::parse_scheme(const char *cp) { auto b = cp; if (is_scheme_start(*cp)) { do ++cp; while (is_scheme(*cp)); if (*cp == ':') { m_scheme.assign(b, cp); to_lower(m_scheme); ++cp; } else cp = b; } return cp; } const char *uri::parse_hierpart(const char *cp) { if (*cp == '/') { ++cp; if (*cp == '/') { ++cp; cp = parse_authority(cp); if (*cp == '/') m_absolutePath = true; while (*cp == '/') { ++cp; cp = parse_segment(cp); } if (m_path.empty() and not m_host.empty() and m_scheme == "file") throw uri_parse_error(); } else { m_absolutePath = true; cp = parse_segment(cp); while (*cp == '/') { ++cp; cp = parse_segment(cp); } } } else if (*cp != '?' and *cp != '#' and *cp != 0) { cp = parse_segment_nz(cp); while (*cp == '/') { ++cp; cp = parse_segment(cp); } } return cp; } const char *uri::parse_authority(const char *cp) { auto b = cp; while (is_userinfo(cp)) ++cp; if (*cp == '@') { m_userinfo.assign(b, cp); cp = parse_host(cp + 1); } else cp = parse_host(b); if (*cp == ':') { ++cp; while (*cp >= '0' and *cp <= '9') m_port = 10 * m_port + *cp++ - '0'; } return cp; } const char *uri::parse_host(const char *cp) { auto b = cp; if (*cp == '[') { ++cp; do ++cp; while (*cp != 0 and *cp != ']'); if (*cp != ']') throw uri_parse_error(); ++cp; static std::regex rx(IP_LITERAL); if (not std::regex_match(b, cp, rx)) throw uri_parse_error(); } else { while (is_reg_name(cp)) ++cp; } m_host.assign(b, cp); to_lower(m_host); if (m_host.empty()) throw uri_parse_error(); return cp; } const char *uri::parse_segment(const char *cp) { auto b = cp; while (is_pchar(cp)) ++cp; m_path.emplace_back(decode_url({ b, static_cast(cp - b) })); return cp; } const char *uri::parse_segment_nz(const char *cp) { cp = parse_segment(cp); if (m_path.back().empty()) throw uri_parse_error(); return cp; } const char *uri::parse_segment_nz_nc(const char *cp) { auto b = cp; if (not(is_unreserved(*cp) or is_pct_encoded(cp) or is_sub_delim(*cp))) throw uri_parse_error(); while (is_unreserved(*cp) or is_pct_encoded(cp) or is_sub_delim(*cp)) ++cp; m_path.emplace_back(decode_url({ b, static_cast(cp - b) })); return cp; } void uri::parse(const char *s) { m_scheme.clear(); m_userinfo.clear(); m_host.clear(); m_port = 0; m_path.clear(); m_query.clear(); m_fragment.clear(); m_absolutePath = false; auto cp = parse_scheme(s); cp = parse_hierpart(cp); if (*cp == '?') { ++cp; auto b = cp; while (*cp == '?' or *cp == '/' or is_pchar(cp)) ++cp; m_query.assign(b, cp); } if (*cp == '#') { ++cp; auto b = cp; while (*cp == '?' or *cp == '/' or is_pchar(cp)) ++cp; m_fragment.assign(b, cp); } // if (*cp != 0 or static_cast(cp - b) != s.length()) if (*cp != 0) throw uri_parse_error(); } void uri::remove_dot_segments() { std::vector out; auto in = m_path.begin(); while (in != m_path.end()) { if (*in == ".") { ++in; if (in == m_path.end()) { out.emplace_back(); break; } continue; } if (*in == "..") { if (not out.empty()) out.pop_back(); ++in; if (in == m_path.end()) { out.emplace_back(); break; } continue; } out.push_back(*in); ++in; continue; } std::swap(m_path, out); } void uri::transform(const uri &base) { if (m_scheme.empty()) { m_scheme = base.m_scheme; if (not has_authority()) { if (m_path.empty()) { m_absolutePath = base.m_absolutePath; m_path = base.m_path; if (m_query.empty()) m_query = base.m_query; } else if (not m_absolutePath) { if (base.m_path.size() > 1) m_path.insert(m_path.begin(), base.m_path.begin(), base.m_path.end() - 1); remove_dot_segments(); } m_userinfo = base.m_userinfo; m_host = base.m_host; m_port = base.m_port; } } } // -------------------------------------------------------------------- void uri::write(std::ostream &os, bool encoded) const { if (not m_scheme.empty()) os << m_scheme << ':'; bool write_slash = m_absolutePath; if (has_authority()) { os << "//"; if (not m_userinfo.empty()) os << m_userinfo << '@'; os << m_host; if (m_port != 0) os << ':' << m_port; write_slash = true; } for (auto &segment : m_path) { if (write_slash) os << '/'; write_slash = true; for (auto c : segment) { if (not encoded or is_unreserved(c) or is_sub_delim(c) or c == ':' or c == '@') os << c; else os << '%' << kHex[(c >> 4) & 0x0f] << kHex[c & 0x0f]; } } if (not m_query.empty()) os << '?' << m_query; if (not m_fragment.empty()) os << '#' << m_fragment; } // -------------------------------------------------------------------- // decode_url function std::string decode_url(std::string_view s) { std::string result; for (auto c = s.begin(); c != s.end(); ++c) { if (*c == '%') { if (s.end() - c >= 3) { int value; std::string s2(c + 1, c + 3); std::istringstream is(s2); if (is >> std::hex >> value) { result += static_cast(value); c += 2; } } } else if (*c == '+') result += ' '; else result += *c; } return result; } // -------------------------------------------------------------------- // encode_url function std::string encode_url(std::string_view s) { std::string result; for (char c : s) { auto a = static_cast(c); if (not uri::is_unreserved(a)) { result += '%'; result += kHex[a >> 4]; result += kHex[a & 15]; } else result += c; } return result; } // -------------------------------------------------------------------- bool is_valid_uri(const std::string &s) { bool result = true; try { uri u(s); } catch (...) { result = false; } return result; } bool is_fully_qualified_uri(const std::string &s) { bool result = true; try { uri u(s); result = not(u.get_scheme().empty() or u.get_path().empty()); } catch (...) { result = false; } return result; } bool is_valid_connect_host(std::string_view host) { std::regex rx(HOST ":" PORT); return std::regex_match(host.data(), host.data() + host.length(), rx); } } // namespace zeep libzeep-7.3.2/test/0000775000175000017500000000000015150027072013775 5ustar maartenmaartenlibzeep-7.3.2/test/CMakeLists.txt0000664000175000017500000000321215150027072016533 0ustar maartenmaarten# Copyright Maarten L. Hekkelman 2025 # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) CPMFindPackage( NAME Catch2 GIT_REPOSITORY "https://github.com/catchorg/Catch2" VERSION 3.4.0 EXCLUDE_FROM_ALL YES) CPMFindPackage( NAME mcfp GIT_REPOSITORY "https://forge.hekkelman.net/maarten/mcfp.git" VERSION 1.4.2 EXCLUDE_FROM_ALL YES) # unit parser serializer xpath json crypto http processor webapp soap rest security uri list(APPEND zeep_tests crypto el http processor webapp # soap rest security uri ) if(HTTP_HAS_UNIX_DAEMON) list(APPEND zeep_tests daemon) endif() if(USE_RSRC) list(APPEND zeep_tests rsrc_webapp) endif() add_library(zeep-test-lib OBJECT ${CMAKE_CURRENT_SOURCE_DIR}/test-main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/client-test-code.cpp ) target_link_libraries(zeep-test-lib zeep mcfp::mcfp Catch2::Catch2) foreach(TEST IN LISTS zeep_tests) set(ZEEP_TEST "${TEST}-test") set(ZEEP_TEST_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/${ZEEP_TEST}.cpp") add_executable(${ZEEP_TEST}) target_sources(${ZEEP_TEST} PRIVATE ${ZEEP_TEST_SOURCE}) if(USE_RSRC AND("${TEST}" STREQUAL "processor" OR "${TEST}" STREQUAL "rsrc_webapp")) mrc_target_resources(${ZEEP_TEST} ${CMAKE_CURRENT_SOURCE_DIR}/fragment-file.xhtml) endif() target_link_libraries(${ZEEP_TEST} PRIVATE zeep-test-lib) if(MSVC) # Specify unwind semantics so that MSVC knowns how to handle exceptions target_compile_options(${ZEEP_TEST} PRIVATE /EHsc) endif() add_test(NAME ${ZEEP_TEST} COMMAND $ --docroot ${CMAKE_CURRENT_SOURCE_DIR}) endforeach()libzeep-7.3.2/test/client-test-code.cpp0000664000175000017500000000361715150027072017653 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2025 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/asio.hpp" #include "zeep/http/message-parser.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/streambuf.hpp" #include #include #include namespace zh = zeep::http; zh::reply simple_request(uint16_t port, const std::string &req) { using asio_ns::ip::tcp; #if BOOST_VERSION > 107000 asio_ns::io_context io_context; tcp::resolver resolver(io_context); tcp::resolver::results_type endpoints = resolver.resolve("localhost", std::to_string(port)); tcp::socket socket(io_context); asio_ns::connect(socket, endpoints); #else asio_ns::io_context io_context; tcp::resolver resolver(io_context); auto endpoint_iterator = resolver.resolve("localhost", std::to_string(port)); tcp::socket socket(io_context); asio_ns::connect(socket, endpoint_iterator); #endif asio_system_ns::error_code ignored_error; asio_ns::write(socket, asio_ns::buffer(req), ignored_error); zh::reply result; zh::reply_parser p; for (;;) { std::array buf; // NOLINT(hicpp-member-init) asio_system_ns::error_code error; size_t len = socket.read_some(asio_ns::buffer(buf), error); if (error == asio_ns::error::eof) break; // Connection closed cleanly by peer. else if (error) throw asio_system_ns::system_error(error); // Some other error. zeep::char_streambuf sb(buf.data(), len); auto r = p.parse(sb); if (r == true) { result = p.get_reply(); break; } } return result; } zh::reply simple_request(uint16_t port, const zeep::http::request &req) { std::ostringstream os; os << req; return simple_request(port, os.str()); }libzeep-7.3.2/test/client-test-code.hpp0000664000175000017500000000112115150027072017644 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, Radboud University 2008-2013. // Copyright Maarten L. Hekkelman, 2014-2025 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once #include #include #include #include #include zeep::http::reply simple_request(uint16_t port, const std::string& req); zeep::http::reply simple_request(uint16_t port, const zeep::http::request& req); libzeep-7.3.2/test/crypto-test.cpp0000664000175000017500000001171615150027072017004 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include TEST_CASE("http_base64_1") { using namespace std::literals; auto in = R"(Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.)"s; auto out = R"(TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4= )"s; auto test = zeep::encode_base64(in, 76); CHECK(test == out); auto s = zeep::decode_base64(test); CHECK(s == in); } TEST_CASE("http_base32_1") { using namespace std::literals; auto in = R"(Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.)"s; auto out = R"(JVQW4IDJOMQGI2LTORUW4Z3VNFZWQZLEFQQG433UEBXW43DZEBRHSIDINFZSA4TFMFZW63RMEBRH K5BAMJ4SA5DINFZSA43JNZTXK3DBOIQHAYLTONUW63RAMZZG63JAN52GQZLSEBQW42LNMFWHGLBA O5UGSY3IEBUXGIDBEBWHK43UEBXWMIDUNBSSA3LJNZSCYIDUNBQXIIDCPEQGCIDQMVZHGZLWMVZG C3TDMUQG6ZRAMRSWY2LHNB2CA2LOEB2GQZJAMNXW45DJNZ2WKZBAMFXGIIDJNZSGKZTBORUWOYLC NRSSAZ3FNZSXEYLUNFXW4IDPMYQGW3TPO5WGKZDHMUWCAZLYMNSWKZDTEB2GQZJAONUG64TUEB3G K2DFNVSW4Y3FEBXWMIDBNZ4SAY3BOJXGC3BAOBWGKYLTOVZGKLQ= )"s; auto test = zeep::encode_base32(in, 76); CHECK(test == out); auto s = zeep::decode_base32(test); CHECK(s == in); } TEST_CASE("http_base64_2") { using namespace std::literals; const std::string tests[] = { "1", "12", "123", "1234", { '\0' }, { '\0', '\001' }, { '\0', '\001', '\002' } }; for (const auto &test : tests) { auto enc = zeep::encode_base64(test, 76); auto dec = zeep::decode_base64(enc); CHECK(dec == test); } } TEST_CASE("crypto_md5_1") { auto h = zeep::encode_hex(zeep::md5("1234")); CHECK(h == "81dc9bdb52d04dc20036dbd8313ed055"); } TEST_CASE("crypto_sha1_1") { auto h = zeep::encode_hex(zeep::sha1("The quick brown fox jumps over the lazy dog")); CHECK(h == "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12"); } TEST_CASE("crypto_sha256_1") { auto h = zeep::encode_hex(zeep::sha256("")); CHECK(h == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); h = zeep::encode_hex(zeep::sha256("1")); CHECK(h == "6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b"); h = zeep::encode_hex(zeep::sha256("The SHA (Secure Hash Algorithm) is one of a number of cryptographic hash functions. A cryptographic hash is like a signature for a data set. If you would like to compare two sets of raw data (source of the file, text or similar) it is always better to hash it and compare SHA256 values. It is like the fingerprints of the data. Even if only one symbol is changed the algorithm will produce different hash value. SHA256 algorithm generates an almost-unique, fixed size 256-bit (32-byte) hash. Hash is so called a one way function. This makes it suitable for checking integrity of your data, challenge hash authentication, anti-tamper, digital signatures, blockchain.")); CHECK(h == "ae8bd70b42c2877e6800f3da2800044c8694f201242a484d38bb7941645e8876"); } TEST_CASE("crypto_hmac_1") { auto h = zeep::encode_hex(zeep::hmac_sha256("The quick brown fox jumps over the lazy dog", "key")); CHECK(h == "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"); h = zeep::encode_base64(zeep::hmac_sha1("The quick brown fox jumps over the lazy dog", "key")); CHECK(h == "3nybhbi3iqa8ino29wqQcBydtNk="); h = zeep::encode_hex(zeep::hmac_sha256("The quick brown fox jumps over the lazy dog", "key")); CHECK(h == "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"); h = zeep::encode_hex(zeep::hmac_md5("The quick brown fox jumps over the lazy dog", "key")); CHECK(h == "80070713463e7749b90c2dc24911e275"); } TEST_CASE("crypto_pbkdf2") { auto h = zeep::encode_hex(zeep::pbkdf2_hmac_sha256("1234", "key", 10, 16)); CHECK(h == "458d81e7a1defc5d0b61708a7dc06233"); } TEST_CASE("streambuf_1") { const char s[] = "Hello, world!"; auto sb = zeep::char_streambuf(s); std::istream is(&sb); auto len = is.seekg(0, std::ios_base::end).tellg(); CHECK(len == strlen(s)); is.seekg(0); std::vector b(len); is.read(b.data(), len); CHECK(is.tellg() == len); CHECK(std::string(b.begin(), b.end()) == s); }libzeep-7.3.2/test/daemon-test.cpp0000664000175000017500000000453115150027072016724 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "client-test-code.hpp" #include "zeep/http/controller.hpp" #include "zeep/http/daemon.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/server.hpp" #include #include #include #include #include #include #include #include #include namespace zh = zeep::http; // a very simple controller, serving only /test/one and /test/three class my_controller : public zh::controller { public: my_controller() : zh::controller("/") { map_get_request("test", &my_controller::test); } zh::reply test() { return { zh::status_type::ok }; } }; TEST_CASE("daemon-test-1") { // start up a http server and stop it again std::filesystem::path log_dir = std::filesystem::temp_directory_path() / "daemon-test"; std::filesystem::remove_all(log_dir); std::filesystem::create_directories(log_dir); std::filesystem::path access_file, error_file; access_file = log_dir / "access.log"; error_file = log_dir / "error.log"; auto pw = getpwuid(getuid()); REQUIRE(pw != nullptr); zh::daemon d([]() { auto s = new zh::server; s->add_controller(new my_controller()); return s; }, log_dir / "daemon-test.pid", access_file.string(), error_file.string()); std::random_device rng; uint16_t port = 1024 + (rng() % 10240); std::clog << "starting daemon at port " << port << '\n'; SECTION("single process") { d.start("::", port, 1, pw->pw_name); } SECTION("pre-forked process") { d.start("::", port, 1, 1, pw->pw_name); } using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); auto reply = simple_request(port, "GET /test HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::ok); std::filesystem::rename(access_file, log_dir / "access.log.1"); std::filesystem::rename(error_file, log_dir / "error.log.1"); d.reload(); std::this_thread::sleep_for(100ms); reply = simple_request(port, "GET /test HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::ok); d.stop(); CHECK(std::filesystem::file_size(access_file) > 0); CHECK(std::filesystem::file_size(error_file) > 0); }libzeep-7.3.2/test/el-test.cpp0000664000175000017500000000632215150027072016061 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; namespace e = zeep::el; struct Opname { string id; map standen; template void serialize(Archive &ar, uint64_t /*version*/) { // clang-format off ar & zeem::name_value_pair("id", id) & zeem::name_value_pair("standen", standen); // clang-format on } auto operator<=>(const Opname &) const = default; }; static_assert(not std::is_constructible_v); using a_map_type = map; static_assert(e::is_serializable_map_type_v); static_assert(std::is_constructible_v); TEST_CASE("test-1") { Opname opn{ "1", { { "een", 0.1f }, { "twee", 0.2f } } }; e::object o = e::serializer::serialize(opn); std::cout << o << "\n"; Opname opn2 = e::serializer::deserialize(o); CHECK(opn == opn2); } TEST_CASE("test-2") { std::vector opnames{ { "1", { { "een", 0.1f }, { "twee", 0.2f } } }, { "2", { { "drie", 0.3f }, { "vier", 0.4f } } }, }; e::object o = e::serializer>::serialize(opnames); std::cout << o << "\n"; auto opn2 = e::serializer>::deserialize(o); CHECK(opnames == opn2); } TEST_CASE("test-3") { Opname opn{ "1", { { "een", 0.1f }, { "twee", 0.2f } } }; auto o_opn = std::make_optional(opn); // static_assert(zeep::el::is_serializable_optional_type_v>, ""); e::object o = e::serializer>::serialize(o_opn); std::cout << o << "\n"; auto opn2 = e::serializer>::deserialize(o); CHECK(*o_opn == opn2); // check with empty o_opn.reset(); o = e::serializer>::serialize(o_opn); std::cout << o << "\n"; opn2 = e::serializer>::deserialize(o); CHECK_FALSE(opn2.has_value()); } TEST_CASE("test-4") { auto now = std::chrono::system_clock::now(); auto o = e::serializer::serialize(now); std::cout << o << '\n'; auto n = e::serializer::deserialize(o); CHECK(n == now); } TEST_CASE("test-5") { zeep::http::scope scope; Opname opn{ "1", { { "een", 0.1f }, { "twee", 0.2f } } }; static_assert(e::is_serializable_to_object_v); scope.put("o1", e::to_object(opn)); std::vector opn_v{ opn, opn }; scope.put("o2", e::to_object(opn_v)); } TEST_CASE("test-6") { enum class Status { RUNNING, STOPPED }; zeem::value_serializer::init({ { Status::RUNNING, "running" }, { Status::STOPPED, "stopped" } }); Status status = Status::RUNNING; e::object o = e::serializer::serialize(status); std::cout << o << "\n"; auto status2 = e::serializer::deserialize(o); CHECK(status == status2); }libzeep-7.3.2/test/fragment-file.xhtml0000664000175000017500000000037515150027072017600 0ustar maartenmaarten
fragment-1
fragment-2
libzeep-7.3.2/test/http-test.cpp0000664000175000017500000003415715150027072016447 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "../src/signals.hpp" #include "client-test-code.hpp" #include "zeep/config.hpp" #include "zeep/crypto.hpp" #include "zeep/http/controller.hpp" #include "zeep/http/daemon.hpp" #include "zeep/http/login-controller.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/http/security.hpp" #include "zeep/http/server.hpp" #include "zeep/uri.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace zh = zeep::http; TEST_CASE("http_base64_1") { using namespace std::literals; auto in = R"(Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.)"s; auto out = R"(TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4= )"s; auto test = zeep::encode_base64(in, 76); CHECK(test == out); auto s = zeep::decode_base64(test); CHECK(s == in); } TEST_CASE("http_base64_2") { using namespace std::literals; const std::string tests[] = { "1", "12", "123", "1234", { '\0' }, { '\0', '\001' }, { '\0', '\001', '\002' } }; for (const auto &test : tests) { auto enc = zeep::encode_base64(test, 76); auto dec = zeep::decode_base64(enc); CHECK(dec == test); } } // TEST_CASE("connection_read") // { // #pragma message("write test for avail/used") // } TEST_CASE("request_params_1") { zh::request req{ "GET", "http://www.example.com/index?a=A;b=B&c=C%24" }; CHECK(req.get_parameter("a") == "A"); CHECK(req.get_parameter("b") == "B"); CHECK(req.get_parameter("c") == "C$"); } TEST_CASE("webapp_6") { zh::request req("GET", "/", { 1, 0 }, { { "Content-Type", "multipart/form-data; boundary=xYzZY" } }, "--xYzZY\r\n" "Content-Disposition: form-data; name=\"pdb-file\"; filename=\"1cbs.cif.gz\"\r\n" "Content-Encoding: gzip\r\n" "Content-Type: chemical/x-cif\r\n" "\r\n" "hello, world!\n\r\n" "--xYzZY\r\n" "Content-Disposition: form-data; name=\"mtz-file\"; filename=\"1cbs_map.mtz\"\r\n" "Content-Type: text/plain\r\n" "\r\n" "And again, hello!\n\r\n" "--xYzZY--\r\n"); auto fp1 = req.get_file_parameter("pdb-file"); CHECK(fp1.filename == "1cbs.cif.gz"); CHECK(fp1.mimetype == "chemical/x-cif"); CHECK(std::string(fp1.data, fp1.data + fp1.length) == "hello, world!\n"); auto fp2 = req.get_file_parameter("mtz-file"); CHECK(fp2.filename == "1cbs_map.mtz"); CHECK(fp2.mimetype == "text/plain"); CHECK(std::string(fp2.data, fp2.data + fp2.length) == "And again, hello!\n"); } // a very simple controller, serving only /test/one and /test/three class my_controller : public zeep::http::controller { public: my_controller() : zeep::http::controller("/test") { } bool handle_request(zeep::http::request &req, zeep::http::reply &rep) override { bool result = false; if (req.get_uri() == "/test/one" or req.get_uri() == "/test/three") { rep = zeep::http::reply::stock_reply(zeep::http::status_type::ok); result = true; } return result; } }; TEST_CASE("webapp_7") { // start up a http server and stop it again zh::daemon d([]() { auto s = new zh::server; s->add_controller(new my_controller()); return s; }, "zeep-http-test"); std::random_device rng; uint16_t port = 1024 + (rng() % 10240); std::thread t([&d, port] { return d.run_foreground("::", port); }); std::clog << "started daemon at port " << port << '\n'; using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); try { auto reply = simple_request(port, "GET / HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::not_found); reply = simple_request(port, "XXX / HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::bad_request); reply = simple_request(port, "GET /test/one HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::ok); reply = simple_request(port, "GET /test/two HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::not_found); } catch (const std::exception &e) { std::clog << e.what() << '\n'; } zeep::signal_catcher::signal_hangup(t); t.join(); } // Single process variant #if HTTP_HAS_UNIX_DAEMON TEST_CASE("webapp_8") { // start up a http server and stop it again zh::daemon d([]() { auto s = new zh::server; s->add_controller(new my_controller()); return s; }, "/tmp/libzeep-tests/zeep-http-test.pid", "/tmp/libzeep-tests/zeep-http-test-access.log", "/tmp/libzeep-tests/zeep-http-test-error.log"); std::random_device rng; uint16_t port = 1024 + (rng() % 10240); d.start("::", port, 1, ""); std::clog << "started daemon for test_8 at port " << port << '\n'; using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); try { auto reply = simple_request(port, "GET / HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::not_found); reply = simple_request(port, "XXX / HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::bad_request); reply = simple_request(port, "GET /test/one HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::ok); reply = simple_request(port, "GET /test/two HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::not_found); } catch (const std::exception &e) { std::clog << e.what() << '\n'; throw; } d.stop(); } #endif // authentication test TEST_CASE("server_with_security_1") { class my_user_service : public zeep::http::user_service { public: [[nodiscard]] zeep::http::user_details load_user(const std::string &username) const override { if (username != "scott") throw zeep::http::user_unknown_exception(); return { username, m_pwenc.encode("tiger"), { "admin" } }; } zeep::http::pbkdf2_sha256_password_encoder m_pwenc; } users; std::string secret = "geheim"; zh::daemon d([&]() { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) auto s = new zh::server(new zeep::http::security_context(secret, users, new zeep::http::pbkdf2_sha256_password_encoder())); s->add_controller(new my_controller()); s->add_controller(new zeep::http::login_controller()); auto& sec = s->get_security_context(); sec.add_rule("/test/three", "admin"); sec.add_rule("/**", {}); return s; }, "zeep-http-test"); std::random_device rng; uint16_t port = 1024 + (rng() % 10240); std::thread t([&d, port] { d.run_foreground("::", port); }); std::clog << "started daemon at port " << port << '\n'; using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); try { auto reply = simple_request(port, "XXX / HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::bad_request); reply = simple_request(port, "GET /test/one HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::ok); reply = simple_request(port, "GET /test/two HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::not_found); reply = simple_request(port, "GET /test/three HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::unauthorized); // now try to log in and see if we can access all of the above // we use a request object now, to store cookies zeep::http::request req{ "POST", "/login", { 1, 0 }, { { "Content-Type", "application/x-www-form-urlencoded" } }, "username=scott&password=tiger" }; // first test is to send a POST to login, but without the csrf token reply = simple_request(port, req); CHECK(reply.get_status() == zh::status_type::forbidden); // OK, fetch the login form then and pry the csrf token out of it req.set_method("GET"); reply = simple_request(port, req); REQUIRE(reply.get_status() == zh::status_type::ok); // copy the cookie auto csrfCookie = reply.get_cookie("csrf-token"); req.set_cookie("csrf-token", csrfCookie); zeem::document form(reply.get_content()); auto csrf = form.find_first("//input[@name='_csrf']"); REQUIRE(csrf != form.end()); CHECK(form.find_first("//input[@name='username']") != form.end()); CHECK(form.find_first("//input[@name='password']") != form.end()); CHECK(form.find_first("//input[@name='not-there']") == form.end()); CHECK(csrf->get_attribute("value") == csrfCookie); // try again to authenticate req.set_method("POST"); req.set_content("username=scott&password=tiger&_csrf=" + csrfCookie, "application/x-www-form-urlencoded"); reply = simple_request(port, req); CHECK(reply.get_status() == zh::status_type::see_other); auto accessToken = reply.get_cookie("access_token"); req.set_cookie("access_token", accessToken); // now try that admin page again req.set_uri("/test/three"); req.set_method("GET"); reply = simple_request(port, req); CHECK(reply.get_status() == zh::status_type::ok); } catch (const std::exception &e) { std::clog << e.what() << '\n'; } zeep::signal_catcher::signal_hangup(t); t.join(); } // -------------------------------------------------------------------- TEST_CASE("long_filename_test_1") { // start up a http server and stop it again zh::daemon d([]() { auto s = new zh::server; s->add_controller(new my_controller()); return s; }, "zeep-http-test"); std::random_device rng; uint16_t port = 1024 + (rng() % 10240); std::thread t([&d, port] { return d.run_foreground("::", port); }); std::clog << "started daemon at port " << port << '\n'; using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); try { auto reply = simple_request(port, "GET /%E3%80%82%E7%84%B6%E8%80%8C%EF%BC%8C%E9%9C%80%E8%A6%81%E6%B3%A8%E6%84%8F%E7%9A%84%E6%98%AF%EF%BC%8C%E8%AF%A5%E7%BD%91%E7%AB%99%E5%B7%B2%E7%BB%8F%E5%BE%88%E4%B9%85%E6%B2%A1%E6%9C%89%E6%9B%B4%E6%96%B0%E4%BA%86%EF%BC%8C%E5%9B%A0%E6%AD%A4%E5%8F%AF%E8%83%BD%E6%97%A0%E6%B3%95%E6%8F%90%E4%BE%9B%E6%9C%80%E6%96%B0%E7%9A%84%E8%BD%AF%E4%BB%B6%E7%89%88%E6%9C%AC%E5%92%8C%E7%9B%B8%E5%85%B3%E8%B5%84%E6%BA%90%E3%80%82 HTTP/1.1\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::not_found); } catch (const std::exception &e) { std::clog << e.what() << '\n'; } zeep::signal_catcher::signal_hangup(t); t.join(); } // -------------------------------------------------------------------- TEST_CASE("pen_test_resilience_1") { // start up a http server and stop it again zh::daemon d([]() { auto s = new zh::server; s->add_controller(new my_controller()); return s; }, "zeep-http-test"); std::random_device rng; uint16_t port = 1024 + (rng() % 10240); std::thread t([&d, port] { return d.run_foreground("::", port); }); std::clog << "started daemon at port " << port << '\n'; using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); try { auto reply = simple_request(port, "GET //plus/mytag_js.php?aid=9999&nocache=90sec HTTP/1.1\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::not_found); reply = simple_request(port, "GET //plus/erraddsave.php?dopost=saveedit&a=b&arrs1[]=99&c=d&arrs1[]=102&arrs1[]=103&arrs1[]=95&arrs1[]=100&arrs1[]=98&arrs1[]=112&arrs1[]=114&arrs1[]=101&arrs1[]=102&arrs1[]=105&arrs1[]=120&arrs2[]=109&arrs2[]=121&arrs2[]=97&arrs2[]=100&arrs2[]=96&arrs2[]=32&arrs2[]=40&arrs2[]=97&arrs2[]=105&arrs2[]=100&arrs2[]=44&arrs2[]=110&arrs2[]=111&arrs2[]=114&arrs2[]=109&arrs2[]=98&arrs2[]=111&arrs2[]=100&arrs2[]=121&arrs2[]=41&arrs2[]=32&arrs2[]=86&arrs2[]=65&arrs2[]=76&arrs2[]=85&arrs2[]=69&arrs2[]=83&arrs2[]=40&arrs2[]=56&arrs2[]=56&arrs2[]=56&arrs2[]=56&arrs2[]=44&arrs2[]=39&arrs2[]=60&arrs2[]=63&arrs2[]=112&arrs2[]=104&arrs2[]=112&arrs2[]=32&arrs2[]=105&arrs2[]=102&arrs2[]=40&arrs2[]=105&arrs2[]=115&arrs2[]=115&arrs2[]=101&arrs2[]=116&arrs2[]=40&arrs2[]=36&arrs2[]=95&arrs2[]=80&arrs2[]=79&arrs2[]=83&arrs2[]=84&arrs2[]=91&arrs2[]=39&arrs2[]=39&arrs2[]=108&arrs2[]=101&arrs2[]=109&arrs2[]=111&arrs2[]=110&arrs2[]=39&arrs2[]=39&arrs2[]=93&arrs2[]=41&arrs2[]=41&arrs2[]=123&arrs2[]=36&arrs2[]=97&arrs2[]=61&arrs2[]=115&arrs2[]=116&arrs2[]=114&arrs2[]=114&arrs2[]=101&arrs2[]=118&arrs2[]=40&arrs2[]=39&arrs2[]=39&arrs2[]=101&arrs2[]=99&arrs2[]=97&arrs2[]=108&arrs2[]=112&arrs2[]=101&arrs2[]=114&arrs2[]=95&arrs2[]=103&arrs2[]=101&arrs2[]=114&arrs2[]=112&arrs2[]=39&arrs2[]=39&arrs2[]=41&arrs2[]=59&arrs2[]=36&arrs2[]=98&arrs2[]=61&arrs2[]=115&arrs2[]=116&arrs2[]=114&arrs2[]=114&arrs2[]=101&arrs2[]=118&arrs2[]=40&arrs2[]=39&arrs2[]=39&arrs2[]=101&arrs2[]=100&arrs2[]=111&arrs2[]=99&arrs2[]=101&arrs2[]=100&arrs2[]=95&arrs2[]=52&arrs2[]=54&arrs2[]=101&arrs2[]=115&arrs2[]=97&arrs2[]=98&arrs2[]=39&arrs2[]=39&arrs2[]=41&arrs2[]=59&arrs2[]=36&arrs2[]=97&arrs2[]=40&arrs2[]=39&arrs2[]=39&arrs2[]=47&arrs2[]=94&arrs2[]=47&arrs2[]=101&arrs2[]=39&arrs2[]=39&arrs2[]=44&arrs2[]=36&arrs2[]=98&arrs2[]=40&arrs2[]=39&arrs2[]=39&arrs2[]=90&arrs2[]=88&arrs2[]=90&arrs2[]=104&arrs2[]=98&arrs2[]=67&arrs2[]=104&arrs2[]=105&arrs2[]=89&arrs2[]=88&arrs2[]=78&arrs2[]=108&arrs2[]=78&arrs2[]=106&arrs2[]=82&arrs2[]=102&arrs2[]=90&arrs2[]=71&arrs2[]=86&arrs2[]=106&arrs2[]=98&arrs2[]=50&arrs2[]=82&arrs2[]=108&arrs2[]=75&arrs2[]=67&arrs2[]=82&arrs2[]=102&arrs2[]=85&arrs2[]=107&arrs2[]=86&arrs2[]=82&arrs2[]=86&arrs2[]=85&arrs2[]=86&arrs2[]=84&arrs2[]=86&arrs2[]=70&arrs2[]=116&arrs2[]=54&arrs2[]=77&arrs2[]=70&arrs2[]=48&arrs2[]=112&arrs2[]=75&arrs2[]=81&arrs2[]=61&arrs2[]=61&arrs2[]=39&arrs2[]=39&arrs2[]=41&arrs2[]=44&arrs2[]=48&arrs2[]=41&arrs2[]=59&arrs2[]=125&arrs2[]=63&arrs2[]=62&arrs2[]=39&arrs2[]=41&arrs2[]=59&arrs2[]=0 HTTP/1.1\r\n\r\n"); CHECK(reply.get_status() == zh::status_type::bad_request); } catch (const std::exception &e) { std::clog << e.what() << '\n'; } zeep::signal_catcher::signal_hangup(t); t.join(); }libzeep-7.3.2/test/processor-test.cpp0000664000175000017500000006011415150027072017477 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2025 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "test-main.hpp" #include "zeep/el/processing.hpp" #include "zeep/http/header.hpp" #include "zeep/http/request.hpp" #include "zeep/uri.hpp" #include "zeep/el/object.hpp" #include "zeep/http/scope.hpp" #include "zeep/http/tag-processor.hpp" #include "zeep/http/template-processor.hpp" #include #include #include #include #include #include #include #include #include #include using namespace zeem::literals; void process_and_compare(zeem::document &a, zeem::document &b, const zeep::http::scope &scope = {}) { zeep::http::template_processor p(gDocrootDir); zeep::http::tag_processor tp; tp.process_xml(a.child(), scope, "", p); CHECK(a == b); } TEST_CASE("test_0") { std::string s = "application/pdf"; CHECK(not zeep::http::process_el({}, s)); CHECK(s == "application/pdf"); } TEST_CASE("test_1") { auto doc = R"(
)"_xml; auto doc_test = R"(
)"_xml; process_and_compare(doc, doc_test); } TEST_CASE("test_2") { auto doc = R"( )"_xml; auto doc_test = R"( )"_xml; zeep::http::scope scope; scope.put("b", zeep::el::object{ "a", "b", "c" }); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_3") { auto doc = R"( )"_xml; auto doc_test = R"( <hallo, wereld!> )"_xml; zeep::http::scope scope; scope.put("x", ""); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_3a") { auto doc = R"( )"_xml; auto doc_test = R"( hallo, wereld! )"_xml; zeep::http::scope scope; scope.put("x", "hallo, wereld!"); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_4") { auto doc = R"( [[${x}]] )"_xml; auto doc_test = R"( hallo, wereld! )"_xml; zeep::http::scope scope; scope.put("x", "hallo, wereld!"); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_5") { auto doc = R"( [(${x})] )"_xml; auto doc_test = R"( hallo, wereld! )"_xml; zeep::http::scope scope; scope.put("x", "hallo, wereld!"); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_6") { auto doc = R"( )"_xml; auto doc_test = R"( )"_xml; zeep::http::scope scope; scope.put("x", "\"'hallo, wereld!'\""); scope.put("y", "Een \"moeilijke\" string"); scope.put("a", zeep::el::object{ "a", "b", "c" }); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_8") { auto doc = R"( )"_xml; auto doc_test = R"( abc )"_xml; zeep::http::scope scope; scope.put("a", zeep::el::object{ "a", "b", "c" }); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_8a") { auto doc = R"( )"_xml; auto doc_test = R"( abc )"_xml; zeep::http::scope scope; scope.put("a", zeep::el::object{ "a", "b", "c" }); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_9") { auto doc = R"( )"_xml; auto doc_test = R"( {"count":1,"current":"a","even":false,"first":true,"index":0,"last":false,"odd":true,"size":3}{"count":2,"current":"b","even":true,"first":false,"index":1,"last":false,"odd":false,"size":3}{"count":3,"current":"c","even":false,"first":false,"index":2,"last":true,"odd":true,"size":3} )"_xml; zeep::http::scope scope; scope.put("a", zeep::el::object{ "a", "b", "c" }); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_10") { auto doc = R"( )"_xml; auto doc_test = R"( )"_xml; zeep::http::scope scope; scope.put("id", "my-id-101"); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_11") { auto doc = R"( )"_xml; auto doc_test = R"( )"_xml; zeep::http::scope scope; scope.put("id", "my-id-101"); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_12") { auto doc = R"( )"_xml; auto doc_test = R"( )"_xml; zeep::http::scope scope; scope.put("ok", true); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_13") { auto doc = R"( )"_xml; auto doc_test = R"( s )"_xml; zeep::http::scope scope; scope.put("ok", true); scope.put("s", "s"); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_14") { try { std::locale l("nl_NL.UTF-8"); if (l.name() != "nl_NL.UTF-8") throw std::runtime_error("locale name not equal, not installed?"); auto doc = R"( )"_xml; auto doc_test = R"( 7 augustus 2019, 12:14 )"_xml; zeep::http::template_processor p(gDocrootDir); zeep::http::tag_processor tp; zeep::http::request req("GET", "/", { 1, 0 }, { { "Accept-Language", "nl, en-US;q=0.7, en;q=0.3" } }, ""); zeep::http::scope scope(req); scope.put("ok", true); process_and_compare(doc, doc_test, scope); } catch (const std::runtime_error &) { std::clog << "skipping test 14 since locale nl_NL.UTF-8 is not available\n"; } } // The latest version of macOS finally does this test right. // But I don't know how to test for this newest version of macOS // so lets simply skip this test on all macOS versions. #ifndef __APPLE__ TEST_CASE("test_15") { try { std::locale l("da_DK.UTF-8"); if (l.name() != "da_DK.UTF-8") throw std::runtime_error("locale name not equal, not installed?"); auto doc = R"( )"_xml; auto doc_test = R"( 7 august 2019, 12:14 )"_xml; zeep::http::template_processor p(gDocrootDir); zeep::http::tag_processor tp; zeep::http::request req("GET", "/", { 1, 0 }, { { "Accept-Language", "da, en-US;q=0.7, en;q=0.3" } }, ""); zeep::http::scope scope(req); scope.put("ok", true); process_and_compare(doc, doc_test, scope); } catch (const std::runtime_error &) { std::clog << "skipping test 15 since locale da_DK.UTF-8 is not available\n"; } } #endif TEST_CASE("test_16") { try { std::locale l("en_GB.UTF-8"); if (l.name() != "en_GB.UTF-8") throw std::runtime_error("locale name not equal, not installed?"); auto doc = R"( )"_xml; auto doc_test = R"( 12,345.68 -12.34 12.06 K )"_xml; zeep::http::request req("GET", "/", { 1, 0 }, { { "Accept-Language", "en-GB, en-US;q=0.7, en;q=0.3" } }, ""); zeep::http::scope scope(req); scope.put("ok", true); process_and_compare(doc, doc_test, scope); } catch (const std::runtime_error &) { std::clog << "skipping test 16 since locale en_GB.UTF-8 is not available\n"; } } // TEST_CASE("test_17") // { // try // { // std::locale l("fr_FR.UTF-8"); // auto doc = R"( // // // // )"_xml; // auto doc_test = R"( // // 12 345,68 // // )"_xml; // zeep::http::request req("GET", "/", { 1, 0 }, { { "Accept-Language", "fr_FR, en-US;q=0.7, en;q=0.3" } }); // zeep::http::scope scope(req); // scope.put("ok", true); // process_and_compare(doc, doc_test, scope); // } // catch (const std::runtime_error& e) // { // std::clog << "skipping test 17 since locale fr_FR.UTF-8 is not available\n"; // } // } TEST_CASE("test_18") { auto doc = R"( )"_xml; auto doc_test = R"( x )"_xml; zeep::http::scope scope; zeep::el::object p; p["n"] = "x"; scope.put("p", p); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_19") { auto doc = R"( )"_xml; auto doc_test = R"( )"_xml; zeep::http::scope scope; scope.put("b", true); scope.put("c", false); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_20") { auto doc = R"(
1
2
3
*
1
2
3
*
1
2
2
3
*
)"_xml; auto doc_test = R"(
2
2
2
)"_xml; zeep::http::scope scope; scope.put("a", 2); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_21") { auto doc = R"( )"_xml; auto doc_test = R"( a-b )"_xml; zeep::http::scope scope; scope.put("a", "a"); scope.put("b", "b"); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_22") { auto doc = R"(
hello world
)"_xml; auto doc_test = R"(
hello world
hello world
hello world
hello world
hello world
hello world
hello world
fragment-1
fragment-1
fragment-1
fragment-2
fragment-2
fragment-2
)"_xml; zeep::http::scope scope; scope.put("b", "b"); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_22a") { zeep::http::template_processor p(gDocrootDir); zeem::document doc1; p.load_template("fragment-file :: frag1", doc1); auto doct = R"(
fragment-1
)"_xml; CHECK(doc1 == doct); if (doc1 != doct) { std::clog << doc1 << '\n' << doct << '\n'; } } TEST_CASE("test_23") { auto doc = R"( )"_xml; auto doc_test = R"( link link?b=b link/b link?b=b&test=test%26 link/bb link?c=bla%20met%20%3C%20en%20%3D )"_xml; zeep::http::scope scope; scope.put("b", "b"); scope.put("c", "bla met < en ="); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_24") { auto doc = R"( )"_xml; auto doc_test = R"( een twee drie een b en bla met < en = een twee b;:;bla met < en = )"_xml; zeep::http::scope scope; scope.put("b", "b"); scope.put("c", "bla met < en ="); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_25") { auto doc = R"( )"_xml; auto doc_test = R"( aap noot mies )"_xml; process_and_compare(doc, doc_test); } TEST_CASE("test_26") { auto doc = R"( )"_xml; auto doc_test = R"( )"_xml; process_and_compare(doc, doc_test); } TEST_CASE("test_27") { auto doc = R"( )"_xml; auto doc_test = R"( Error processing element 'span': Assertion failed for '1==0' )"_xml; process_and_compare(doc, doc_test); } TEST_CASE("test_28") { auto doc = R"( in een blokmet een em )"_xml; auto doc_test = R"( in een blokmet een em )"_xml; process_and_compare(doc, doc_test); } TEST_CASE("test_29") { auto doc = R"xml( fragment ref
)xml"_xml; auto doc_test = R"( fragmentref fragmentref fragmentref fragmentref )"_xml; process_and_compare(doc, doc_test); } TEST_CASE("test_30") { auto doc = R"xml( ref-1
The div
)xml"_xml; auto doc_test = R"(
The div
The div
)"_xml; process_and_compare(doc, doc_test); } TEST_CASE("test_31") { auto doc = R"xml(
De titel is vervangen
)xml"_xml; auto doc_test = R"( )"_xml; process_and_compare(doc, doc_test); } TEST_CASE("test_32") { auto doc = R"xml( ref-1
)xml"_xml; auto doc_test = R"(
ref-1
)"_xml; process_and_compare(doc, doc_test); } TEST_CASE("test_32a") { auto doc = R"xml( frag
)xml"_xml; auto doc_test = R"( frag
frag
)"_xml; process_and_compare(doc, doc_test); } TEST_CASE("test_32b") { auto doc = R"xml( )xml"_xml; auto doc_test = R"( hoi )"_xml; zeep::http::scope scope; scope.put("h", "hoi"); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_32c") { auto doc = R"xml( )xml"_xml; auto doc_test = R"( hoi )"_xml; zeep::http::scope scope; scope.put("h", "hoi"); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_32d") { auto doc = R"xml( )xml"_xml; auto doc_test = R"( hoi )"_xml; zeep::http::scope scope; zeep::el::object h{ { "txt", "hoi" } }; scope.put("h", h); process_and_compare(doc, doc_test, scope); } // TEST_CASE("test_32d") // { // auto doc = R"xml( // // // // // )xml"_xml; // auto doc_test = R"( // // // hoi // )"_xml; // zeep::http::scope scope; // scope.put("h", "hoi"); // process_and_compare(doc, doc_test, scope); // } TEST_CASE("test_33") { auto doc = R"xml(
)xml"_xml; auto doc_test = R"(
)"_xml; process_and_compare(doc, doc_test); } TEST_CASE("test_34") { auto doc = R"xml( )xml"_xml; auto doc_test = R"( S T )"_xml; zeep::http::scope scope; zeep::el::object j; j.push_back(zeep::el::object{ { "s", "S" } }); j.push_back(zeep::el::object{ { "s", "T" } }); scope.put("a", j); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_35") { auto doc = R"xml( test [(${a}.${b})] )xml"_xml; auto doc_test = R"( test Error processing ${a}.${b} )"_xml; zeep::http::scope scope; scope.put("a", "aap"); scope.put("b", "noot"); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_36") { auto doc = R"xml( )xml"_xml; auto doc_test = R"( )"_xml; zeep::http::scope scope; scope.put("x", 2.0); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_37") { auto doc = R"xml( [[${a}]][[${a}]] )xml"_xml; auto doc_test = R"( xx )"_xml; zeep::http::scope scope; scope.put("a", "x"); process_and_compare(doc, doc_test, scope); } TEST_CASE("test_38") { auto doc = R"xml( )xml"_xml; auto doc_test = R"( bla bla )"_xml; zeep::http::scope scope; scope.put("a", "x"); process_and_compare(doc, doc_test, scope); }libzeep-7.3.2/test/rest-test.cpp0000664000175000017500000002075615150027072016445 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2025 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include #include "../src/signals.hpp" #include "client-test-code.hpp" #include "zeep/el/object.hpp" #include "zeep/el/serializer.hpp" #include "zeep/http/asio.hpp" #include "zeep/http/controller.hpp" #include "zeep/http/daemon.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/http/scope.hpp" #include "zeep/http/server.hpp" #include "zeep/http/status.hpp" #include "zeep/uri.hpp" #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; struct Opname { string id; map standen; template void serialize(Archive &ar, uint64_t /*version*/) { // clang-format off ar & zeem::name_value_pair("id", id) & zeem::name_value_pair("standen", standen); // clang-format on } auto operator<=>(const Opname &) const = default; }; static_assert(not std::is_constructible_v); using a_map_type = map; static_assert(zeep::el::is_serializable_map_type_v); TEST_CASE("foo") { Opname opn{ "1", { { "een", 0.1f }, { "twee", 0.2f } } }; zeep::el::object o = zeep::el::serializer::serialize(opn); std::cout << o << "\n"; Opname opn2 = zeep::el::serializer::deserialize(o); CHECK(opn == opn2); } TEST_CASE("bar") { std::vector opnames{ { "1", { { "een", 0.1f }, { "twee", 0.2f } } }, { "2", { { "drie", 0.3f }, { "vier", 0.4f } } }, }; zeep::el::object o = zeep::el::serializer>::serialize(opnames); std::cout << o << "\n"; auto opn2 = zeep::el::serializer>::deserialize(o); CHECK(opnames == opn2); } enum class aggregatie_type { dag, week, maand, jaar }; enum class grafiek_type { warmte, electriciteit, electriciteit_hoog, electriciteit_laag, electriciteit_verbruik, electriciteit_levering, electriciteit_verbruik_hoog, electriciteit_verbruik_laag, electriciteit_levering_hoog, electriciteit_levering_laag }; struct GrafiekData { string type; map punten; map vsGem; template void serialize(Archive &ar, uint64_t /* version */) { // clang-format off ar & zeem::name_value_pair("type", type) & zeem::name_value_pair("punten", punten) & zeem::name_value_pair("vsgem", vsGem); // clang-format on } }; using Opnames = std::vector; class e_rest_controller : public zeep::http::controller { public: e_rest_controller() : zeep::http::controller("ajax") { map_post_request("opname", &e_rest_controller::post_opname, "opname"); map_put_request("opname/{id}", &e_rest_controller::put_opname, "id", "opname"); map_get_request("opname/{id}", &e_rest_controller::get_opname, "id"); map_get_request("opname", &e_rest_controller::get_all_opnames); map_delete_request("opname/{id}", &e_rest_controller::delete_opname, "id"); map_get_request("data/{type}/{aggr}", &e_rest_controller::get_grafiek, "type", "aggr"); map_get_request("opname", &e_rest_controller::get_opnames); map_put_request("opnames", &e_rest_controller::set_opnames, "opnames"); map_get_request("all_data", &e_rest_controller::get_all_data); map_get_request("scope_test", &e_rest_controller::scope_test, "id"); } // CRUD routines string post_opname(const Opname& /*opname*/) { return {}; } void put_opname(const string& /*opnameId*/, const string& /*opnameId*/) { {}; } Opnames get_opnames() { return { {}, {} }; } void set_opnames(const Opnames& /*opnames*/) { } Opname get_opname(const string& id) { if (id == "xxx") throw zeep::http::http_status_exception(zeep::http::status_type::not_found); return {}; } Opname get_last_opname() { return {}; } vector get_all_opnames() { return {}; } void delete_opname(const string& /*id*/) { } GrafiekData get_grafiek(grafiek_type /*type*/, grafiek_type /*type*/) { return {}; } zeep::http::reply get_all_data() { return { zeep::http::status_type::ok, { 1, 0 }, { { "Content-Length", "13" }, { "Content-Type", "text/plain" } }, "Hello, world!" }; } zeep::http::reply scope_test(const zeep::http::scope &scope, int /*id*/) { zeep::http::reply result{ zeep::http::status_type::ok, { 1, 0 }, { { "Content-Length", "13" }, { "Content-Type", "text/plain" } }, "Hello, world!" }; if (scope.get_request().get_accept("application/json") == 1.0f) result.set_content(zeep::el::object{ { "message", "Hello, world!" } }); return result; } }; TEST_CASE("rest_1") { zeep::value_serializer::init({ // { aggregatie_type::dag, "dag" }, { aggregatie_type::week, "week" }, { aggregatie_type::maand, "maand" }, { aggregatie_type::jaar, "jaar" } }); zeep::value_serializer::init({ // { grafiek_type::warmte, "warmte" }, { grafiek_type::electriciteit, "electriciteit" }, { grafiek_type::electriciteit_hoog, "electriciteit-hoog" }, { grafiek_type::electriciteit_laag, "electriciteit-laag" }, { grafiek_type::electriciteit_verbruik, "electriciteit-verbruik" }, { grafiek_type::electriciteit_levering, "electriciteit-levering" }, { grafiek_type::electriciteit_verbruik_hoog, "electriciteit-verbruik-hoog" }, { grafiek_type::electriciteit_verbruik_laag, "electriciteit-verbruik-laag" }, { grafiek_type::electriciteit_levering_hoog, "electriciteit-levering-hoog" }, { grafiek_type::electriciteit_levering_laag, "electriciteit-levering-laag" } }); // simply see if the above compiles e_rest_controller rc; zeep::http::reply rep; asio_ns::io_context io_context; asio_ns::ip::tcp::socket s(io_context); zeep::http::request req{ "GET", "/ajax/all_data" }; CHECK(rc.dispatch_request(s, req, rep)); CHECK(rep.get_status() == zeep::http::status_type::ok); CHECK(rep.get_content_type() == "text/plain"); } TEST_CASE("rest_2") { // start up a http server and stop it again zeep::http::daemon d([]() { auto s = new zeep::http::server; s->add_controller(new e_rest_controller()); return s; }, "zeep-http-test"); std::random_device rng; uint16_t port = 1024 + (rng() % 10240); std::thread t([&d, port] { return d.run_foreground("::", port); }); std::clog << "started daemon at port " << port << '\n'; using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); try { auto rep = simple_request(port, "GET /ajax/all_data HTTP/1.0\r\n\r\n"); CHECK(rep.get_status() == zeep::http::status_type::ok); CHECK(rep.get_content_type() == "text/plain"); auto reply = simple_request(port, "GET /ajax/xxxx HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zeep::http::status_type::not_found); // reply = simple_request(port, "GET /ajax/opname/xxx HTTP/1.0\r\n\r\n"); reply = simple_request(port, zeep::http::request("GET", "/ajax/opname/xxx", { 1, 0 }, { { "Accept", "application/json" } })); CHECK(reply.get_status() == zeep::http::status_type::not_found); CHECK(reply.get_content_type() == "application/json"); } catch (const std::exception &e) { std::clog << e.what() << '\n'; } zeep::signal_catcher::signal_hangup(t); t.join(); } TEST_CASE("rest_3") { // start up a http server and stop it again zeep::http::daemon d([]() { auto s = new zeep::http::server; s->add_controller(new e_rest_controller()); return s; }, "zeep-http-test"); std::random_device rng; uint16_t port = 1024 + (rng() % 10240); std::thread t([&d, port] { return d.run_foreground("::", port); }); std::clog << "started daemon at port " << port << '\n'; using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); try { zeep::http::request req_1{ "GET", "/ajax/scope_test", { 1, 0 }, { { "accept", "text/plain" } } }; auto rep_1 = simple_request(port, req_1); CHECK(rep_1.get_status() == zeep::http::status_type::ok); CHECK(rep_1.get_content_type() == "text/plain"); zeep::http::request req_2{ "GET", "/ajax/scope_test", { 1, 0 }, { { "accept", "application/json" } } }; auto rep_2 = simple_request(port, req_2); CHECK(rep_2.get_status() == zeep::http::status_type::ok); CHECK(rep_2.get_content_type() == "application/json"); } catch (const std::exception &e) { std::clog << e.what() << '\n'; } zeep::signal_catcher::signal_hangup(t); t.join(); } libzeep-7.3.2/test/rsrc_webapp-test.cpp0000664000175000017500000000357315150027072017775 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/http/scope.hpp" #include "zeep/http/tag-processor.hpp" #include "zeep/http/template-processor.hpp" #include #include #include #include #include #include using namespace zeem::literals; TEST_CASE("test_22") { auto doc = R"(
hello world
)"_xml; auto doc_test = R"(
hello world
hello world
hello world
hello world
hello world
hello world
hello world
fragment-1
fragment-1
fragment-1
fragment-2
fragment-2
fragment-2
)"_xml; zeep::http::tag_processor tp; zeep::http::rsrc_based_html_template_processor p; zeep::http::scope scope; scope.put("b", "b"); tp.process_xml(doc.child(), scope, "", p); CHECK(doc == doc_test); if (doc != doc_test) { std::cerr << doc << '\n' << doc_test << '\n'; } }libzeep-7.3.2/test/security-test.cpp0000664000175000017500000000426315150027072017332 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include "zeep/exception.hpp" #include "zeep/http/reply.hpp" #include "zeep/http/request.hpp" #include "zeep/http/security.hpp" #include "zeep/uri.hpp" #include #include #include #include #include #include namespace zh = zeep::http; TEST_CASE("sec_1") { zh::reply rep; CHECK_THROWS_AS(rep = zh::reply::redirect("http://example.com\r\nSet-Cookie: wrong=false;"), zeep::exception); CHECK_THROWS_AS(rep = zh::reply::redirect("http://example.com%0D%0ASet-Cookie: wrong=false;"), zeep::exception); rep = zh::reply::redirect("http://example.com/%0D%0ASet-Cookie:%20wrong=false;"); CHECK(rep.get_header("Location") == "http://example.com/%0D%0ASet-Cookie:%20wrong=false;"); rep = zh::reply::redirect("http://example.com"); CHECK(rep.get_header("Location") == "http://example.com"); /* std::clog << rep << '\n'; std::ostringstream os; os << rep; zh::reply_parser p; std::string s = os.str(); zeep::char_streambuf sb(s.c_str(), s.length()); p.parse(sb); auto r2 = p.get_reply(); std::clog << r2 << '\n'; BOOST_CHECK(r2.get_cookie("wrong").empty()); */ } TEST_CASE("sec_2") { zh::simple_user_service users({ { "scott", "tiger", { "USER" } } }); zeep::http::security_context sc("1234", users, false); sc.add_rule("/**", { "USER" }); auto user = users.load_user("scott"); { // default expires one year from now zh::reply rep; sc.add_authorization_headers(rep, user); zh::request req{ "GET", "/" }; req.set_cookie("access_token", rep.get_cookie("access_token")); CHECK_NOTHROW(sc.validate_request(req)); } { // check with 1 second zh::reply rep; sc.add_authorization_headers(rep, user, std::chrono::seconds{ 1 }); zh::request req{ "GET", "/" }; req.set_cookie("access_token", rep.get_cookie("access_token")); std::this_thread::sleep_for(std::chrono::seconds{ 2 }); CHECK_THROWS_AS(sc.validate_request(req), zeep::exception); } } libzeep-7.3.2/test/soap-test.cpp0000664000175000017500000001346415150027072016430 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include #include #define BOOST_TEST_MODULE SOAP_Test #include using namespace std; namespace z = zeep; namespace zh = zeep::http; struct TestStruct { int a; string s; template void serialize(Archive &ar, unsigned long) { ar & zx::make_element_nvp("a", a) & zx::make_element_nvp("s", s); } }; static_assert(z::has_serialize_v, "oops"); struct my_test_controller : public zh::soap_controller { my_test_controller() : zh::soap_controller("ws", "test", "http://www.hekkelman.com/libzeep/soap") { set_service("testService"); map_action("Test", &my_test_controller::test_method_1, "x"); map_action("Test2", &my_test_controller::test_method_2, "s"); map_action("Test3", &my_test_controller::test_method_3, "t"); } int test_method_1(int x) { BOOST_TEST(x == 42); return x; } void test_method_2(const std::string &s) { BOOST_TEST(s == "42"); } TestStruct test_method_3(const TestStruct &t) { return { t.a + 1, t.s + to_string(t.a) }; } }; BOOST_AUTO_TEST_CASE(soap_1) { using namespace zx::literals; my_test_controller srv; auto payload_test_1 = R"( 42 )"; zh::request req("POST", "/ws", { 1, 0 }, {}, payload_test_1); zh::reply rep; srv.handle_request(req, rep); BOOST_REQUIRE(rep.get_status() == 200); std::stringstream srep; srep << rep; std::string line; while (getline(srep, line)) { if (line.empty() or line == "\r") break; } zx::document repDoc(srep); auto test = R"( 42 )"_xml; BOOST_TEST(repDoc == test); } BOOST_AUTO_TEST_CASE(soap_2) { using namespace zx::literals; my_test_controller srv; auto payload_test = R"( 42 )"; zh::request req("POST", "/ws", { 1, 0 }, {}, payload_test); zh::reply rep; srv.handle_request(req, rep); BOOST_REQUIRE(rep.get_status() == 200); std::stringstream srep; srep << rep; std::string line; while (getline(srep, line)) { if (line.empty() or line == "\r") break; } zx::document repDoc(srep); auto test = R"( )"_xml; BOOST_TEST(repDoc == test); } BOOST_AUTO_TEST_CASE(soap_3) { using namespace zx::literals; my_test_controller srv; auto payload_test = R"( 42 42 )"; zh::request req("POST", "/ws", { 1, 0 }, {}, payload_test); zh::reply rep; srv.handle_request(req, rep); BOOST_REQUIRE(rep.get_status() == 200); std::stringstream srep; srep << rep; std::string line; while (getline(srep, line)) { if (line.empty() or line == "\r") break; } zx::document repDoc(srep); auto test = R"( 434242 )"_xml; BOOST_TEST(repDoc == test); } BOOST_AUTO_TEST_CASE(soap_3f) { using namespace zx::literals; my_test_controller srv; auto payload_test = R"( 42 42 )"; zh::request req("POST", "/ws", { 1, 0 }, {}, payload_test); zh::reply rep; srv.handle_request(req, rep); BOOST_TEST(rep.get_status() == 500); std::stringstream srep; srep << rep; std::string line; while (getline(srep, line)) { if (line.empty() or line == "\r") break; } zx::document repDoc(srep); auto test = R"( soap:Server Invalid namespace for request )"_xml; BOOST_TEST(repDoc == test); } BOOST_AUTO_TEST_CASE(soap_w1) { using namespace zx::literals; my_test_controller srv; zeem::document doc; doc.emplace_back(srv.make_wsdl()); cerr << setw(2) << doc << endl; }libzeep-7.3.2/test/test-main.cpp0000664000175000017500000000312515150027072016403 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2025 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #define CATCH_CONFIG_RUNNER #include #include "test-main.hpp" std::filesystem::path gTestDir; std::filesystem::path gDocrootDir; int main(int argc, char *argv[]) { gTestDir = std::filesystem::current_path(); Catch::Session session; // There must be exactly one instance // Build a new parser on top of Catch2's using namespace Catch::Clara; auto cli = session.cli(); // Get Catch2's command line parser cli |= Opt(gTestDir, "data-dir") // bind variable to a new option, with a hint string ["-t"]["--data-dir"] // the option names it will respond to ("The directory containing the tests") // description string for the help output | Opt(gDocrootDir, "docroot") // bind variable to a new option, with a hint string ["-d"]["--docroot"] // the option names it will respond to ("The directory containing the docroot data files"); // description string for the help output // Now pass the new composite back to Catch2 so it uses that session.cli(cli); // Let Catch2 (using Clara) parse the command line int returnCode = session.applyCommandLine(argc, argv); if (returnCode != 0) // Indicates a command line error return returnCode; return session.run(); } libzeep-7.3.2/test/test-main.hpp0000664000175000017500000000047415150027072016414 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2023-2025 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once #include extern std::filesystem::path gTestDir, gDocrootDir; libzeep-7.3.2/test/uri-test.cpp0000664000175000017500000002405715150027072016265 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include TEST_CASE("cc_1") { for (int ch = 0; ch <= 255; ++ch) { // std::cout << ch << ' ' << char(ch) << '\n'; CHECK((std::isalpha(ch) != 0) == zeep::uri::is_scheme_start(ch)); CHECK((std::isxdigit(ch) != 0) == zeep::uri::is_xdigit(ch)); } } TEST_CASE("uri_1") { zeep::is_valid_uri("http://a/"); zeep::is_valid_uri("http://a:80/"); zeep::is_valid_uri("http://a.b/"); zeep::is_valid_uri("http://a/b"); zeep::is_valid_uri("http://user@a/b"); zeep::is_valid_uri("http://user:pass@a/b"); zeep::is_valid_uri("http://user:pass@a:80/b"); zeep::is_valid_uri("http://a?q"); zeep::is_valid_uri("http://a#f"); zeep::is_valid_uri("http://a/b?q"); zeep::is_valid_uri("http://a/b#f"); zeep::is_valid_uri("http://a/b/c?q"); zeep::is_valid_uri("http://a/b/c#f"); zeep::is_valid_uri("http://a/b/c.d?q"); zeep::is_valid_uri("http://a/b/c.d#f"); zeep::is_valid_uri("http://user@localhost/segment/index.html#frag"); zeep::is_valid_uri("http://user@[::1]/segment/index.html#frag"); zeep::is_valid_uri("http://user:pass@[::1]/segment/index.html#frag"); zeep::is_valid_uri("http://user@localhost/segment/index.html?query"); zeep::is_valid_uri("http://user@[::1]/segment/index.html?query"); zeep::is_valid_uri("http://user:pass@[::1]/segment/index.html?query"); zeep::is_valid_uri("http://user@localhost/segment/index.html?query#frag"); zeep::is_valid_uri("http://user@[::1]/segment/index.html?query#frag"); zeep::is_valid_uri("http://user:pass@[::1]/segment/index.html?query#frag"); } TEST_CASE("uri_2") { zeep::uri url("http://user:pass@[::1]/segment/index.html?query#frag"); CHECK(url.get_scheme() == "http"); CHECK(url.get_host() == "[::1]"); CHECK(url.get_path().string() == "/segment/index.html"); CHECK(url.get_query(false) == "query"); CHECK(url.get_fragment(false) == "frag"); } TEST_CASE("uri_3") { zeep::uri url("http://www.example.com/~maarten"); CHECK(url.get_path().string() == "/~maarten"); } TEST_CASE("uri_4") { zeep::uri url("http://www.example.com/%7Emaarten"); CHECK(url.get_path().string() == "/~maarten"); } TEST_CASE("uri_5") { // This is a bit dubious... but it is valid according to RFC3986 zeep::uri uri("http://a/b%0D%0ASet-Cookie:%20false"); CHECK(uri.get_segments().front() == "b\r\nSet-Cookie: false"); } TEST_CASE("uri_6a") { zeep::uri uri("file:/a/b"); CHECK(uri.is_absolute()); CHECK(uri.get_path().string() == "/a/b"); } TEST_CASE("uri_6b") { zeep::uri uri("file://a/b"); CHECK(uri.is_absolute()); CHECK(uri.get_host() == "a"); CHECK(uri.get_path().string() == "/b"); } TEST_CASE("uri_6c") { CHECK_THROWS_AS(zeep::uri("file://a"), zeep::uri_parse_error); CHECK_THROWS_AS(zeep::uri("file://a?b"), zeep::uri_parse_error); CHECK_THROWS_AS(zeep::uri("file://a#c"), zeep::uri_parse_error); } TEST_CASE("normalize_1") { zeep::uri base("http://a/b/c/d;p?q"); CHECK(zeep::uri("g:h", base).string() == "g:h"); CHECK(zeep::uri("g", base).string() == "http://a/b/c/g"); CHECK(zeep::uri("./g", base).string() == "http://a/b/c/g"); CHECK(zeep::uri("g/", base).string() == "http://a/b/c/g/"); CHECK(zeep::uri("/g", base).string() == "http://a/g"); CHECK(zeep::uri("//g", base).string() == "http://g"); CHECK(zeep::uri("?y", base).string() == "http://a/b/c/d;p?y"); CHECK(zeep::uri("g?y", base).string() == "http://a/b/c/g?y"); CHECK(zeep::uri("#s", base).string() == "http://a/b/c/d;p?q#s"); CHECK(zeep::uri("g#s", base).string() == "http://a/b/c/g#s"); CHECK(zeep::uri("g?y#s", base).string() == "http://a/b/c/g?y#s"); CHECK(zeep::uri(";x", base).string() == "http://a/b/c/;x"); CHECK(zeep::uri("g;x", base).string() == "http://a/b/c/g;x"); CHECK(zeep::uri("g;x?y#s", base).string() == "http://a/b/c/g;x?y#s"); CHECK(zeep::uri("", base).string() == "http://a/b/c/d;p?q"); CHECK(zeep::uri(".", base).string() == "http://a/b/c/"); CHECK(zeep::uri("./", base).string() == "http://a/b/c/"); CHECK(zeep::uri("..", base).string() == "http://a/b/"); CHECK(zeep::uri("../", base).string() == "http://a/b/"); CHECK(zeep::uri("../g", base).string() == "http://a/b/g"); CHECK(zeep::uri("../..", base).string() == "http://a/"); CHECK(zeep::uri("../../", base).string() == "http://a/"); CHECK(zeep::uri("../../g", base).string() == "http://a/g"); } TEST_CASE("normalize_2") { zeep::uri base("http://a/b/c/d;p?q"); CHECK(zeep::uri("../../../g", base).string() == "http://a/g"); CHECK(zeep::uri("../../../../g", base).string() == "http://a/g"); CHECK(zeep::uri("/./g", base).string() == "http://a/g"); CHECK(zeep::uri("/../g", base).string() == "http://a/g"); CHECK(zeep::uri("g.", base).string() == "http://a/b/c/g."); CHECK(zeep::uri(".g", base).string() == "http://a/b/c/.g"); CHECK(zeep::uri("g..", base).string() == "http://a/b/c/g.."); CHECK(zeep::uri("..g", base).string() == "http://a/b/c/..g"); CHECK(zeep::uri("./../g", base).string() == "http://a/b/g"); CHECK(zeep::uri("./g/.", base).string() == "http://a/b/c/g/"); CHECK(zeep::uri("g/./h", base).string() == "http://a/b/c/g/h"); CHECK(zeep::uri("g/../h", base).string() == "http://a/b/c/h"); CHECK(zeep::uri("g;x=1/./y", base).string() == "http://a/b/c/g;x=1/y"); CHECK(zeep::uri("g;x=1/../y", base).string() == "http://a/b/c/y"); CHECK(zeep::uri("g?y/./x", base).string() == "http://a/b/c/g?y/./x"); CHECK(zeep::uri("g?y/../x", base).string() == "http://a/b/c/g?y/../x"); CHECK(zeep::uri("g#s/./x", base).string() == "http://a/b/c/g#s/./x"); CHECK(zeep::uri("g#s/../x", base).string() == "http://a/b/c/g#s/../x"); // ; for strict parsers CHECK(zeep::uri("http:g", base).string() == "http:g"); // / "http://a/b/c/g" ; for backward compatibility } TEST_CASE("path_1") { zeep::uri t("http://a/b"); t.set_path("c"); CHECK(t.get_path().string() == "c"); t.set_path("/c"); CHECK(t.get_path().string() == "/c"); t.set_path("/c/"); CHECK(t.get_path().string() == "/c/"); t.set_path("c/d"); CHECK(t.get_path().string() == "c/d"); t.set_path("/c/d"); CHECK(t.get_path().string() == "/c/d"); t.set_path("/c/d/"); CHECK(t.get_path().string() == "/c/d/"); } TEST_CASE("path_2") { zeep::uri t("http://a/b"); zeep::uri u; u = t / zeep::uri("c"); CHECK(u.string() == "http://a/b/c"); u = t / zeep::uri("/c"); CHECK(u.string() == "http://a/b/c"); u = t / zeep::uri("/c/"); CHECK(u.string() == "http://a/b/c/"); u = t / zeep::uri("c/d"); CHECK(u.string() == "http://a/b/c/d"); u = t / zeep::uri("/c/d"); CHECK(u.string() == "http://a/b/c/d"); u = t / zeep::uri("/c/d/"); CHECK(u.string() == "http://a/b/c/d/"); } TEST_CASE("relative_1") { zeep::uri base("http://a/b/c/d;p?q"); zeep::uri u; CHECK(zeep::uri("g:h").relative(base).string() == "g:h"); CHECK(zeep::uri("http://a/b/c/g").relative(base).string() == "g"); CHECK(zeep::uri("http://a/b/c/g/").relative(base).string() == "g/"); CHECK(zeep::uri("http://a/g").relative(base).string() == "/g"); CHECK(zeep::uri("http://g").relative(base).string() == "//g"); CHECK(zeep::uri("http://a/b/c/d;p?y").relative(base).string() == "?y"); CHECK(zeep::uri("http://a/b/c/g?y").relative(base).string() == "g?y"); CHECK(zeep::uri("http://a/b/c/d;p?q#s").relative(base).string() == "#s"); CHECK(zeep::uri("http://a/b/c/g#s").relative(base).string() == "g#s"); CHECK(zeep::uri("http://a/b/c/g?y#s").relative(base).string() == "g?y#s"); CHECK(zeep::uri("http://a/b/c/;x").relative(base).string() == ";x"); CHECK(zeep::uri("http://a/b/c/g;x").relative(base).string() == "g;x"); CHECK(zeep::uri("http://a/b/c/g;x?y#s").relative(base).string() == "g;x?y#s"); CHECK(zeep::uri("http://a/b/c/d;p?q").relative(base).string() == ""); CHECK(zeep::uri("http://a/b/c/").relative(base).string() == "."); CHECK(zeep::uri("http://a/b/").relative(base).string() == ".."); CHECK(zeep::uri("http://a/b/g").relative(base).string() == "../g"); } TEST_CASE("relative_2") { zeep::uri base("http://a/b/c/d;p?q"); zeep::uri u; CHECK(zeep::uri(zeep::uri("g:h").relative(base).string(), base).string() == "g:h"); CHECK(zeep::uri(zeep::uri("http://a/b/c/g").relative(base).string(), base).string() == "http://a/b/c/g"); CHECK(zeep::uri(zeep::uri("http://a/b/c/g/").relative(base).string(), base).string() == "http://a/b/c/g/"); CHECK(zeep::uri(zeep::uri("http://a/g").relative(base).string(), base).string() == "http://a/g"); CHECK(zeep::uri(zeep::uri("http://g").relative(base).string(), base).string() == "http://g"); CHECK(zeep::uri(zeep::uri("http://a/b/c/d;p?y").relative(base).string(), base).string() == "http://a/b/c/d;p?y"); CHECK(zeep::uri(zeep::uri("http://a/b/c/g?y").relative(base).string(), base).string() == "http://a/b/c/g?y"); CHECK(zeep::uri(zeep::uri("http://a/b/c/d;p?q#s").relative(base).string(), base).string() == "http://a/b/c/d;p?q#s"); CHECK(zeep::uri(zeep::uri("http://a/b/c/g#s").relative(base).string(), base).string() == "http://a/b/c/g#s"); CHECK(zeep::uri(zeep::uri("http://a/b/c/g?y#s").relative(base).string(), base).string() == "http://a/b/c/g?y#s"); CHECK(zeep::uri(zeep::uri("http://a/b/c/;x").relative(base).string(), base).string() == "http://a/b/c/;x"); CHECK(zeep::uri(zeep::uri("http://a/b/c/g;x").relative(base).string(), base).string() == "http://a/b/c/g;x"); CHECK(zeep::uri(zeep::uri("http://a/b/c/g;x?y#s").relative(base).string(), base).string() == "http://a/b/c/g;x?y#s"); CHECK(zeep::uri(zeep::uri("http://a/b/c/d;p?q").relative(base).string(), base).string() == "http://a/b/c/d;p?q"); CHECK(zeep::uri(zeep::uri("http://a/b/c/").relative(base).string(), base).string() == "http://a/b/c/"); CHECK(zeep::uri(zeep::uri("http://a/b/").relative(base).string(), base).string() == "http://a/b/"); CHECK(zeep::uri(zeep::uri("http://a/b/g").relative(base).string(), base).string() == "http://a/b/g"); } TEST_CASE("encoding_1") { // http://a/höken/Ðuh?¤ zeep::uri u("http://a/h%C3%B6ken/%C3%90uh?%C2%A4"); CHECK(zeep::decode_url(u.get_path().string()) == "/höken/Ðuh"); CHECK(zeep::decode_url(u.get_query(false)) == "¤"); CHECK(u.get_query(true) == "¤"); }libzeep-7.3.2/test/webapp-test.cpp0000664000175000017500000003416315150027072016743 0ustar maartenmaarten// Copyright Maarten L. Hekkelman 2026 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include #include "../src/signals.hpp" #include "client-test-code.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using webapp = zeep::http::html_controller_v1; void compare(zeem::document &a, zeem::document &b) { CHECK(a == b); if (a != b) { std::cerr << std::string(80, '-') << '\n' << a << '\n' << std::string(80, '-') << '\n' << b << '\n' << std::string(80, '-') << '\n'; } } TEST_CASE("webapp_1") { class my_webapp : public webapp { public: my_webapp() { // mount("test", &my_webapp::handle_test); mount_get("test", &my_webapp::handle_get_test); mount_post("test", &my_webapp::handle_post_test); } void handle_test(const zeep::http::request & /*request*/, const zeep::http::scope & /*scope*/, zeep::http::reply &reply) { reply = zeep::http::reply::stock_reply(zeep::http::status_type::ok); } void handle_get_test(const zeep::http::request & /*request*/, const zeep::http::scope & /*scope*/, zeep::http::reply &reply) { reply = zeep::http::reply::stock_reply(zeep::http::status_type::ok); reply.set_content("get", "text/plain"); } void handle_post_test(const zeep::http::request & /*request*/, const zeep::http::scope & /*scope*/, zeep::http::reply &reply) { reply = zeep::http::reply::stock_reply(zeep::http::status_type::ok); reply.set_content("post", "text/plain"); } } app; zeep::http::request req("GET", "/test", { 1, 0 }); zeep::http::reply rep; app.handle_request(req, rep); CHECK(rep.get_status() == zeep::http::status_type::ok); CHECK(rep.get_content() == "get"); req.set_method("POST"); app.handle_request(req, rep); CHECK(rep.get_status() == zeep::http::status_type::ok); CHECK(rep.get_content() == "post"); req.set_method("DELETE"); app.handle_request(req, rep); CHECK(rep.get_status() == zeep::http::status_type::not_found); } // TEST_CASE("webapp_2") // { // webapp app; // app.mount("test", &zeep::http::controller::handle_file); // zeep::http::request req; // req.method = zeep::"GET"; // req.set_uri("/test"); // zeep::http::reply rep; // app.handle_request(req, rep); // CHECK(rep.get_status() == zeep::http::internal_server_error); // } // TEST_CASE("webapp_4") // { // using namespace zeem::literals; // webapp app; // zx::document doc; // app.load_template("fragment-file :: frag1", doc); // auto test1 = R"( //
fragment-1
)"_xml; // compare(doc, test1); // doc.clear(); // app.load_template("fragment-file :: #frag2", doc); // auto test2 = R"( //
fragment-2
)"_xml; // compare(doc, test2); // } // test various ways of mounting handlers TEST_CASE("webapp_5") { class my_webapp : public webapp { public: my_webapp() { mount("test", &my_webapp::handle_test1); mount("*/*.x", &my_webapp::handle_test2); mount("**/*.x", &my_webapp::handle_test2b); mount("test/*", &my_webapp::handle_test3); mount("test/**", &my_webapp::handle_test4); mount("{css,scripts}/", &my_webapp::handle_testf); } void handle_test1(const zeep::http::request & /*request*/, const zeep::http::scope & /*scope*/, zeep::http::reply &reply) { reply = zeep::http::reply::stock_reply(zeep::http::status_type::ok); reply.set_content("1", "text/plain"); } void handle_test2(const zeep::http::request & /*request*/, const zeep::http::scope & /*scope*/, zeep::http::reply &reply) { reply = zeep::http::reply::stock_reply(zeep::http::status_type::ok); reply.set_content("2", "text/plain"); } void handle_test2b(const zeep::http::request & /*request*/, const zeep::http::scope & /*scope*/, zeep::http::reply &reply) { reply = zeep::http::reply::stock_reply(zeep::http::status_type::ok); reply.set_content("2b", "text/plain"); } void handle_test3(const zeep::http::request & /*request*/, const zeep::http::scope & /*scope*/, zeep::http::reply &reply) { reply = zeep::http::reply::stock_reply(zeep::http::status_type::ok); reply.set_content("3", "text/plain"); } void handle_test4(const zeep::http::request & /*request*/, const zeep::http::scope & /*scope*/, zeep::http::reply &reply) { reply = zeep::http::reply::stock_reply(zeep::http::status_type::ok); reply.set_content("4", "text/plain"); } void handle_testf(const zeep::http::request & /*request*/, const zeep::http::scope & /*scope*/, zeep::http::reply &reply) { reply = zeep::http::reply::stock_reply(zeep::http::status_type::ok); reply.set_content("f", "text/plain"); } } app; zeep::http::request req("GET", "/test"); zeep::http::reply rep; app.handle_request(req, rep); CHECK(rep.get_status() == zeep::http::status_type::ok); CHECK(rep.get_content() == "1"); req.set_uri("/test/x"); app.handle_request(req, rep); CHECK(rep.get_status() == zeep::http::status_type::ok); CHECK(rep.get_content() == "3"); req.set_uri("/test/x/x"); app.handle_request(req, rep); CHECK(rep.get_status() == zeep::http::status_type::ok); CHECK(rep.get_content() == "4"); req.set_uri("iew.x"); app.handle_request(req, rep); CHECK(rep.get_status() == zeep::http::status_type::ok); CHECK(rep.get_content() == "2b"); req.set_uri("x/iew.x"); app.handle_request(req, rep); CHECK(rep.get_status() == zeep::http::status_type::ok); CHECK(rep.get_content() == "2"); req.set_uri("x/x/iew.x"); app.handle_request(req, rep); CHECK(rep.get_status() == zeep::http::status_type::ok); CHECK(rep.get_content() == "2b"); req.set_uri("css/styles/my-style.css"); app.handle_request(req, rep); CHECK(rep.get_status() == zeep::http::status_type::ok); CHECK(rep.get_content() == "f"); req.set_uri("scripts/x.js"); app.handle_request(req, rep); CHECK(rep.get_status() == zeep::http::status_type::ok); CHECK(rep.get_content() == "f"); } class hello_controller : public zeep::http::html_controller_v1 { public: hello_controller() : zeep::http::html_controller_v1("/") { mount("", &hello_controller::handle_index); } void handle_index(const zeep::http::request & /*req*/, const zeep::http::scope & /*scope*/, zeep::http::reply &rep) { rep = zeep::http::reply::stock_reply(zeep::http::status_type::ok); rep.set_content("Hello", "text/plain"); } }; TEST_CASE("webapp_8") { // start up a http server with a html_controller and stop it again zeep::http::daemon d([]() { auto server = new zeep::http::server; server->add_controller(new hello_controller()); return server; }, "zeep-http-test"); std::random_device rng; uint16_t port = 1024 + (rng() % 10240); std::thread t([&d, port] { return d.run_foreground("::", port); }); std::clog << "started daemon at port " << port << '\n'; using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); try { auto reply = simple_request(port, "GET / HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zeep::http::status_type::ok); CHECK(reply.get_content() == "Hello"); } catch (const std::exception &ex) { std::clog << ex.what() << '\n'; } zeep::signal_catcher::signal_hangup(t); t.join(); } TEST_CASE("webapp_10") { zeep::http::server srv; srv.add_controller(new hello_controller()); std::thread t([&srv]() mutable { using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); srv.stop(); }); std::random_device rng; uint16_t port = 1024 + (rng() % 10240); srv.bind("::", port); srv.run(2); t.join(); } // // a more generic set of tests, should be in a separate file I guess // TEST_CASE("split_1") // { // std::vector p; // zeep::split(p, ",een,twee"s, ",", false); // REQUIRE(p.size() == 3); // CHECK(p[0] == ""); // CHECK(p[1] == "een"); // CHECK(p[2] == "twee"); // zeep::split(p, ",een,twee"s, ",", true); // REQUIRE(p.size() == 2); // CHECK(p[0] == "een"); // CHECK(p[1] == "twee"); // } // -------------------------------------------------------------------- // // test various ways of mounting handlers // TEST_CASE("webapp_5") // { // class my_webapp : public webapp_2 // { // public: // my_webapp() { // mount("test", &my_webapp::handle_test1); // mount("*/*.x", &my_webapp::handle_test2); // mount("**/*.x", &my_webapp::handle_test2b); // mount("test/*", &my_webapp::handle_test3); // mount("test/**", &my_webapp::handle_test4); // mount("{css,scripts}/", &my_webapp::handle_testf); // } // virtual void handle_test1(const zeep::http::request& /*request*/, const zeep::http::scope& /*scope*/, zeep::http::reply& reply) // { // reply = zeep::http::reply::stock_reply(zeep::http::ok); // reply.set_content("1", "text/plain"); // } // virtual void handle_test2(const zeep::http::request& /*request*/, const zeep::http::scope& /*scope*/, zeep::http::reply& reply) // { // reply = zeep::http::reply::stock_reply(zeep::http::ok); // reply.set_content("2", "text/plain"); // } // virtual void handle_test2b(const zeep::http::request& /*request*/, const zeep::http::scope& /*scope*/, zeep::http::reply& reply) // { // reply = zeep::http::reply::stock_reply(zeep::http::ok); // reply.set_content("2b", "text/plain"); // } // virtual void handle_test3(const zeep::http::request& /*request*/, const zeep::http::scope& /*scope*/, zeep::http::reply& reply) // { // reply = zeep::http::reply::stock_reply(zeep::http::ok); // reply.set_content("3", "text/plain"); // } // virtual void handle_test4(const zeep::http::request& /*request*/, const zeep::http::scope& /*scope*/, zeep::http::reply& reply) // { // reply = zeep::http::reply::stock_reply(zeep::http::ok); // reply.set_content("4", "text/plain"); // } // virtual void handle_testf(const zeep::http::request& /*request*/, const zeep::http::scope& /*scope*/, zeep::http::reply& reply) // { // reply = zeep::http::reply::stock_reply(zeep::http::ok); // reply.set_content("f", "text/plain"); // } // } app; // zeep::http::request req("GET", "/test"); // zeep::http::reply rep; // app.handle_request(req, rep); // CHECK(rep.get_status() == zeep::http::ok); // CHECK(rep.get_content() == "1"); // req.set_uri("/test/x"); // app.handle_request(req, rep); // CHECK(rep.get_status() == zeep::http::ok); // CHECK(rep.get_content() == "3"); // req.set_uri("/test/x/x"); // app.handle_request(req, rep); // CHECK(rep.get_status() == zeep::http::ok); // CHECK(rep.get_content() == "4"); // req.set_uri("iew.x"); // app.handle_request(req, rep); // CHECK(rep.get_status() == zeep::http::ok); // CHECK(rep.get_content() == "2b"); // req.set_uri("x/iew.x"); // app.handle_request(req, rep); // CHECK(rep.get_status() == zeep::http::ok); // CHECK(rep.get_content() == "2"); // req.set_uri("x/x/iew.x"); // app.handle_request(req, rep); // CHECK(rep.get_status() == zeep::http::ok); // CHECK(rep.get_content() == "2b"); // req.set_uri("css/styles/my-style.css"); // app.handle_request(req, rep); // CHECK(rep.get_status() == zeep::http::ok); // CHECK(rep.get_content() == "f"); // req.set_uri("scripts/x.js"); // app.handle_request(req, rep); // CHECK(rep.get_status() == zeep::http::ok); // CHECK(rep.get_content() == "f"); // } class hello_controller_2 : public zeep::http::html_controller { public: hello_controller_2() : zeep::http::html_controller("/") { map_get("", &hello_controller_2::handle_index, "user"); map_get("hello/{user}", &hello_controller_2::handle_hello, "user"); map_get("hello/{user}/x", &hello_controller_2::handle_hello, "user"); } zeep::http::reply handle_index([[maybe_unused]] const zeep::http::scope &scope, const std::optional &user) { auto rep = zeep::http::reply::stock_reply(zeep::http::status_type::ok); rep.set_content("Hello, " + user.value_or("world") + "!", "text/plain"); return rep; } zeep::http::reply handle_hello([[maybe_unused]] const zeep::http::scope &scope, const std::optional &user) { auto rep = zeep::http::reply::stock_reply(zeep::http::status_type::ok); rep.set_content("Hello, " + user.value_or("world") + "!", "text/plain"); return rep; } }; TEST_CASE("controller_2_1") { // start up a http server with a html_controller and stop it again zeep::http::daemon d([]() { auto server = new zeep::http::server; server->add_controller(new hello_controller_2()); return server; }, "zeep-http-test"); std::random_device rng; uint16_t port = 1024 + (rng() % 10240); std::thread t([&d, port] { return d.run_foreground("::", port); }); std::clog << "started daemon at port " << port << '\n'; using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); try { auto reply = simple_request(port, "GET / HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zeep::http::status_type::ok); CHECK(reply.get_content() == "Hello, world!"); reply = simple_request(port, "GET /?user=maarten HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zeep::http::status_type::ok); CHECK(reply.get_content() == "Hello, maarten!"); reply = simple_request(port, "GET /hello/maarten HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zeep::http::status_type::ok); CHECK(reply.get_content() == "Hello, maarten!"); reply = simple_request(port, "GET /hello/maarten/x HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zeep::http::status_type::ok); CHECK(reply.get_content() == "Hello, maarten!"); reply = simple_request(port, "GET /hello//x HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zeep::http::status_type::ok); CHECK(reply.get_content() == "Hello, world!"); reply = simple_request(port, "GET /hello/dani%C3%ABlle/x HTTP/1.0\r\n\r\n"); CHECK(reply.get_status() == zeep::http::status_type::ok); CHECK(reply.get_content() == "Hello, daniëlle!"); } catch (const std::exception &ex) { std::clog << ex.what() << '\n'; } zeep::signal_catcher::signal_hangup(t); t.join(); }