From daf0485ec77463dbaeba3b1b0ffeefc8a89f5399 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 05:26:42 +0200 Subject: Adding upstream version 0.8.7. Signed-off-by: Daniel Baumann --- .clang-format | 66 +++ .gitattributes | 2 + .github/workflows/build-and-test-deb.yml | 114 +++++ .github/workflows/build-and-test-msys.yml | 90 ++++ .github/workflows/build-and-test-rh.yml | 98 ++++ .github/workflows/build-and-test.yml | 142 ++++++ .github/workflows/codeql.yml | 86 ++++ .github/workflows/coverage.yml | 71 +++ .github/workflows/coverity.yml | 63 +++ .github/workflows/lint.yml | 56 +++ .github/workflows/nix.yml | 64 +++ .gitignore | 7 + CMakeLists.txt | 273 +++++++++++ LICENSE.md | 34 ++ README.adoc | 230 +++++++++ cmake/sexp-samples-folder.h.in | 24 + cmake/sexpp.pc.in | 9 + cmake/version.cmake | 162 +++++++ codecov.yml | 24 + default.nix | 49 ++ flake.lock | 42 ++ flake.nix | 47 ++ include/sexpp/ext-key-format.h | 95 ++++ include/sexpp/sexp-error.h | 73 +++ include/sexpp/sexp-public.h | 30 ++ include/sexpp/sexp.h | 466 +++++++++++++++++++ man/sexpp.1 | 67 +++ samples/baseline/sexp-sample-a | 19 + samples/baseline/sexp-sample-b | 1 + samples/baseline/sexp-sample-c | Bin 0 -> 430 bytes samples/compat/g10/canonical.key | Bin 0 -> 710 bytes samples/compat/g23/correct.key | 43 ++ samples/compat/g23/correct_mult_fields.key | 45 ++ samples/compat/g23/correct_no_eol.key | 43 ++ samples/compat/g23/correct_with_comment.key | 44 ++ samples/compat/g23/correct_with_comment_at_eof.key | 45 ++ samples/compat/g23/correct_with_empty_line.key | 44 ++ .../compat/g23/correct_with_two_empty_lines.key | 45 ++ samples/compat/g23/correct_with_windows_eol.key | 45 ++ samples/compat/g23/malformed_invalid_name_char.key | 43 ++ .../g23/malformed_invalid_name_first_char.key | 43 ++ samples/compat/g23/malformed_name_break.key | 44 ++ samples/compat/g23/malformed_name_eof.key | 43 ++ samples/compat/g23/malformed_no_key.key | 43 ++ samples/compat/g23/malformed_two_keys.key | 84 ++++ src/ext-key-format.cpp | 308 ++++++++++++ src/sexp-char-defs.cpp | 345 ++++++++++++++ src/sexp-depth-manager.cpp | 50 ++ src/sexp-error.cpp | 56 +++ src/sexp-input.cpp | 514 +++++++++++++++++++++ src/sexp-main.cpp | 231 +++++++++ src/sexp-object.cpp | 188 ++++++++ src/sexp-output.cpp | 197 ++++++++ src/sexp-simple-string.cpp | 191 ++++++++ tests/include/sexp-tests.h | 42 ++ tests/scripts/tests.sh | 191 ++++++++ tests/src/baseline-tests.cpp | 115 +++++ tests/src/compare-files.cpp | 126 +++++ tests/src/exception-tests.cpp | 331 +++++++++++++ tests/src/g10-compat-tests.cpp | 51 ++ tests/src/g23-compat-tests.cpp | 139 ++++++ tests/src/g23-exception-tests.cpp | 108 +++++ tests/src/primitives-tests.cpp | 394 ++++++++++++++++ version.txt | 1 + 64 files changed, 6736 insertions(+) create mode 100644 .clang-format create mode 100644 .gitattributes create mode 100644 .github/workflows/build-and-test-deb.yml create mode 100644 .github/workflows/build-and-test-msys.yml create mode 100644 .github/workflows/build-and-test-rh.yml create mode 100644 .github/workflows/build-and-test.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/coverage.yml create mode 100644 .github/workflows/coverity.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/nix.yml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE.md create mode 100644 README.adoc create mode 100644 cmake/sexp-samples-folder.h.in create mode 100644 cmake/sexpp.pc.in create mode 100644 cmake/version.cmake create mode 100644 codecov.yml create mode 100644 default.nix create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 include/sexpp/ext-key-format.h create mode 100644 include/sexpp/sexp-error.h create mode 100644 include/sexpp/sexp-public.h create mode 100644 include/sexpp/sexp.h create mode 100644 man/sexpp.1 create mode 100644 samples/baseline/sexp-sample-a create mode 100644 samples/baseline/sexp-sample-b create mode 100644 samples/baseline/sexp-sample-c create mode 100644 samples/compat/g10/canonical.key create mode 100755 samples/compat/g23/correct.key create mode 100755 samples/compat/g23/correct_mult_fields.key create mode 100755 samples/compat/g23/correct_no_eol.key create mode 100755 samples/compat/g23/correct_with_comment.key create mode 100755 samples/compat/g23/correct_with_comment_at_eof.key create mode 100755 samples/compat/g23/correct_with_empty_line.key create mode 100755 samples/compat/g23/correct_with_two_empty_lines.key create mode 100644 samples/compat/g23/correct_with_windows_eol.key create mode 100755 samples/compat/g23/malformed_invalid_name_char.key create mode 100755 samples/compat/g23/malformed_invalid_name_first_char.key create mode 100755 samples/compat/g23/malformed_name_break.key create mode 100755 samples/compat/g23/malformed_name_eof.key create mode 100755 samples/compat/g23/malformed_no_key.key create mode 100755 samples/compat/g23/malformed_two_keys.key create mode 100644 src/ext-key-format.cpp create mode 100644 src/sexp-char-defs.cpp create mode 100644 src/sexp-depth-manager.cpp create mode 100644 src/sexp-error.cpp create mode 100644 src/sexp-input.cpp create mode 100644 src/sexp-main.cpp create mode 100644 src/sexp-object.cpp create mode 100644 src/sexp-output.cpp create mode 100644 src/sexp-simple-string.cpp create mode 100644 tests/include/sexp-tests.h create mode 100755 tests/scripts/tests.sh create mode 100644 tests/src/baseline-tests.cpp create mode 100644 tests/src/compare-files.cpp create mode 100644 tests/src/exception-tests.cpp create mode 100644 tests/src/g10-compat-tests.cpp create mode 100644 tests/src/g23-compat-tests.cpp create mode 100644 tests/src/g23-exception-tests.cpp create mode 100644 tests/src/primitives-tests.cpp create mode 100644 version.txt diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3b17299 --- /dev/null +++ b/.clang-format @@ -0,0 +1,66 @@ +--- +Language: Cpp +# BasedOnStyle: Mozilla +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: true +AlignEscapedNewlinesLeft: true +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterStruct: false + AfterUnion: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: false +BreakStringLiterals: true +ColumnLimit: 95 +ContinuationIndentWidth: 2 +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +IncludeIsMainRegex: '$' +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SpaceAfterCStyleCast: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 8 +UseTab: Never +... diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6bd52cb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.giattributes text eol=lf +samples/** -text diff --git a/.github/workflows/build-and-test-deb.yml b/.github/workflows/build-and-test-deb.yml new file mode 100644 index 0000000..a9c0eb4 --- /dev/null +++ b/.github/workflows/build-and-test-deb.yml @@ -0,0 +1,114 @@ +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +name: build-and-test-deb + +on: + push: + branches: [ main ] + pull_request: + workflow_dispatch: + +concurrency: + group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}' + cancel-in-progress: true + +env: +# Looks like the last version that supports i386 + CMAKE_VERSION: '3.20.6-2' + MAKEFLAGS: j4 + +jobs: + build-and-test: + name: ${{ matrix.image }}-${{ matrix.env.CC }} shared lib ${{ matrix.shared }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + image: [ 'i386/debian:10', 'i386/debian:11', 'amd64/debian:11' ] + env: [ {CC: gcc, CXX: g++}, {CC: clang, CXX: clang++} ] + shared: [on, off] + + container: ${{ matrix.image }} + env: ${{ matrix.env }} + steps: + - name: Install packages + run: | + apt-get update + apt-get -y install sudo git wget build-essential libssl-dev software-properties-common + + - name: Checkout + # Because i386 support v1 only (no node run-time) + uses: actions/checkout@v1 + with: + fetch-depth: 1 + + - name: Install cmake/i386 + if: startsWith(matrix.image, 'i386') + run: | + wget -nv https://github.com/xpack-dev-tools/cmake-xpack/releases/download/v${{ env.CMAKE_VERSION }}/xpack-cmake-${{ env.CMAKE_VERSION }}-linux-ia32.tar.gz + tar -zxf xpack-cmake-${{ env.CMAKE_VERSION }}-linux-ia32.tar.gz --directory /usr/local --strip-components=1 --skip-old-files + + - name: Install cmake/amd64 + if: startsWith(matrix.image, 'amd64') + run: | + wget -nv https://github.com/xpack-dev-tools/cmake-xpack/releases/download/v${{ env.CMAKE_VERSION }}/xpack-cmake-${{ env.CMAKE_VERSION }}-linux-x64.tar.gz + tar -zxf xpack-cmake-${{ env.CMAKE_VERSION }}-linux-x64.tar.gz --directory /usr/local --strip-components=1 --skip-old-files + + - name: Install clang + if: matrix.env.CC == 'clang' + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - + apt-add-repository "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch main" + apt-get install -y clang + + - name: Adjust environment + run: | + echo "SEXP_INSTALL_PATH=$PWD/install" >> $GITHUB_ENV + echo "SHARED_LIB=${{ matrix.shared }}" >> $GITHUB_ENV + + - name: Configure + run: cmake . -Bbuild -DCMAKE_INSTALL_PREFIX=${{ env.SEXP_INSTALL_PATH }} -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=${{ matrix.shared }} + + - name: Build + run: cmake --build build --config Release + + - name: Run tests + run: ctest --test-dir build --output-on-failure + + - name: Checkout shell test framework/amd64 only + # i386 supports actions/checkout@v1 only (no Node.js 32-bit binary) + # and and actions/checkout@v1 has some issues with nested repositories + if: startsWith(matrix.image, 'amd64') + uses: actions/checkout@v3 + with: + repository: kward/shunit2 + path: ${{github.workspace}}/tests/shunit2 + fetch-depth: 1 + + - name: Install + run: cmake --install build + + - name: Run additional tests + # See above, just assume that in i386 config it was installed correctly + if: startsWith(matrix.image, 'amd64') + shell: bash + run: tests/scripts/tests.sh diff --git a/.github/workflows/build-and-test-msys.yml b/.github/workflows/build-and-test-msys.yml new file mode 100644 index 0000000..99e2096 --- /dev/null +++ b/.github/workflows/build-and-test-msys.yml @@ -0,0 +1,90 @@ +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +name: build-and-test-msys + +on: + push: + branches: [ main ] + pull_request: + workflow_dispatch: + +concurrency: + group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}' + cancel-in-progress: true + +env: + MAKEFLAGS: j4 + +defaults: + run: + shell: msys2 {0} +jobs: + build-and-test: + name: MSys-${{ matrix.msystem }} shared lib ${{ matrix.shared }} + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + msystem: [ ucrt64, mingw64, clang64 ] + shared: [on, off] + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + + - name: Install MSys + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.msystem }} + pacboy: >- + toolchain:p + cmake:p + path-type: minimal + + - name: Adjust environment + shell: bash + run: | + echo "SEXP_INSTALL_PATH=$(cygpath -u $PWD/install)" >> $GITHUB_ENV + echo "SHARED_LIB=${{ matrix.shared }}" >> $GITHUB_ENV + + - name: Configure + run: cmake -Bbuild -DCMAKE_INSTALL_PREFIX=${{ env.SEXP_INSTALL_PATH }} -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=${{ matrix.shared }} + + - name: Build + run: cmake --build build --config Release + + - name: Run tests + run: ctest --test-dir build --output-on-failure + + - name: Checkout shell test framework + uses: actions/checkout@v3 + with: + repository: kward/shunit2 + path: ${{ github.workspace }}/tests/shunit2 + fetch-depth: 1 + + - name: Install + run: cmake --install build + + - name: Run additional tests + run: tests/scripts/tests.sh diff --git a/.github/workflows/build-and-test-rh.yml b/.github/workflows/build-and-test-rh.yml new file mode 100644 index 0000000..c30fc19 --- /dev/null +++ b/.github/workflows/build-and-test-rh.yml @@ -0,0 +1,98 @@ +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +name: build-and-test-rh + +on: + push: + branches: [ main ] + pull_request: + workflow_dispatch: + +concurrency: + group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}' + cancel-in-progress: true + +env: + MAKEFLAGS: j4 + +jobs: + build-and-test: + name: ${{ matrix.image }}-${{ matrix.env.CC }} shared lib ${{ matrix.shared }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + image: [ 'centos:7', 'tgagor/centos:stream8', 'quay.io/centos/centos:stream9', 'fedora:35', 'fedora:36' ] + env: [ {CC: gcc, CXX: g++}, {CC: clang, CXX: clang++} ] + shared: [ on, off ] + container: ${{ matrix.image }} + env: ${{ matrix.env }} + steps: + - name: Install packages CentOS 7 + if: matrix.image == 'centos:7' + run: | + yum -y install http://opensource.wandisco.com/centos/7/git/x86_64/wandisco-git-release-7-2.noarch.rpm + yum -y install sudo git make wget gcc gcc-c++ clang + wget -nv https://github.com/Kitware/CMake/releases/download/v3.24.3/cmake-3.24.3-linux-x86_64.sh + chmod a+x cmake-3.24.3-linux-x86_64.sh + ./cmake-3.24.3-linux-x86_64.sh --skip-license --exclude-subdir --prefix=/usr/local + + - name: Install packages not CentOS 7 + if: matrix.image != 'centos:7' + run: yum -y install sudo git gcc gcc-c++ make cmake clang + + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + + - name: Adjust environment + run: | + echo "SEXP_INSTALL_PATH=$PWD/install" >> $GITHUB_ENV + echo "SHARED_LIB=${{ matrix.shared }}" >> $GITHUB_ENV + + - name: Configure + run: cmake . -Bbuild -DCMAKE_INSTALL_PREFIX=${{ env.SEXP_INSTALL_PATH }} -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=${{ matrix.shared }} + + - name: Build + run: cmake --build build --config Release + + - name: Run tests + run: ctest --test-dir build --output-on-failure + + - name: Install + run: cmake --install build + + - name: Install xargs + if: matrix.image == 'fedora:35' + run: sudo yum -y install findutils + + - name: Checkout shell test framework + uses: actions/checkout@v3 + with: + repository: kward/shunit2 + path: ${{github.workspace}}/tests/shunit2 + fetch-depth: 1 + + - name: Run additional tests + shell: bash + run: tests/scripts/tests.sh diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..ca1ffa6 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,142 @@ +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +name: build-and-test + +on: + push: + branches: [ main ] + pull_request: + workflow_dispatch: + +concurrency: + group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}' + cancel-in-progress: true + +env: + MAKEFLAGS: j4 + +jobs: + build-and-test: + runs-on: ${{ matrix.os }} + name: ${{ matrix.os }}-${{ matrix.env.CC }} shared lib ${{ matrix.shared }} + strategy: + fail-fast: false + matrix: + os: [ ubuntu-20.04, macos-11, windows-2019 ] + env: [ { } ] + shared: [on, off] + # on macos gcc is an alias to clang by default, so there is no real value in macos clang run + include: + - { os: ubuntu-20.04, env: { CC: clang, CXX: clang++ }, shared: on } + - { os: ubuntu-20.04, env: { CC: clang, CXX: clang++ }, shared: off } + # Builds sexp dll with MSVC is not supported + exclude: + - { os: windows-2019, env: { }, shared: on } + + env: ${{ matrix.env }} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + + - name: Adjust environment for Windows build + if: runner.os == 'Windows' + shell: bash + run: echo "OSTYPE=windows" >> $GITHUB_ENV + + - name: Adjust environment + run: echo "SHARED_LIB=${{ matrix.shared }}" >> $GITHUB_ENV + + - name: Configure + run: cmake -Bbuild -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=${{ matrix.shared }} + + - name: Build + run: cmake --build build --config Release + + - name: Run tests + run: ctest --test-dir build --output-on-failure + + - name: Checkout shell test framework + uses: actions/checkout@v3 + with: + repository: kward/shunit2 + path: ${{github.workspace}}/tests/shunit2 + fetch-depth: 1 + + - name: Install + run: cmake --install build + + - name: Run additional tests + shell: bash + run: tests/scripts/tests.sh + + offline-gtest: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + + - name: Install googletest + run: | + sudo apt-get -y install googletest + sudo cmake -B/usr/src/googletest/build /usr/src/googletest + sudo cmake --build /usr/src/googletest/build + sudo cmake --install /usr/src/googletest/build + + - name: Configure + run: | + cmake -Bbuild \ + -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install \ + -DCMAKE_BUILD_TYPE=Release \ + -DDOWNLOAD_GTEST=OFF + + - name: Build + run: cmake --build build + + - name: Run tests + run: ctest --test-dir build --output-on-failure + + sanitizers: + runs-on: ubuntu-latest + env: + CC: clang + CXX: clang++ + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + + - name: Configure + run: | + cmake -Bbuild \ + -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install \ + -DWITH_SANITIZERS=ON + + - name: Build + run: cmake --build build + + - name: Run tests + run: ctest --test-dir build --output-on-failure diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..ed38ede --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,86 @@ +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + +concurrency: + group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}' + cancel-in-progress: true + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..55df2ff --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,71 @@ +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +name: coverage + +on: + push: + branches: [ main ] + pull_request: + workflow_dispatch: + +concurrency: + group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}' + cancel-in-progress: true + +jobs: + coverage: + runs-on: ubuntu-latest + env: + CC: gcc + CXX: g++ + CODECOV_TOKEN: dbb06ac6-97ef-4d04-a0c6-24f7e3d8e1d6 + MAKEFLAGS: j4 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + + - name: Configure + run: | + cmake -Bbuild \ + -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install \ + -DWITH_COVERAGE=ON + + - name: Build + run: cmake --build build + + - name: Run tests + run: ctest --test-dir build --output-on-failure + + - name: Upload to codecov.io + run: | + curl -Os https://uploader.codecov.io/latest/linux/codecov + curl https://uploader.codecov.io/verification.gpg | gpg --no-default-keyring --keyring trustedkeys.gpg --import + curl -Os https://uploader.codecov.io/latest/linux/codecov + curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM + curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM.sig + gpgv codecov.SHA256SUM.sig codecov.SHA256SUM + shasum -a 256 -c codecov.SHA256SUM + find "build" -type f -name '*.gcno' -exec gcov -p {} + + chmod +x codecov + ./codecov -t ${{ env.CODECOV_TOKEN }} diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml new file mode 100644 index 0000000..5c74eeb --- /dev/null +++ b/.github/workflows/coverity.yml @@ -0,0 +1,63 @@ +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +name: coverity + +on: + push: + branches: [ main ] + pull_request: + workflow_dispatch: + +concurrency: + group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}' + cancel-in-progress: true + +jobs: + coverity: + runs-on: ubuntu-latest + env: + CC: gcc + CXX: g++ + MAKEFLAGS: j4 + COVERITY_TOKEN: qjcM1CWLcq9PJB3yL0ZXIw + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + + - name: Install googletest + run: | + sudo apt-get -y install googletest + sudo cmake -B/usr/src/googletest/build /usr/src/googletest + sudo cmake --build /usr/src/googletest/build + sudo cmake --install /usr/src/googletest/build + + - name: Configure + run: cmake -Bbuild -DDOWNLOAD_GTEST=OFF + + - name: Coverity Scan + uses: vapier/coverity-scan-action@v1 + with: + email: maxirmx@sw.consulting + token: ${{ env.COVERITY_TOKEN }} + command: cmake --build build diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..0a80e40 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,56 @@ +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +name: lint + +on: + workflow_dispatch: + + pull_request: + paths-ignore: + - '/.*' + - '!.clang-format' + - '**.adoc' + - '**.md' + +jobs: + clang-format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: DoozyX/clang-format-lint-action@v0.11 + with: + source: '.' + extensions: 'h,cpp,c' + clangFormatVersion: 11 +# inplace: True +# - uses: EndBug/add-and-commit@v9 +# with: +# author_name: A robot on behalf of Maxim Samsonov +# author_email: m.samsonov@computer.org +# message: 'Committing clang-format changes' +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ludeeus/action-shellcheck@master diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 0000000..ed19bce --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,64 @@ +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +name: nix + +on: + push: + branches: [ main ] + pull_request: + workflow_dispatch: + +concurrency: + group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}' + cancel-in-progress: true + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + + - name: nix setup + uses: cachix/install-nix-action@v22 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - name: nix build + run: nix build + + - name: Checkout shell test framework + uses: actions/checkout@v3 + with: + repository: kward/shunit2 + path: ${{github.workspace}}/tests/shunit2 + fetch-depth: 1 + + - name: Run tests + run: DIR_INSTALL=result tests/scripts/tests.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66516f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +### CMake and build artifacts ### +/build +/baseline +/tests/include/sexp-samples-folder.h +/.vscode +/result +/tests/shunit2 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9ac363c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,273 @@ +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +# 3.14 -- FetchContent +cmake_minimum_required(VERSION 3.14) + +include(cmake/version.cmake) +determine_version("${CMAKE_CURRENT_SOURCE_DIR}" SEXPP) +# project name, etc +project(sexpp + VERSION "${SEXPP_VERSION}" + LANGUAGES C CXX + DESCRIPTION "S-expressions parser and generator C++ library, fully compliant to [https://people.csail.mit.edu/rivest/Sexp.txt]" +) + +option(WITH_SEXP_TESTS "Build tests" ON) +option(WITH_SEXP_CLI "Build sexp console application" ON) +option(WITH_SANITIZERS "Enable ASAN and other sanitizers" OFF) +option(WITH_COVERAGE "Enable coverage report" OFF) +option(DOWNLOAD_GTEST "Download googletest" ON) +option(BUILD_SHARED_LIBS "Build shared library" OFF) + +include(GNUInstallDirs) +include(CheckCXXSourceCompiles) + +if (BUILD_SHARED_LIBS) + set(TYPE "SHARED") +else (BUILD_SHARED_LIBS) + set(TYPE "STATIC") +endif (BUILD_SHARED_LIBS) + +if (BUILD_SHARED_LIBS AND MSVC) + message(FATAL_ERROR "Building sexp shared library with MSVC is not supported") +endif(BUILD_SHARED_LIBS AND MSVC) + + +message(STATUS "Building ${TYPE} library") + +if (WITH_SANITIZERS) + if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + message(FATAL_ERROR "Sanitizers work with clang compiler only.") + endif() + if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + message(STATUS "Forcing build type to Debug for sanitizers") + set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type. Forced to Debug" FORCE) + set(WITH_TESTS ON CACHE STRING "Forced to ON" FORCE) + endif() +endif() + +if (WITH_COVERAGE) + if (NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU") + message(FATAL_ERROR "Coverage works with GNU compiler only.") + endif() + if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + message(STATUS "Forcing build type to Debug for coverage") + set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type. Forced to Debug" FORCE) + set(WITH_TESTS ON CACHE STRING "Forced to ON" FORCE) + endif() +endif() + +if(NOT CMAKE_BUILD_TYPE) + message(STATUS "Defaulting build type to Debug") + set(CMAKE_BUILD_TYPE Debug) +endif(NOT CMAKE_BUILD_TYPE) + +message(STATUS "Building ${CMAKE_BUILD_TYPE} configuration") + +if (WITH_SANITIZERS) + add_compile_options(-fsanitize=leak,address,undefined -fno-omit-frame-pointer -fno-common -O1) + link_libraries(-fsanitize=leak,address,undefined) +endif(WITH_SANITIZERS) + +if (WITH_COVERAGE) + add_compile_options(--coverage -O0) + link_libraries(--coverage) +endif(WITH_COVERAGE) + +# set warning flags at the top level +if(NOT MSVC) + add_compile_options( + -Wall -Wextra + -Wunreachable-code -Wpointer-arith + -Wmissing-declarations + ) +# relax some warnings a bit + add_compile_options( + -Wno-pedantic + -Wno-ignored-qualifiers + -Wno-unused-parameter + -Wno-missing-field-initializers + ) + +endif(NOT MSVC) + +add_library(sexpp ${TYPE} + "src/sexp-input.cpp" + "src/sexp-output.cpp" + "src/sexp-object.cpp" + "src/sexp-simple-string.cpp" + "src/sexp-char-defs.cpp" + "src/sexp-error.cpp" + "src/sexp-depth-manager.cpp" + "src/ext-key-format.cpp" + "include/sexpp/sexp.h" + "include/sexpp/sexp-error.h" + "include/sexpp/ext-key-format.h" +) + +target_compile_features(sexpp PUBLIC cxx_std_11) +target_include_directories(sexpp PUBLIC + $ + $ +) + +if (BUILD_SHARED_LIBS) + target_compile_definitions(sexpp PUBLIC BUILD_SHARED_LIBS) + set_target_properties(sexpp PROPERTIES CXX_VISIBILITY_PRESET "hidden") +endif (BUILD_SHARED_LIBS) + +set_target_properties(sexpp PROPERTIES + POSITION_INDEPENDENT_CODE ON + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" + VERSION "${SEXPP_VERSION}" + SOVERSION "${SEXPP_MAJOR_VERSION}" + OUTPUT_NAME "sexpp" +) + +if (WITH_SEXP_CLI) + add_executable (sexpp-cli + src/sexp-main.cpp + include/sexpp/sexp.h + include/sexpp/sexp-error.h + ) + target_include_directories (sexpp-cli PUBLIC include) + target_link_libraries(sexpp-cli PRIVATE sexpp) + target_compile_features(sexpp-cli PUBLIC cxx_std_11) + set_target_properties(sexpp-cli PROPERTIES + RUNTIME_OUTPUT_NAME sexpp + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin + ) +endif (WITH_SEXP_CLI) + + +if(WITH_SEXP_TESTS) + CHECK_CXX_SOURCE_COMPILES( + "#include + #include + struct test_struct { + std::string str; + }; + int main() { + bool b = std::is_copy_constructible::value; + return b ? 0 : -1 ; + }" + CAN_USE_NEW_GTEST + ) + + if(CAN_USE_NEW_GTEST) + set(GTEST_TAG "release-1.12.1") + else(CAN_USE_NEW_GTEST) + if(WITH_COVERAGE) + message(FATAL_ERROR "Coverage requires newer version of GTest that won't compile with your toolset") + endif(WITH_COVERAGE) + set(GTEST_TAG "release-1.8.1") + endif(CAN_USE_NEW_GTEST) + + include(CTest) + enable_testing() + include(GoogleTest) + + set(SEXP_SAMPLES_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/samples") + + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/sexp-samples-folder.h.in" + "${CMAKE_CURRENT_SOURCE_DIR}/tests/include/sexp-samples-folder.h" + @ONLY + ) + + add_executable(sexpp-tests + "tests/src/baseline-tests.cpp" + "tests/src/exception-tests.cpp" + "tests/src/primitives-tests.cpp" + "tests/src/g10-compat-tests.cpp" + "tests/src/g23-compat-tests.cpp" + "tests/src/g23-exception-tests.cpp" + "tests/src/compare-files.cpp" + "tests/include/sexp-tests.h" + ) + + if(DOWNLOAD_GTEST) + message(STATUS "Fetching googletest") + include(FetchContent) + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG ${GTEST_TAG} + ) + # maintain compiler/linker settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + # do not install gtest + set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) + # explicitely disable unneeded gmock build + set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) + + FetchContent_MakeAvailable(googletest) + set(GTestMain gtest_main) + set(GTest gtest) + else(DOWNLOAD_GTEST) + find_package(GTest REQUIRED) + set(GTestMain GTest::Main) + set(GTest GTest::GTest) + + if(NOT MSVC) + target_link_libraries(sexpp-tests PRIVATE pthread) + endif(NOT MSVC) + endif(DOWNLOAD_GTEST) + + target_link_libraries(sexpp-tests PRIVATE + sexpp + ${GTestMain} + ${GTest} + ) + + target_include_directories(sexpp-tests PRIVATE + tests/include + include + "${GTEST_INCLUDE_DIRS}" + ) + + target_compile_features(sexpp-tests PUBLIC cxx_std_11) + set_target_properties(sexpp-tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + + gtest_discover_tests(sexpp-tests) +endif(WITH_SEXP_TESTS) + +set(CONFIGURED_PC "${CMAKE_CURRENT_BINARY_DIR}/sexpp.pc") +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/sexpp.pc.in" + "${CONFIGURED_PC}" + @ONLY +) + +if (WIN32 AND BUILD_SHARED_LIBS) + install(TARGETS sexpp RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") + install(TARGETS sexpp ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}") +else(WIN32 AND BUILD_SHARED_LIBS) + install(TARGETS sexpp DESTINATION "${CMAKE_INSTALL_LIBDIR}") +endif(WIN32 AND BUILD_SHARED_LIBS) + +install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +install(FILES "${CONFIGURED_PC}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +if(WITH_SEXP_CLI) + install(TARGETS sexpp-cli DESTINATION "${CMAKE_INSTALL_BINDIR}") + install(FILES man/sexpp.1 DESTINATION "${CMAKE_INSTALL_MANDIR}/man1") +endif(WITH_SEXP_CLI) diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..2e579d2 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,34 @@ +Original MIT License +==================== + +Copyright 1997 Ronald L. Rivest, Butler Lampson +Copyright 1997 MIT Laboratory for Computer Science + +The code is available under the "MIT License" (open source). + +License text available at: +https://opensource.org/licenses/MIT + + +Ribose MIT License +==================== + +Copyright 2021-2022 Ribose Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..765685c --- /dev/null +++ b/README.adoc @@ -0,0 +1,230 @@ += S-Expressions parser and generator library in C\++ (SEXP in C++) + +image:https://github.com/rnpgp/sexp/workflows/build-and-test/badge.svg["Build status Ubuntu/macOS/Windows", link="https://github.com/rnpgp/sexp/actions?workflow=build-and-test"] +image:https://github.com/rnpgp/sexp/workflows/build-and-test-rh/badge.svg["Build status CentOS/Fedora", link="https://github.com/rnpgp/sexp/actions?workflow=build-and-test-rh"] +image:https://github.com/rnpgp/sexp/workflows/build-and-test-deb/badge.svg["Build status Debian", link="https://github.com/rnpgp/sexp/actions?workflow=build-and-test-deb"] +image:https://github.com/rnpgp/sexp/workflows/build-and-test-msys/badge.svg["Build status MSys", link="https://github.com/rnpgp/sexp/actions?workflow=build-and-test-msys"] + + +image:https://codecov.io/gh/rnpgp/sexpp/branch/main/graph/badge.svg["Code coverage", link="https://codecov.io/gh/rnpgp/sexpp"] +image:https://github.com/rnpgp/sexpp/workflows/CodeQL/badge.svg["CodeQL analysis", link="https://github.com/rnpgp/sexpp/actions?workflow=CodeQL"] +image:https://scan.coverity.com/projects/28717/badge.svg["Coverity Scan Build Status", link="https://scan.coverity.com/projects/rnpgp-sexpp"] + + +== Purpose + +This is a C++ library for working with S-Expressions. + +This implementation is derived from the reference SEXP C library developed by +Prof. Ronald Rivest and Prof. Butler Lampson of MIT LCS (now CSAIL). + +This library differs from the original C implementation in the following ways: + +* It aims to be reuseable in C++ implementations and is importable via CMake. +* It includes a test suite for correctness testing and tests against malformed + S-Expressions. +* It supports, and is tested against, all major platforms, including: +** Ubuntu, Debian, Fedora, CentOS +** macOS +** Windows +** msys +* It implements additional interface to work with S-Expressions wrapped by GnuPG + 2.3+ extended format + (https://github.com/gpg/gnupg/blob/master/agent/keyformat.txt[specification]). + + +The original C library was available at (but no longer accessible): + +* http://people.csail.mit.edu/rivest/sexp.html + + +== Background + +S-Expressions are a data structure for representing complex data as a variation +on https://en.wikipedia.org/wiki/Lisp_(programming_language)[LISP] S-Expressions. + +S-Expressions were originally adopted for use in +http://theory.lcs.mit.edu/~cis/sdsi.html[SDSI] and +http://world.std.com/~cme/html/spki.html[SPKI]. + +SDSI has been developed by Prof. +https://people.csail.mit.edu/rivest/index.html[Ronald L. Rivest] and +Prof. Butler Lampson of +http://www.lcs.mit.edu/[MIT's Laboratory for Computer Science], +members of +http://theory.lcs.mit.edu/~cis[LCS's Cryptography and Information Security] +research group. + +NOTE: SDSI research has been supported by DARPA contract DABT63-96-C-0018, +"Security for Distributed Computer Systems". + +NOTE: SPKI has been developed by +http://www.clark.net/pub/cme/home.html[Carl Ellison] and others in the IETF SPKI +working group. + + +== S-Expressions specification + +* https://datatracker.ietf.org/doc/draft-rivest-sexp/ + +NOTE: The "SEXP 1.0 guide" used to be at +https://people.csail.mit.edu/rivest/Sexp.txt. + + +== Code + +The library is a deep rework to C++ of the original +https://people.csail.mit.edu/rivest/sexp.html[SEXP library] that maintains full +support of original specification. + +While most applications will not need anything but the simple canonical and +transport formats; however, the code here is considerably more complex because +it also supports the advanced format, both for input and for output. + + +== Building and installation + +[source,sh] +---- +$ mkdir build +$ cd build +$ cmake .. +$ cmake --build . +$ ctest +$ cmake --install . +---- + + +== CMake script options + +`BUILD_SHARED_LIBS:BOOL`:: +(default: `OFF`) +build shared library + +`WITH_SEXP_TESTS:BOOL`:: +(default: `ON`) +build tests + +`DOWNLOAD_GTEST`:: +(default: `ON`) +if tests are built, automatically download googletest from GitHub; +when set to `OFF`, the googletest binary package needs to be available for SEXP +tests. + +`WITH_SEXP_CLI:BOOL`:: +(default: `ON`) build the `sexp` command-line utility + +`WITH_SANITIZERS:BOOL`:: +(default: `OFF`) +build with address and other sanitizers (requires clang compiler) + + + +== SEXPP command-line utility + +The `sexpp` command-line utility is reference parser and generator of +S-Expressions. It can read, parse and print out SEXP in all defined formats. + +=== Switches + +.`sexpp` switches +[options="header"] +|=== +| Switch | Description | Default + +3+| Input +| `-i ` | input file name | read input from console (stdin) +| `-p` | prompt input if reading from console | disabled +| `-s` | treat input as a single SEXP string | disabled, input is treated as an S-Expression + +3+| Output +| `-o ` | output file name: | write output to console (stdout) +| `-a` | generate advanced transport format | enabled if no format is specified +| `-b` | generate base-64 transport format | disabled +| `-c` | generate canonical format | disabled +| `-l` | suppress linefeeds after output | disabled +| `-w ` | set output line width (0 implies no constraint)| 75 + +3+| Miscellaneous +| `-x` | execute repeatedly until EOF | process single S-Expression then exit +| `-h` | print help message and exit | + +|=== + +Running without switches implies: `-p -a -b -c -x`. + +=== Usage examples + +Prompt for S-Expressions input from console, parse and output it to +`certificate.dat` in base64 transport format. + +[source] +---- +$ sexpp -o certificate.dat -p -b + +> Input: +> (aa bb (cc dd)) +> +> Writing base64 (of canonical) output to 'certificate.dat' +---- + +Parse all S-Expressions from `certificate.dat`, output them to console in +advanced transport format with no prompts: + +[source,sh] +---- +$ sexpp -i certificate.dat -x + +> (2:aa2:bb(2:cc2:dd)) +---- + +Parse S-Expressions from `certificate.dat`, output it to console in canonical, +base64 and advanced format with prompts and no width limitation: + +[source,sh] +---- +$ sexpp -i certificate.dat -a -b -c -p -w 0 + +> Reading input from certificate.dat +> +> Canonical output: +> (2:aa2:bb(2:cc2:dd)) +> Base64 (of canonical) output: +> {KDI6YWEyOmJiKDI6Y2MyOmRkKSk=} +> Advanced transport output: +> (aa bb (cc dd)) +---- + +Repeatedly prompt for S-Expressions input from console, parse and output it +console in advanced, base64 and canonical formats: + +[source,sh] +---- +$ sexpp -p -a -b -c -x +---- + +or just + +[source,sh] +---- +$ sexpp + +> Input: +> (abc def (ghi jkl)) +> +> Canonical output: +> (3:abc3:def(3:ghi3:jkl)) +> Base64 (of canonical) output: +> {KDM6YWJjMzpkZWYoMzpnaGkzOmprbCkp} +> Advanced transport output: +> (abc def (ghi jkl)) +> +> Input: +> ^C +---- + +== License + +Copyright Ribose. + +The code is made available as open-source software under the MIT License. diff --git a/cmake/sexp-samples-folder.h.in b/cmake/sexp-samples-folder.h.in new file mode 100644 index 0000000..7b652e8 --- /dev/null +++ b/cmake/sexp-samples-folder.h.in @@ -0,0 +1,24 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +const std::string sexp_samples_folder = "@SEXP_SAMPLES_FOLDER@"; diff --git a/cmake/sexpp.pc.in b/cmake/sexpp.pc.in new file mode 100644 index 0000000..1ab63e1 --- /dev/null +++ b/cmake/sexpp.pc.in @@ -0,0 +1,9 @@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ + +Name: sexp +Description: C++ library for working with S-Expressions +Version: @PROJECT_VERSION@ +URL: https://github.com/rnpgp/sexp +Libs: -L${libdir} -lsexpp +Cflags: -I${includedir} diff --git a/cmake/version.cmake b/cmake/version.cmake new file mode 100644 index 0000000..41287e4 --- /dev/null +++ b/cmake/version.cmake @@ -0,0 +1,162 @@ +# +# Copyright 2018-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +# desired length of commit hash +set(GIT_REV_LEN 7) + +# call git, store output in var (can fail) +macro(_git var) + execute_process( + COMMAND "${GIT_EXECUTABLE}" ${ARGN} + WORKING_DIRECTORY "${source_dir}" + RESULT_VARIABLE _git_ec + OUTPUT_VARIABLE ${var} + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) +endmacro() + +function(extract_version_info version var_prefix) + # extract the main components + # v1.9.0-3-g5b92266+1546836556 + # v1.9.0-3-g5b92266-dirty+1546836556 + string(REGEX MATCH "^v?(([0-9]+)\\.[0-9]+\\.[0-9]+)(-([0-9]+)-g([0-9a-f]+)(-dirty)?)?(\\+([0-9]+))?$" matches "${version}") + if (NOT matches) + message(FATAL_ERROR "Failed to extract version components from ${version}.") + endif() + set(${var_prefix}_VERSION "${CMAKE_MATCH_1}" PARENT_SCOPE) # 1.9.0 + set(${var_prefix}_MAJOR_VERSION "${CMAKE_MATCH_2}" PARENT_SCOPE) # 1 + if (NOT CMAKE_MATCH_4) + set(CMAKE_MATCH_4 "0") + endif() + set(${var_prefix}_VERSION_NCOMMITS "${CMAKE_MATCH_4}" PARENT_SCOPE) # 3 + if (NOT CMAKE_MATCH_5) + set(CMAKE_MATCH_5 "0") + endif() + set(${var_prefix}_VERSION_GIT_REV "${CMAKE_MATCH_5}" PARENT_SCOPE) # 5b92266 + if (CMAKE_MATCH_6 STREQUAL "-dirty") + set(${var_prefix}_VERSION_IS_DIRTY TRUE PARENT_SCOPE) + else() + set(${var_prefix}_VERSION_IS_DIRTY FALSE PARENT_SCOPE) + endif() + # timestamp is optional, default to 0 + if (NOT CMAKE_MATCH_8) + set(CMAKE_MATCH_8 "0") + endif() + set(${var_prefix}_VERSION_COMMIT_TIMESTAMP "${CMAKE_MATCH_8}" PARENT_SCOPE) # 1546836556 +endfunction() + +function(determine_version source_dir var_prefix) + set(has_release_tag NO) + set(has_version_txt NO) + set(local_prefix "_determine_ver") + # find out base version via version.txt + set(base_version "0.0.0") + if (EXISTS "${source_dir}/version.txt") + set(has_version_txt YES) + file(STRINGS "${source_dir}/version.txt" version_file) + extract_version_info("${version_file}" "${local_prefix}") + set(base_version "${${local_prefix}_VERSION}") + message(STATUS "Found version.txt with ${version_file}") + else() + message(STATUS "Found no version.txt.") + endif() + # for GIT_EXECUTABLE + find_package(Git) + # get a description of the version, something like: + # v1.9.1-0-g38ffe82 (a tagged release) + # v1.9.1-0-g38ffe82-dirty (a tagged release with local modifications) + # v1.9.0-3-g5b92266 (post-release snapshot) + # v1.9.0-3-g5b92266-dirty (post-release snapshot with local modifications) + _git(version describe --abbrev=${GIT_REV_LEN} --match "v[0-9]*" --long --dirty) + if (NOT _git_ec EQUAL 0) + # no annotated tags, fake one + message(STATUS "Found no annotated tags.") + _git(revision rev-parse --short=${GIT_REV_LEN} --verify HEAD) + if (_git_ec EQUAL 0) + set(version "v${base_version}-0-g${revision}") + # check if dirty (this won't detect untracked files, but should be ok) + _git(changes diff-index --quiet HEAD --) + if (NOT _git_ec EQUAL 0) + string(APPEND version "-dirty") + endif() + # append the commit timestamp of the most recent commit (only + # in non-release branches -- typically master) + _git(commit_timestamp show -s --format=%ct) + if (_git_ec EQUAL 0) + string(APPEND version "+${commit_timestamp}") + endif() + elseif(has_version_txt) + # Nothing to get from git - so use version.txt completely + set(version "${version_file}") + else() + # Sad case - no git, no version.txt + set(version "v${base_version}") + endif() + else() + set(has_release_tag YES) + message(STATUS "Found annotated tag ${version}") + endif() + extract_version_info("${version}" "${local_prefix}") + if ("${has_version_txt}" AND NOT ${base_version} STREQUAL ${local_prefix}_VERSION) + message(WARNING "Tagged version ${${local_prefix}_VERSION} doesn't match one from the version.txt: ${base_version}") + if (${base_version} VERSION_GREATER ${local_prefix}_VERSION) + set(${local_prefix}_VERSION ${base_version}) + endif() + endif() + foreach(suffix VERSION VERSION_NCOMMITS VERSION_GIT_REV VERSION_IS_DIRTY VERSION_COMMIT_TIMESTAMP) + if (NOT DEFINED ${local_prefix}_${suffix}) + message(FATAL_ERROR "Unable to determine version.") + endif() + set(${var_prefix}_${suffix} "${${local_prefix}_${suffix}}" PARENT_SCOPE) + message(STATUS "${var_prefix}_${suffix}: ${${local_prefix}_${suffix}}") + endforeach() + # Set VERSION_SUFFIX and VERSION_FULL. When making changes, be aware that + # this is used in packaging as well and will affect ordering. + # | state | version_full | + # |-----------------------------------------------------| + # | exact tag | 0.9.0 | + # | exact tag, dirty | 0.9.0+git20180604 | + # | after tag | 0.9.0+git20180604.1.085039f | + # | no tag, version.txt | 0.9.0+git20180604.2ee02af | + # | no tag, no version.txt| 0.0.0+git20180604.2ee02af | + string(TIMESTAMP date "%Y%m%d" UTC) + set(version_suffix "") + if (NOT ${local_prefix}_VERSION_NCOMMITS EQUAL 0) + # 0.9.0+git20150604.4.289818b + string(APPEND version_suffix "+git${date}.${${local_prefix}_VERSION_NCOMMITS}.${${local_prefix}_VERSION_GIT_REV}") + elseif ((NOT has_release_tag) AND ((NOT has_version_txt) OR ("${base_version}" STREQUAL "0.0.0") OR (NOT "${revision}" STREQUAL ""))) + # 0.9.0+git20150604.289818b + string(APPEND version_suffix "+git${date}.${${local_prefix}_VERSION_GIT_REV}") + elseif(${local_prefix}_VERSION_IS_DIRTY) + # 0.9.0+git20150604 + string(APPEND version_suffix "+git${date}") + endif() + set(version_full "${${local_prefix}_VERSION}${version_suffix}") + # set the results + set(${var_prefix}_VERSION_SUFFIX "${version_suffix}" PARENT_SCOPE) + set(${var_prefix}_VERSION_FULL "${version_full}" PARENT_SCOPE) + set(${var_prefix}_MAJOR_VERSION "${${local_prefix}_MAJOR_VERSION}" PARENT_SCOPE) # 1 + # for informational purposes + message(STATUS "${var_prefix}_MAJOR_VERSION: ${${local_prefix}_MAJOR_VERSION}") + message(STATUS "${var_prefix}_VERSION_SUFFIX: ${version_suffix}") + message(STATUS "${var_prefix}_VERSION_FULL: ${version_full}") +endfunction() diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..d5eedac --- /dev/null +++ b/codecov.yml @@ -0,0 +1,24 @@ +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +ignore: + - "tests" + - "src/sexp-main.cpp" diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..618fa25 --- /dev/null +++ b/default.nix @@ -0,0 +1,49 @@ +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +{ pkgs ? import { } +, lib ? pkgs.lib +, stdenv ? pkgs.stdenv +}: + +stdenv.mkDerivation rec { + pname = "sexp"; + version = "unstable"; + + src = ./.; + + cmakeFlags = [ + "-DCMAKE_INSTALL_PREFIX=${placeholder "out"}" + "-DWITH_SEXP_TESTS=on" + "-DWITH_SEXP_CLI=on" + "-DDOWNLOAD_GTEST=off" + ]; + + nativeBuildInputs = with pkgs; [ cmake gtest pkg-config ]; + + meta = with lib; { + homepage = "https://github.com/rnpgp/sexp"; + description = "S-expressions parser and generator C++ library, fully compliant to [https://people.csail.mit.edu/rivest/Sexp.txt]"; + license = licenses.bsd2; + platforms = platforms.all; + maintainers = with maintainers; [ ribose-jeffreylau ]; + }; +} \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..a31de2c --- /dev/null +++ b/flake.lock @@ -0,0 +1,42 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1669023499, + "narHash": "sha256-iXvCW2VaH4LJD1AElk82/FaR7yepj/BqLYOJ3GAhdKQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "c107fb66da7c150110fe301c38932eeccf5f9824", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c2fb794 --- /dev/null +++ b/flake.nix @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +{ + description = "S-expressions parser and generator C++ library, fully compliant to [https://people.csail.mit.edu/rivest/Sexp.txt]"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + thePackage = pkgs.callPackage ./default.nix { }; + in + rec { + defaultApp = flake-utils.lib.mkApp { + drv = defaultPackage; + }; + defaultPackage = thePackage; + devShell = pkgs.mkShell { + buildInputs = [ + thePackage + ]; + }; + }); +} diff --git a/include/sexpp/ext-key-format.h b/include/sexpp/ext-key-format.h new file mode 100644 index 0000000..bef746f --- /dev/null +++ b/include/sexpp/ext-key-format.h @@ -0,0 +1,95 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +#pragma once + +#include + +#include "sexp-public.h" +#include "sexp.h" + +namespace ext_key_format { + +void SEXP_PUBLIC_SYMBOL ext_key_error( + sexp::sexp_exception_t::severity level, const char *msg, size_t c1, size_t c2, int pos); + +class ext_key_input_stream_t; + +class SEXP_PUBLIC_SYMBOL extended_private_key_t { + public: + // Comparison of names is done case insensitively !!! + struct ci_less { + // case-independent (ci) compare_less binary function + bool operator()(const std::string &s1, const std::string &s2) const + { + return std::lexicographical_compare( + s1.begin(), s1.end(), s2.begin(), s2.end(), [](char a, char b) { + return std::tolower(a) < std::tolower(b); + }); + } + }; + + // C++ 11 compatible version (no std::equals) + static bool iequals(const std::string &a, const std::string &b) + { + size_t sz = a.size(); + if (b.size() != sz) + return false; + for (size_t i = 0; i < sz; ++i) + if (tolower(a[i]) != tolower(b[i])) + return false; + return true; + } + + typedef std::multimap fields_map_t; + + sexp::sexp_list_t key; + fields_map_t fields; + + void parse(ext_key_input_stream_t &is); +}; + +class SEXP_PUBLIC_SYMBOL ext_key_input_stream_t : public sexp::sexp_input_stream_t { + private: + static const bool namechar[256]; /* true if allowed in the name field */ + + static bool is_newline_char(int c) { return c == '\r' || c == '\n'; }; + static bool is_namechar(int c) { return ((c >= 0 && c <= 255) && namechar[c]); } + + bool is_scanning_value; + bool has_key; + + int skip_line(void); + virtual int read_char(void); + std::string scan_name(int c); + std::string scan_value(void); + + public: + ext_key_input_stream_t(std::istream *i, size_t md = 0) + : sexp_input_stream_t(i, md), is_scanning_value(false), has_key(false) + { + } + virtual ~ext_key_input_stream_t() = default; + void scan(extended_private_key_t &extended_key); +}; +} // namespace ext_key_format diff --git a/include/sexpp/sexp-error.h b/include/sexpp/sexp-error.h new file mode 100644 index 0000000..3ed345f --- /dev/null +++ b/include/sexpp/sexp-error.h @@ -0,0 +1,73 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +#pragma once + +#include +#include +#include +#include + +#include "sexp-public.h" + +namespace sexp { + +class SEXP_PUBLIC_SYMBOL sexp_exception_t : public std::exception { + public: + enum severity { error = 0, warning = 1 }; + + protected: + static severity verbosity; + static bool interactive; + + int position; // May be EOF aka -1 + severity level; + std::string message; + + public: + sexp_exception_t(std::string error_message, + severity error_level, + int error_position, + const char *prefix = "SEXP") + : position{error_position}, level{error_level}, + message{format(prefix, std::move(error_message), error_level, error_position)} {}; + + static std::string format(std::string prf, + std::string message, + severity level, + int position); + + static bool shall_throw(severity level) { return level == error || verbosity != error; }; + virtual const char *what(void) const throw() { return message.c_str(); }; + severity get_level(void) const { return level; }; + uint32_t get_position(void) const { return position; }; + static severity get_verbosity(void) { return verbosity; }; + static bool is_interactive(void) { return interactive; }; + static void set_verbosity(severity new_verbosity) { verbosity = new_verbosity; }; + static void set_interactive(bool new_interactive) { interactive = new_interactive; }; +}; + +void SEXP_PUBLIC_SYMBOL +sexp_error(sexp_exception_t::severity level, const char *msg, size_t c1, size_t c2, int pos); + +} // namespace sexp diff --git a/include/sexpp/sexp-public.h b/include/sexpp/sexp-public.h new file mode 100644 index 0000000..ab0674e --- /dev/null +++ b/include/sexpp/sexp-public.h @@ -0,0 +1,30 @@ +/** + * + * Copyright 2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +#pragma once + +#ifdef BUILD_SHARED_LIBS +#define SEXP_PUBLIC_SYMBOL __attribute__((visibility("default"))) +#else +#define SEXP_PUBLIC_SYMBOL +#endif diff --git a/include/sexpp/sexp.h b/include/sexpp/sexp.h new file mode 100644 index 0000000..bb6ae4e --- /dev/null +++ b/include/sexpp/sexp.h @@ -0,0 +1,466 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + * Original copyright + * + * SEXP standard header file: sexp.h + * Ronald L. Rivest + * 6/29/1997 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sexp-public.h" +#include "sexp-error.h" + +namespace sexp { + +/* + * SEXP octet_t definitions + * We maintain some presumable redundancy with ctype + * However, we do enforce 'C' locale this way + */ + +class SEXP_PUBLIC_SYMBOL sexp_char_defs_t { + protected: + static const bool base64digit[256]; /* true if c is base64 digit */ + static const bool tokenchar[256]; /* true if c can be in a token */ + static const unsigned char values[256][3]; /* values of c as { dec. hex, base64 } digit */ + static std::locale c_locale; + + static bool is_white_space(int c) + { + return c >= 0 && c <= 255 && std::isspace((char) c, c_locale); + }; + static bool is_dec_digit(int c) + { + return c >= 0 && c <= 255 && std::isdigit((char) c, c_locale); + }; + static bool is_hex_digit(int c) + { + return c >= 0 && c <= 255 && std::isxdigit((char) c, c_locale); + }; + static bool is_base64_digit(int c) { return c >= 0 && c <= 255 && base64digit[c]; }; + static bool is_token_char(int c) { return c >= 0 && c <= 255 && tokenchar[c]; }; + static bool is_alpha(int c) + { + return c >= 0 && c <= 255 && std::isalpha((char) c, c_locale); + }; + + /* decvalue(c) is value of c as dec digit */ + static unsigned char decvalue(int c) { return (c >= 0 && c <= 255) ? values[c][0] : 0; }; + /* hexvalue(c) is value of c as a hex digit */ + static unsigned char hexvalue(int c) { return (c >= 0 && c <= 255) ? values[c][1] : 0; }; + /* base64value(c) is value of c as base64 digit */ + static unsigned char base64value(int c) + { + return (c >= 0 && c <= 255) ? values[c][2] : 0; + }; +}; + +class sexp_string_t; +class sexp_list_t; + +class sexp_output_stream_t; +class sexp_input_stream_t; + +/* + * SEXP simple string + */ + +typedef uint8_t octet_t; + +class SEXP_PUBLIC_SYMBOL sexp_simple_string_t : public std::basic_string, + private sexp_char_defs_t { + public: + sexp_simple_string_t(void) = default; + sexp_simple_string_t(const octet_t *dt) : std::basic_string{dt} {} + sexp_simple_string_t(const octet_t *bt, size_t ln) : std::basic_string{bt, ln} {} + sexp_simple_string_t &append(int c) + { + (*this) += (octet_t)(c & 0xFF); + return *this; + } + // Returns length for printing simple string as a token + size_t advanced_length_token(void) const { return length(); } + // Returns length for printing simple string as a base64 string + size_t advanced_length_base64(void) const { return (2 + 4 * ((length() + 2) / 3)); } + // Returns length for printing simple string ss in quoted-string mode + size_t advanced_length_quoted(void) const { return (1 + length() + 1); } + // Returns length for printing simple string ss in hexadecimal mode + size_t advanced_length_hexadecimal(void) const { return (1 + 2 * length() + 1); } + size_t advanced_length(sexp_output_stream_t *os) const; + + sexp_output_stream_t *print_canonical_verbatim(sexp_output_stream_t *os) const; + sexp_output_stream_t *print_advanced(sexp_output_stream_t *os) const; + sexp_output_stream_t *print_token(sexp_output_stream_t *os) const; + sexp_output_stream_t *print_quoted(sexp_output_stream_t *os) const; + sexp_output_stream_t *print_hexadecimal(sexp_output_stream_t *os) const; + sexp_output_stream_t *print_base64(sexp_output_stream_t *os) const; + + bool can_print_as_quoted_string(void) const; + bool can_print_as_token(const sexp_output_stream_t *os) const; + + bool operator==(const char *right) const noexcept + { + return length() == std::strlen(right) && std::memcmp(data(), right, length()) == 0; + } + + bool operator!=(const char *right) const noexcept + { + return length() != std::strlen(right) || std::memcmp(data(), right, length()) != 0; + } + + unsigned as_unsigned() const noexcept + { + return empty() ? std::numeric_limits::max() : + (unsigned) atoi(reinterpret_cast(c_str())); + } +}; + +inline bool operator==(const sexp_simple_string_t *left, const std::string &right) noexcept +{ + return *left == right.c_str(); +} + +inline bool operator!=(const sexp_simple_string_t *left, const std::string &right) noexcept +{ + return *left != right.c_str(); +} + +/* + * SEXP object + */ + +class SEXP_PUBLIC_SYMBOL sexp_object_t { + public: + virtual ~sexp_object_t(){}; + + virtual sexp_output_stream_t *print_canonical(sexp_output_stream_t *os) const = 0; + virtual sexp_output_stream_t *print_advanced(sexp_output_stream_t *os) const; + virtual size_t advanced_length(sexp_output_stream_t *os) const = 0; + + virtual sexp_list_t * sexp_list_view(void) noexcept { return nullptr; } + virtual sexp_string_t *sexp_string_view(void) noexcept { return nullptr; } + virtual bool is_sexp_list(void) const noexcept { return false; } + virtual bool is_sexp_string(void) const noexcept { return false; } + + virtual const sexp_list_t *sexp_list_at( + std::vector>::size_type pos) const noexcept + { + return nullptr; + } + virtual const sexp_string_t *sexp_string_at( + std::vector>::size_type pos) const noexcept + { + return nullptr; + } + virtual const sexp_simple_string_t *sexp_simple_string_at( + std::vector>::size_type pos) const noexcept + { + return nullptr; + } + virtual bool operator==(const char *right) const noexcept { return false; } + virtual bool operator!=(const char *right) const noexcept { return true; } + virtual unsigned as_unsigned() const noexcept + { + return std::numeric_limits::max(); + } +}; + +/* + * SEXP string + */ + +class SEXP_PUBLIC_SYMBOL sexp_string_t : public sexp_object_t { + protected: + bool with_presentation_hint; + sexp_simple_string_t presentation_hint; + sexp_simple_string_t data_string; + + public: + sexp_string_t(const octet_t *dt) : with_presentation_hint(false), data_string(dt) {} + sexp_string_t(const octet_t *bt, size_t ln) + : with_presentation_hint(false), data_string(bt, ln) + { + } + sexp_string_t(const std::string &str) + : with_presentation_hint(false), + data_string(reinterpret_cast(str.data())) + { + } + sexp_string_t(void) : with_presentation_hint(false) {} + sexp_string_t(sexp_input_stream_t *sis) { parse(sis); }; + + const bool has_presentation_hint(void) const noexcept { return with_presentation_hint; } + const sexp_simple_string_t &get_string(void) const noexcept { return data_string; } + const sexp_simple_string_t &set_string(const sexp_simple_string_t &ss) + { + return data_string = ss; + } + const sexp_simple_string_t &get_presentation_hint(void) const noexcept + { + return presentation_hint; + } + const sexp_simple_string_t &set_presentation_hint(const sexp_simple_string_t &ph) + { + with_presentation_hint = true; + return presentation_hint = ph; + } + + virtual sexp_output_stream_t *print_canonical(sexp_output_stream_t *os) const; + virtual sexp_output_stream_t *print_advanced(sexp_output_stream_t *os) const; + virtual size_t advanced_length(sexp_output_stream_t *os) const; + + virtual sexp_string_t *sexp_string_view(void) noexcept { return this; } + virtual bool is_sexp_string(void) const noexcept { return true; } + + virtual bool operator==(const char *right) const noexcept { return data_string == right; } + virtual bool operator!=(const char *right) const noexcept { return data_string != right; } + + void parse(sexp_input_stream_t *sis); + virtual unsigned as_unsigned() const noexcept { return data_string.as_unsigned(); } +}; + +inline bool operator==(const sexp_string_t *left, const std::string &right) noexcept +{ + return *left == right.c_str(); +} + +inline bool operator!=(const sexp_string_t *left, const std::string &right) noexcept +{ + return *left != right.c_str(); +} + +/* + * SEXP list + */ + +class SEXP_PUBLIC_SYMBOL sexp_list_t : public sexp_object_t, + public std::vector> { + public: + virtual ~sexp_list_t() {} + + virtual sexp_output_stream_t *print_canonical(sexp_output_stream_t *os) const; + virtual sexp_output_stream_t *print_advanced(sexp_output_stream_t *os) const; + virtual size_t advanced_length(sexp_output_stream_t *os) const; + + virtual sexp_list_t *sexp_list_view(void) noexcept { return this; } + virtual bool is_sexp_list(void) const noexcept { return true; } + + virtual const sexp_list_t *sexp_list_at(size_type pos) const noexcept + { + return pos < size() ? (*at(pos)).sexp_list_view() : nullptr; + } + virtual const sexp_string_t *sexp_string_at(size_type pos) const noexcept + { + return pos < size() ? (*at(pos)).sexp_string_view() : nullptr; + } + const sexp_simple_string_t *sexp_simple_string_at(size_type pos) const noexcept + { + auto s = sexp_string_at(pos); + return s != nullptr ? &s->get_string() : nullptr; + } + + void parse(sexp_input_stream_t *sis); +}; + +/* + sexp_depth_manager controls maximum allowed nesting of sexp lists + for sexp_input_stream, sexp_output_stream processing + One still can create an object with deeper nesting manually +*/ + +class SEXP_PUBLIC_SYMBOL sexp_depth_manager { + public: + static const size_t DEFAULT_MAX_DEPTH = 1024; + + private: + size_t depth; /* current depth of nested SEXP lists */ + size_t max_depth; /* maximum allowed depth of nested SEXP lists, 0 if no limit */ + protected: + sexp_depth_manager(size_t m_depth = DEFAULT_MAX_DEPTH); + void reset_depth(size_t m_depth); + void increase_depth(int count = -1); + void decrease_depth(void); +}; + +/* + * SEXP input stream + */ + +class SEXP_PUBLIC_SYMBOL sexp_input_stream_t : public sexp_char_defs_t, sexp_depth_manager { + protected: + std::istream *input_file; + uint32_t byte_size; /* 4 or 6 or 8 == currently scanning mode */ + int next_char; /* character currently being scanned */ + uint32_t bits; /* Bits waiting to be used */ + uint32_t n_bits; /* number of such bits waiting to be used */ + int count; /* number of 8-bit characters output by get_char */ + + virtual int read_char(void); + + public: + sexp_input_stream_t(std::istream *i, + size_t max_depth = sexp_depth_manager::DEFAULT_MAX_DEPTH); + virtual ~sexp_input_stream_t() = default; + sexp_input_stream_t * set_input(std::istream *i, + size_t max_depth = sexp_depth_manager::DEFAULT_MAX_DEPTH); + sexp_input_stream_t * set_byte_size(uint32_t new_byte_size); + uint32_t get_byte_size(void) { return byte_size; } + sexp_input_stream_t * get_char(void); + sexp_input_stream_t * skip_white_space(void); + sexp_input_stream_t * skip_char(int c); + std::shared_ptr scan_to_eof(); + std::shared_ptr scan_object(void); + std::shared_ptr scan_string(void); + std::shared_ptr scan_list(void); + sexp_simple_string_t scan_simple_string(void); + void scan_token(sexp_simple_string_t &ss); + void scan_verbatim_string(sexp_simple_string_t &ss, uint32_t length); + void scan_quoted_string(sexp_simple_string_t &ss, uint32_t length); + void scan_hexadecimal_string(sexp_simple_string_t &ss, uint32_t length); + void scan_base64_string(sexp_simple_string_t &ss, uint32_t length); + uint32_t scan_decimal_string(void); + + int get_next_char(void) const { return next_char; } + int set_next_char(int c) { return next_char = c; } + + sexp_input_stream_t *open_list(void); + sexp_input_stream_t *close_list(void); +}; + +/* + * SEXP output stream + */ + +class SEXP_PUBLIC_SYMBOL sexp_output_stream_t : sexp_depth_manager { + public: + const uint32_t default_line_length = 75; + enum sexp_print_mode { /* PRINTING MODES */ + canonical = 1, /* standard for hashing and tranmission */ + base64 = 2, /* base64 version of canonical */ + advanced = 3 /* pretty-printed */ + }; + + protected: + std::ostream * output_file; + uint32_t base64_count; /* number of hex or base64 chars printed this region */ + uint32_t byte_size; /* 4 or 6 or 8 depending on output mode */ + uint32_t bits; /* bits waiting to go out */ + uint32_t n_bits; /* number of bits waiting to go out */ + sexp_print_mode mode; /* base64, advanced, or canonical */ + uint32_t column; /* column where next character will go */ + uint32_t max_column; /* max usable column, or 0 if no maximum */ + uint32_t indent; /* current indentation level (starts at 0) */ + public: + sexp_output_stream_t(std::ostream *o, + size_t max_depth = sexp_depth_manager::DEFAULT_MAX_DEPTH); + sexp_output_stream_t *set_output(std::ostream *o, + size_t max_depth = sexp_depth_manager::DEFAULT_MAX_DEPTH); + sexp_output_stream_t *put_char(int c); /* output a character */ + sexp_output_stream_t *new_line(sexp_print_mode mode); /* go to next line (and indent) */ + sexp_output_stream_t *var_put_char(int c); + sexp_output_stream_t *flush(void); + sexp_output_stream_t *print_decimal(uint64_t n); + + sexp_output_stream_t *change_output_byte_size(int newByteSize, sexp_print_mode mode); + + sexp_output_stream_t *print_canonical(const std::shared_ptr &obj) + { + return obj->print_canonical(this); + } + sexp_output_stream_t *print_advanced(const std::shared_ptr &obj) + { + return obj->print_advanced(this); + }; + sexp_output_stream_t *print_base64(const std::shared_ptr &obj); + sexp_output_stream_t *print_canonical(const sexp_simple_string_t *ss) + { + return ss->print_canonical_verbatim(this); + } + sexp_output_stream_t *print_advanced(const sexp_simple_string_t *ss) + { + return ss->print_advanced(this); + }; + + uint32_t get_byte_size(void) const { return byte_size; } + uint32_t get_column(void) const { return column; } + sexp_output_stream_t *reset_column(void) + { + column = 0; + return this; + } + uint32_t get_max_column(void) const { return max_column; } + sexp_output_stream_t *set_max_column(uint32_t mc) + { + max_column = mc; + return this; + } + sexp_output_stream_t *inc_indent(void) + { + ++indent; + return this; + } + sexp_output_stream_t *dec_indent(void) + { + --indent; + return this; + } + + sexp_output_stream_t *open_list(void) + { + put_char('('); + increase_depth(); + + return this; + } + sexp_output_stream_t *close_list(void) + { + put_char(')')->decrease_depth(); + return this; + } + sexp_output_stream_t *var_open_list(void) + { + var_put_char('(')->increase_depth(); + return this; + } + sexp_output_stream_t *var_close_list(void) + { + var_put_char(')')->decrease_depth(); + return this; + } +}; + +} // namespace sexp diff --git a/man/sexpp.1 b/man/sexpp.1 new file mode 100644 index 0000000..5a6965c --- /dev/null +++ b/man/sexpp.1 @@ -0,0 +1,67 @@ +.TH SEXPP "1" "June 2023" "sexpp" "User Commands" + +.SH NAME + +sexpp - Read, parse, and print out S-expressions + +.SH SYNOPSIS + +.B cat certificate-file | sexpp -a -x + +.SH DESCRIPTION + +\fBsexpp\fP typically reads an S-expression from standard input and rewrites it to standard output. + +Running without switches implies: -p -a -b -c -x + +.SH INPUT OPTIONS + +.B -i filename +Takes input from file instead of stdin. +.TP +.B -p +Prompts user for console input. +.TP +.B -s +Treat input up to EOF as a single string, instead of parsing. + +.SH CONTROL LOOP + +The main routine typically reads one S-expression, prints it out again, and stops. This may be modified: +.TP +.B -x +Execute main loop repeatedly until end of file. + +.SH OUTPUT OPTIONS + +The output format is normally canonical, but this can be changed. +More than one output format can be requested at once. + +.TP +.B -o filename +Write output to file instead of stdout. +.TP +.B -a +Write output in advanced transport format. +.TP +.B -b +Write output in base-64 output format. +.TP +.B -c +Write output in canonical format. +.TP +.B -l +Suppress linefeeds after output. +.TP +.B -w width +Changes line width to specified width (0 implies no line-width constraint). Default: 75 + +.SH AUTHOR + +The \fBsexpp\fP project is maintained by Maxim Samsonov on behalf of Ribose, Inc. +It is based on code from Ron Rivest and Butler Lampson. +This manual page was written by Daniel Kahn Gillmor for the Debian project, but may be used by others. + +.SH SEE ALSO + +https://datatracker.ietf.org/doc/draft-rivest-sexp/ diff --git a/samples/baseline/sexp-sample-a b/samples/baseline/sexp-sample-a new file mode 100644 index 0000000..63d8742 --- /dev/null +++ b/samples/baseline/sexp-sample-a @@ -0,0 +1,19 @@ +(certificate + (issuer + (name + (public-key + rsa-with-md5 + (e |NFGq/E3wh9f4rJIQVXhS|) + (n |d738/4ghP9rFZ0gAIYZ5q9y6iskDJwASi5rEQpEQq8ZyMZeIZzIAR2I5iGE=|)) + aid-committee)) + (subject + (ref + (public-key + rsa-with-md5 + (e |NFGq/E3wh9f4rJIQVXhS|) + (n |d738/4ghP9rFZ0gAIYZ5q9y6iskDJwASi5rEQpEQq8ZyMZeIZzIAR2I5iGE=|)) + tom + mother)) + (not-before "1997-01-01_09:00:00") + (not-after "1998-01-01_09:00:00") + (tag (spend (account "12345678") (* numeric range "1" "1000")))) \ No newline at end of file diff --git a/samples/baseline/sexp-sample-b b/samples/baseline/sexp-sample-b new file mode 100644 index 0000000..6311e61 --- /dev/null +++ b/samples/baseline/sexp-sample-b @@ -0,0 +1 @@ +{KDExOmNlcnRpZmljYXRlKDY6aXNzdWVyKDQ6bmFtZSgxMDpwdWJsaWMta2V5MTI6cnNhLXdpdGgtbWQ1KDE6ZTE1OjRRqvxN8IfX+KySEFV4UikoMTpuNDQ6d738/4ghP9rFZ0gAIYZ5q9y6iskDJwASi5rEQpEQq8ZyMZeIZzIAR2I5iGEpKTEzOmFpZC1jb21taXR0ZWUpKSg3OnN1YmplY3QoMzpyZWYoMTA6cHVibGljLWtleTEyOnJzYS13aXRoLW1kNSgxOmUxNTo0Uar8TfCH1/iskhBVeFIpKDE6bjQ0One9/P+IIT/axWdIACGGeavcuorJAycAEouaxEKREKvGcjGXiGcyAEdiOYhhKSkzOnRvbTY6bW90aGVyKSkoMTA6bm90LWJlZm9yZTE5OjE5OTctMDEtMDFfMDk6MDA6MDApKDk6bm90LWFmdGVyMTk6MTk5OC0wMS0wMV8wOTowMDowMCkoMzp0YWcoNTpzcGVuZCg3OmFjY291bnQ4OjEyMzQ1Njc4KSgxOio3Om51bWVyaWM1OnJhbmdlMToxNDoxMDAwKSkpKQ==} diff --git a/samples/baseline/sexp-sample-c b/samples/baseline/sexp-sample-c new file mode 100644 index 0000000..a02692a Binary files /dev/null and b/samples/baseline/sexp-sample-c differ diff --git a/samples/compat/g10/canonical.key b/samples/compat/g10/canonical.key new file mode 100644 index 0000000..423784e Binary files /dev/null and b/samples/compat/g10/canonical.key differ diff --git a/samples/compat/g23/correct.key b/samples/compat/g23/correct.key new file mode 100755 index 0000000..1e9ee7b --- /dev/null +++ b/samples/compat/g23/correct.key @@ -0,0 +1,43 @@ +Description: RSA/RSA +Created: 20221130T160847 +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) diff --git a/samples/compat/g23/correct_mult_fields.key b/samples/compat/g23/correct_mult_fields.key new file mode 100755 index 0000000..1a6ddd5 --- /dev/null +++ b/samples/compat/g23/correct_mult_fields.key @@ -0,0 +1,45 @@ +Description: RSA/RSA +Created: 20221130T160847 +Description: RSA/RSA +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) +Description: RSA/RSA diff --git a/samples/compat/g23/correct_no_eol.key b/samples/compat/g23/correct_no_eol.key new file mode 100755 index 0000000..641d58e --- /dev/null +++ b/samples/compat/g23/correct_no_eol.key @@ -0,0 +1,43 @@ +Description: RSA/RSA with no terminating end-of-line +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) +Created: 20221130T160847 \ No newline at end of file diff --git a/samples/compat/g23/correct_with_comment.key b/samples/compat/g23/correct_with_comment.key new file mode 100755 index 0000000..74a7db9 --- /dev/null +++ b/samples/compat/g23/correct_with_comment.key @@ -0,0 +1,44 @@ +Description: RSA/RSA with a comment +Created: 20221130T160847 +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + # *** this is a comment, dude *** + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) diff --git a/samples/compat/g23/correct_with_comment_at_eof.key b/samples/compat/g23/correct_with_comment_at_eof.key new file mode 100755 index 0000000..fbc800b --- /dev/null +++ b/samples/compat/g23/correct_with_comment_at_eof.key @@ -0,0 +1,45 @@ +Description: RSA/RSA with a comment +Created: 20221130T160847 +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + # *** this is a comment, dude *** + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) + # another comment \ No newline at end of file diff --git a/samples/compat/g23/correct_with_empty_line.key b/samples/compat/g23/correct_with_empty_line.key new file mode 100755 index 0000000..72e5485 --- /dev/null +++ b/samples/compat/g23/correct_with_empty_line.key @@ -0,0 +1,44 @@ +Description: RSA/RSA with empty line (aka comment) +Created: 20221130T160847 +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) diff --git a/samples/compat/g23/correct_with_two_empty_lines.key b/samples/compat/g23/correct_with_two_empty_lines.key new file mode 100755 index 0000000..0074a5d --- /dev/null +++ b/samples/compat/g23/correct_with_two_empty_lines.key @@ -0,0 +1,45 @@ +Description: RSA/RSA with empty line (aka comment) +Created: 20221130T160847 +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + + + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) diff --git a/samples/compat/g23/correct_with_windows_eol.key b/samples/compat/g23/correct_with_windows_eol.key new file mode 100644 index 0000000..10e3e1d --- /dev/null +++ b/samples/compat/g23/correct_with_windows_eol.key @@ -0,0 +1,45 @@ +Description: RSA/RSA with empty line (aka comment) +Created: 20221130T160847 +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + + + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) diff --git a/samples/compat/g23/malformed_invalid_name_char.key b/samples/compat/g23/malformed_invalid_name_char.key new file mode 100755 index 0000000..f4394a1 --- /dev/null +++ b/samples/compat/g23/malformed_invalid_name_char.key @@ -0,0 +1,43 @@ +Description: RSA/RSA +Created@: 20221130T160847 +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) diff --git a/samples/compat/g23/malformed_invalid_name_first_char.key b/samples/compat/g23/malformed_invalid_name_first_char.key new file mode 100755 index 0000000..f872c80 --- /dev/null +++ b/samples/compat/g23/malformed_invalid_name_first_char.key @@ -0,0 +1,43 @@ +Description: RSA/RSA +1Created: 20221130T160847 +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) diff --git a/samples/compat/g23/malformed_name_break.key b/samples/compat/g23/malformed_name_break.key new file mode 100755 index 0000000..24cd7ba --- /dev/null +++ b/samples/compat/g23/malformed_name_break.key @@ -0,0 +1,44 @@ +Descr + iption: RSA/RSA +Created: 20221130T160847 +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) diff --git a/samples/compat/g23/malformed_name_eof.key b/samples/compat/g23/malformed_name_eof.key new file mode 100755 index 0000000..4f96ea3 --- /dev/null +++ b/samples/compat/g23/malformed_name_eof.key @@ -0,0 +1,43 @@ +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) +Description: RSA/RSA +Create \ No newline at end of file diff --git a/samples/compat/g23/malformed_no_key.key b/samples/compat/g23/malformed_no_key.key new file mode 100755 index 0000000..5d36252 --- /dev/null +++ b/samples/compat/g23/malformed_no_key.key @@ -0,0 +1,43 @@ +Description: RSA/RSA +Created: 20221130T160847 +May: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) diff --git a/samples/compat/g23/malformed_two_keys.key b/samples/compat/g23/malformed_two_keys.key new file mode 100755 index 0000000..a7de9e5 --- /dev/null +++ b/samples/compat/g23/malformed_two_keys.key @@ -0,0 +1,84 @@ +Description: RSA/RSA +Created: 20221130T160847 +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) +Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F + 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04 + FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0 + 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB + 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D + 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3 + 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731 + D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726 + 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9 + C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F + 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9 + 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d + #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9 + 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F + FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3 + 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D + A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9 + AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE + 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D + 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2 + C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F + 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC + F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71 + #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283 + 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357 + 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA + 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7 + 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D + F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q + #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7 + 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB + 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD + 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405 + B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E + B465658607B1D75C6223B8F9FC8852481026AD#)(u + #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16 + BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1 + A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415 + 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E + FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC + A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#))) diff --git a/src/ext-key-format.cpp b/src/ext-key-format.cpp new file mode 100644 index 0000000..7a402cb --- /dev/null +++ b/src/ext-key-format.cpp @@ -0,0 +1,308 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +#include "sexpp/ext-key-format.h" + +using namespace sexp; + +namespace ext_key_format { + +void ext_key_error( + sexp_exception_t::severity level, const char *msg, size_t c1, size_t c2, int pos) +{ + char tmp[256]; + sexp_exception_t::severity l = (sexp_exception_t::severity) level; + snprintf(tmp, sizeof(tmp) / sizeof(tmp[0]), msg, c1, c2); + if (sexp_exception_t::shall_throw(l)) + throw sexp_exception_t(tmp, l, pos, "EXTENDED KEY FORMAT"); + if (sexp_exception_t::is_interactive()) { + std::cout.flush() << std::endl + << "*** " + << sexp_exception_t::format("EXTENDED KEY FORMAT", tmp, l, pos) + << " ***" << std::endl; + } +} + +// Valid characters are all ASCII letters, numbers and the hyphen. +// true if allowed in the name field +const bool ext_key_input_stream_t::namechar[256] = { + /* 0x00 */ false, /* 0x01 */ false, /* 0x02 */ false, + /* 0x03 */ false, /* 0x04 */ false, /* 0x05 */ false, + /* 0x06 */ false, /* 0x07 */ false, /* 0x08 */ false, + /* 0x09 */ false, /* 0x0a */ false, /* 0x0b */ false, + /* 0x0c */ false, /* 0x0d */ false, /* 0x0e */ false, + /* 0x0f */ false, /* 0x10 */ false, /* 0x11 */ false, + /* 0x12 */ false, /* 0x13 */ false, /* 0x14 */ false, + /* 0x15 */ false, /* 0x16 */ false, /* 0x17 */ false, + /* 0x18 */ false, /* 0x19 */ false, /* 0x1a */ false, + /* 0x1b */ false, /* 0x1c */ false, /* 0x1d */ false, + /* 0x1e */ false, /* 0x1f */ false, /* 0x20 */ false, + /* 0x21 ! */ false, /* 0x22 " */ false, /* 0x23 # */ false, + /* 0x24 $ */ false, /* 0x25 % */ false, /* 0x26 & */ false, + /* 0x27 ' */ false, /* 0x28 ( */ false, /* 0x29 ) */ false, + /* 0x2a * */ false, /* 0x2b + */ false, /* 0x2c , */ false, + /* 0x2d - */ true, /* 0x2e . */ false, /* 0x2f / */ false, + /* 0x30 0 */ true, /* 0x31 1 */ true, /* 0x32 2 */ true, + /* 0x33 3 */ true, /* 0x34 4 */ true, /* 0x35 5 */ true, + /* 0x36 6 */ true, /* 0x37 7 */ true, /* 0x38 8 */ true, + /* 0x39 9 */ true, /* 0x3a : */ false, /* 0x3b ; */ false, + /* 0x3c < */ false, /* 0x3d = */ false, /* 0x3e > */ false, + /* 0x3f ? */ false, /* 0x40 @ */ false, /* 0x41 A */ true, + /* 0x42 B */ true, /* 0x43 C */ true, /* 0x44 D */ true, + /* 0x45 E */ true, /* 0x46 F */ true, /* 0x47 G */ true, + /* 0x48 H */ true, /* 0x49 I */ true, /* 0x4a J */ true, + /* 0x4b K */ true, /* 0x4c L */ true, /* 0x4d M */ true, + /* 0x4e N */ true, /* 0x4f O */ true, /* 0x50 P */ true, + /* 0x51 Q */ true, /* 0x52 R */ true, /* 0x53 S */ true, + /* 0x54 T */ true, /* 0x55 U */ true, /* 0x56 V */ true, + /* 0x57 W */ true, /* 0x58 X */ true, /* 0x59 Y */ true, + /* 0x5a Z */ true, /* 0x5b [ */ false, /* 0x5c \ */ false, + /* 0x5d ] */ false, /* 0x5e ^ */ false, /* 0x5f _ */ false, + /* 0x60 ` */ false, /* 0x61 a */ true, /* 0x62 b */ true, + /* 0x63 c */ true, /* 0x64 d */ true, /* 0x65 e */ true, + /* 0x66 f */ true, /* 0x67 g */ true, /* 0x68 h */ true, + /* 0x69 i */ true, /* 0x6a j */ true, /* 0x6b k */ true, + /* 0x6c l */ true, /* 0x6d m */ true, /* 0x6e n */ true, + /* 0x6f o */ true, /* 0x70 p */ true, /* 0x71 q */ true, + /* 0x72 r */ true, /* 0x73 s */ true, /* 0x74 t */ true, + /* 0x75 u */ true, /* 0x76 v */ true, /* 0x77 w */ true, + /* 0x78 x */ true, /* 0x79 y */ true, /* 0x7a z */ true, + /* 0x7b { */ false, /* 0x7c | */ false, /* 0x7d } */ false, + /* 0x7e ~ */ false, /* 0x7f */ false, /* 0x80 */ false, + /* 0x81 */ false, /* 0x82 */ false, /* 0x83 */ false, + /* 0x84 */ false, /* 0x85 */ false, /* 0x86 */ false, + /* 0x87 */ false, /* 0x88 */ false, /* 0x89 */ false, + /* 0x8a */ false, /* 0x8b */ false, /* 0x8c */ false, + /* 0x8d */ false, /* 0x8e */ false, /* 0x8f */ false, + /* 0x90 */ false, /* 0x91 */ false, /* 0x92 */ false, + /* 0x93 */ false, /* 0x94 */ false, /* 0x95 */ false, + /* 0x96 */ false, /* 0x97 */ false, /* 0x98 */ false, + /* 0x99 */ false, /* 0x9a */ false, /* 0x9b */ false, + /* 0x9c */ false, /* 0x9d */ false, /* 0x9e */ false, + /* 0x9f */ false, /* 0xa0 */ false, /* 0xa1 */ false, + /* 0xa2 */ false, /* 0xa3 */ false, /* 0xa4 */ false, + /* 0xa5 */ false, /* 0xa6 */ false, /* 0xa7 */ false, + /* 0xa8 */ false, /* 0xa9 */ false, /* 0xaa */ false, + /* 0xab */ false, /* 0xac */ false, /* 0xad */ false, + /* 0xae */ false, /* 0xaf */ false, /* 0xb0 */ false, + /* 0xb1 */ false, /* 0xb2 */ false, /* 0xb3 */ false, + /* 0xb4 */ false, /* 0xb5 */ false, /* 0xb6 */ false, + /* 0xb7 */ false, /* 0xb8 */ false, /* 0xb9 */ false, + /* 0xba */ false, /* 0xbb */ false, /* 0xbc */ false, + /* 0xbd */ false, /* 0xbe */ false, /* 0xbf */ false, + /* 0xc0 */ false, /* 0xc1 */ false, /* 0xc2 */ false, + /* 0xc3 */ false, /* 0xc4 */ false, /* 0xc5 */ false, + /* 0xc6 */ false, /* 0xc7 */ false, /* 0xc8 */ false, + /* 0xc9 */ false, /* 0xca */ false, /* 0xcb */ false, + /* 0xcc */ false, /* 0xcd */ false, /* 0xce */ false, + /* 0xcf */ false, /* 0xd0 */ false, /* 0xd1 */ false, + /* 0xd2 */ false, /* 0xd3 */ false, /* 0xd4 */ false, + /* 0xd5 */ false, /* 0xd6 */ false, /* 0xd7 */ false, + /* 0xd8 */ false, /* 0xd9 */ false, /* 0xda */ false, + /* 0xdb */ false, /* 0xdc */ false, /* 0xdd */ false, + /* 0xde */ false, /* 0xdf */ false, /* 0xe0 */ false, + /* 0xe1 */ false, /* 0xe2 */ false, /* 0xe3 */ false, + /* 0xe4 */ false, /* 0xe5 */ false, /* 0xe6 */ false, + /* 0xe7 */ false, /* 0xe8 */ false, /* 0xe9 */ false, + /* 0xea */ false, /* 0xeb */ false, /* 0xec */ false, + /* 0xed */ false, /* 0xee */ false, /* 0xef */ false, + /* 0xf0 */ false, /* 0xf1 */ false, /* 0xf2 */ false, + /* 0xf3 */ false, /* 0xf4 */ false, /* 0xf5 */ false, + /* 0xf6 */ false, /* 0xf7 */ false, /* 0xf8 */ false, + /* 0xf9 */ false, /* 0xfa */ false, /* 0xfb */ false, + /* 0xfc */ false, /* 0xfd */ false, /* 0xfe */ false}; + +/* + * ext_key_input_stream_t::skip_line + */ +int ext_key_input_stream_t::skip_line(void) +{ + int c; + do { + c = input_file->get(); + } while (!is_newline_char(c) && c != EOF); + return c; +} + +/* + * ext_key_input_stream_t::read_char + */ +int ext_key_input_stream_t::read_char(void) +{ + int lookahead_1 = input_file->get(); + count++; + if (is_scanning_value && is_newline_char(lookahead_1)) { + while (true) { + int lookahead_2 = input_file->peek(); + if (lookahead_1 == '\r' && lookahead_2 == '\n') { + lookahead_1 = input_file->get(); + count++; + lookahead_2 = input_file->peek(); + } + if (lookahead_2 == ' ') { + input_file->get(); + count++; + lookahead_2 = input_file->peek(); + if (lookahead_2 == '#') { + lookahead_1 = skip_line(); + continue; + } + if (is_newline_char(lookahead_2)) { + lookahead_1 = lookahead_2; + continue; + } + lookahead_1 = input_file->get(); + count++; + } + return lookahead_1; + } + } + return lookahead_1; +} + +/* + * ext_key_input_stream_t::scan_name + * A name must start with a letter and end with a colon. Valid characters are all ASCII + * letters, numbers and the hyphen. Comparison of names is done case insensitively. Names may + * be used several times to represent an array of values. Note that the name “Key” is special + * in that it is madandory must occur only once. + */ + +std::string ext_key_input_stream_t::scan_name(int c) +{ + std::string name; + if (!is_alpha(c)) { + ext_key_error(sexp_exception_t::error, + isprint(next_char) ? + "unexpected character '%c' (0x%x) found starting a name field" : + "unexpected character '0x%x' found starting a name field", + c, + c, + count); + } else { + name += (char) c; + c = read_char(); + while (c != ':') { + if (c == EOF) { + ext_key_error(sexp_exception_t::error, "unexpected end of file", 0, 0, count); + } + if (is_newline_char(c)) { + ext_key_error(sexp_exception_t::error, "unexpected end of line", 0, 0, count); + } + if (!is_namechar(c)) { + ext_key_error(sexp_exception_t::error, + isprint(next_char) ? + "unexpected character '%c' (0x%x) found in a name field" : + "unexpected character '0x%x' found in a name field", + c, + c, + count); + } + name += (int) c; + c = read_char(); + } + } + return name; +} + +/* + * ext_key_input_stream_t::scan_value + * Values are UTF-8 encoded strings. Values can be wrapped at any point, and continued in + * the next line indicated by leading whitespace. A continuation line with one leading space + * does not introduce a blank so that the lines can be effectively concatenated. A blank + * line as part of a continuation line encodes a newline. + */ +std::string ext_key_input_stream_t::scan_value(void) +{ + std::string value; + int c; + do { + c = read_char(); + } while (is_white_space(c)); + while (c != EOF && !is_newline_char(c)) { + value += c; + c = read_char(); + } + return value; +} + +/* + * ext_key_input_stream_t::scan + * GnuPG 2.3+ uses a new format to store private keys that is both more flexible and easier to + * read and edit by human beings. The new format stores name, value-pairs using the common mail + * and http header convention. + */ +void ext_key_input_stream_t::scan(extended_private_key_t &res) +{ + set_byte_size(8); + int c = read_char(); + if (c == '(') { + set_next_char(c); + res.key.parse(this); + has_key = true; + } else { + while (c != EOF) { + // Comparison of names is done case insensitively + std::string name = scan_name(c); + // The name “Key” is special in that it is mandatory and must occur only once. + // The associated value holds the actual S-expression with the cryptographic key. + // The S-expression is formatted using the ‘Advanced Format’ + // (GCRYSEXP_FMT_ADVANCED) that avoids non-printable characters so that the file + // can be easily inspected and edited. + is_scanning_value = true; + if (extended_private_key_t::iequals(name, "key")) { + if (has_key) { + ext_key_error(sexp_exception_t::error, + "'key' field must occur only once", + 0, + 0, + count); + } + do { + c = read_char(); + } while (is_white_space(c)); + set_next_char(c); + res.key.parse(this); + has_key = true; + } else { + std::string value = scan_value(); + res.fields.insert(std::pair{name, value}); + } + c = read_char(); + is_scanning_value = false; + } + } + if (!has_key) { + ext_key_error(sexp_exception_t::error, "missing mandatory 'key' field", 0, 0, count); + } +} + +/* + * extended_private_key_t::parse + */ +void extended_private_key_t::parse(ext_key_input_stream_t &is) +{ + is.scan(*this); +} + +} // namespace ext_key_format \ No newline at end of file diff --git a/src/sexp-char-defs.cpp b/src/sexp-char-defs.cpp new file mode 100644 index 0000000..f31b62d --- /dev/null +++ b/src/sexp-char-defs.cpp @@ -0,0 +1,345 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + * Original copyright + * + * SEXP implementation code sexp-input.c + * Ron Rivest + * 7/21/1997 + */ + +#include "sexpp/sexp.h" + +namespace sexp { + +/**************************************/ +/* CHARACTER ROUTINES AND DEFINITIONS */ +/**************************************/ +std::locale sexp_char_defs_t::c_locale{"C"}; + +const unsigned char sexp_char_defs_t::values[256][3] = + {/* values of c as { dec. hex, base64 } digit */ + {/* 0x00 */ 0x00, 0x00, 0x00}, {/* 0x01 */ 0x00, 0x00, 0x00}, + {/* 0x02 */ 0x00, 0x00, 0x00}, {/* 0x03 */ 0x00, 0x00, 0x00}, + {/* 0x04 */ 0x00, 0x00, 0x00}, {/* 0x05 */ 0x00, 0x00, 0x00}, + {/* 0x06 */ 0x00, 0x00, 0x00}, {/* 0x07 */ 0x00, 0x00, 0x00}, + {/* 0x08 */ 0x00, 0x00, 0x00}, {/* 0x09 */ 0x00, 0x00, 0x00}, + {/* 0x0a */ 0x00, 0x00, 0x00}, {/* 0x0b */ 0x00, 0x00, 0x00}, + {/* 0x0c */ 0x00, 0x00, 0x00}, {/* 0x0d */ 0x00, 0x00, 0x00}, + {/* 0x0e */ 0x00, 0x00, 0x00}, {/* 0x0f */ 0x00, 0x00, 0x00}, + {/* 0x10 */ 0x00, 0x00, 0x00}, {/* 0x11 */ 0x00, 0x00, 0x00}, + {/* 0x12 */ 0x00, 0x00, 0x00}, {/* 0x13 */ 0x00, 0x00, 0x00}, + {/* 0x14 */ 0x00, 0x00, 0x00}, {/* 0x15 */ 0x00, 0x00, 0x00}, + {/* 0x16 */ 0x00, 0x00, 0x00}, {/* 0x17 */ 0x00, 0x00, 0x00}, + {/* 0x18 */ 0x00, 0x00, 0x00}, {/* 0x19 */ 0x00, 0x00, 0x00}, + {/* 0x1a */ 0x00, 0x00, 0x00}, {/* 0x1b */ 0x00, 0x00, 0x00}, + {/* 0x1c */ 0x00, 0x00, 0x00}, {/* 0x1d */ 0x00, 0x00, 0x00}, + {/* 0x1e */ 0x00, 0x00, 0x00}, {/* 0x1f */ 0x00, 0x00, 0x00}, + {/* 0x20 */ 0x00, 0x00, 0x00}, {/* 0x21 ! */ 0x00, 0x00, 0x00}, + {/* 0x22 " */ 0x00, 0x00, 0x00}, {/* 0x23 # */ 0x00, 0x00, 0x00}, + {/* 0x24 $ */ 0x00, 0x00, 0x00}, {/* 0x25 % */ 0x00, 0x00, 0x00}, + {/* 0x26 & */ 0x00, 0x00, 0x00}, {/* 0x27 ' */ 0x00, 0x00, 0x00}, + {/* 0x28 ( */ 0x00, 0x00, 0x00}, {/* 0x29 ) */ 0x00, 0x00, 0x00}, + {/* 0x2a * */ 0x00, 0x00, 0x00}, {/* 0x2b + */ 0x00, 0x00, 0x3e}, + {/* 0x2c , */ 0x00, 0x00, 0x00}, {/* 0x2d - */ 0x00, 0x00, 0x00}, + {/* 0x2e . */ 0x00, 0x00, 0x00}, {/* 0x2f / */ 0x00, 0x00, 0x3f}, + {/* 0x30 0 */ 0x00, 0x00, 0x34}, {/* 0x31 1 */ 0x01, 0x01, 0x35}, + {/* 0x32 2 */ 0x02, 0x02, 0x36}, {/* 0x33 3 */ 0x03, 0x03, 0x37}, + {/* 0x34 4 */ 0x04, 0x04, 0x38}, {/* 0x35 5 */ 0x05, 0x05, 0x39}, + {/* 0x36 6 */ 0x06, 0x06, 0x3a}, {/* 0x37 7 */ 0x07, 0x07, 0x3b}, + {/* 0x38 8 */ 0x08, 0x08, 0x3c}, {/* 0x39 9 */ 0x09, 0x09, 0x3d}, + {/* 0x3a : */ 0x00, 0x00, 0x00}, {/* 0x3b ; */ 0x00, 0x00, 0x00}, + {/* 0x3c < */ 0x00, 0x00, 0x00}, {/* 0x3d = */ 0x00, 0x00, 0x00}, + {/* 0x3e > */ 0x00, 0x00, 0x00}, {/* 0x3f ? */ 0x00, 0x00, 0x00}, + {/* 0x40 @ */ 0x00, 0x00, 0x00}, {/* 0x41 A */ 0x00, 0x0a, 0x00}, + {/* 0x42 B */ 0x00, 0x0b, 0x01}, {/* 0x43 C */ 0x00, 0x0c, 0x02}, + {/* 0x44 D */ 0x00, 0x0d, 0x03}, {/* 0x45 E */ 0x00, 0x0e, 0x04}, + {/* 0x46 F */ 0x00, 0x0f, 0x05}, {/* 0x47 G */ 0x00, 0x00, 0x06}, + {/* 0x48 H */ 0x00, 0x00, 0x07}, {/* 0x49 I */ 0x00, 0x00, 0x08}, + {/* 0x4a J */ 0x00, 0x00, 0x09}, {/* 0x4b K */ 0x00, 0x00, 0x0a}, + {/* 0x4c L */ 0x00, 0x00, 0x0b}, {/* 0x4d M */ 0x00, 0x00, 0x0c}, + {/* 0x4e N */ 0x00, 0x00, 0x0d}, {/* 0x4f O */ 0x00, 0x00, 0x0e}, + {/* 0x50 P */ 0x00, 0x00, 0x0f}, {/* 0x51 Q */ 0x00, 0x00, 0x10}, + {/* 0x52 R */ 0x00, 0x00, 0x11}, {/* 0x53 S */ 0x00, 0x00, 0x12}, + {/* 0x54 T */ 0x00, 0x00, 0x13}, {/* 0x55 U */ 0x00, 0x00, 0x14}, + {/* 0x56 V */ 0x00, 0x00, 0x15}, {/* 0x57 W */ 0x00, 0x00, 0x16}, + {/* 0x58 X */ 0x00, 0x00, 0x17}, {/* 0x59 Y */ 0x00, 0x00, 0x18}, + {/* 0x5a Z */ 0x00, 0x00, 0x19}, {/* 0x5b [ */ 0x00, 0x00, 0x00}, + {/* 0x5c \ */ 0x00, 0x00, 0x00}, {/* 0x5d ] */ 0x00, 0x00, 0x00}, + {/* 0x5e ^ */ 0x00, 0x00, 0x00}, {/* 0x5f _ */ 0x00, 0x00, 0x00}, + {/* 0x60 ` */ 0x00, 0x00, 0x00}, {/* 0x61 a */ 0x00, 0x0a, 0x1a}, + {/* 0x62 b */ 0x00, 0x0b, 0x1b}, {/* 0x63 c */ 0x00, 0x0c, 0x1c}, + {/* 0x64 d */ 0x00, 0x0d, 0x1d}, {/* 0x65 e */ 0x00, 0x0e, 0x1e}, + {/* 0x66 f */ 0x00, 0x0f, 0x1f}, {/* 0x67 g */ 0x00, 0x00, 0x20}, + {/* 0x68 h */ 0x00, 0x00, 0x21}, {/* 0x69 i */ 0x00, 0x00, 0x22}, + {/* 0x6a j */ 0x00, 0x00, 0x23}, {/* 0x6b k */ 0x00, 0x00, 0x24}, + {/* 0x6c l */ 0x00, 0x00, 0x25}, {/* 0x6d m */ 0x00, 0x00, 0x26}, + {/* 0x6e n */ 0x00, 0x00, 0x27}, {/* 0x6f o */ 0x00, 0x00, 0x28}, + {/* 0x70 p */ 0x00, 0x00, 0x29}, {/* 0x71 q */ 0x00, 0x00, 0x2a}, + {/* 0x72 r */ 0x00, 0x00, 0x2b}, {/* 0x73 s */ 0x00, 0x00, 0x2c}, + {/* 0x74 t */ 0x00, 0x00, 0x2d}, {/* 0x75 u */ 0x00, 0x00, 0x2e}, + {/* 0x76 v */ 0x00, 0x00, 0x2f}, {/* 0x77 w */ 0x00, 0x00, 0x30}, + {/* 0x78 x */ 0x00, 0x00, 0x31}, {/* 0x79 y */ 0x00, 0x00, 0x32}, + {/* 0x7a z */ 0x00, 0x00, 0x33}, {/* 0x7b { */ 0x00, 0x00, 0x00}, + {/* 0x7c | */ 0x00, 0x00, 0x00}, {/* 0x7d } */ 0x00, 0x00, 0x00}, + {/* 0x7e ~ */ 0x00, 0x00, 0x00}, {/* 0x7f */ 0x00, 0x00, 0x00}, + {/* 0x80 */ 0x00, 0x00, 0x00}, {/* 0x81 */ 0x00, 0x00, 0x00}, + {/* 0x82 */ 0x00, 0x00, 0x00}, {/* 0x83 */ 0x00, 0x00, 0x00}, + {/* 0x84 */ 0x00, 0x00, 0x00}, {/* 0x85 */ 0x00, 0x00, 0x00}, + {/* 0x86 */ 0x00, 0x00, 0x00}, {/* 0x87 */ 0x00, 0x00, 0x00}, + {/* 0x88 */ 0x00, 0x00, 0x00}, {/* 0x89 */ 0x00, 0x00, 0x00}, + {/* 0x8a */ 0x00, 0x00, 0x00}, {/* 0x8b */ 0x00, 0x00, 0x00}, + {/* 0x8c */ 0x00, 0x00, 0x00}, {/* 0x8d */ 0x00, 0x00, 0x00}, + {/* 0x8e */ 0x00, 0x00, 0x00}, {/* 0x8f */ 0x00, 0x00, 0x00}, + {/* 0x90 */ 0x00, 0x00, 0x00}, {/* 0x91 */ 0x00, 0x00, 0x00}, + {/* 0x92 */ 0x00, 0x00, 0x00}, {/* 0x93 */ 0x00, 0x00, 0x00}, + {/* 0x94 */ 0x00, 0x00, 0x00}, {/* 0x95 */ 0x00, 0x00, 0x00}, + {/* 0x96 */ 0x00, 0x00, 0x00}, {/* 0x97 */ 0x00, 0x00, 0x00}, + {/* 0x98 */ 0x00, 0x00, 0x00}, {/* 0x99 */ 0x00, 0x00, 0x00}, + {/* 0x9a */ 0x00, 0x00, 0x00}, {/* 0x9b */ 0x00, 0x00, 0x00}, + {/* 0x9c */ 0x00, 0x00, 0x00}, {/* 0x9d */ 0x00, 0x00, 0x00}, + {/* 0x9e */ 0x00, 0x00, 0x00}, {/* 0x9f */ 0x00, 0x00, 0x00}, + {/* 0xa0 */ 0x00, 0x00, 0x00}, {/* 0xa1 */ 0x00, 0x00, 0x00}, + {/* 0xa2 */ 0x00, 0x00, 0x00}, {/* 0xa3 */ 0x00, 0x00, 0x00}, + {/* 0xa4 */ 0x00, 0x00, 0x00}, {/* 0xa5 */ 0x00, 0x00, 0x00}, + {/* 0xa6 */ 0x00, 0x00, 0x00}, {/* 0xa7 */ 0x00, 0x00, 0x00}, + {/* 0xa8 */ 0x00, 0x00, 0x00}, {/* 0xa9 */ 0x00, 0x00, 0x00}, + {/* 0xaa */ 0x00, 0x00, 0x00}, {/* 0xab */ 0x00, 0x00, 0x00}, + {/* 0xac */ 0x00, 0x00, 0x00}, {/* 0xad */ 0x00, 0x00, 0x00}, + {/* 0xae */ 0x00, 0x00, 0x00}, {/* 0xaf */ 0x00, 0x00, 0x00}, + {/* 0xb0 */ 0x00, 0x00, 0x00}, {/* 0xb1 */ 0x00, 0x00, 0x00}, + {/* 0xb2 */ 0x00, 0x00, 0x00}, {/* 0xb3 */ 0x00, 0x00, 0x00}, + {/* 0xb4 */ 0x00, 0x00, 0x00}, {/* 0xb5 */ 0x00, 0x00, 0x00}, + {/* 0xb6 */ 0x00, 0x00, 0x00}, {/* 0xb7 */ 0x00, 0x00, 0x00}, + {/* 0xb8 */ 0x00, 0x00, 0x00}, {/* 0xb9 */ 0x00, 0x00, 0x00}, + {/* 0xba */ 0x00, 0x00, 0x00}, {/* 0xbb */ 0x00, 0x00, 0x00}, + {/* 0xbc */ 0x00, 0x00, 0x00}, {/* 0xbd */ 0x00, 0x00, 0x00}, + {/* 0xbe */ 0x00, 0x00, 0x00}, {/* 0xbf */ 0x00, 0x00, 0x00}, + {/* 0xc0 */ 0x00, 0x00, 0x00}, {/* 0xc1 */ 0x00, 0x00, 0x00}, + {/* 0xc2 */ 0x00, 0x00, 0x00}, {/* 0xc3 */ 0x00, 0x00, 0x00}, + {/* 0xc4 */ 0x00, 0x00, 0x00}, {/* 0xc5 */ 0x00, 0x00, 0x00}, + {/* 0xc6 */ 0x00, 0x00, 0x00}, {/* 0xc7 */ 0x00, 0x00, 0x00}, + {/* 0xc8 */ 0x00, 0x00, 0x00}, {/* 0xc9 */ 0x00, 0x00, 0x00}, + {/* 0xca */ 0x00, 0x00, 0x00}, {/* 0xcb */ 0x00, 0x00, 0x00}, + {/* 0xcc */ 0x00, 0x00, 0x00}, {/* 0xcd */ 0x00, 0x00, 0x00}, + {/* 0xce */ 0x00, 0x00, 0x00}, {/* 0xcf */ 0x00, 0x00, 0x00}, + {/* 0xd0 */ 0x00, 0x00, 0x00}, {/* 0xd1 */ 0x00, 0x00, 0x00}, + {/* 0xd2 */ 0x00, 0x00, 0x00}, {/* 0xd3 */ 0x00, 0x00, 0x00}, + {/* 0xd4 */ 0x00, 0x00, 0x00}, {/* 0xd5 */ 0x00, 0x00, 0x00}, + {/* 0xd6 */ 0x00, 0x00, 0x00}, {/* 0xd7 */ 0x00, 0x00, 0x00}, + {/* 0xd8 */ 0x00, 0x00, 0x00}, {/* 0xd9 */ 0x00, 0x00, 0x00}, + {/* 0xda */ 0x00, 0x00, 0x00}, {/* 0xdb */ 0x00, 0x00, 0x00}, + {/* 0xdc */ 0x00, 0x00, 0x00}, {/* 0xdd */ 0x00, 0x00, 0x00}, + {/* 0xde */ 0x00, 0x00, 0x00}, {/* 0xdf */ 0x00, 0x00, 0x00}, + {/* 0xe0 */ 0x00, 0x00, 0x00}, {/* 0xe1 */ 0x00, 0x00, 0x00}, + {/* 0xe2 */ 0x00, 0x00, 0x00}, {/* 0xe3 */ 0x00, 0x00, 0x00}, + {/* 0xe4 */ 0x00, 0x00, 0x00}, {/* 0xe5 */ 0x00, 0x00, 0x00}, + {/* 0xe6 */ 0x00, 0x00, 0x00}, {/* 0xe7 */ 0x00, 0x00, 0x00}, + {/* 0xe8 */ 0x00, 0x00, 0x00}, {/* 0xe9 */ 0x00, 0x00, 0x00}, + {/* 0xea */ 0x00, 0x00, 0x00}, {/* 0xeb */ 0x00, 0x00, 0x00}, + {/* 0xec */ 0x00, 0x00, 0x00}, {/* 0xed */ 0x00, 0x00, 0x00}, + {/* 0xee */ 0x00, 0x00, 0x00}, {/* 0xef */ 0x00, 0x00, 0x00}, + {/* 0xf0 */ 0x00, 0x00, 0x00}, {/* 0xf1 */ 0x00, 0x00, 0x00}, + {/* 0xf2 */ 0x00, 0x00, 0x00}, {/* 0xf3 */ 0x00, 0x00, 0x00}, + {/* 0xf4 */ 0x00, 0x00, 0x00}, {/* 0xf5 */ 0x00, 0x00, 0x00}, + {/* 0xf6 */ 0x00, 0x00, 0x00}, {/* 0xf7 */ 0x00, 0x00, 0x00}, + {/* 0xf8 */ 0x00, 0x00, 0x00}, {/* 0xf9 */ 0x00, 0x00, 0x00}, + {/* 0xfa */ 0x00, 0x00, 0x00}, {/* 0xfb */ 0x00, 0x00, 0x00}, + {/* 0xfc */ 0x00, 0x00, 0x00}, {/* 0xfd */ 0x00, 0x00, 0x00}, + {/* 0xfe */ 0x00, 0x00, 0x00}, {/* 0xff */ 0x00, 0x00, 0x00}}; + +const bool sexp_char_defs_t::base64digit[256] = + {/* c is base64 digit */ + /* 0x00 */ false, /* 0x01 */ false, /* 0x02 */ false, + /* 0x03 */ false, /* 0x04 */ false, /* 0x05 */ false, + /* 0x06 */ false, /* 0x07 */ false, /* 0x08 */ false, + /* 0x09 */ false, /* 0x0a */ false, /* 0x0b */ false, + /* 0x0c */ false, /* 0x0d */ false, /* 0x0e */ false, + /* 0x0f */ false, /* 0x10 */ false, /* 0x11 */ false, + /* 0x12 */ false, /* 0x13 */ false, /* 0x14 */ false, + /* 0x15 */ false, /* 0x16 */ false, /* 0x17 */ false, + /* 0x18 */ false, /* 0x19 */ false, /* 0x1a */ false, + /* 0x1b */ false, /* 0x1c */ false, /* 0x1d */ false, + /* 0x1e */ false, /* 0x1f */ false, /* 0x20 */ false, + /* 0x21 ! */ false, /* 0x22 " */ false, /* 0x23 # */ false, + /* 0x24 $ */ false, /* 0x25 % */ false, /* 0x26 & */ false, + /* 0x27 ' */ false, /* 0x28 ( */ false, /* 0x29 ) */ false, + /* 0x2a * */ false, /* 0x2b + */ true, /* 0x2c , */ false, + /* 0x2d - */ false, /* 0x2e . */ false, /* 0x2f / */ true, + /* 0x30 0 */ true, /* 0x31 1 */ true, /* 0x32 2 */ true, + /* 0x33 3 */ true, /* 0x34 4 */ true, /* 0x35 5 */ true, + /* 0x36 6 */ true, /* 0x37 7 */ true, /* 0x38 8 */ true, + /* 0x39 9 */ true, /* 0x3a : */ false, /* 0x3b ; */ false, + /* 0x3c < */ false, /* 0x3d = */ false, /* 0x3e > */ false, + /* 0x3f ? */ false, /* 0x40 @ */ false, /* 0x41 A */ true, + /* 0x42 B */ true, /* 0x43 C */ true, /* 0x44 D */ true, + /* 0x45 E */ true, /* 0x46 F */ true, /* 0x47 G */ true, + /* 0x48 H */ true, /* 0x49 I */ true, /* 0x4a J */ true, + /* 0x4b K */ true, /* 0x4c L */ true, /* 0x4d M */ true, + /* 0x4e N */ true, /* 0x4f O */ true, /* 0x50 P */ true, + /* 0x51 Q */ true, /* 0x52 R */ true, /* 0x53 S */ true, + /* 0x54 T */ true, /* 0x55 U */ true, /* 0x56 V */ true, + /* 0x57 W */ true, /* 0x58 X */ true, /* 0x59 Y */ true, + /* 0x5a Z */ true, /* 0x5b [ */ false, /* 0x5c \ */ false, + /* 0x5d ] */ false, /* 0x5e ^ */ false, /* 0x5f _ */ false, + /* 0x60 ` */ false, /* 0x61 a */ true, /* 0x62 b */ true, + /* 0x63 c */ true, /* 0x64 d */ true, /* 0x65 e */ true, + /* 0x66 f */ true, /* 0x67 g */ true, /* 0x68 h */ true, + /* 0x69 i */ true, /* 0x6a j */ true, /* 0x6b k */ true, + /* 0x6c l */ true, /* 0x6d m */ true, /* 0x6e n */ true, + /* 0x6f o */ true, /* 0x70 p */ true, /* 0x71 q */ true, + /* 0x72 r */ true, /* 0x73 s */ true, /* 0x74 t */ true, + /* 0x75 u */ true, /* 0x76 v */ true, /* 0x77 w */ true, + /* 0x78 x */ true, /* 0x79 y */ true, /* 0x7a z */ true, + /* 0x7b { */ false, /* 0x7c | */ false, /* 0x7d } */ false, + /* 0x7e ~ */ false, /* 0x7f */ false, /* 0x80 */ false, + /* 0x81 */ false, /* 0x82 */ false, /* 0x83 */ false, + /* 0x84 */ false, /* 0x85 */ false, /* 0x86 */ false, + /* 0x87 */ false, /* 0x88 */ false, /* 0x89 */ false, + /* 0x8a */ false, /* 0x8b */ false, /* 0x8c */ false, + /* 0x8d */ false, /* 0x8e */ false, /* 0x8f */ false, + /* 0x90 */ false, /* 0x91 */ false, /* 0x92 */ false, + /* 0x93 */ false, /* 0x94 */ false, /* 0x95 */ false, + /* 0x96 */ false, /* 0x97 */ false, /* 0x98 */ false, + /* 0x99 */ false, /* 0x9a */ false, /* 0x9b */ false, + /* 0x9c */ false, /* 0x9d */ false, /* 0x9e */ false, + /* 0x9f */ false, /* 0xa0 */ false, /* 0xa1 */ false, + /* 0xa2 */ false, /* 0xa3 */ false, /* 0xa4 */ false, + /* 0xa5 */ false, /* 0xa6 */ false, /* 0xa7 */ false, + /* 0xa8 */ false, /* 0xa9 */ false, /* 0xaa */ false, + /* 0xab */ false, /* 0xac */ false, /* 0xad */ false, + /* 0xae */ false, /* 0xaf */ false, /* 0xb0 */ false, + /* 0xb1 */ false, /* 0xb2 */ false, /* 0xb3 */ false, + /* 0xb4 */ false, /* 0xb5 */ false, /* 0xb6 */ false, + /* 0xb7 */ false, /* 0xb8 */ false, /* 0xb9 */ false, + /* 0xba */ false, /* 0xbb */ false, /* 0xbc */ false, + /* 0xbd */ false, /* 0xbe */ false, /* 0xbf */ false, + /* 0xc0 */ false, /* 0xc1 */ false, /* 0xc2 */ false, + /* 0xc3 */ false, /* 0xc4 */ false, /* 0xc5 */ false, + /* 0xc6 */ false, /* 0xc7 */ false, /* 0xc8 */ false, + /* 0xc9 */ false, /* 0xca */ false, /* 0xcb */ false, + /* 0xcc */ false, /* 0xcd */ false, /* 0xce */ false, + /* 0xcf */ false, /* 0xd0 */ false, /* 0xd1 */ false, + /* 0xd2 */ false, /* 0xd3 */ false, /* 0xd4 */ false, + /* 0xd5 */ false, /* 0xd6 */ false, /* 0xd7 */ false, + /* 0xd8 */ false, /* 0xd9 */ false, /* 0xda */ false, + /* 0xdb */ false, /* 0xdc */ false, /* 0xdd */ false, + /* 0xde */ false, /* 0xdf */ false, /* 0xe0 */ false, + /* 0xe1 */ false, /* 0xe2 */ false, /* 0xe3 */ false, + /* 0xe4 */ false, /* 0xe5 */ false, /* 0xe6 */ false, + /* 0xe7 */ false, /* 0xe8 */ false, /* 0xe9 */ false, + /* 0xea */ false, /* 0xeb */ false, /* 0xec */ false, + /* 0xed */ false, /* 0xee */ false, /* 0xef */ false, + /* 0xf0 */ false, /* 0xf1 */ false, /* 0xf2 */ false, + /* 0xf3 */ false, /* 0xf4 */ false, /* 0xf5 */ false, + /* 0xf6 */ false, /* 0xf7 */ false, /* 0xf8 */ false, + /* 0xf9 */ false, /* 0xfa */ false, /* 0xfb */ false, + /* 0xfc */ false, /* 0xfd */ false, /* 0xfe */ false}; + +const bool sexp_char_defs_t::tokenchar[256] = + {/* c can be in a token */ + /* 0x00 */ false, /* 0x01 */ false, /* 0x02 */ false, + /* 0x03 */ false, /* 0x04 */ false, /* 0x05 */ false, + /* 0x06 */ false, /* 0x07 */ false, /* 0x08 */ false, + /* 0x09 */ false, /* 0x0a */ false, /* 0x0b */ false, + /* 0x0c */ false, /* 0x0d */ false, /* 0x0e */ false, + /* 0x0f */ false, /* 0x10 */ false, /* 0x11 */ false, + /* 0x12 */ false, /* 0x13 */ false, /* 0x14 */ false, + /* 0x15 */ false, /* 0x16 */ false, /* 0x17 */ false, + /* 0x18 */ false, /* 0x19 */ false, /* 0x1a */ false, + /* 0x1b */ false, /* 0x1c */ false, /* 0x1d */ false, + /* 0x1e */ false, /* 0x1f */ false, /* 0x20 */ false, + /* 0x21 ! */ false, /* 0x22 " */ false, /* 0x23 # */ false, + /* 0x24 $ */ false, /* 0x25 % */ false, /* 0x26 & */ false, + /* 0x27 ' */ false, /* 0x28 ( */ false, /* 0x29 ) */ false, + /* 0x2a * */ true, /* 0x2b + */ true, /* 0x2c , */ false, + /* 0x2d - */ true, /* 0x2e . */ true, /* 0x2f / */ true, + /* 0x30 0 */ true, /* 0x31 1 */ true, /* 0x32 2 */ true, + /* 0x33 3 */ true, /* 0x34 4 */ true, /* 0x35 5 */ true, + /* 0x36 6 */ true, /* 0x37 7 */ true, /* 0x38 8 */ true, + /* 0x39 9 */ true, /* 0x3a : */ true, /* 0x3b ; */ false, + /* 0x3c < */ false, /* 0x3d = */ true, /* 0x3e > */ false, + /* 0x3f ? */ false, /* 0x40 @ */ false, /* 0x41 A */ true, + /* 0x42 B */ true, /* 0x43 C */ true, /* 0x44 D */ true, + /* 0x45 E */ true, /* 0x46 F */ true, /* 0x47 G */ true, + /* 0x48 H */ true, /* 0x49 I */ true, /* 0x4a J */ true, + /* 0x4b K */ true, /* 0x4c L */ true, /* 0x4d M */ true, + /* 0x4e N */ true, /* 0x4f O */ true, /* 0x50 P */ true, + /* 0x51 Q */ true, /* 0x52 R */ true, /* 0x53 S */ true, + /* 0x54 T */ true, /* 0x55 U */ true, /* 0x56 V */ true, + /* 0x57 W */ true, /* 0x58 X */ true, /* 0x59 Y */ true, + /* 0x5a Z */ true, /* 0x5b [ */ false, /* 0x5c \ */ false, + /* 0x5d ] */ false, /* 0x5e ^ */ false, /* 0x5f _ */ true, + /* 0x60 ` */ false, /* 0x61 a */ true, /* 0x62 b */ true, + /* 0x63 c */ true, /* 0x64 d */ true, /* 0x65 e */ true, + /* 0x66 f */ true, /* 0x67 g */ true, /* 0x68 h */ true, + /* 0x69 i */ true, /* 0x6a j */ true, /* 0x6b k */ true, + /* 0x6c l */ true, /* 0x6d m */ true, /* 0x6e n */ true, + /* 0x6f o */ true, /* 0x70 p */ true, /* 0x71 q */ true, + /* 0x72 r */ true, /* 0x73 s */ true, /* 0x74 t */ true, + /* 0x75 u */ true, /* 0x76 v */ true, /* 0x77 w */ true, + /* 0x78 x */ true, /* 0x79 y */ true, /* 0x7a z */ true, + /* 0x7b { */ false, /* 0x7c | */ false, /* 0x7d } */ false, + /* 0x7e ~ */ false, /* 0x7f */ false, /* 0x80 */ false, + /* 0x81 */ false, /* 0x82 */ false, /* 0x83 */ false, + /* 0x84 */ false, /* 0x85 */ false, /* 0x86 */ false, + /* 0x87 */ false, /* 0x88 */ false, /* 0x89 */ false, + /* 0x8a */ false, /* 0x8b */ false, /* 0x8c */ false, + /* 0x8d */ false, /* 0x8e */ false, /* 0x8f */ false, + /* 0x90 */ false, /* 0x91 */ false, /* 0x92 */ false, + /* 0x93 */ false, /* 0x94 */ false, /* 0x95 */ false, + /* 0x96 */ false, /* 0x97 */ false, /* 0x98 */ false, + /* 0x99 */ false, /* 0x9a */ false, /* 0x9b */ false, + /* 0x9c */ false, /* 0x9d */ false, /* 0x9e */ false, + /* 0x9f */ false, /* 0xa0 */ false, /* 0xa1 */ false, + /* 0xa2 */ false, /* 0xa3 */ false, /* 0xa4 */ false, + /* 0xa5 */ false, /* 0xa6 */ false, /* 0xa7 */ false, + /* 0xa8 */ false, /* 0xa9 */ false, /* 0xaa */ false, + /* 0xab */ false, /* 0xac */ false, /* 0xad */ false, + /* 0xae */ false, /* 0xaf */ false, /* 0xb0 */ false, + /* 0xb1 */ false, /* 0xb2 */ false, /* 0xb3 */ false, + /* 0xb4 */ false, /* 0xb5 */ false, /* 0xb6 */ false, + /* 0xb7 */ false, /* 0xb8 */ false, /* 0xb9 */ false, + /* 0xba */ false, /* 0xbb */ false, /* 0xbc */ false, + /* 0xbd */ false, /* 0xbe */ false, /* 0xbf */ false, + /* 0xc0 */ false, /* 0xc1 */ false, /* 0xc2 */ false, + /* 0xc3 */ false, /* 0xc4 */ false, /* 0xc5 */ false, + /* 0xc6 */ false, /* 0xc7 */ false, /* 0xc8 */ false, + /* 0xc9 */ false, /* 0xca */ false, /* 0xcb */ false, + /* 0xcc */ false, /* 0xcd */ false, /* 0xce */ false, + /* 0xcf */ false, /* 0xd0 */ false, /* 0xd1 */ false, + /* 0xd2 */ false, /* 0xd3 */ false, /* 0xd4 */ false, + /* 0xd5 */ false, /* 0xd6 */ false, /* 0xd7 */ false, + /* 0xd8 */ false, /* 0xd9 */ false, /* 0xda */ false, + /* 0xdb */ false, /* 0xdc */ false, /* 0xdd */ false, + /* 0xde */ false, /* 0xdf */ false, /* 0xe0 */ false, + /* 0xe1 */ false, /* 0xe2 */ false, /* 0xe3 */ false, + /* 0xe4 */ false, /* 0xe5 */ false, /* 0xe6 */ false, + /* 0xe7 */ false, /* 0xe8 */ false, /* 0xe9 */ false, + /* 0xea */ false, /* 0xeb */ false, /* 0xec */ false, + /* 0xed */ false, /* 0xee */ false, /* 0xef */ false, + /* 0xf0 */ false, /* 0xf1 */ false, /* 0xf2 */ false, + /* 0xf3 */ false, /* 0xf4 */ false, /* 0xf5 */ false, + /* 0xf6 */ false, /* 0xf7 */ false, /* 0xf8 */ false, + /* 0xf9 */ false, /* 0xfa */ false, /* 0xfb */ false, + /* 0xfc */ false, /* 0xfd */ false, /* 0xfe */ false}; + +} // namespace sexp diff --git a/src/sexp-depth-manager.cpp b/src/sexp-depth-manager.cpp new file mode 100644 index 0000000..2f26e27 --- /dev/null +++ b/src/sexp-depth-manager.cpp @@ -0,0 +1,50 @@ +/** + * + * Copyright 2023 Ribose Inc. + * + * 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. + * + **/ + +#include "sexpp/sexp.h" + +namespace sexp { + +sexp_depth_manager::sexp_depth_manager(size_t m_depth) +{ + reset_depth(m_depth); +} +void sexp_depth_manager::reset_depth(size_t m_depth) +{ + depth = 0; + max_depth = m_depth; +} +void sexp_depth_manager::increase_depth(int count) +{ + if (max_depth != 0 && ++depth > max_depth) + sexp_error(sexp_exception_t::error, + "Maximum allowed SEXP list depth (%u) is exceeded", + max_depth, + 0, + count); +} +void sexp_depth_manager::decrease_depth(void) +{ + depth--; +} +} // namespace sexp diff --git a/src/sexp-error.cpp b/src/sexp-error.cpp new file mode 100644 index 0000000..3558f02 --- /dev/null +++ b/src/sexp-error.cpp @@ -0,0 +1,56 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +#include "sexpp/sexp-error.h" + +namespace sexp { + +sexp_exception_t::severity sexp_exception_t::verbosity = sexp_exception_t::error; +bool sexp_exception_t::interactive = false; + +std::string sexp_exception_t::format(std::string prf, + std::string message, + severity level, + int position) +{ + std::string r = prf + (level == error ? " ERROR: " : " WARNING: ") + message; + if (position >= 0) + r += " at position " + std::to_string(position); + return r; +}; + +void sexp_error( + sexp_exception_t::severity level, const char *msg, size_t c1, size_t c2, int pos) +{ + char tmp[256]; + sexp_exception_t::severity l = (sexp_exception_t::severity) level; + snprintf(tmp, sizeof(tmp) / sizeof(tmp[0]), msg, c1, c2); + if (sexp_exception_t::shall_throw(l)) + throw sexp_exception_t(tmp, l, pos); + if (sexp_exception_t::is_interactive()) { + std::cout.flush() << std::endl + << "*** " << sexp_exception_t::format("SEXP", tmp, l, pos) << " ***" + << std::endl; + } +} +} // namespace sexp \ No newline at end of file diff --git a/src/sexp-input.cpp b/src/sexp-input.cpp new file mode 100644 index 0000000..cb21686 --- /dev/null +++ b/src/sexp-input.cpp @@ -0,0 +1,514 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + * Original copyright + * + * SEXP implementation code sexp-input.c + * Ron Rivest + * 7/21/1997 + */ + +#include "sexpp/sexp.h" + +namespace sexp { + +/* + * sexp_input_stream_t::sexp_input_stream_t + * Creates and initializes new sexp_input_stream_t object. + */ + +sexp_input_stream_t::sexp_input_stream_t(std::istream *i, size_t m_depth) +{ + set_input(i, m_depth); +} + +/* + * sexp_input_stream_t::set_input(std::istream *i, size_t m_depth) + */ + +sexp_input_stream_t *sexp_input_stream_t::set_input(std::istream *i, size_t m_depth) +{ + input_file = i; + byte_size = 8; + next_char = ' '; + bits = 0; + n_bits = 0; + count = -1; + reset_depth(m_depth); + return this; +} + +/* + * sexp_input_stream_t::set_byte_size(newByteSize) + */ +sexp_input_stream_t *sexp_input_stream_t::set_byte_size(uint32_t newByteSize) +{ + byte_size = newByteSize; + n_bits = 0; + bits = 0; + return this; +} + +int sexp_input_stream_t::read_char(void) +{ + count++; + return input_file->get(); +} + +/* + * sexp_input_stream_t::get_char() + * This is one possible character input routine for an input stream. + * (This version uses the standard input stream.) + * get_char places next 8-bit character into is->next_char. + * It also updates the count of number of 8-bit characters read. + * The value EOF is obtained when no more input is available. + * This code handles 4-bit/6-bit/8-bit channels. + */ +sexp_input_stream_t *sexp_input_stream_t::get_char(void) +{ + int c; + if (next_char == EOF) { + byte_size = 8; + return this; + } + + while (true) { + c = next_char = read_char(); + if (c == EOF) + return this; + if ((byte_size == 6 && (c == '|' || c == '}')) || (byte_size == 4 && (c == '#'))) { + // end of region reached; return terminating character, after checking for + // unused bits + if (n_bits > 0 && (((1 << n_bits) - 1) & bits) != 0) { + sexp_error(sexp_exception_t::warning, + "%d-bit region ended with %d unused bits left-over", + byte_size, + n_bits, + count); + } + return set_byte_size(8); + } else if (byte_size != 8 && is_white_space(c)) + ; /* ignore white space in hex and base64 regions */ + else if (byte_size == 6 && c == '=') + ; /* ignore equals signs in base64 regions */ + else if (byte_size == 8) { + return this; + } else if (byte_size < 8) { + bits = bits << byte_size; + n_bits += byte_size; + if (byte_size == 6 && is_base64_digit(c)) + bits = bits | base64value(c); + else if (byte_size == 4 && is_hex_digit(c)) + bits = bits | hexvalue(c); + else { + sexp_error(sexp_exception_t::error, + "character '%c' found in %u-bit coding region", + next_char, + byte_size, + count); + } + if (n_bits >= 8) { + next_char = (bits >> (n_bits - 8)) & 0xFF; + n_bits -= 8; + return this; + } + } + } +} + +/* + * sexp_input_stream_t::skip_white_space + * Skip over any white space on the given sexp_input_stream_t. + */ +sexp_input_stream_t *sexp_input_stream_t::skip_white_space(void) +{ + while (is_white_space(next_char)) + get_char(); + return this; +} + +/* + * sexp_input_stream_t::skip_char(c) + * Skip the following input character on input stream is, if it is + * equal to the character c. If it is not equal, then an error occurs. + */ +sexp_input_stream_t *sexp_input_stream_t::skip_char(int c) +{ + if (next_char != c) + sexp_error(sexp_exception_t::error, + "character '%c' found where '%c' was expected", + next_char, + c, + count); + return get_char(); +} + +/* + * sexp_input_stream_t::scan_token(ss) + * scan one or more characters into simple string ss as a token. + */ +void sexp_input_stream_t::scan_token(sexp_simple_string_t &ss) +{ + skip_white_space(); + while (is_token_char(next_char)) { + ss.append(next_char); + get_char(); + } +} + +/* + * sexp_input_stream_t::scan_to_eof(void) + * scan one or more characters (until EOF reached) + * return an object that is just that string + */ +std::shared_ptr sexp_input_stream_t::scan_to_eof(void) +{ + sexp_simple_string_t ss; + skip_white_space(); + while (next_char != EOF) { + ss.append(next_char); + get_char(); + } + auto s = std::make_shared(); + s->set_string(ss); + return s; +} + +/* + * scan_decimal_string(is) + * returns long integer that is value of decimal number + */ +uint32_t sexp_input_stream_t::scan_decimal_string(void) +{ + uint32_t value = 0; + uint32_t i = 0; + while (is_dec_digit(next_char)) { + value = value * 10 + decvalue(next_char); + get_char(); + if (i++ > 8) + sexp_error(sexp_exception_t::error, "Decimal number is too long", 0, 0, count); + } + return value; +} + +/* + * sexp_input_stream_t::scan_verbatim_string(is,ss,length) + * Reads verbatim string of given length into simple string ss. + */ +void sexp_input_stream_t::scan_verbatim_string(sexp_simple_string_t &ss, uint32_t length) +{ + skip_white_space()->skip_char(':'); + + // Some length is specified always, this is ensured by the caller's logic + assert(length != std::numeric_limits::max()); + for (uint32_t i = 0; i < length; i++) { + ss.append(next_char); + get_char(); + } +} + +/* + * sexp_input_stream_t::scan_quoted_string(ss,length) + * Reads quoted string of given length into simple string ss. + * Handles ordinary C escapes. + * If of indefinite length, length is std::numeric_limits::max(). + */ +void sexp_input_stream_t::scan_quoted_string(sexp_simple_string_t &ss, uint32_t length) +{ + skip_char('"'); + while (ss.length() <= length) { + if (next_char == '\"') { + if (length == std::numeric_limits::max() || (ss.length() == length)) { + skip_char('\"'); + return; + } else + sexp_error(sexp_exception_t::error, + "Declared length was %d, but quoted string ended too early", + (int) length, + 0, + count); + } else if (next_char == '\\') /* handle escape sequence */ + { + get_char(); + switch (next_char) { + case 'b': + ss.append('\b'); + break; + case 't': + ss.append('\t'); + break; + case 'v': + ss.append('\v'); + break; + case 'n': + ss.append('\n'); + break; + case 'f': + ss.append('\f'); + break; + case 'r': + ss.append('\r'); + break; + case '\"': + ss.append('\"'); + break; + case '\'': + ss.append('\''); + break; + case '\\': + ss.append('\\'); + break; + case 'x': /* hexadecimal number */ + { + int j, val; + val = 0; + get_char(); + for (j = 0; j < 2; j++) { + if (is_hex_digit(next_char)) { + val = ((val << 4) | hexvalue(next_char)); + if (j < 1) { + get_char(); + } + } else + sexp_error(sexp_exception_t::error, + "Hex character \x5cx%x... too short", + val, + 0, + count); + } + ss.append(val); + } break; + case '\n': /* ignore backslash line feed */ + get_char(); /* also ignore following carriage-return if present */ + if (next_char != '\r') + continue; + break; + case '\r': /* ignore backslash carriage-return */ + get_char(); /* also ignore following linefeed if present */ + if (next_char != '\n') + continue; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { /* octal number */ + int j, val; + val = 0; + for (j = 0; j < 3; j++) { + if (next_char >= '0' && next_char <= '7') { + val = ((val << 3) | (next_char - '0')); + if (j < 2) + get_char(); + } else + sexp_error(sexp_exception_t::error, + "Octal character \\%o... too short", + val, + 0, + count); + } + if (val > 255) + sexp_error(sexp_exception_t::error, + "Octal character \\%o... too big", + val, + 0, + count); + ss.append(val); + } break; + default: + sexp_error(sexp_exception_t::error, + "Unknown escape sequence \\%c", + next_char, + 0, + count); + } + } /* end of handling escape sequence */ + else if (next_char == EOF) { + sexp_error(sexp_exception_t::error, "unexpected end of file", 0, 0, count); + } else { + ss.append(next_char); + } + get_char(); + } /* end of main while loop */ +} + +/* + * scan_hexadecimal_string(ss,length) + * Reads hexadecimal string into simple string ss. + * String is of given length result, or length = std::numeric_limits::max() + * if indefinite length. + */ +void sexp_input_stream_t::scan_hexadecimal_string(sexp_simple_string_t &ss, uint32_t length) +{ + set_byte_size(4)->skip_char('#'); + while (next_char != EOF && (next_char != '#' || get_byte_size() == 4)) { + ss.append(next_char); + get_char(); + } + skip_char('#'); + if (ss.length() != length && length != std::numeric_limits::max()) + sexp_error(sexp_exception_t::warning, + "Hex string has length %d different than declared length %d", + ss.length(), + length, + count); +} + +/* + * sexp_input_stream_t::scan_base64_string(ss,length) + * Reads base64 string into simple string ss. + * String is of given length result, or length = std::numeric_limits::max() + * if indefinite length. + */ +void sexp_input_stream_t::scan_base64_string(sexp_simple_string_t &ss, uint32_t length) +{ + set_byte_size(6)->skip_char('|'); + while (next_char != EOF && (next_char != '|' || get_byte_size() == 6)) { + ss.append(next_char); + get_char(); + } + skip_char('|'); + if (ss.length() != length && length != std::numeric_limits::max()) + sexp_error(sexp_exception_t::warning, + "Base64 string has length %d different than declared length %d", + ss.length(), + length, + count); +} + +/* + * sexp_input_stream_t::scan_simple_string(void) + * Reads and returns a simple string from the input stream. + * Determines type of simple string from the initial character, and + * dispatches to appropriate routine based on that. + */ +sexp_simple_string_t sexp_input_stream_t::scan_simple_string(void) +{ + int length; + sexp_simple_string_t ss; + skip_white_space(); + /* Note that it is important in the following code to test for token-ness + * before checking the other cases, so that a token may begin with ":", + * which would otherwise be treated as a verbatim string missing a length. + */ + if (is_token_char(next_char) && !is_dec_digit(next_char)) { + scan_token(ss); + } else { + length = is_dec_digit(next_char) ? scan_decimal_string() : + std::numeric_limits::max(); + + switch (next_char) { + case '\"': + scan_quoted_string(ss, length); + break; + case '#': + scan_hexadecimal_string(ss, length); + break; + case '|': + scan_base64_string(ss, length); + break; + case ':': + // ':' is 'tokenchar', so some length shall be defined + scan_verbatim_string(ss, length); + break; + default: { + const char *const msg = (next_char == EOF) ? "unexpected end of file" : + isprint(next_char) ? "illegal character '%c' (0x%x)" : + "illegal character 0x%x"; + sexp_error(sexp_exception_t::error, msg, next_char, next_char, count); + } + } + } + + if (ss.length() == 0) + sexp_error(sexp_exception_t::warning, "Simple string has zero length", 0, 0, count); + return ss; +} + +/* + * sexp_input_stream_t::scan_string(void) + * Reads and returns a string [presentationhint]string from input stream. + */ +std::shared_ptr sexp_input_stream_t::scan_string(void) +{ + auto s = std::make_shared(); + ; + s->parse(this); + return s; +} + +/* + * sexp_input_stream_t::scan_list(void) + * Read and return a sexp_list_t from the input stream. + */ +std::shared_ptr sexp_input_stream_t::scan_list(void) +{ + auto list = std::make_shared(); + list->parse(this); + return list; +} + +/* + * sexp_input_stream_t::scan_object(void) + * Reads and returns a sexp_object_t from the given input stream. + */ +std::shared_ptr sexp_input_stream_t::scan_object(void) +{ + std::shared_ptr object; + skip_white_space(); + if (next_char == '{' && byte_size != 6) { + set_byte_size(6)->skip_char('{'); + object = scan_object(); + skip_char('}'); + } else { + if (next_char == '(') + object = scan_list(); + else + object = scan_string(); + } + return object; +} + +/* + * sexp_input_stream_t::open_list(void) + */ +sexp_input_stream_t *sexp_input_stream_t::open_list(void) +{ + skip_char('('); + // gcc 4.8.5 generates wrong code in case of chaining like + // skip_char('(')->increase_depth(count) + increase_depth(count); + return this; +} +/* + * sexp_input_stream_t::close_list(void) + */ +sexp_input_stream_t *sexp_input_stream_t::close_list(void) +{ + skip_char(')'); + decrease_depth(); + return this; +} + +} // namespace sexp diff --git a/src/sexp-main.cpp b/src/sexp-main.cpp new file mode 100644 index 0000000..46fae63 --- /dev/null +++ b/src/sexp-main.cpp @@ -0,0 +1,231 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + * Original copyright + * + * SEXP implementation code sexp-main.c + * Ron Rivest + * 6/29/1997 + **/ + +#include + +#include "sexpp/sexp.h" + +using namespace sexp; + +const char *help = "The program 'sexp' reads, parses, and prints out S-expressions.\n" + " INPUT:\n" + " Input is normally taken from stdin, but this can be changed:\n" + " -i filename -- takes input from file instead.\n" + " -p -- prompts user for console input\n" + " Input is normally parsed, but this can be changed:\n" + " -s -- treat input up to EOF as a single string\n" + " CONTROL LOOP:\n" + " The main routine typically reads one S-expression, prints it out " + "again, \n" + " and stops. This may be modified:\n" + " -x -- execute main loop repeatedly until EOF\n" + " OUTPUT:\n" + " Output is normally written to stdout, but this can be changed:\n" + " -o filename -- write output to file instead\n" + " The output format is normally canonical, but this can be changed:\n" + " -a -- write output in advanced transport format\n" + " -b -- write output in base-64 output format\n" + " -c -- write output in canonical format\n" + " -l -- suppress linefeeds after output\n" + " More than one output format can be requested at once.\n" + " There is normally a line-width of 75 on output, but:\n" + " -w width -- changes line width to specified width.\n" + " (0 implies no line-width constraint)\n" + " Running without switches implies: -p -a -b -c -x\n" + " Typical usage: cat certificate-file | sexp -a -x \n"; + +/*************************************************************************/ +/* main(argc,argv) + */ +int main(int argc, char **argv) +{ + char *c; + bool swa = true, swb = true, swc = true, swp = true, sws = false, swx = true, swl = false; + int i; + int ret = -1; + sexp_exception_t::set_interactive(true); + std::ifstream * ifs = nullptr; + sexp_input_stream_t * is = nullptr; + std::ofstream * ofs = nullptr; + sexp_output_stream_t *os = nullptr; + std::string ofname; + std::string ifname; + try { + std::shared_ptr object; + + is = new sexp_input_stream_t(&std::cin); + os = new sexp_output_stream_t(&std::cout); + + if (argc > 1) + swa = swb = swc = swp = sws = swx = swl = false; + for (i = 1; i < argc; i++) { + c = argv[i]; + if (*c != '-') + throw sexp_exception_t( + std::string("Unrecognized switch ") + c, sexp_exception_t::error, EOF); + c++; + if (*c == 'a') + swa = true; /* advanced output */ + else if (*c == 'b') + swb = true; /* base-64 output */ + else if (*c == 'c') + swc = true; /* canonical output */ + else if (*c == 'h') { /* help */ + std::cout << help; + exit(0); + } else if (*c == 'i') { /* input file */ + if (i + 1 < argc) + i++; + ifs = new std::ifstream(argv[i], std::ifstream::binary); + if (ifs->fail()) + sexp_error(sexp_exception_t::error, "Can't open input file.", 0, 0, EOF); + is->set_input(ifs); + ifname = argv[i]; + } else if (*c == 'l') + swl = true; /* suppress linefeeds after output */ + else if (*c == 'o') { /* output file */ + if (i + 1 < argc) + i++; + ofs = new std::ofstream(argv[i], std::ifstream::binary); + if (ofs->fail()) + sexp_error(sexp_exception_t::error, "Can't open output file.", 0, 0, EOF); + os->set_output(ofs); + ofname = argv[i]; + } else if (*c == 'p') + swp = true; /* prompt for input */ + else if (*c == 's') + sws = true; /* treat input as one big string */ + else if (*c == 'w') { /* set output width */ + if (i + 1 < argc) + i++; + os->set_max_column(atoi(argv[i])); + } else if (*c == 'x') + swx = true; /* execute repeatedly */ + else + throw sexp_exception_t( + std::string("Unrecognized switch ") + argv[i], sexp_exception_t::error, EOF); + } + + if (swa == false && swb == false && swc == false) + swc = true; /* must have some output format! */ + + /* main loop */ + if (swp == 0) + is->get_char(); + else + is->set_next_char(-2); /* this is not EOF */ + while (is->get_next_char() != EOF) { + if (swp) { + if (ifname.empty()) + std::cout << "Input:"; + else + std::cout << "Reading input from " << ifname; + std::cout << std::endl; + std::cout.flush(); + } + + is->set_byte_size(8); + if (is->get_next_char() == -2) + is->get_char(); + + is->skip_white_space(); + if (is->get_next_char() == EOF) + break; + + object = sws ? is->scan_to_eof() : is->scan_object(); + + if (swp) + std::cout << std::endl; + + if (swc) { + if (swp) { + if (ofname.empty()) + std::cout << "Canonical output:" << std::endl; + else + std::cout << "Writing canonical output to '" << ofname << "'"; + } + object->print_canonical(os); + if (!swl) { + std::cout << std::endl; + } + } + + if (swb) { + if (swp) { + if (ofname.empty()) + std::cout << "Base64 (of canonical) output:" << std::endl; + else + std::cout << "Writing base64 (of canonical) output to '" << ofname + << "'"; + } + os->set_output(ofs ? ofs : &std::cout)->print_base64(object); + if (!swl) { + std::cout << std::endl; + std::cout.flush(); + } + } + + if (swa) { + if (swp) { + if (ofname.empty()) + std::cout << "Advanced transport output:" << std::endl; + else + std::cout << "Writing advanced transport output to '" << ofname << "'"; + } + os->set_output(ofs ? ofs : &std::cout)->print_advanced(object); + if (!swl) { + std::cout << std::endl; + std::cout.flush(); + } + } + + if (!swx) + break; + if (!swp) + is->skip_white_space(); + else if (!swl) { + std::cout << std::endl; + std::cout.flush(); + } + } + ret = 0; + } catch (sexp_exception_t &e) { + std::cout << e.what() << std::endl; + } catch (...) { + std::cout << "UNEXPECTED ERROR" << std::endl; + } + if (is) + delete is; + if (ifs) + delete ifs; + if (os) + delete os; + if (ofs) + delete ofs; + return ret; +} \ No newline at end of file diff --git a/src/sexp-object.cpp b/src/sexp-object.cpp new file mode 100644 index 0000000..d01ae11 --- /dev/null +++ b/src/sexp-object.cpp @@ -0,0 +1,188 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + * Original copyright + * + * SEXP implementation code sexp-output.c + * Ron Rivest + * 5/5/1997 + */ + +#include "sexpp/sexp.h" + +namespace sexp { + +/* + * sexp_string_t::parse(sis) + * Parses the strin from input stream + */ + +void sexp_string_t::parse(sexp_input_stream_t *sis) +{ + if (sis->get_next_char() == '[') { /* scan presentation hint */ + sis->skip_char('['); + set_presentation_hint(sis->scan_simple_string()); + sis->skip_white_space()->skip_char(']')->skip_white_space(); + } + set_string(sis->scan_simple_string()); +} + +/* + * sexp_string_t::print_canonical(os) + * Prints out sexp string onto output stream os + */ +sexp_output_stream_t *sexp_string_t::print_canonical(sexp_output_stream_t *os) const +{ + if (with_presentation_hint) { + os->var_put_char('['); + presentation_hint.print_canonical_verbatim(os); + os->var_put_char(']'); + } + data_string.print_canonical_verbatim(os); + return os; +} + +/* + * sexp_string_t::print_advanced(os) + * Prints out sexp string onto output stream os + */ +sexp_output_stream_t *sexp_string_t::print_advanced(sexp_output_stream_t *os) const +{ + sexp_object_t::print_advanced(os); + if (with_presentation_hint) { + os->put_char('['); + presentation_hint.print_advanced(os); + os->put_char(']'); + } + data_string.print_advanced(os); + return os; +} + +/* + * sexp_string_t::advanced_length(os) + * Returns length of printed image of string + */ +size_t sexp_string_t::advanced_length(sexp_output_stream_t *os) const +{ + size_t len = 0; + if (with_presentation_hint) + len += 2 + presentation_hint.advanced_length(os); + len += data_string.advanced_length(os); + return len; +} + +/* + * sexp_list_t::parse(sis) + * Parses the list from input stream + */ + +void sexp_list_t::parse(sexp_input_stream_t *sis) +{ + sis->open_list()->skip_white_space(); + if (sis->get_next_char() == ')') { + ; + } else { + push_back(sis->scan_object()); + } + + while (true) { + sis->skip_white_space(); + if (sis->get_next_char() == ')') { /* we just grabbed last element of list */ + sis->close_list(); + return; + + } else { + push_back(sis->scan_object()); + } + } +} + +/* + * sexp_list_t::print_canonical(os) + * Prints out the list "list" onto output stream os + */ +sexp_output_stream_t *sexp_list_t::print_canonical(sexp_output_stream_t *os) const +{ + os->var_open_list(); + std::for_each(begin(), end(), [os](const std::shared_ptr &obj) { + obj->print_canonical(os); + }); + os->var_close_list(); + return os; +} + +/* + * sexp_list_t::print_advanced(os) + * Prints out the list onto output stream os. + * Uses print-length to determine length of the image. If it all fits + * on the current line, then it is printed that way. Otherwise, it is + * written out in "vertical" mode, with items of the list starting in + * the same column on successive lines. + */ +sexp_output_stream_t *sexp_list_t::print_advanced(sexp_output_stream_t *os) const +{ + sexp_object_t::print_advanced(os); + int vertical = false; + int firstelement = true; + os->open_list()->inc_indent(); + vertical = (advanced_length(os) > os->get_max_column() - os->get_column()); + + std::for_each(begin(), end(), [&](const std::shared_ptr &obj) { + if (!firstelement) { + if (vertical) + os->new_line(sexp_output_stream_t::advanced); + else + os->put_char(' '); + } + obj->print_advanced(os); + firstelement = false; + }); + + if (os->get_max_column() > 0 && os->get_column() > os->get_max_column() - 2) + os->new_line(sexp_output_stream_t::advanced); + return os->dec_indent()->put_char(')'); +} + +/* + * sexp_list_t::advanced_length(os) + * Returns length of printed image of list given as iterator + */ +size_t sexp_list_t::advanced_length(sexp_output_stream_t *os) const +{ + size_t len = 1; /* for left paren */ + std::for_each(begin(), end(), [&](const std::shared_ptr &obj) { + len += obj->advanced_length(os); + }); + return (len + 1); /* for final paren */ +} + +/* + * sexp_object_t::print_advanced(os) + * Prints out object on output stream os + */ +sexp_output_stream_t *sexp_object_t::print_advanced(sexp_output_stream_t *os) const +{ + if (os->get_max_column() > 0 && os->get_column() > os->get_max_column() - 4) + os->new_line(sexp_output_stream_t::advanced); + return os; +} + +} // namespace sexp \ No newline at end of file diff --git a/src/sexp-output.cpp b/src/sexp-output.cpp new file mode 100644 index 0000000..07b476c --- /dev/null +++ b/src/sexp-output.cpp @@ -0,0 +1,197 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + * Original copyright + * + * SEXP implementation code sexp-output.c + * Ron Rivest + * 5/5/1997 + */ + +#include "sexpp/sexp.h" + +namespace sexp { + +static const char *hexDigits = "0123456789ABCDEF"; +static const char *base64Digits = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* + * sexp_output_stream_t::sexp_output_stream_t + * Creates and initializes new sexp_output_stream_t object. + */ +sexp_output_stream_t::sexp_output_stream_t(std::ostream *o, size_t m_depth) +{ + set_output(o, m_depth); +} + +/* + * sexp_output_stream_t::set_output + * Re-initializes new sexp_output_stream_t object. + */ +sexp_output_stream_t *sexp_output_stream_t::set_output(std::ostream *o, size_t m_depth) +{ + output_file = o; + byte_size = 8; + bits = 0; + n_bits = 0; + mode = canonical; + column = 0; + max_column = default_line_length; + indent = 0; + base64_count = 0; + reset_depth(m_depth); + return this; +} + +/* + * sexp_output_stream_t::put_char(c) + * Puts the character c out on the output stream os. + * Keeps track of the "column" the next output char will go to. + */ +sexp_output_stream_t *sexp_output_stream_t::put_char(int c) +{ + output_file->put(c); + column++; + return this; +} + +/* + * sexp_output_stream_t::var_put_char(c) + * put_char with variable sized output bytes considered. + * int c; -- this is always an eight-bit byte being output + */ +sexp_output_stream_t *sexp_output_stream_t::var_put_char(int c) +{ + c &= 0xFF; + bits = (bits << 8) | c; + n_bits += 8; + while (n_bits >= byte_size) { + if ((byte_size == 6 || byte_size == 4 || c == '}' || c == '{' || c == '#' || + c == '|') && + max_column > 0 && column >= max_column) + new_line(mode); + if (byte_size == 4) + put_char(hexDigits[(bits >> (n_bits - 4)) & 0x0F]); + else if (byte_size == 6) + put_char(base64Digits[(bits >> (n_bits - 6)) & 0x3F]); + else if (byte_size == 8) + put_char(bits & 0xFF); + n_bits -= byte_size; + base64_count++; + } + return this; +} + +/* + * sexp_output_stream_t::change_output_byte_size(newByteSize,newMode) + * Change os->byte_size to newByteSize + * record mode in output stream for automatic line breaks + */ +sexp_output_stream_t *sexp_output_stream_t::change_output_byte_size(int newByteSize, + sexp_print_mode newMode) +{ + if (newByteSize != 4 && newByteSize != 6 && newByteSize != 8) + sexp_error(sexp_exception_t::error, "Illegal output base %d", newByteSize, 0, EOF); + if (newByteSize != 8 && byte_size != 8) + sexp_error(sexp_exception_t::error, + "Illegal change of output byte size from %d to %d", + byte_size, + newByteSize, + EOF); + byte_size = newByteSize; + n_bits = 0; + bits = 0; + base64_count = 0; + mode = newMode; + return this; +} + +/* + * sexp_output_stream_t::flush() + * flush out any remaining bits + */ +sexp_output_stream_t *sexp_output_stream_t::flush(void) +{ + if (n_bits > 0) { + assert(byte_size == 6); + put_char(base64Digits[(bits << (6 - n_bits)) & 0x3F]); + n_bits = 0; + base64_count++; + } + if (byte_size == 6) { /* and add switch here */ + while ((base64_count & 3) != 0) { + if (max_column > 0 && column >= max_column) + new_line(mode); + put_char('='); + base64_count++; + } + } + return this; +} + +/* + * sexp_output_stream_t::new_line(mode) + * Outputs a newline symbol to the output stream os. + * For advanced mode, also outputs indentation as one blank per + * indentation level (but never indents more than half of max_column). + * Resets column for next output character. + */ +sexp_output_stream_t *sexp_output_stream_t::new_line(sexp_print_mode mode) +{ + if (mode == advanced || mode == base64) { + put_char('\n'); + column = 0; + } + if (mode == advanced) { + for (uint32_t i = 0; i < indent && (4 * i) < max_column; i++) + put_char(' '); + } + return this; +} + +/* + * sexp_output_stream_t::print_decimal(n) + * print out n in decimal to output stream os + */ +sexp_output_stream_t *sexp_output_stream_t::print_decimal(uint64_t n) +{ + char buffer[20]; // 64*ln(2)/ln(10) + // since itoa is not a part of any standard + snprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), "%" PRIu64, n); + for (uint32_t i = 0; buffer[i] != 0; i++) + var_put_char(buffer[i]); + return this; +} + +/* + * base64 MODE + * Same as canonical, except all characters get put out as base 64 ones + */ + +sexp_output_stream_t *sexp_output_stream_t::print_base64( + const std::shared_ptr &object) +{ + change_output_byte_size(8, base64)->var_put_char('{')->change_output_byte_size(6, base64); + print_canonical(object); + return flush()->change_output_byte_size(8, base64)->var_put_char('}'); +} +} // namespace sexp diff --git a/src/sexp-simple-string.cpp b/src/sexp-simple-string.cpp new file mode 100644 index 0000000..0e4c869 --- /dev/null +++ b/src/sexp-simple-string.cpp @@ -0,0 +1,191 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + * Original copyright + * + * SEXP implementation code sexp-output.c + * Ron Rivest + * 5/5/1997 + */ + +#include "sexpp/sexp.h" + +namespace sexp { +/* + * sexp_simple_string_t::print_canonical_verbatim(os) + * Print out simple string on output stream os as verbatim string. + */ +sexp_output_stream_t *sexp_simple_string_t::print_canonical_verbatim( + sexp_output_stream_t *os) const +{ + const octet_t *c = c_str(); + /* print out len: */ + os->print_decimal(length())->var_put_char(':'); + /* print characters in fragment */ + for (uint32_t i = 0; i < length(); i++) + os->var_put_char((int) *c++); + return os; +} + +/* + * sexp_simple_string_t::advanced_length(os) + * Returns length of printed image of s + */ +size_t sexp_simple_string_t::advanced_length(sexp_output_stream_t *os) const +{ + if (can_print_as_token(os)) + return advanced_length_token(); + else if (can_print_as_quoted_string()) + return advanced_length_quoted(); + else if (length() <= 4 && os->get_byte_size() == 8) + return advanced_length_hexadecimal(); + else if (os->get_byte_size() == 8) + return advanced_length_base64(); + else + return 0; /* an error condition */ +} + +/* + * sexp_simple_string_t::print_token(os) + * Prints out simple string ss as a token (assumes that this is OK). + * May run over max-column, but there is no fragmentation allowed... + */ +sexp_output_stream_t *sexp_simple_string_t::print_token(sexp_output_stream_t *os) const +{ + const octet_t *c = c_str(); + if (os->get_max_column() > 0 && os->get_column() > (os->get_max_column() - length())) + os->new_line(sexp_output_stream_t::advanced); + for (uint32_t i = 0; i < length(); i++) + os->put_char((int) (*c++)); + return os; +} + +/* + * sexp_simple_string_t::print_base64(os) + * Prints out simple string ss as a base64 value. + */ +sexp_output_stream_t *sexp_simple_string_t::print_base64(sexp_output_stream_t *os) const +{ + const octet_t *c = c_str(); + os->var_put_char('|')->change_output_byte_size(6, sexp_output_stream_t::advanced); + for (uint32_t i = 0; i < length(); i++) + os->var_put_char((int) (*c++)); + return os->flush() + ->change_output_byte_size(8, sexp_output_stream_t::advanced) + ->var_put_char('|'); +} + +/* + * sexp_simple_string_t::print_hexadecimal(os) + * Prints out simple string as a hexadecimal value. + */ +sexp_output_stream_t *sexp_simple_string_t::print_hexadecimal(sexp_output_stream_t *os) const +{ + const octet_t *c = c_str(); + os->put_char('#')->change_output_byte_size(4, sexp_output_stream_t::advanced); + for (uint32_t i = 0; i < length(); i++) + os->var_put_char((int) (*c++)); + return os->flush() + ->change_output_byte_size(8, sexp_output_stream_t::advanced) + ->put_char('#'); +} + +/* + * sexp_simple_string_t::print_quoted(os) + * Prints out simple string ss as a quoted string + * This code assumes that all characters are tokenchars and blanks, + * so no escape sequences need to be generated. + * May run over max-column, but there is no fragmentation allowed... + */ +sexp_output_stream_t *sexp_simple_string_t::print_quoted(sexp_output_stream_t *os) const +{ + const octet_t *c = c_str(); + os->put_char('\"'); + for (uint32_t i = 0; i < length(); i++) { + if (os->get_max_column() > 0 && os->get_column() >= os->get_max_column() - 2) { + os->put_char('\\')->put_char('\n'); + os->reset_column(); + } + os->put_char(*c++); + } + return os->put_char('\"'); +} + +/* + * sexp_simple_string_t::print_advanced(os) + * Prints out simple string onto output stream ss + */ +sexp_output_stream_t *sexp_simple_string_t::print_advanced(sexp_output_stream_t *os) const +{ + if (can_print_as_token(os)) + print_token(os); + else if (can_print_as_quoted_string()) + print_quoted(os); + else if (length() <= 4 && os->get_byte_size() == 8) + print_hexadecimal(os); + else if (os->get_byte_size() == 8) + print_base64(os); + else + sexp_error(sexp_exception_t::error, + "Can't print in advanced mode with restricted output character set", + 0, + 0, + EOF); + return os; +} + +/* + * sexp_simple_string_t::can_print_as_quoted_string(void) + * Returns true if simple string can be printed as a quoted string. + * Must have only tokenchars and blanks. + */ +bool sexp_simple_string_t::can_print_as_quoted_string(void) const +{ + const octet_t *c = c_str(); + for (uint32_t i = 0; i < length(); i++, c++) { + if (!is_token_char((int) (*c)) && *c != ' ') + return false; + } + return true; +} + +/* + * sexp_simple_string_t::can_print_as_token(os) + * Returns true if simple string can be printed as a token. + * Doesn't begin with a digit, and all characters are tokenchars. + */ +bool sexp_simple_string_t::can_print_as_token(const sexp_output_stream_t *os) const +{ + const octet_t *c = c_str(); + if (length() <= 0) + return false; + if (is_dec_digit((int) *c)) + return false; + if (os->get_max_column() > 0 && os->get_column() + length() >= os->get_max_column()) + return false; + for (uint32_t i = 0; i < length(); i++) { + if (!is_token_char((int) (*c++))) + return false; + } + return true; +} + +} // namespace sexp diff --git a/tests/include/sexp-tests.h b/tests/include/sexp-tests.h new file mode 100644 index 0000000..497fc91 --- /dev/null +++ b/tests/include/sexp-tests.h @@ -0,0 +1,42 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +#pragma once + +#include +#include +#include +#include + +#include + +#include "sexpp/sexp.h" +#include "sexp-samples-folder.h" + +bool compare_binary_files(const std::string &filename1, const std::string &filename2); +bool compare_text_files(const std::string &filename1, const std::string &filename2); + +bool compare_binary_files(const std::string &filename1, std::istream &file2); +bool compare_text_files(const std::string &filename1, std::istream &file2); + +std::istream &safe_get_line(std::istream &is, std::string &t); diff --git a/tests/scripts/tests.sh b/tests/scripts/tests.sh new file mode 100755 index 0000000..4c37c78 --- /dev/null +++ b/tests/scripts/tests.sh @@ -0,0 +1,191 @@ +#! /bin/bash +# +# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) +# +# 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. +# + +# More safety, by turning some bugs into errors. +# Without `errexit` you don’t need ! and can replace +# PIPESTATUS with a simple $? +set -o errexit -o pipefail -o noclobber -o nounset + +assert_installed() { + assertTrue "$1 was not installed" "[ -f $1 ]" +} + +assert_installed_var() { + assertTrue "{$1,$2}/$3 was not installed" "[ -f $1/$3 ] || [ -f $2/$3 ]" +} + +assert_installed_var2() { + assertTrue "$1/{$2,$3} was not installed" "[ -f $1/$2 ] || [ -f $1/$3 ]" +} + +# ...................................................................... +# Check that sexp is installed as expected +test_install_script() { + echo "==> Install script test" + + if [[ "${SHARED_LIB:-}" == "on" ]]; then + version=$(cat "$DIR_ROOT"/version.txt) + version_major="${version:0:1}" + case "$OSTYPE" in + darwin*) + assert_installed "$DIR_INS_B/sexpp" + assert_installed "$DIR_INS_L/libsexpp.dylib" + assert_installed "$DIR_INS_L/libsexpp.$version_major.dylib" + assert_installed "$DIR_INS_L/libsexpp.$version.dylib" + ;; + windows ) + assert_installed "$DIR_INS_B/sexpp.exe" + assert_installed "$DIR_INS_B/sexpp.dll" + assert_installed "$DIR_INS_L/sexpp.lib" + ;; + msys) + + assert_installed "$DIR_INS_B/sexpp.exe" + assert_installed "$DIR_INS_B/libsexpp.dll" + assert_installed "$DIR_INS_L/libsexpp.dll.a" + ;; + *) + assert_installed "$DIR_INS_B/sexpp" + assert_installed_var "$DIR_INS_L" "$DIR_INS_L64" "libsexpp.so" + assert_installed_var "$DIR_INS_L" "$DIR_INS_L64" "libsexpp.so.$version_major" + assert_installed_var "$DIR_INS_L" "$DIR_INS_L64" "libsexpp.so.$version" + ;; + esac + else + case "$OSTYPE" in + windows) + assert_installed "$DIR_INS_B/sexpp.exe" + assert_installed "$DIR_INS_L/sexpp.lib" + ;; + msys) + assert_installed "$DIR_INS_B/sexpp.exe" + assert_installed "$DIR_INS_L/libsexpp.a" + ;; + *) + assert_installed "$DIR_INS_B/sexpp" + assert_installed_var "$DIR_INS_L" "$DIR_INS_L64" "libsexpp.a" + ;; + esac + fi + + assert_installed_var "$DIR_INS_P" "$DIR_INS_P64" "sexpp.pc" + assert_installed_var2 "$DIR_INS_M/man1" "sexpp.1" "sexpp.1.gz" + + assert_installed "$DIR_INS_I/sexp.h" + assert_installed "$DIR_INS_I/sexp-error.h" +} + +# ...................................................................... +# Check sexp client application +# THese are the examples from README.adoc +test_sexp_cli() { + echo "==> SEXP client application test" + +# On Windows there will be CRLF vs LS mismatch +# We would rather skip these tests + if [[ "$OSTYPE" == "windows" || "$OSTYPE" == "msys" ]]; then + startSkipping + fi + + app="$DIR_INS_B/sexpp" +# shellcheck disable=SC2251 +! IFS= read -r -d '' expected << EOM +Input: + +Writing base64 (of canonical) output to certificate.dat +EOM + export LD_LIBRARY_PATH="$DIR_INS_L":"$DIR_INS_L64" + rm -f input1.dat + echo "(aa bb (cc dd))" > input1.dat + output=$("$app" -o certificate.dat -p -b < input1.dat) +# $expected possibly includes extra EOL at the end -- it depends on OS + assertContains "$expected" "$output" + output=$(cat certificate.dat) + assertEquals "{KDI6YWEyOmJiKDI6Y2MyOmRkKSk=}" "$output" + + output=$("$app" -i certificate.dat -x) + assertEquals "(2:aa2:bb(2:cc2:dd))" "$output" + +# shellcheck disable=SC2251 +! IFS= read -r -d '' expected << EOM +Reading input from certificate.dat + +Canonical output: +(2:aa2:bb(2:cc2:dd)) +Base64 (of canonical) output: +{KDI6YWEyOmJiKDI6Y2MyOmRkKSk=} +Advanced transport output: +(aa bb (cc dd)) +EOM + + output=$("$app" -i certificate.dat -a -b -c -p -w 0) + assertContains "$expected" "$output" + +# shellcheck disable=SC2251 +! IFS= read -r -d '' expected << EOM +Input: + +Canonical output: +(3:abc3:def(3:ghi3:jkl)) +Base64 (of canonical) output: +{KDM6YWJjMzpkZWYoMzpnaGkzOmprbCkp} +Advanced transport output: +(abc def (ghi jkl)) + +Input: +EOM + rm -f input2.dat + echo "(abc def (ghi jkl))" > input2.dat + output=$("$app" < input2.dat) + assertContains "$expected" "$output" + + if [[ "$OSTYPE" == "windows" || "$OSTYPE" == "msys" ]]; then + endSkipping + fi + +} + +# ...................................................................... +# main + +DIR00=$( dirname "$0" ) +DIR0=$( cd "$DIR00" && pwd ) +DIR1="${DIR_ROOT:=$DIR0/../..}" +DIR_ROOT=$( cd "$DIR1" && pwd ) + +if [[ -z "${DIR_INSTALL:-}" ]]; then + DIR_INSTALL="$DIR_ROOT/install" +fi + +DIR_INS_B="$DIR_INSTALL/bin" +DIR_INS_L="$DIR_INSTALL/lib" +DIR_INS_L64="$DIR_INSTALL/lib64" +DIR_INS_M="$DIR_INSTALL/share/man" +DIR_INS_P="$DIR_INS_L/pkgconfig" +DIR_INS_P64="$DIR_INS_L64/pkgconfig" +DIR_INS_I="$DIR_INSTALL/include/sexpp" + +DIR_TESTS=$( cd "$DIR0/.." && pwd) + +echo "Running sexp additional tests" +# shellcheck source=/dev/null +. "$DIR_TESTS"/shunit2/shunit2 diff --git a/tests/src/baseline-tests.cpp b/tests/src/baseline-tests.cpp new file mode 100644 index 0000000..bbd59a4 --- /dev/null +++ b/tests/src/baseline-tests.cpp @@ -0,0 +1,115 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +#include "sexp-tests.h" + +using namespace sexp; +using ::testing::UnitTest; + +namespace { +class BaselineTests : public testing::Test { + protected: + static const uint32_t base_sample_advanced = 0; + static const uint32_t base_sample_base64 = 1; + static const uint32_t base_sample_canonical = 2; + static const uint32_t n_base_samples = base_sample_canonical + 1; + + static std::string base_samples[n_base_samples]; + + BaselineTests() + { + base_samples[base_sample_advanced] = sexp_samples_folder + "/baseline/sexp-sample-a"; + base_samples[base_sample_base64] = sexp_samples_folder + "/baseline/sexp-sample-b"; + base_samples[base_sample_canonical] = sexp_samples_folder + "/baseline/sexp-sample-c"; + }; +}; + +const uint32_t BaselineTests::n_base_samples; +const uint32_t BaselineTests::base_sample_advanced; +const uint32_t BaselineTests::base_sample_base64; +const uint32_t BaselineTests::base_sample_canonical; +std::string BaselineTests::base_samples[n_base_samples]; + +TEST_F(BaselineTests, Scan2Canonical) +{ + for (uint32_t i = 0; i < n_base_samples; i++) { + std::ifstream ifs(base_samples[i], std::ifstream::binary); + bool r = ifs.fail(); + EXPECT_FALSE(r); + + if (!ifs.fail()) { + sexp_input_stream_t is(&ifs); + const auto obj = is.set_byte_size(8)->get_char()->scan_object(); + + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + os.print_canonical(obj); + + std::istringstream iss(oss.str(), std::ios_base::binary); + EXPECT_TRUE(compare_binary_files(base_samples[base_sample_canonical], iss)); + } + } +} + +TEST_F(BaselineTests, Scan2Base64) +{ + for (uint32_t i = 0; i < n_base_samples; i++) { + std::ifstream ifs(base_samples[i], std::ifstream::binary); + EXPECT_FALSE(ifs.fail()); + + if (!ifs.fail()) { + sexp_input_stream_t is(&ifs); + const auto obj = is.set_byte_size(8)->get_char()->scan_object(); + + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + + os.set_max_column(0)->print_base64(obj); + oss << std::endl; + + std::istringstream iss(oss.str(), std::ios_base::binary); + EXPECT_TRUE(compare_text_files(base_samples[base_sample_base64], iss)); + } + } +} + +TEST_F(BaselineTests, Scan2Advanced) +{ + for (uint32_t i = 0; i < n_base_samples; i++) { + std::ifstream ifs(base_samples[i], std::ifstream::binary); + EXPECT_FALSE(ifs.fail()); + + if (!ifs.fail()) { + sexp_input_stream_t is(&ifs); + const auto obj = is.set_byte_size(8)->get_char()->scan_object(); + + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + os.print_advanced(obj); + + std::istringstream iss(oss.str(), std::ios_base::binary); + EXPECT_TRUE(compare_text_files(base_samples[base_sample_advanced], iss)); + } + } +} +} // namespace diff --git a/tests/src/compare-files.cpp b/tests/src/compare-files.cpp new file mode 100644 index 0000000..49caec5 --- /dev/null +++ b/tests/src/compare-files.cpp @@ -0,0 +1,126 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +#include +#include + +#include "sexp-tests.h" + +bool compare_binary_files(const std::string &filename1, const std::string &filename2) +{ + bool res = false; + std::ifstream file1(filename1, std::ifstream::ate | std::ifstream::binary); + std::ifstream file2(filename2, std::ifstream::ate | std::ifstream::binary); + + if (file1.tellg() == file2.tellg()) { // otherwise different file size + file1.seekg(0); + file2.seekg(0); + + std::istreambuf_iterator begin1(file1); + std::istreambuf_iterator begin2(file2); + + res = std::equal(begin1, std::istreambuf_iterator(), begin2); + } + + return res; +} + +bool compare_binary_files(const std::string &filename1, std::istream &file2) +{ + std::ifstream file1(filename1, std::ifstream::binary); + + std::istreambuf_iterator begin1(file1); + std::istreambuf_iterator begin2(file2); + return std::equal(begin1, std::istreambuf_iterator(), begin2); +} + +std::istream &safe_get_line(std::istream &is, std::string &t) +{ + t.clear(); + + // The characters in the stream are read one-by-one using a std::streambuf. + // That is faster than reading them one-by-one using the std::istream. + // Code that uses streambuf this way must be guarded by a sentry object. + // The sentry object performs various tasks, + // such as thread synchronization and updating the stream state. + + std::istream::sentry se(is, true); + std::streambuf * sb = is.rdbuf(); + + for (;;) { + int c = sb->sbumpc(); + switch (c) { + case '\n': + return is; + case '\r': + if (sb->sgetc() == '\n') + sb->sbumpc(); + return is; + case std::streambuf::traits_type::eof(): + // Also handle the case when the last line has no line ending + if (t.empty()) + is.setstate(std::ios::eofbit); + return is; + default: + t += (char) c; + } + } +} + +bool compare_text_files(const std::string &filename1, const std::string &filename2) +{ + bool res = true; + std::ifstream file1(filename1, std::ifstream::binary); + std::ifstream file2(filename2, std::ifstream::binary); + std::string s1, s2; + + while (res) { + if (file1.eof() && file2.eof()) + break; + safe_get_line(file1, s1); + safe_get_line(file2, s2); + if (s1 != s2) + res = false; + } + + return res; +} + +bool compare_text_files(const std::string &filename1, std::istream &file2) +{ + bool res = true; + std::ifstream file1(filename1, std::ifstream::binary); + std::string s1, s2; + file2.seekg(0); + + while (res) { + if (file1.eof() && file2.eof()) + break; + safe_get_line(file1, s1); + safe_get_line(file2, s2); + if (s1 != s2) + res = false; + } + + return res; +} diff --git a/tests/src/exception-tests.cpp b/tests/src/exception-tests.cpp new file mode 100644 index 0000000..318b2b8 --- /dev/null +++ b/tests/src/exception-tests.cpp @@ -0,0 +1,331 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +#include "sexp-tests.h" + +using namespace sexp; + +namespace { +class ExceptionTests : public testing::Test { + protected: + static void do_scan_with_exception(const char *str_in, const char *msg) + { + try { + std::istringstream iss(str_in); + sexp_input_stream_t is(&iss); + is.set_byte_size(8)->get_char()->scan_object(); + FAIL() << "sexp::sexp_exception_t expected but has not been thrown"; + } catch (sexp::sexp_exception_t &e) { + EXPECT_STREQ(e.what(), msg); + } + } +}; + +TEST_F(ExceptionTests, UnexpectedEof) +{ + do_scan_with_exception("(4:This2:is1:a4:test", + "SEXP ERROR: unexpected end of file at position 20"); +} + +TEST_F(ExceptionTests, UnexpectedCharacter4bit) +{ + do_scan_with_exception( + "(4:This2:is1:a4:test #)", + "SEXP ERROR: character ')' found in 4-bit coding region at position 22"); +} + +TEST_F(ExceptionTests, IllegalCharacter) +{ + do_scan_with_exception("(This is a test ?)", + "SEXP ERROR: illegal character '?' (0x3f) at position 16"); +} + +TEST_F(ExceptionTests, UnexpectedEofAfterQoute) +{ + do_scan_with_exception("(\")\n", "SEXP ERROR: unexpected end of file at position 4"); +} + +TEST_F(ExceptionTests, IllegalCharacterBase64) +{ + do_scan_with_exception("(Test {KDQ6VGhpczI6aXMxOmE0OnRlc3Qq})", + "SEXP ERROR: illegal character '}' (0x7d) at position 35"); +} + +TEST_F(ExceptionTests, InvalidHex) +{ + do_scan_with_exception("(\"\\x1U\")", + "SEXP ERROR: Hex character \\x1... too short at position 5"); +} + +TEST_F(ExceptionTests, InvalidOctal) +{ + do_scan_with_exception("(\"\\12U\")", + "SEXP ERROR: Octal character \\12... too short at position 5"); +} + +TEST_F(ExceptionTests, TooBigOctal) +{ + do_scan_with_exception("(\"\\666U\")", + "SEXP ERROR: Octal character \\666... too big at position 5"); +} + +TEST_F(ExceptionTests, InvalidEscape) +{ + do_scan_with_exception("(\"\\?\")", + "SEXP ERROR: Unknown escape sequence \\? at position 3"); +} + +TEST_F(ExceptionTests, StringTooShortQuoted) +{ + do_scan_with_exception( + "(4\"ABC\")", + "SEXP ERROR: Declared length was 4, but quoted string ended too early at position 6"); +} + +TEST_F(ExceptionTests, StringTooShortBase64) +{ + sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::warning); + do_scan_with_exception("(8|NDpBQkNE|)", + "SEXP WARNING: Base64 string has length 6 different than declared " + "length 8 at position 12"); + sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error); +} + +TEST_F(ExceptionTests, StringTooShortHex) +{ + sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::warning); + do_scan_with_exception( + "(8#AAABFCAD#)", + "SEXP WARNING: Hex string has length 4 different than declared length 8 at position 12"); + sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error); +} + +TEST_F(ExceptionTests, StringBadLength) +{ + do_scan_with_exception("(1A:AAABFCAD)", + "SEXP ERROR: illegal character 'A' (0x41) at position 2"); +} + +TEST_F(ExceptionTests, DecimalTooLong) +{ + do_scan_with_exception("(1234567890:AAABFCAD)", + "SEXP ERROR: Decimal number is too long at position 11"); +} + +TEST_F(ExceptionTests, Base64CurlyBracket) +{ + // "ey..." in base64 encoding translates to "{..." + do_scan_with_exception("({ey})", "SEXP ERROR: illegal character '{' (0x7b) at position 3"); +} + +TEST_F(ExceptionTests, UnusedBits) +{ + sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::warning); + do_scan_with_exception( + "(Test |AABBCCDD11|)", + "SEXP WARNING: 6-bit region ended with 4 unused bits left-over at position 17"); + sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error); +} + +TEST_F(ExceptionTests, NotAListWhenExpected) +{ + try { + std::istringstream iss( + "|d738/4ghP9rFZ0gAIYZ5q9y6iskDJwASi5rEQpEQq8ZyMZeIZzIAR2I5iGE=|"); + sexp_input_stream_t is(&iss); + + sexp_list_t a_list; + a_list.parse(is.set_byte_size(8)->get_char()); + FAIL() << "sexp::sexp_exception_t expected but has not been thrown"; + } catch (sexp::sexp_exception_t &e) { + EXPECT_STREQ(e.what(), + "SEXP ERROR: character '|' found where '(' was expected at position 0"); + } +} + +TEST_F(ExceptionTests, InvalidByteSizeAndMode) +{ + try { + std::istringstream iss("(3:a\011c)"); + sexp_input_stream_t is(&iss); + std::shared_ptr obj = is.set_byte_size(8)->get_char()->scan_object(); + + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + os.change_output_byte_size(4, sexp_output_stream_t::advanced)->print_advanced(obj); + FAIL() << "sexp::sexp_exception_t expected but has not been thrown"; + } catch (sexp::sexp_exception_t &e) { + EXPECT_STREQ( + e.what(), + "SEXP ERROR: Can't print in advanced mode with restricted output character set"); + } +} + +TEST_F(ExceptionTests, SexpWarning) +{ + testing::internal::CaptureStdout(); + sexp::sexp_exception_t::set_interactive(true); + sexp_error(sexp_exception_t::warning, "Test warning", 0, 0, 200); + std::string output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "\n*** SEXP WARNING: Test warning at position 200 ***\n"); + sexp::sexp_exception_t::set_interactive(false); +} + +static void do_parse_list_from_string(const char *str) +{ + std::istringstream iss(str); + sexp_input_stream_t is(&iss); + sexp_list_t lst; + lst.parse(is.set_byte_size(8)->get_char()); +} + +static void do_parse_list_from_string_with_limit(const char *str, size_t m_depth) +{ + std::istringstream iss(str); + sexp_input_stream_t is(&iss, m_depth); + sexp_list_t lst; + lst.parse(is.set_byte_size(8)->get_char()); +} + +TEST_F(ExceptionTests, MaxDepthParse) +{ + const char *depth_1 = "(sexp_list_1)"; + const char *depth_4 = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4))))"; + const char *depth_4e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 ())))"; + const char *depth_5 = + "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 (sexp_list_5)))))"; + const char *depth_5e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 ()))))"; + + do_parse_list_from_string(depth_1); + do_parse_list_from_string(depth_4); + do_parse_list_from_string(depth_4e); + do_parse_list_from_string(depth_5); + do_parse_list_from_string(depth_5e); + + do_parse_list_from_string_with_limit(depth_1, 4); + do_parse_list_from_string_with_limit(depth_4, 4); + do_parse_list_from_string_with_limit(depth_4e, 4); + + try { + do_parse_list_from_string_with_limit(depth_5, 4); + FAIL() << "sexp::sexp_exception_t expected but has not been thrown"; + } catch (sexp::sexp_exception_t &e) { + EXPECT_STREQ( + e.what(), + "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded at position 53"); + } + + try { + do_parse_list_from_string_with_limit(depth_5e, 4); + FAIL() << "sexp::sexp_exception_t expected but has not been thrown"; + } catch (sexp::sexp_exception_t &e) { + EXPECT_STREQ( + e.what(), + "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded at position 53"); + } +} + +static void do_print_list_from_string(const char *str, bool advanced, size_t m_depth = 0) +{ + std::istringstream iss(str); + sexp_input_stream_t is(&iss); + sexp_list_t lst; + lst.parse(is.set_byte_size(8)->get_char()); + + std::ostringstream oss(str); + sexp_output_stream_t os(&oss, m_depth); + if (advanced) + lst.print_advanced(&os); + else + lst.print_canonical(&os); +} + +TEST_F(ExceptionTests, MaxDepthPrintAdvanced) +{ + const char *depth_1 = "(sexp_list_1)"; + const char *depth_4 = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4))))"; + const char *depth_4e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 ())))"; + const char *depth_5 = + "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 (sexp_list_5)))))"; + const char *depth_5e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 ()))))"; + + do_print_list_from_string(depth_1, true); + do_print_list_from_string(depth_4, true); + do_print_list_from_string(depth_4e, true); + do_print_list_from_string(depth_5, true); + do_print_list_from_string(depth_5e, true); + + do_print_list_from_string(depth_1, true, 4); + do_print_list_from_string(depth_4, true, 4); + do_print_list_from_string(depth_4e, true, 4); + + try { + do_print_list_from_string(depth_5, true, 4); + FAIL() << "sexp::sexp_exception_t expected but has not been thrown"; + } catch (sexp::sexp_exception_t &e) { + EXPECT_STREQ(e.what(), "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded"); + } + + try { + do_print_list_from_string(depth_5e, true, 4); + FAIL() << "sexp::sexp_exception_t expected but has not been thrown"; + } catch (sexp::sexp_exception_t &e) { + EXPECT_STREQ(e.what(), "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded"); + } +} + +TEST_F(ExceptionTests, MaxDepthPrintCanonical) +{ + const char *depth_1 = "(sexp_list_1)"; + const char *depth_4 = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4))))"; + const char *depth_4e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 ())))"; + const char *depth_5 = + "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 (sexp_list_5)))))"; + const char *depth_5e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 ()))))"; + + do_print_list_from_string(depth_1, false); + do_print_list_from_string(depth_4, false); + do_print_list_from_string(depth_4e, false); + do_print_list_from_string(depth_5, false); + do_print_list_from_string(depth_5e, false); + + do_print_list_from_string(depth_1, false, 4); + do_print_list_from_string(depth_4, false, 4); + do_print_list_from_string(depth_4e, false, 4); + + try { + do_print_list_from_string(depth_5, false, 4); + FAIL() << "sexp::sexp_exception_t expected but has not been thrown"; + } catch (sexp::sexp_exception_t &e) { + EXPECT_STREQ(e.what(), "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded"); + } + + try { + do_print_list_from_string(depth_5e, false, 4); + FAIL() << "sexp::sexp_exception_t expected but has not been thrown"; + } catch (sexp::sexp_exception_t &e) { + EXPECT_STREQ(e.what(), "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded"); + } +} + +} // namespace diff --git a/tests/src/g10-compat-tests.cpp b/tests/src/g10-compat-tests.cpp new file mode 100644 index 0000000..62f1180 --- /dev/null +++ b/tests/src/g10-compat-tests.cpp @@ -0,0 +1,51 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +#include "sexp-tests.h" + +using namespace sexp; + +namespace { +class G10CompatTests : public testing::Test { +}; + +TEST_F(G10CompatTests, Canonical) +{ + std::string keyfile(sexp_samples_folder + "/compat/g10/canonical.key"); + std::ifstream ifs(keyfile, std::ifstream::binary); + EXPECT_FALSE(ifs.fail()); + + if (!ifs.fail()) { + sexp_input_stream_t is(&ifs); + const auto obj = is.set_byte_size(8)->get_char()->scan_object(); + + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + os.print_canonical(obj); + + std::istringstream iss(oss.str(), std::ios_base::binary); + EXPECT_TRUE(compare_binary_files(keyfile, iss)); + } +} + +} // namespace diff --git a/tests/src/g23-compat-tests.cpp b/tests/src/g23-compat-tests.cpp new file mode 100644 index 0000000..3a2e756 --- /dev/null +++ b/tests/src/g23-compat-tests.cpp @@ -0,0 +1,139 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +#include "sexp-tests.h" +#include "sexpp/ext-key-format.h" + +using namespace sexp; +using namespace ext_key_format; + +using ::testing::UnitTest; + +namespace { +class G23CompatTests : public testing::Test { + protected: + static void scan_and_check_correct(const char *fn) + { + std::ifstream ifs(sexp_samples_folder + "/compat/g23/" + fn, std::ifstream::binary); + bool r = ifs.fail(); + EXPECT_FALSE(r); + + if (!ifs.fail()) { + ext_key_input_stream_t is(&ifs); + extended_private_key_t extended_key; + is.scan(extended_key); + EXPECT_EQ(extended_key.fields.size(), 2); + EXPECT_EQ(extended_key.fields.count("Created"), 1); + EXPECT_EQ(extended_key.fields.count("creaTed"), 1); + EXPECT_EQ(extended_key.fields.count("something"), 0); + + auto search = extended_key.fields.find("Created"); + if (search != extended_key.fields.end()) { + EXPECT_EQ(search->second, "20221130T160847"); + } + } + } +}; + +TEST_F(G23CompatTests, G10Test) +{ + std::ifstream ifs(sexp_samples_folder + "/compat/g10/canonical.key", + std::ifstream::binary); + bool r = ifs.fail(); + EXPECT_FALSE(r); + + if (!ifs.fail()) { + ext_key_input_stream_t is(&ifs); + extended_private_key_t extended_key; + is.scan(extended_key); + EXPECT_EQ(extended_key.fields.size(), 0); + } +} + +// Correct extended key format +TEST_F(G23CompatTests, G23Correct) +{ + scan_and_check_correct("correct.key"); +} + +// Correct extended key format, no terminating end of line +TEST_F(G23CompatTests, G23CorrectNoEol) +{ + scan_and_check_correct("correct_no_eol.key"); +} + +// Correct extended key format, with a comment +TEST_F(G23CompatTests, G23CorrectWithComment) +{ + scan_and_check_correct("correct_with_comment.key"); +} + +// Correct extended key format, with an empty line (which is comment a well) +TEST_F(G23CompatTests, G23CorrectWithTwoEmptyLines) +{ + scan_and_check_correct("correct_with_two_empty_lines.key"); +} + +// Correct extended key format, with two empty linea +TEST_F(G23CompatTests, G23CorrectWithEmptyLine) +{ + scan_and_check_correct("correct_with_empty_line.key"); +} + +// Correct extended key format, witg windows line endings +TEST_F(G23CompatTests, G23CorrectWithWindowsEol) +{ + scan_and_check_correct("correct_with_windows_eol.key"); +} + +// Correct extended key format, with a comment at the end of file +TEST_F(G23CompatTests, G23CorrectWithCommentAtEof) +{ + scan_and_check_correct("correct_with_comment_at_eof.key"); +} + +// Correct extended key format, with multiple fields of the same name +TEST_F(G23CompatTests, G23CorrectWithMultFields) +{ + std::ifstream ifs(sexp_samples_folder + "/compat/g23/correct_mult_fields.key", + std::ifstream::binary); + bool r = ifs.fail(); + EXPECT_FALSE(r); + + if (!ifs.fail()) { + ext_key_input_stream_t is(&ifs); + extended_private_key_t extended_key; + extended_key.parse(is); + EXPECT_EQ(extended_key.fields.size(), 4); + EXPECT_EQ(extended_key.fields.count("Created"), 1); + EXPECT_EQ(extended_key.fields.count("Description"), 3); + EXPECT_EQ(extended_key.fields.count("something"), 0); + + auto search = extended_key.fields.find("Description"); + if (search != extended_key.fields.end()) { + EXPECT_EQ(search->second, "RSA/RSA"); + } + } +} + +} // namespace diff --git a/tests/src/g23-exception-tests.cpp b/tests/src/g23-exception-tests.cpp new file mode 100644 index 0000000..4f4413d --- /dev/null +++ b/tests/src/g23-exception-tests.cpp @@ -0,0 +1,108 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +#include "sexp-tests.h" +#include "sexpp/ext-key-format.h" + +using namespace sexp; +using namespace ext_key_format; + +using ::testing::UnitTest; + +namespace { +class G23ExceptionTests : public testing::Test { + protected: + static void do_scan_ex(const char *fn, const char *msg) + { + std::ifstream ifs(sexp_samples_folder + "/compat/g23/" + fn, std::ifstream::binary); + EXPECT_FALSE(ifs.fail()); + if (!ifs.fail()) { + try { + ext_key_input_stream_t is(&ifs); + extended_private_key_t extended_key; + is.scan(extended_key); + FAIL() << "sexp::sexp_exception_t expected but has not been thrown"; + } catch (sexp::sexp_exception_t &e) { + EXPECT_STREQ(e.what(), msg); + } + } + } +}; + +// Malformed extended key format, line break inside name +TEST_F(G23ExceptionTests, G23MalformedNameBreak) +{ + do_scan_ex("malformed_name_break.key", + "EXTENDED KEY FORMAT ERROR: unexpected end of line at position 5"); +} + +// Malformed extended key format, eof break inside name +TEST_F(G23ExceptionTests, G23MalformedNameEof) +{ + do_scan_ex("malformed_name_eof.key", + "EXTENDED KEY FORMAT ERROR: unexpected end of file at position 2800"); +} + +// Malformed extended key format, invalid character name +TEST_F(G23ExceptionTests, G23MalformedInvalidNameChar) +{ + do_scan_ex( + "malformed_invalid_name_char.key", + "EXTENDED KEY FORMAT ERROR: unexpected character '@' (0x40) found in a name field " + "at position 28"); +} + +// Malformed extended key format, invalid character name +TEST_F(G23ExceptionTests, G23MalformedInvalidNameFirstChar) +{ + do_scan_ex( + "malformed_invalid_name_first_char.key", + "EXTENDED KEY FORMAT ERROR: unexpected character '1' (0x31) found starting a name field " + "at position 21"); +} + +// Malformed extended key format, no key field +TEST_F(G23ExceptionTests, G23MalformedNoKey) +{ + do_scan_ex("malformed_no_key.key", + "EXTENDED KEY FORMAT ERROR: missing mandatory 'key' field at position 2819"); +} + +// Malformed extended key format, two key fields +TEST_F(G23ExceptionTests, G23MalformedTwoKeys) +{ + do_scan_ex("malformed_two_keys.key", + "EXTENDED KEY FORMAT ERROR: 'key' field must occur only once at position 2822"); +} + +TEST_F(G23ExceptionTests, G23Warning) +{ + testing::internal::CaptureStdout(); + sexp::sexp_exception_t::set_interactive(true); + ext_key_error(sexp_exception_t::warning, "Test warning", 0, 0, 200); + std::string output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "\n*** EXTENDED KEY FORMAT WARNING: Test warning at position 200 ***\n"); + sexp::sexp_exception_t::set_interactive(false); +} + +} // namespace diff --git a/tests/src/primitives-tests.cpp b/tests/src/primitives-tests.cpp new file mode 100644 index 0000000..0689d27 --- /dev/null +++ b/tests/src/primitives-tests.cpp @@ -0,0 +1,394 @@ +/** + * + * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com) + * + * 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. + * + */ + +#include "sexp-tests.h" + +using namespace sexp; + +namespace { +class PrimitivesTests : public testing::Test { + protected: + static void do_test_advanced(const char *str_in, const char *str_out = nullptr) + { + std::istringstream iss(str_in); + sexp_input_stream_t is(&iss); + const auto obj = is.set_byte_size(8)->get_char()->scan_object(); + + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + os.print_advanced(obj); + const char *sample = str_out == nullptr ? str_in : str_out; + EXPECT_EQ(oss.str(), sample); + } + + static void do_test_canonical(const char *str_in, const char *str_out = nullptr) + { + std::istringstream iss(str_in); + sexp_input_stream_t is(&iss); + const auto obj = is.set_byte_size(8)->get_char()->scan_object(); + + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + os.print_canonical(obj); + const char *sample = str_out == nullptr ? str_in : str_out; + EXPECT_EQ(oss.str(), sample); + } +}; + +TEST_F(PrimitivesTests, EmptyList) +{ + do_test_canonical("( )", "()"); + do_test_advanced("( )", "()"); +} + +TEST_F(PrimitivesTests, EmptyString) +{ + sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error); + do_test_canonical("(\"\")", "(0:)"); + do_test_advanced("(\"\")", "(\"\")"); +} + +TEST_F(PrimitivesTests, String) +{ + sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error); + do_test_canonical("(ab)", "(2:ab)"); + do_test_advanced("(ab)", "(ab)"); +} + +TEST_F(PrimitivesTests, QuotedStringWithOctal) +{ + sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error); + do_test_canonical("\"ab\\015\"", "3:ab\r"); + do_test_advanced("\"ab\\015\"", "#61620D#"); +} + +TEST_F(PrimitivesTests, QuotedStringWithEscape) +{ + sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error); + do_test_canonical("\"ab\\tc\"", "4:ab\tc"); + do_test_advanced("4:ab\tc", "#61620963#"); +} + +TEST_F(PrimitivesTests, HexString) +{ + sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error); + do_test_canonical("#616263#", "3:abc"); + do_test_advanced("#616263#", "abc"); +} + +TEST_F(PrimitivesTests, ListList) +{ + do_test_canonical("(string-level-1 (string-level-2) )", + "(14:string-level-1(14:string-level-2))"); + do_test_advanced("(string-level-1 (string-level-2) )", + "(string-level-1 (string-level-2))"); +} + +TEST_F(PrimitivesTests, Base64Ofoctet_t) +{ + do_test_canonical("|YWJj|", "3:abc"); + do_test_advanced("|YWJj|", "abc"); +} + +TEST_F(PrimitivesTests, Base64OfVerbatium) +{ + do_test_canonical("{MzphYmM=}", "3:abc"); + do_test_advanced("{MzphYmM=}", "abc"); +} + +TEST_F(PrimitivesTests, MultilineLinux) +{ + do_test_canonical("\"abcd\\\nef\"", "6:abcdef"); + do_test_advanced("\"abcd\\\nef\"", "abcdef"); +} + +TEST_F(PrimitivesTests, MultilineMac) +{ + do_test_canonical("\"abcd\\\ref\"", "6:abcdef"); + do_test_advanced("\"abcd\\\ref\"", "abcdef"); +} + +TEST_F(PrimitivesTests, MultilineWin) +{ + do_test_canonical("\"abcd\\\r\nef\"", "6:abcdef"); + do_test_advanced("\"abcd\\\r\nef\"", "abcdef"); +} + +TEST_F(PrimitivesTests, MultilineBsd) +{ + do_test_canonical("\"abcd\\\n\ref\"", "6:abcdef"); + do_test_advanced("\"abcd\\\n\ref\"", "abcdef"); +} + +TEST_F(PrimitivesTests, Wrap) +{ + const char *reallyLong = "(a (b (c ddddddddddddddd" + "ddddddddddddddddddddddddddddddddddddddddddddddddd" + "ddddddddddddddddddddddddddddddddddddddddddddddddd" + "ddddddddddddddddddddddddddddddddddddddddddddddddd" + "ddddddd)))"; + const char *stillLong = "(1:a(1:b(1:c169:ddddddddd" + "ddddddddddddddddddddddddddddddddddddddddddddddddd" + "ddddddddddddddddddddddddddddddddddddddddddddddddd" + "ddddddddddddddddddddddddddddddddddddddddddddddddd" + "ddddddddddddd)))"; + const char *broken = "(a\n" + " (b\n" + " (c\n" + " " + "\"ddddddddddddddddddddddddddddddddddddddddddddddddddddd" + "dddddddddddddddd\\\n" + "ddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + "dddddddddddddddddd\\\n" + "ddddddddddddddddddddddddddd\")))"; + do_test_canonical(reallyLong, stillLong); + do_test_advanced(reallyLong, broken); +} + +TEST_F(PrimitivesTests, Escapes) +{ + do_test_canonical("(\"\\b\\t\\v\\n\\f\\r\\\"\\'\\\\\")", "(9:\b\t\v\n\f\r\"'\\)"); + do_test_advanced("(\"\\b\\t\\v\\n\\f\\r\\\"\\'\\\\\")", "(|CAkLCgwNIidc|)"); + + do_test_canonical("(\"\\040\\041\\042\\043\\044\")", "(5: !\"#$)"); + do_test_advanced("(\"\\065\\061\\062\\063\\064\")", "(\"51234\")"); + + do_test_canonical("(\"\\x40\\x41\\x42\\x43\\x44\")", "(5:@ABCD)"); + do_test_advanced("(\"\\x65\\x61\\x62\\x63\\x64\")", "(eabcd)"); +} + +TEST_F(PrimitivesTests, at4rnp) +{ + const char *str_in = "(rnp_block (rnp_list1 rnp_list2))"; + + std::istringstream iss(str_in); + sexp_input_stream_t is(&iss); + + sexp_list_t lst; + lst.parse(is.set_byte_size(8)->get_char()); + + EXPECT_EQ(lst.sexp_list_at(0), nullptr); + EXPECT_NE(lst.sexp_list_at(1), nullptr); + + EXPECT_NE(lst.sexp_string_at(0), nullptr); + EXPECT_EQ(lst.sexp_string_at(1), nullptr); + + const sexp_object_t *obj = lst.sexp_list_at(1); + + if (obj != nullptr) { + EXPECT_EQ(obj->sexp_list_at(0), nullptr); + EXPECT_EQ(obj->sexp_list_at(1), nullptr); + } + + const sexp_string_t *sstr = lst.sexp_string_at(0); + EXPECT_STREQ(reinterpret_cast(sstr->get_string().c_str()), "rnp_block"); +} + +TEST_F(PrimitivesTests, eq4rnp) +{ + const char *str_in = "(rnp_block (rnp_list1 rnp_list2))"; + + std::istringstream iss(str_in); + sexp_input_stream_t is(&iss); + + sexp_list_t lst; + lst.parse(is.set_byte_size(8)->get_char()); + + EXPECT_TRUE(*lst.at(0) == "rnp_block"); + EXPECT_FALSE(*lst.at(0) == "not_rnp_block"); + EXPECT_FALSE(*lst.at(1) == "rnp_block"); + EXPECT_FALSE(*lst.at(1) == "not_rnp_block"); + + EXPECT_TRUE(*lst.sexp_string_at(0) == "rnp_block"); + EXPECT_FALSE(*lst.sexp_string_at(0) == "not_rnp_block"); + EXPECT_TRUE(*lst.sexp_simple_string_at(0) == "rnp_block"); + EXPECT_FALSE(*lst.sexp_simple_string_at(0) == "not_rnp_block"); + + EXPECT_TRUE(*lst.sexp_list_at(1)->at(0) == "rnp_list1"); + EXPECT_TRUE(*lst.sexp_list_at(1)->sexp_string_at(1) == "rnp_list2"); + + EXPECT_TRUE(lst.sexp_string_at(0) == std::string("rnp_block")); + EXPECT_FALSE(lst.sexp_string_at(0) == std::string("not_rnp_block")); + EXPECT_TRUE(lst.sexp_simple_string_at(0) == std::string("rnp_block")); + EXPECT_FALSE(lst.sexp_simple_string_at(0) == std::string("not_rnp_block")); +} + +TEST_F(PrimitivesTests, ne4rnp) +{ + const char *str_in = "(rnp_block (rnp_list1 rnp_list2))"; + + std::istringstream iss(str_in); + sexp_input_stream_t is(&iss); + + sexp_list_t lst; + lst.parse(is.set_byte_size(8)->get_char()); + + EXPECT_FALSE(*lst.at(0) != "rnp_block"); + EXPECT_TRUE(*lst.at(0) != "not_rnp_block"); + EXPECT_TRUE(*lst.at(1) != "rnp_block"); + EXPECT_TRUE(*lst.at(1) != "not_rnp_block"); + + EXPECT_FALSE(*lst.sexp_string_at(0) != "rnp_block"); + EXPECT_TRUE(*lst.sexp_string_at(0) != "not_rnp_block"); + EXPECT_FALSE(*lst.sexp_simple_string_at(0) != "rnp_block"); + EXPECT_TRUE(*lst.sexp_simple_string_at(0) != "not_rnp_block"); + + EXPECT_FALSE(*lst.sexp_list_at(1)->at(0) != "rnp_list1"); + EXPECT_FALSE(*lst.sexp_list_at(1)->sexp_string_at(1) != "rnp_list2"); + + EXPECT_FALSE(lst.sexp_string_at(0) != std::string("rnp_block")); + EXPECT_TRUE(lst.sexp_string_at(0) != std::string("not_rnp_block")); + EXPECT_FALSE(lst.sexp_simple_string_at(0) != std::string("rnp_block")); + EXPECT_TRUE(lst.sexp_simple_string_at(0) != std::string("not_rnp_block")); +} + +TEST_F(PrimitivesTests, u4rnp) +{ + const char *str_in1 = "(unsigned_value \"12345\")"; + const char *str_in2 = "(14:unsigned_value5:54321)"; + + std::istringstream iss1(str_in1); + std::istringstream iss2(str_in2); + + sexp_input_stream_t is(&iss1); + sexp_list_t lst; + lst.parse(is.set_byte_size(8)->get_char()); + EXPECT_EQ(lst.sexp_string_at(1)->as_unsigned(), 12345); + + lst.clear(); + lst.parse(is.set_input(&iss2)->set_byte_size(8)->get_char()); + EXPECT_EQ(lst.sexp_string_at(1)->as_unsigned(), 54321); +} + +TEST_F(PrimitivesTests, proInheritance) +{ + sexp_list_t lst; + EXPECT_FALSE(lst.is_sexp_string()); + EXPECT_TRUE(lst.is_sexp_list()); + EXPECT_EQ(lst.sexp_string_view(), nullptr); + EXPECT_EQ(lst.sexp_list_view(), &lst); + EXPECT_EQ(lst.as_unsigned(), std::numeric_limits::max()); + EXPECT_EQ(lst.sexp_list_at(0), nullptr); + EXPECT_EQ(lst.sexp_string_at(0), nullptr); + EXPECT_EQ(lst.sexp_simple_string_at(0), nullptr); + + sexp_string_t str; + EXPECT_FALSE(str.is_sexp_list()); + EXPECT_TRUE(str.is_sexp_string()); + EXPECT_EQ(str.sexp_string_view(), &str); + EXPECT_EQ(str.sexp_list_view(), nullptr); + EXPECT_EQ(str.sexp_list_at(0), nullptr); + EXPECT_EQ(str.sexp_string_at(0), nullptr); + EXPECT_EQ(str.sexp_simple_string_at(0), nullptr); +} + +TEST_F(PrimitivesTests, DisplayHint) +{ + do_test_canonical("(URL [URI]www.ribose.com)", "(3:URL[3:URI]14:www.ribose.com)"); + do_test_advanced("(3:URL[3:URI]14:www.ribose.com)", "(URL [URI]www.ribose.com)"); +} + +TEST_F(PrimitivesTests, scanToEof) +{ + const char *str_in = "ABCD"; + + std::istringstream iss(str_in); + sexp_input_stream_t is(&iss); + + auto object = is.scan_to_eof(); + EXPECT_TRUE(object->is_sexp_string()); + + is.set_byte_size(4); + EXPECT_EQ(is.get_byte_size(), 4); + + EXPECT_EQ(is.get_char(), &is); + EXPECT_EQ(is.get_byte_size(), 8); +} + +TEST_F(PrimitivesTests, ChangeOutputByteSizeTest) +{ + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + + EXPECT_EQ(os.change_output_byte_size(8, sexp_output_stream_t::advanced), &os); + + try { + os.change_output_byte_size(7, sexp_output_stream_t::advanced); + FAIL() << "sexp::sexp_exception_t expected but has not been thrown"; + } catch (sexp::sexp_exception_t &e) { + EXPECT_STREQ(e.what(), "SEXP ERROR: Illegal output base 7"); + } + + EXPECT_EQ(os.change_output_byte_size(4, sexp_output_stream_t::advanced), &os); + + try { + os.change_output_byte_size(6, sexp_output_stream_t::advanced); + FAIL() << "sexp::sexp_exception_t expected but has not been thrown"; + } catch (sexp::sexp_exception_t &e) { + EXPECT_STREQ(e.what(), "SEXP ERROR: Illegal change of output byte size from 4 to 6"); + } +} + +TEST_F(PrimitivesTests, FlushTest) +{ + std::ostringstream oss1(std::ios_base::binary); + std::ostringstream oss2(std::ios_base::binary); + sexp_output_stream_t os(&oss1); + + EXPECT_EQ( + os.change_output_byte_size(6, sexp_output_stream_t::advanced)->print_decimal(1)->flush(), + &os); + EXPECT_EQ(oss1.str(), "MQ=="); + os.set_output(&oss2) + ->change_output_byte_size(6, sexp_output_stream_t::advanced) + ->set_max_column(2) + ->print_decimal(2) + ->flush(); + EXPECT_EQ(oss2.str(), "Mg\n=="); +} + +TEST_F(PrimitivesTests, ListWrapTest) +{ + std::istringstream iss("(abc)"); + sexp_input_stream_t is(&iss); + const auto obj = is.set_byte_size(8)->get_char()->scan_object(); + + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + os.set_max_column(5)->print_advanced(obj); + EXPECT_EQ(oss.str(), "(abc\n )"); +} + +TEST_F(PrimitivesTests, EnsureHexTest) +{ + std::istringstream iss("(3:a\011c)"); + sexp_input_stream_t is(&iss); + const auto obj = is.set_byte_size(8)->get_char()->scan_object(); + + std::ostringstream oss(std::ios_base::binary); + sexp_output_stream_t os(&oss); + os.print_advanced(obj); + EXPECT_EQ(oss.str(), "(#610963#)"); +} + +} // namespace diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..1e9b46b --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +0.8.7 -- cgit v1.2.3