diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..c121c157 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,56 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG] Title" +labels: bug +assignees: etr + +--- + + + +### Prerequisites + +* [ ] Put an X between the brackets on this line if you have checked that your issue isn't already filed: https://github.com/search?l=&q=repo%3Aetr%2Flibhttpserver&type=Issues + +### Description + +[Description of the issue] + +### Steps to Reproduce + +1. [First Step] +2. [Second Step] +3. [and so on...] + +**Expected behavior:** [What you expect to happen] + +**Actual behavior:** [What actually happens] + +**Reproduces how often:** [What percentage of the time does it reproduce?] + +### Versions + +* OS version (if on linux, the output of "uname -a") +* libhttpserver version (please specify whether compiled or packaged) +* libmicrohttpd version (please specify whether compiled or packaged) + +If you have problems during build: +* Compiler version +* autotools version + +### Additional Information + +Any additional information, configuration (especially build configuration flags if you compiled the libraries) or data that might be necessary to reproduce the issue. + +If you have problems during build, please attach your config.log and the full scope of your error from make. + +If you have problems at execution, please: +* attach the stacktrace in case of crash (a coredump would be even better). +* provide a main that reproduces the error. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..a9833cf0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: feature-request +assignees: etr + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe why the feature or enhancement you are proposing fits the library.** +A clear and concise explanation of the reason it fits into the library's mission. + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE/bug_fix.md b/.github/PULL_REQUEST_TEMPLATE/bug_fix.md new file mode 100644 index 00000000..edcbbff7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/bug_fix.md @@ -0,0 +1,59 @@ +### Requirements for Contributing a Bug Fix + +* Fill out the template below. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. +* The pull request must only fix an existing bug. To contribute other changes, you must use a different template. You can see all templates at https://github.com/etr/libhttpserver/tree/master/.github/PULL_REQUEST_TEMPLATE. +* The pull request must update the test suite to demonstrate the changed functionality. +* After you create the pull request, all status checks must be pass before a maintainer reviews your contribution. For more details, please see https://github.com/etr/libhttpserver/tree/master/CONTRIBUTING.md#pull-requests. + +### Identify the Bug + + + +### Description of the Change + + + +### Alternate Designs + + + +### Possible Drawbacks + + + +### Verification Process + + + +### Release Notes + + diff --git a/.github/PULL_REQUEST_TEMPLATE/documentation.md b/.github/PULL_REQUEST_TEMPLATE/documentation.md new file mode 100644 index 00000000..2fc5a29e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/documentation.md @@ -0,0 +1,30 @@ +### Requirements for Contributing Documentation + +* Fill out the template below. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. +* The pull request must only contribute documentation (for example, markdown files or API docs). To contribute other changes, you must use a different template. You can see all templates at https://github.com/etr/libhttpserver/tree/master/.github/PULL_REQUEST_TEMPLATE. + +### Description of the Change + + + +### Release Notes + + diff --git a/.github/PULL_REQUEST_TEMPLATE/feature_change.md b/.github/PULL_REQUEST_TEMPLATE/feature_change.md new file mode 100644 index 00000000..826d72cc --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/feature_change.md @@ -0,0 +1,62 @@ +### Requirements for Adding, Changing, or Removing a Feature + +* Fill out the template below. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. +* The pull request must contribute a change that has been endorsed by the maintainer team. See details in the template below. +* The pull request must update the test suite to exercise the updated functionality. +* After you create the pull request, all status checks must be pass before a maintainer reviews your contribution. For more details, please see https://github.com/etr/libhttpserver/tree/master/CONTRIBUTING.md#pull-requests. + +### Issue or RFC Endorsed by Maintainers + + + +### Description of the Change + + + +### Alternate Designs + + + +### Possible Drawbacks + + + +### Verification Process + + + +### Release Notes + + diff --git a/.github/PULL_REQUEST_TEMPLATE/performance_improvement.md b/.github/PULL_REQUEST_TEMPLATE/performance_improvement.md new file mode 100644 index 00000000..82ea175f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/performance_improvement.md @@ -0,0 +1,55 @@ +### Requirements for Contributing a Performance Improvement + +* Fill out the template below. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. +* The pull request must only affect performance of existing functionality. To contribute other changes, you must use a different template. You can see all templates at https://github.com/etr/libhttpserver/tree/master/.github/PULL_REQUEST_TEMPLATE. +* After you create the pull request, all status checks must be pass before a maintainer reviews your contribution. For more details, please see https://github.com/etr/libhttpserver/tree/master/CONTRIBUTING.md#pull-requests. + +### Description of the Change + + + +### Quantitative Performance Benefits + + + +### Possible Drawbacks + + + +### Verification Process + + + +### Applicable Issues + + + +### Release Notes + + diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml new file mode 100644 index 00000000..632cedfb --- /dev/null +++ b/.github/workflows/changelog-check.yml @@ -0,0 +1,84 @@ +name: "ChangeLog Check" + +on: + pull_request: + branches: [master] + +jobs: + changelog: + name: Verify ChangeLog updated + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check for ChangeLog update + run: | + # Get list of changed files + changed_files=$(git diff --name-only origin/master...HEAD) + + if [ -z "$changed_files" ]; then + echo "No files changed." + exit 0 + fi + + # Check if only exempt files were changed + # Exempt: .github/*, CLAUDE.md, README*, CONTRIBUTING*, CODE_OF_CONDUCT*, + # .gitignore, CPPLINT.cfg, *.md in root + has_non_exempt=false + changelog_modified=false + + while IFS= read -r file; do + # Check if ChangeLog itself was modified + if [ "$file" = "ChangeLog" ]; then + changelog_modified=true + continue + fi + + # Check exempt patterns + case "$file" in + .github/*) continue ;; + CLAUDE.md) continue ;; + README*) continue ;; + CONTRIBUTING*) continue ;; + CODE_OF_CONDUCT*) continue ;; + .gitignore) continue ;; + CPPLINT.cfg) continue ;; + esac + + # Check for *.md files in repo root (no slashes in path) + if echo "$file" | grep -qE '^[^/]+\.md$'; then + continue + fi + + has_non_exempt=true + done <<< "$changed_files" + + if [ "$has_non_exempt" = "false" ]; then + echo "Only exempt files changed — ChangeLog update not required." + exit 0 + fi + + if [ "$changelog_modified" = "false" ]; then + echo "::error::ChangeLog was not updated. All pull requests with code changes must include a ChangeLog entry." + echo "" + echo "Please add a tab-indented entry under the first 'Version X.Y.Z' header in ChangeLog." + echo "See CONTRIBUTING.md for format details." + echo "" + echo "If this PR only changes documentation or CI files, add the [skip changelog] label or ensure" + echo "only exempt paths are modified (.github/*, *.md in root, .gitignore, CPPLINT.cfg)." + exit 1 + fi + + echo "ChangeLog was modified — checking format." + + # Validate first line matches Version header format + first_line=$(head -n 1 ChangeLog) + if ! echo "$first_line" | grep -qE '^Version [0-9]+\.[0-9]+\.[0-9]+'; then + echo "::error::First line of ChangeLog must match 'Version X.Y.Z' format (got: '$first_line')." + exit 1 + fi + + echo "ChangeLog format looks good." diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..cc0f95be --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,73 @@ +name: "CodeQL" + +on: + push: + branches: [master] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '0 4 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['cpp'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + - name: Install libmicrohttpd dependency + run: | + curl https://s3.amazonaws.com/libhttpserver/libmicrohttpd_releases/libmicrohttpd-0.9.64.tar.gz -o libmicrohttpd-0.9.64.tar.gz ; + tar -xzf libmicrohttpd-0.9.64.tar.gz ; + cd libmicrohttpd-0.9.64 ; + ./configure --disable-examples ; + make ; + sudo make install ; + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + + # Autobuild attempts to build any compiled languages (C/C++, C#, 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@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + - name: Manual steps to build the library + run: | + ./bootstrap ; + ./configure --enable-same-directory-build; + make ; + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..81db8332 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,326 @@ +name: "Release" + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+*' + workflow_dispatch: + inputs: + tag: + description: 'Tag to release (e.g., 0.20.0 or 0.20.0-rc1)' + required: true + +permissions: + contents: write + +jobs: + validate: + name: Validate release + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + is_prerelease: ${{ steps.version.outputs.is_prerelease }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Determine version + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + VERSION="${{ github.event.inputs.tag }}" + else + VERSION="${GITHUB_REF#refs/tags/}" + fi + VERSION="${VERSION#v}" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + if echo "$VERSION" | grep -qE '-(rc|alpha|beta)'; then + echo "is_prerelease=true" >> "$GITHUB_OUTPUT" + else + echo "is_prerelease=false" >> "$GITHUB_OUTPUT" + fi + + echo "Version: $VERSION" + + - name: Validate version consistency + run: | + chmod +x scripts/validate-version.sh + scripts/validate-version.sh "${{ steps.version.outputs.version }}" + + - name: Extract release notes + run: | + chmod +x scripts/extract-release-notes.sh + scripts/extract-release-notes.sh "${{ steps.version.outputs.version }}" > release-notes.md + echo "--- Release notes ---" + cat release-notes.md + + - name: Upload release notes + uses: actions/upload-artifact@v4 + with: + name: release-notes + path: release-notes.md + + build-dist: + name: Build distribution tarball + runs-on: ubuntu-latest + needs: validate + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y autoconf automake libtool libgnutls28-dev libcurl4-openssl-dev + + - name: Fetch libmicrohttpd from cache + id: cache-libmicrohttpd + uses: actions/cache@v4 + with: + path: libmicrohttpd-0.9.77 + key: ubuntu-latest-gcc-libmicrohttpd-0.9.77-pre-built-v2 + + - name: Build libmicrohttpd (if not cached) + if: steps.cache-libmicrohttpd.outputs.cache-hit != 'true' + run: | + curl https://s3.amazonaws.com/libhttpserver/libmicrohttpd_releases/libmicrohttpd-0.9.77.tar.gz -o libmicrohttpd-0.9.77.tar.gz + tar -xzf libmicrohttpd-0.9.77.tar.gz + cd libmicrohttpd-0.9.77 + ./configure --disable-examples + make + + - name: Install libmicrohttpd + run: | + cd libmicrohttpd-0.9.77 + sudo make install + sudo ldconfig + + - name: Build libhttpserver + run: | + ./bootstrap + mkdir build + cd build + ../configure + make + make check + + - name: Create distribution tarball + run: | + cd build + make dist + + - name: Upload tarball + uses: actions/upload-artifact@v4 + with: + name: dist-tarball + path: build/libhttpserver-*.tar.gz + + verify-dist-linux: + name: Verify tarball (Linux) + runs-on: ubuntu-latest + needs: build-dist + steps: + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgnutls28-dev libcurl4-openssl-dev + + - name: Fetch libmicrohttpd from cache + id: cache-libmicrohttpd + uses: actions/cache@v4 + with: + path: libmicrohttpd-0.9.77 + key: ubuntu-latest-gcc-libmicrohttpd-0.9.77-pre-built-v2 + + - name: Build libmicrohttpd (if not cached) + if: steps.cache-libmicrohttpd.outputs.cache-hit != 'true' + run: | + curl https://s3.amazonaws.com/libhttpserver/libmicrohttpd_releases/libmicrohttpd-0.9.77.tar.gz -o libmicrohttpd-0.9.77.tar.gz + tar -xzf libmicrohttpd-0.9.77.tar.gz + cd libmicrohttpd-0.9.77 + ./configure --disable-examples + make + + - name: Install libmicrohttpd + run: | + cd libmicrohttpd-0.9.77 + sudo make install + sudo ldconfig + + - name: Download tarball + uses: actions/download-artifact@v4 + with: + name: dist-tarball + + - name: Build and test from tarball + run: | + tar -xzf libhttpserver-*.tar.gz + cd libhttpserver-*/ + mkdir build + cd build + ../configure + make + make check + + - name: Print test results on failure + if: failure() + run: | + cd libhttpserver-*/build + cat test/test-suite.log || true + + verify-dist-macos: + name: Verify tarball (macOS) + runs-on: macos-latest + needs: build-dist + steps: + - name: Install build tools + run: brew install autoconf automake libtool + + - name: Fetch libmicrohttpd from cache + id: cache-libmicrohttpd + uses: actions/cache@v4 + with: + path: libmicrohttpd-0.9.77 + key: macos-latest-gcc-libmicrohttpd-0.9.77-pre-built-v2 + + - name: Build libmicrohttpd (if not cached) + if: steps.cache-libmicrohttpd.outputs.cache-hit != 'true' + run: | + curl https://s3.amazonaws.com/libhttpserver/libmicrohttpd_releases/libmicrohttpd-0.9.77.tar.gz -o libmicrohttpd-0.9.77.tar.gz + tar -xzf libmicrohttpd-0.9.77.tar.gz + cd libmicrohttpd-0.9.77 + ./configure --disable-examples + make + + - name: Install libmicrohttpd + run: | + cd libmicrohttpd-0.9.77 + sudo make install + + - name: Fetch curl from cache + id: cache-curl + uses: actions/cache@v4 + with: + path: curl-7.75.0 + key: macos-latest-CURL-pre-built-v2 + + - name: Build curl (if not cached) + if: steps.cache-curl.outputs.cache-hit != 'true' + run: | + curl https://libhttpserver.s3.amazonaws.com/travis_stuff/curl-7.75.0.tar.gz -o curl-7.75.0.tar.gz + tar -xzf curl-7.75.0.tar.gz + cd curl-7.75.0 + ./configure --with-darwinssl --without-ssl + make + + - name: Install curl + run: | + cd curl-7.75.0 + sudo make install + + - name: Download tarball + uses: actions/download-artifact@v4 + with: + name: dist-tarball + + - name: Build and test from tarball + run: | + tar -xzf libhttpserver-*.tar.gz + cd libhttpserver-*/ + mkdir build + cd build + CFLAGS='-mtune=generic' ../configure --disable-fastopen + make + make check + + - name: Print test results on failure + if: failure() + run: | + cd libhttpserver-*/build + cat test/test-suite.log || true + + verify-dist-windows: + name: Verify tarball (Windows) + runs-on: windows-latest + needs: build-dist + defaults: + run: + shell: msys2 {0} + steps: + - name: Setup MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: >- + autotools + base-devel + + - name: Install MinGW64 packages + run: | + pacman --noconfirm -S --needed mingw-w64-x86_64-{toolchain,libtool,make,pkg-config,libsystre,doxygen,gnutls,graphviz,curl} + + - name: Build and install libmicrohttpd + run: | + curl https://s3.amazonaws.com/libhttpserver/libmicrohttpd_releases/libmicrohttpd-0.9.77.tar.gz -o libmicrohttpd-0.9.77.tar.gz + tar -xzf libmicrohttpd-0.9.77.tar.gz + cd libmicrohttpd-0.9.77 + ./configure --disable-examples --enable-poll=no + make + make install + + - name: Download tarball + uses: actions/download-artifact@v4 + with: + name: dist-tarball + + - name: Build and test from tarball + run: | + tar -xzf libhttpserver-*.tar.gz + cd libhttpserver-*/ + mkdir build + cd build + ../configure --disable-fastopen + make + make check + + - name: Print test results on failure + if: failure() + run: | + cd libhttpserver-*/build + cat test/test-suite.log || true + + release: + name: Create GitHub Release + runs-on: ubuntu-latest + needs: [validate, verify-dist-linux, verify-dist-macos, verify-dist-windows] + steps: + - name: Download tarball + uses: actions/download-artifact@v4 + with: + name: dist-tarball + + - name: Download release notes + uses: actions/download-artifact@v4 + with: + name: release-notes + + - name: Create release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + VERSION="${{ needs.validate.outputs.version }}" + IS_PRERELEASE="${{ needs.validate.outputs.is_prerelease }}" + + PRERELEASE_FLAG="" + if [ "$IS_PRERELEASE" = "true" ]; then + PRERELEASE_FLAG="--prerelease" + fi + + gh release create "$VERSION" \ + --title "libhttpserver $VERSION" \ + --notes-file release-notes.md \ + $PRERELEASE_FLAG \ + libhttpserver-*.tar.gz diff --git a/.github/workflows/verify-build.yml b/.github/workflows/verify-build.yml new file mode 100644 index 00000000..17ae2955 --- /dev/null +++ b/.github/workflows/verify-build.yml @@ -0,0 +1,754 @@ +name: "Verify Build" + +on: + push: + branches: [master] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '0 0 * * 0' + +jobs: + verify: + name: Verify + runs-on: ${{ matrix.os }} + env: + DEBUG: ${{ matrix.debug }} + COVERAGE: ${{ matrix.coverage }} + LINKING: ${{ matrix.linking }} + BUILD_TYPE: ${{ matrix.build-type }} + CC: ${{ matrix.c-compiler }} + CXX: ${{ matrix.cc-compiler }} + defaults: + run: + shell: ${{ matrix.shell }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + os-type: [ubuntu, mac] + test-group: [basic] + compiler-family: [none] + c-compiler: [gcc, clang] + cc-compiler: [g++, clang++] + debug: [debug, nodebug] + coverage: [coverage, nocoverage] + linking: [dynamic, static] + build-type: [classic] + shell: [bash] + exclude: + - os: ubuntu-latest + os-type: mac + - os: macos-latest + os-type: ubuntu + - c-compiler: gcc + cc-compiler: clang++ + - c-compiler: clang + cc-compiler: g++ + - c-compiler: clang + debug: debug + - debug: debug + coverage: nocoverage + - debug: nodebug + coverage: coverage + - linking: static + debug: debug + - linking: static + coverage: coverage + include: + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: asan + compiler-family: clang + c-compiler: clang-18 + cc-compiler: clang++-18 + debug: debug + coverage: nocoverage + shell: bash + # This test gives false positives on newer versions of clang + # and ubuntu-18.04 is not supported anymore on github + #- test-group: extra + # os: ubuntu-18.04 + # os-type: ubuntu + # build-type: msan + # compiler-family: clang + # c-compiler: clang-6.0 + # cc-compiler: clang++-6.0 + # debug: debug + # coverage: nocoverage + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: lsan + compiler-family: clang + c-compiler: clang-18 + cc-compiler: clang++-18 + debug: debug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: tsan + compiler-family: clang + c-compiler: clang-18 + cc-compiler: clang++-18 + debug: debug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: ubsan + compiler-family: clang + c-compiler: clang-18 + cc-compiler: clang++-18 + debug: debug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: none + compiler-family: gcc + c-compiler: gcc-9 + cc-compiler: g++-9 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: none + compiler-family: gcc + c-compiler: gcc-10 + cc-compiler: g++-10 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: none + compiler-family: gcc + c-compiler: gcc-11 + cc-compiler: g++-11 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: none + compiler-family: gcc + c-compiler: gcc-12 + cc-compiler: g++-12 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: none + compiler-family: gcc + c-compiler: gcc-13 + cc-compiler: g++-13 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: none + compiler-family: gcc + c-compiler: gcc-14 + cc-compiler: g++-14 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-22.04 + os-type: ubuntu + build-type: none + compiler-family: clang + c-compiler: clang-11 + cc-compiler: clang++-11 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-22.04 + os-type: ubuntu + build-type: none + compiler-family: clang + c-compiler: clang-12 + cc-compiler: clang++-12 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-22.04 + os-type: ubuntu + build-type: none + compiler-family: clang + c-compiler: clang-13 + cc-compiler: clang++-13 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: none + compiler-family: clang + c-compiler: clang-14 + cc-compiler: clang++-14 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: none + compiler-family: clang + c-compiler: clang-15 + cc-compiler: clang++-15 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: none + compiler-family: clang + c-compiler: clang-16 + cc-compiler: clang++-16 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: none + compiler-family: clang + c-compiler: clang-17 + cc-compiler: clang++-17 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: valgrind + compiler-family: gcc + c-compiler: gcc-14 + cc-compiler: g++-14 + debug: nodebug + coverage: nocoverage + shell: bash + # Test build without digest auth support (issue #232) + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: no-dauth + compiler-family: gcc + c-compiler: gcc + cc-compiler: g++ + debug: nodebug + coverage: nocoverage + linking: dynamic + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: iwyu + compiler-family: clang + c-compiler: clang-18 + cc-compiler: clang++-18 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: performance + os: ubuntu-latest + os-type: ubuntu + build-type: select + compiler-family: gcc + c-compiler: gcc-10 + cc-compiler: g++-10 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: performance + os: ubuntu-latest + os-type: ubuntu + build-type: nodelay + compiler-family: gcc + c-compiler: gcc-10 + cc-compiler: g++-10 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: performance + os: ubuntu-latest + os-type: ubuntu + build-type: threads + compiler-family: gcc + c-compiler: gcc-10 + cc-compiler: g++-10 + debug: nodebug + coverage: nocoverage + shell: bash + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: lint + compiler-family: gcc + c-compiler: gcc-10 + cc-compiler: g++-10 + debug: debug + coverage: nocoverage + shell: bash + - test-group: basic + os: windows-latest + os-type: windows + msys-env: MINGW64 + shell: 'msys2 {0}' + build-type: classic + compiler-family: none + c-compiler: gcc + cc-compiler: g++ + debug: nodebug + coverage: nocoverage + linking: dynamic + - test-group: basic + os: windows-latest + os-type: windows + msys-env: MSYS + shell: 'msys2 {0}' + build-type: classic + compiler-family: none + c-compiler: gcc + cc-compiler: g++ + debug: nodebug + coverage: nocoverage + linking: dynamic + # ARM 32-bit cross-compilation (ARMv7 hard-float) + - test-group: cross-compile + os: ubuntu-latest + os-type: ubuntu + build-type: arm32 + compiler-family: arm-cross + c-compiler: arm-linux-gnueabihf-gcc + cc-compiler: arm-linux-gnueabihf-g++ + debug: nodebug + coverage: nocoverage + linking: dynamic + shell: bash + # ARM 64-bit cross-compilation (AArch64) + - test-group: cross-compile + os: ubuntu-latest + os-type: ubuntu + build-type: arm64 + compiler-family: arm-cross + c-compiler: aarch64-linux-gnu-gcc + cc-compiler: aarch64-linux-gnu-g++ + debug: nodebug + coverage: nocoverage + linking: dynamic + shell: bash + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + shell: bash + if: ${{ github.event_name == 'pull_request' }} + + - name: Setup MSYS2 + if: ${{ matrix.os-type == 'windows' }} + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.msys-env }} + update: true + install: >- + autotools + base-devel + + - name: Install MinGW64 packages + if: ${{ matrix.os-type == 'windows' && matrix.msys-env == 'MINGW64' }} + run: | + pacman --noconfirm -S --needed mingw-w64-x86_64-{toolchain,libtool,make,pkg-config,libsystre,doxygen,gnutls,graphviz,curl} + + - name: Install MSYS packages + if: ${{ matrix.os-type == 'windows' && matrix.msys-env == 'MSYS' }} + run: | + pacman --noconfirm -S --needed msys2-devel gcc make libcurl-devel libgnutls-devel + + - name: Install Ubuntu test sources + run: | + sudo add-apt-repository ppa:ubuntu-toolchain-r/test ; + sudo apt-get update ; + if: ${{ matrix.os-type == 'ubuntu' }} + + - name: Install apache benchmark if needed + run: sudo apt-get install apache2-utils ; + if: ${{ matrix.test-group == 'performance' && matrix.os-type == 'ubuntu' }} + + - name: Install optional clang if needed + run: sudo apt-get install ${{ matrix.c-compiler }} + if: ${{ matrix.compiler-family == 'clang' && matrix.os-type == 'ubuntu' }} + + - name: Install optional gcc if needed + run: sudo apt-get install ${{ matrix.cc-compiler }} + if: ${{ matrix.compiler-family == 'gcc' && matrix.os-type == 'ubuntu' }} + + - name: Install ARM cross-compilation toolchain + run: | + sudo apt-get update + if [ "${{ matrix.build-type }}" = "arm32" ]; then + sudo apt-get install -y gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf + elif [ "${{ matrix.build-type }}" = "arm64" ]; then + sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + fi + if: ${{ matrix.compiler-family == 'arm-cross' }} + + - name: Install valgrind if needed + run: sudo apt-get install valgrind + if: ${{ matrix.build-type == 'valgrind' && matrix.os-type == 'ubuntu' }} + + - name: Install cpplint if needed + run: sudo pip3 install cpplint ; + if: ${{ matrix.build-type == 'lint' && matrix.os-type == 'ubuntu' }} + + - name: Install IWYU dependencies if needed + run: | + sudo apt-get install llvm-18-dev libclang-18-dev clang-18 ; + if: ${{ matrix.build-type == 'iwyu' && matrix.os-type == 'ubuntu' }} + + - name: IWYU from cache (for testing) + id: cache-IWYU + uses: actions/cache@v4 + with: + path: include-what-you-use + key: ${{ matrix.os }}-${{ matrix.c-compiler }}-include-what-you-use-pre-built + if: ${{ matrix.build-type == 'iwyu' && matrix.os-type == 'ubuntu' }} + + # Installing iwyu manually because clang and iwyu paths won't match on Ubuntu otherwise. + - name: Build IWYU if requested + run: | + CLANG_ROOT_PATH=`llvm-config-18 --prefix` ; + CLANG_BIN_PATH=`llvm-config-18 --bindir` ; + git clone https://github.com/include-what-you-use/include-what-you-use.git ; + cd include-what-you-use ; + git checkout clang_18 ; + mkdir build_iwyu ; + cd build_iwyu ; + cmake -G "Unix Makefiles" -DCMAKE_PREFIX_PATH=$CLANG_ROOT_PATH -DCMAKE_C_COMPILER=$CLANG_BIN_PATH/clang -DCMAKE_CXX_COMPILER=$CLANG_BIN_PATH/clang++ ../ ; + make ; + sudo make install ; + if: ${{ matrix.build-type == 'iwyu' && matrix.os-type == 'ubuntu' && steps.cache-IWYU.outputs.cache-hit != 'true' }} + + - name: Install IWYU if requested + run: | + cd include-what-you-use/build_iwyu ; + sudo make install ; + if: ${{ matrix.build-type == 'iwyu' && matrix.os-type == 'ubuntu' }} + + - name: CURL from cache (for testing) + id: cache-CURL + uses: actions/cache@v4 + with: + path: curl-7.75.0 + key: ${{ matrix.os }}-CURL-pre-built-v2 + if: ${{ matrix.os == 'macos-latest' }} + + - name: Build CURL (for testing) + run: | + curl https://libhttpserver.s3.amazonaws.com/travis_stuff/curl-7.75.0.tar.gz -o curl-7.75.0.tar.gz ; + tar -xzf curl-7.75.0.tar.gz ; + cd curl-7.75.0 ; + ./configure --with-darwinssl --without-ssl ; + make ; + if: ${{ matrix.os == 'macos-latest' && steps.cache-CURL.outputs.cache-hit != 'true' }} + + - name: Install CURL (for testing on mac only) + run: cd curl-7.75.0 ; sudo make install ; + if: ${{ matrix.os == 'macos-latest' }} + + - name: Install CURL (for testing on linux) + run: sudo apt-get install libcurl4-openssl-dev + if: ${{ matrix.os-type == 'ubuntu' }} + + - name: Install autotools on mac + run: brew install autoconf automake libtool + if: ${{ matrix.os == 'macos-latest' }} + + - name: Setup dependencies (only on linux) + run: | + sudo apt-get install info install-info ; + sudo apt-get install cppcheck ; + if: ${{ matrix.os-type == 'ubuntu' }} + + - name: Setup coverage dependencies (only on linux) + run: | + sudo pip install gcovr ; + if: ${{ matrix.os-type == 'ubuntu' && matrix.c-compiler == 'gcc' && matrix.debug == 'debug' && matrix.coverage == 'coverage' }} + + - name: Setup gnutls dependency (only on linux) + run: | + sudo apt-get install libgnutls28-dev gnutls-bin ; + if: ${{ matrix.os-type == 'ubuntu' }} + + - name: Fetch libmicrohttpd from cache + id: cache-libmicrohttpd + uses: actions/cache@v4 + with: + path: libmicrohttpd-0.9.77 + key: ${{ matrix.os }}-${{ matrix.c-compiler }}-libmicrohttpd-0.9.77-pre-built-v2 + if: ${{ matrix.os-type != 'windows' && matrix.build-type != 'no-dauth' && matrix.compiler-family != 'arm-cross' }} + + - name: Build libmicrohttpd dependency (if not cached) + run: | + curl https://s3.amazonaws.com/libhttpserver/libmicrohttpd_releases/libmicrohttpd-0.9.77.tar.gz -o libmicrohttpd-0.9.77.tar.gz ; + tar -xzf libmicrohttpd-0.9.77.tar.gz ; + cd libmicrohttpd-0.9.77 ; + ./configure --disable-examples ; + make ; + if: ${{ matrix.os-type != 'windows' && matrix.build-type != 'no-dauth' && matrix.compiler-family != 'arm-cross' && steps.cache-libmicrohttpd.outputs.cache-hit != 'true' }} + + - name: Build libmicrohttpd without digest auth (no-dauth test) + run: | + curl https://s3.amazonaws.com/libhttpserver/libmicrohttpd_releases/libmicrohttpd-0.9.77.tar.gz -o libmicrohttpd-0.9.77.tar.gz ; + tar -xzf libmicrohttpd-0.9.77.tar.gz ; + cd libmicrohttpd-0.9.77 ; + ./configure --disable-examples --disable-dauth ; + make ; + if: ${{ matrix.build-type == 'no-dauth' }} + + - name: Install libmicrohttpd + run: cd libmicrohttpd-0.9.77 ; sudo make install ; + if: ${{ matrix.os-type != 'windows' && matrix.compiler-family != 'arm-cross' }} + + - name: Verify digest auth is disabled (no-dauth test) + run: | + # Verify that MHD_queue_auth_fail_response is NOT present in libmicrohttpd + if nm /usr/local/lib/libmicrohttpd.so 2>/dev/null | grep -q MHD_queue_auth_fail_response; then + echo "ERROR: libmicrohttpd was built WITH digest auth support" ; + exit 1 ; + fi + echo "Verified: libmicrohttpd built without digest auth support" ; + if: ${{ matrix.build-type == 'no-dauth' }} + + - name: Build and install libmicrohttpd (Windows) + if: ${{ matrix.os-type == 'windows' }} + run: | + curl https://s3.amazonaws.com/libhttpserver/libmicrohttpd_releases/libmicrohttpd-0.9.77.tar.gz -o libmicrohttpd-0.9.77.tar.gz + tar -xzf libmicrohttpd-0.9.77.tar.gz + cd libmicrohttpd-0.9.77 + ./configure --disable-examples --enable-poll=no + make + make install + + - name: Fetch libmicrohttpd from cache (ARM cross-compile) + id: cache-libmicrohttpd-arm + uses: actions/cache@v4 + with: + path: libmicrohttpd-0.9.77-${{ matrix.build-type }} + key: ${{ matrix.os }}-${{ matrix.build-type }}-libmicrohttpd-0.9.77-cross-compiled + if: ${{ matrix.compiler-family == 'arm-cross' }} + + - name: Cross-compile libmicrohttpd for ARM + run: | + curl https://s3.amazonaws.com/libhttpserver/libmicrohttpd_releases/libmicrohttpd-0.9.77.tar.gz -o libmicrohttpd-0.9.77.tar.gz + tar -xzf libmicrohttpd-0.9.77.tar.gz + mv libmicrohttpd-0.9.77 libmicrohttpd-0.9.77-${{ matrix.build-type }} + cd libmicrohttpd-0.9.77-${{ matrix.build-type }} + mkdir -p ${{ github.workspace }}/arm-sysroot + if [ "${{ matrix.build-type }}" = "arm32" ]; then + ./configure --host=arm-linux-gnueabihf --prefix=${{ github.workspace }}/arm-sysroot --disable-examples --disable-doc + else + ./configure --host=aarch64-linux-gnu --prefix=${{ github.workspace }}/arm-sysroot --disable-examples --disable-doc + fi + make + make install + if: ${{ matrix.compiler-family == 'arm-cross' && steps.cache-libmicrohttpd-arm.outputs.cache-hit != 'true' }} + + - name: Install cross-compiled libmicrohttpd from cache + run: | + cd libmicrohttpd-0.9.77-${{ matrix.build-type }} + mkdir -p ${{ github.workspace }}/arm-sysroot + make install + if: ${{ matrix.compiler-family == 'arm-cross' && steps.cache-libmicrohttpd-arm.outputs.cache-hit == 'true' }} + + - name: Refresh links to shared libs + run: sudo ldconfig ; + if: ${{ matrix.os-type == 'ubuntu' }} + + - name: Run cpplint on code + run: cpplint --extensions=cpp,hpp --headers=hpp --recursive . ; + if: ${{ matrix.build-type == 'lint' && matrix.os-type == 'ubuntu' }} + + - name: Run libhttpserver configure + run: | + # Set memory check flags. They need to stay in step as env variables don't propagate across steps. + if [ "$BUILD_TYPE" = "asan" ]; then export CFLAGS='-fsanitize=address'; export CXXLAGS='-fsanitize=address'; export LDFLAGS='-fsanitize=address'; fi + if [ "$BUILD_TYPE" = "msan" ]; then export CFLAGS='-fsanitize=memory'; export CXXLAGS='-fsanitize=memory'; export LDFLAGS='-fsanitize=memory'; fi + if [ "$BUILD_TYPE" = "lsan" ]; then export CFLAGS='-fsanitize=leak'; export CXXLAGS='-fsanitize=leak'; export LDFLAGS='-fsanitize=leak'; fi + if [ "$BUILD_TYPE" = "tsan" ]; then export CFLAGS='-fsanitize=thread'; export CXXLAGS='-fsanitize=thread'; export LDFLAGS='-fsanitize=thread'; fi + if [ "$BUILD_TYPE" = "ubsan" ]; then export export CFLAGS='-fsanitize=undefined'; export CXXLAGS='-fsanitize=undefined'; export LDFLAGS='-fsanitize=undefined'; fi + + # Additional flags on mac. They need to stay in step as env variables don't propagate across steps. + if [ "${{ matrix.os }}" = "macos-latest" ]; then + export CFLAGS='-mtune=generic' ; + export IPV6_TESTS_ENABLED="true" ; + fi + + ./bootstrap ; + mkdir build ; + cd build ; + if [ "${{ matrix.compiler-family }}" = "arm-cross" ]; then + export CPPFLAGS="-I${{ github.workspace }}/arm-sysroot/include" + export LDFLAGS="-L${{ github.workspace }}/arm-sysroot/lib" + export PKG_CONFIG_PATH="${{ github.workspace }}/arm-sysroot/lib/pkgconfig" + if [ "${{ matrix.build-type }}" = "arm32" ]; then + ../configure --host=arm-linux-gnueabihf --disable-fastopen; + else + ../configure --host=aarch64-linux-gnu --disable-fastopen; + fi + elif [ "$LINKING" = "static" ]; then + ../configure --enable-static --disable-fastopen; + elif [ "$DEBUG" = "debug" ] && [ "$COVERAGE" = "coverage" ]; then + ../configure --enable-debug --enable-coverage --disable-shared --disable-fastopen; + elif [ "$DEBUG" = "debug" ]; then + ../configure --enable-debug --disable-shared --disable-fastopen; + elif [ "$BUILD_TYPE" = "valgrind" ]; then + ../configure --enable-debug --disable-fastopen --disable-valgrind-helgrind --disable-valgrind-drd --disable-valgrind-sgcheck; + elif [ "$BUILD_TYPE" = "iwyu" ]; then + ../configure --disable-examples; + else + ../configure --disable-fastopen; + fi + + - name: Verify libhttpserver detected no digest auth (no-dauth test) + run: | + cd build ; + if grep -q "Digest Auth.*:.*no" config.log; then + echo "Verified: libhttpserver correctly detected digest auth is disabled" ; + else + echo "ERROR: libhttpserver did not detect that digest auth is disabled" ; + grep "Digest Auth" config.log || echo "Digest Auth line not found" ; + exit 1 ; + fi + if: ${{ matrix.build-type == 'no-dauth' }} + + - name: Print config.log + shell: bash + run: | + cd build ; + cat config.log ; + if: ${{ failure() }} + + # Make or run iwyu. If running iwyu, check for the result code to be 2 (IWYU always returns an error code, if it is 2, no corrections are necessary). + - name: Make or run IWYU + run: | + # IWYU always return an error code. If it returns "2" it indicates a success so we manage this within the function below. + function safe_make_iwyu() { + { + make -k CXX='/usr/local/bin/include-what-you-use -Xiwyu --mapping_file=${top_builddir}/../custom_iwyu.imp' CXXFLAGS="-std=c++11 -DHTTPSERVER_COMPILATION -D_REENTRANT $CXXFLAGS" ; + } || { + if [ $? -ne 2 ]; then + return 1; + else + return 0; + fi + } + + return 0; + } + + cd build ; + if [ "$BUILD_TYPE" = "iwyu" ]; then + safe_make_iwyu ; + else + make ; + fi + + - name: Run tests + run: | + cd build ; + make check; + if: ${{ matrix.build-type != 'iwyu' && matrix.compiler-family != 'arm-cross' }} + + - name: Print tests results + shell: bash + run: | + cd build ; + cat test/test-suite.log ; + if: ${{ failure() && matrix.build-type != 'iwyu' && matrix.compiler-family != 'arm-cross' }} + + - name: Run Valgrind checks + run: | + cd build ; + make check-valgrind ; + if: ${{ matrix.build-type == 'valgrind' }} + + - name: Print Valgrind memcheck results + shell: bash + run: | + cd build ; + if [ -f test/test-suite-memcheck.log ]; then + cat test/test-suite-memcheck.log ; + fi + if: ${{ always() && matrix.build-type == 'valgrind' }} + + - name: Run cppcheck + run: | + cd src/ ; + cppcheck --error-exitcode=1 . ; + if: ${{ matrix.os-type == 'ubuntu' && matrix.compiler-family != 'arm-cross' }} + + - name: Run performance tests (select) + run: | + cd build + cd examples + ./benchmark_select 8080 $(nproc) & + sleep 5 && ab -n 1000000 -c 100 127.0.0.1:8080/plaintext + if: ${{ matrix.build-type == 'select' }} + + - name: Run performance tests (nodelay) + run: | + cd build + cd examples + ./benchmark_nodelay 8080 $(nproc) & + sleep 5 && ab -n 1000000 -c 100 127.0.0.1:8080/plaintext + if: ${{ matrix.build-type == 'nodelay' }} + + - name: Run performance tests (threads) + run: | + cd build + cd examples + ./benchmark_threads 8080 & + sleep 5 && ab -n 1000000 -c 100 127.0.0.1:8080/plaintext + if: ${{ matrix.build-type == 'threads' }} + + - name: Generate coverage report + run: | + cd build + gcovr --root .. --filter '../src/' --xml coverage.xml --xml-pretty --print-summary --gcov-ignore-parse-errors=negative_hits.warn_once_per_file + if: ${{ matrix.os-type == 'ubuntu' && matrix.c-compiler == 'gcc' && matrix.debug == 'debug' && matrix.coverage == 'coverage' && success() }} + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: build/coverage.xml + fail_ci_if_error: false + if: ${{ matrix.os-type == 'ubuntu' && matrix.c-compiler == 'gcc' && matrix.debug == 'debug' && matrix.coverage == 'coverage' && success() }} diff --git a/.gitignore b/.gitignore index 72c98a64..addf8862 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ debian/rules redhat/libhttpserver.SPEC libhttpserver.pc libtool +.worktrees +.claude +CLAUDE.md diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ba2f2005..00000000 --- a/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ -language: cpp -compiler: - - gcc - - clang -env: - - DEBUG="debug" - - DEBUG="nodebug" -before_install: - - sudo apt-get install texinfo - - sudo pip install cpp-coveralls - - sudo pip install gcovr - - sudo apt-get install libjson-perl - - sudo apt-get install libjson-xs-perl - - sudo apt-get install libfile-slurp-perl - - wget ftp://ftp.gnu.org/gnu/libmicrohttpd/libmicrohttpd-0.9.37.tar.gz - - tar -xvzf libmicrohttpd-0.9.37.tar.gz - - cd libmicrohttpd-0.9.37 - - ./configure --prefix=/usr - - make - - sudo make install - - cd .. -script: - - ./bootstrap - - mkdir build - - cd build - - if [ $DEBUG = "debug" ]; then ../configure --enable-debug --disable-shared; else ../configure; fi - - make - - make check -after_success: - - if [ $DEBUG = "debug" ]; then ../ci-report-coverage; fi -matrix: - exclude: - - compiler: clang - env: DEBUG="debug" diff --git a/AUTHORS b/AUTHORS index 8bfdb858..ff66cfa2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -9,12 +9,48 @@ Craig Minihan Guo Xiao Philipp Claßen Vitaut Bajaryn +Felipe Zipitría +Steven 'Steve' Kendall +G. Mercat +Thomas Schätzlein +Matt van Niekerk +Jim Phillips (https://github.com/jcphill) +Ilya Brin (https://github.com/ilyabrin) - Support for building on MinGW/Cygwin systems Shane Peelar +Dean M. Sands, III - Support for building on MaxOsX Jan Klimke - Example of SSL usage and operator<< on http_request and http_response Chris Love + +- Added proper error handling to tcp socket creation and binding +Marcel Pursche + +- Fixed error management and regex handling +Julian Picht + +- Fix string termination when loading files in memory +martamoreton (Github: https://github.com/martamoreton) + +- Memory leaks +rdiazmartin + +- Cleanup of multiple parts of the code. Improved testing. Fixed issues with family urls. +bcsgh (Github: https://github.com/bcsgh) + +- Management of large uploads +Walter Landry +Jagat + +- Simplification of microhttpd dependency management +Christian Grothoff + +- Fixes to the behavior of "Method Not Allowed" and other cleanups. Fixed the behavior of the server when receiving invalid responses from libmicrohttpd. Fixed the file_response to handle not existing files and directories. +Alexander Dahl + +- General cleanup. Use of string_view. Support for handling of multiple arguments with the same name. +stuart-byma (https://github.com/stuart-byma) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..565a76c1 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at merlino.sebastiano@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..d923f747 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,245 @@ +# Contributing to libhttpserver + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: + +The following is a set of guidelines for contributing to libhttpserver. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. + +#### Table Of Contents + +[Code of Conduct](#code-of-conduct) + +[I don't want to read this whole thing, I just have a question!!!](#i-dont-want-to-read-this-whole-thing-i-just-have-a-question) + +[How Can I Contribute?](#how-can-i-contribute) + * [Reporting Bugs](#reporting-bugs) + * [Suggesting Enhancements](#suggesting-enhancements) + * [Your First Code Contribution](#your-first-code-contribution) + * [Pull Requests](#pull-requests) + +[Styleguides](#styleguides) + * [Git Commit Messages](#git-commit-messages) + * [Documentation Styleguide](#documentation-styleguide) + +[Additional Notes](#additional-notes) + * [Issue and Pull Request Labels](#issue-and-pull-request-labels) + +## Code of Conduct + +This project and everyone participating in it is governed by the [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [merlino.sebastiano@gmail.com](mailto:merlino.sebastiano@gmail.com). + +## I don't want to read this whole thing I just have a question!!! + +> **Note:** Please don't file an issue to ask a question. You'll get faster results by using the resources below. + +We have an official community board where the community chimes in with helpful advice if you have questions. + +* [libhttpserver on Gitter](https://gitter.im/libhttpserver/community) + +## How Can I Contribute? + +### Reporting Bugs + +This section guides you through submitting a bug report for libhttpserver. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:. + +Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](https://github.com/etr/libhttpserver/blob/master/.github/ISSUE_TEMPLATE/bug_report.md), the information it asks for helps us resolve issues faster. + +> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. + +#### Before Submitting A Bug Report + +* **Try to debug the problem** You might be able to find the cause of the problem and fix things yourself. Most importantly, check if you can reproduce the problem in the latest version of libhttpserver (head on github). +* **Perform a [cursory search](https://github.com/search?l=&q=repo%3Aetr%2Flibhttpserver&type=Issues)** to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one. + +#### How Do I Submit A (Good) Bug Report? + +Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you followed the steps above, create an issue and provide the following information by filling in [the template](https://github.com/etr/libhttpserver/blob/master/.github/ISSUE_TEMPLATE/bug_report.md). + +Explain the problem and include additional details to help maintainers reproduce the problem: + +* **Use a clear and descriptive title** for the issue to identify the problem. +* **Describe the exact steps which reproduce the problem** in as many details as possible. When listing steps, **don't just say what you did, but explain how you did it**. +* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). +* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. +* **Explain which behavior you expected instead and why.** +* **If you're reporting a crash**, include a crash report with a stack trace from the operating system. Include these in the issue in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines), a [file attachment](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests/), or put it in a [gist](https://gist.github.com/) and provide link to that gist. +* **Consider attaching a simple snipped reproducing the problem. ** +* **If the problem is related to performance or memory**, include a CPU profile capture with your report. + +Provide more context by answering these questions: + +* **Did the problem start happening recently** (e.g. after updating to a new version of libhttpserver) or was this always a problem? +* If the problem started happening recently, **can you reproduce the problem in an older version of libhttpserver?** What's the most recent version in which the problem doesn't happen? You can download older versions of libhttpserver from [the releases page](https://github.com/etr/libhttpserver/releases). +* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. + +Include details about your configuration and environment: + +* **Which version of libhttpserver are you using?** +* **What's the name and version of the OS you're using (e.g. "uname -a" on linux) **? +* **What's the version of libmicrohttpd that you have installed? ** +* **Have you installed the libraries (both libhttpserver and libmicrohttpd) manually or through package manager? ** +* **Which options did you use when compiling? ** +* **What compiler version and version of autotools did you use? ** + +### Feature Requests and Enhancements + +This section guides you through submitting an enhancement suggestion for libhttpserver, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions. + +Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in [the template](https://github.com/etr/libhttpserver/blob/master/.github/ISSUE_TEMPLATE/feature_request.md). + +#### Before Submitting An Enhancement Suggestion or a Feature Request + +* **Perform a [cursory search](https://github.com/search?l=&q=repo%3Aetr%2Flibhttpserver&type=Issues)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. + +#### How Do I Submit A (Good) Feature Request / Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue on that repository and provide the following information: + +* **Use a clear and descriptive title** for the issue to identify the suggestion. +* **Provide a step-by-step description of the suggested enhancement** in as many details as possible. +* **Provide a specific example to demonstrate the new feature**. +* **Describe the current behavior** and **explain which behavior you expected instead** and why. +* **Describe which alternatives you have considered**. +* **Explain why this enhancement would be useful** to most users and **why it fits the mission of the library**. + +### Your First Code Contribution + +Unsure where to begin contributing to libhttpserver? You can start by looking through these `beginner` and `help-wanted` issues: + +* [Beginner issues][beginner] - issues which should only require a few lines of code, and a test or two. +* [Help wanted issues][help-wanted] - issues which should be a bit more involved than `beginner` issues. + +Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for impact a given change will have. + +### Pull Requests + +The process described here has several goals: + +- Maintain libhttpserver's quality +- Fix problems that are important to users +- Engage the community in working toward the best possible solution +- Enable a sustainable system for maintainers to review contributions + +Please follow these steps to have your contribution considered by the maintainers: + +1. Follow all instructions in [the template](https://github.com/etr/libhttpserver/blob/master/PULL_REQUEST_TEMPLATE.md) +2. Follow the [styleguides](#styleguides) +3. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing
What if the status checks are failing?If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on the pull request explaining why you believe the failure is unrelated. A maintainer will re-run the status check for you. If we conclude that the failure was a false positive, then we will open an issue to track that problem with our status check suite.
+ +While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted. + +### ChangeLog + +All pull requests that include user-facing changes must update the `ChangeLog` file. CI enforces this requirement (with exemptions for documentation-only and CI-only changes). + +* Add your entry under the **first** `Version X.Y.Z` header at the top of `ChangeLog`. +* Use a tab-indented, one-line summary of each change: + ``` + Added support for new feature X. + ``` +* If your change spans multiple lines, indent continuation lines with two tabs: + ``` + Fixed a bug where long descriptions would cause the parser to + fail on multi-line input. + ``` +* Changes that only touch files in `.github/`, `*.md` in the repository root, `.gitignore`, or `CPPLINT.cfg` are exempt from the ChangeLog requirement. + +## Styleguides + +### Git Commit Messages + +* Limit the first line to 80 characters or less. +* Add a concise description of what your change does. +* Reference issues and pull requests liberally after the first line. + +### Documentation Styleguide + +* Use [Markdown](https://daringfireball.net/projects/markdown). + +## Additional Notes + +### Issue and Pull Request Labels + +This section lists the labels we use to help us track and manage issues and pull requests. + +[GitHub search](https://help.github.com/articles/searching-issues/) makes it easy to use labels for finding groups of issues or pull requests you're interested in. We encourage you to read about [other search filters](https://help.github.com/articles/searching-issues/) which will help you write more focused queries. + +The labels are loosely grouped by their purpose, but it's not required that every issue have a label from every group or that an issue can't have more than one label from the same group. + +Please open an issue on `etr/libhttpserver` if you have suggestions for new labels. + +#### Type of Issue and Issue State + +| Label name | `etr/libhttpserver` :mag_right: | Description | +| --- | --- | --- | +| `feature-request` | [search][search-libhttpserver-repo-label-feature-request] | Feature requests or enhancements. | +| `bug` | [search][search-libhttpserver-repo-label-bug] | Confirmed bugs or reports that are very likely to be bugs. | +| `question` | [search][search-libhttpserver-repo-label-question] | Questions more than bug reports or feature requests (e.g. how do I do X). | +| `feedback` | [search][search-libhttpserver-repo-label-feedback] | General feedback more than bug reports or feature requests. | +| `help-wanted` | [search][search-libhttpserver-repo-label-help-wanted] | The maintainer would appreciate help from the community in resolving these issues. | +| `beginner` | [search][search-libhttpserver-repo-label-beginner] | Less complex issues which would be good first issues to work on for users who want to contribute to libhttpserver. | +| `more-information-needed` | [search][search-libhttpserver-repo-label-more-information-needed] | More information needs to be collected about these problems or feature requests (e.g. steps to reproduce). | +| `needs-reproduction` | [search][search-libhttpserver-repo-label-needs-reproduction] | Likely bugs, but haven't been reliably reproduced. | +| `blocked` | [search][search-libhttpserver-repo-label-blocked] | Issues blocked on other issues. | +| `duplicate` | [search][search-libhttpserver-repo-label-duplicate] | Issues which are duplicates of other issues, i.e. they have been reported before. | +| `wontfix` | [search][search-libhttpserver-repo-label-wontfix] | The maintainers have decided not to fix these issues for now, either because they're working as intended or for some other reason. | +| `invalid` | [search][search-libhttpserver-repo-label-invalid] | Issues which aren't valid (e.g. user errors). | + +#### Topic Categories + +| Label name | `etr/libhttpserver` :mag_right: | Description | +| --- | --- | --- | +| `windows` | [search][search-libhttpserver-repo-label-windows] | Related to Windows. | +| `linux` | [search][search-libhttpserver-repo-label-linux] | Related to Linux. | +| `mac` | [search][search-libhttpserver-repo-label-mac] | Related to macOS. | +| `travis` | [search][search-libhttpserver-repo-label-travis] | Related to travis and CI in general. | +| `tests` | [search][search-libhttpserver-repo-label-tests] | Related to tests (add tests, fix tests, etc...). | +| `documentation` | [search][search-libhttpserver-repo-label-documentation] | Related to any type of documentation. | +| `performance` | [search][search-libhttpserver-repo-label-performance] | Related to performance. | +| `security` | [search][search-libhttpserver-repo-label-security] | Related to security. | +| `api` | [search][search-libhttpserver-repo-label-api] | Related to libhttpserver's public APIs. | +| `git` | [search][search-libhttpserver-repo-label-git] | Related to Git functionality (e.g. problems with gitignore files or with showing the correct file status). | + +#### Pull Request Labels + +| Label name | `etr/libhttpserver` :mag_right: | Description | +| --- | --- | --- | +| `work-in-progress` | [search][search-libhttpserver-repo-label-work-in-progress] | Pull requests which are still being worked on, more changes will follow. | +| `needs-review` | [search][search-libhttpserver-repo-label-needs-review] | Pull requests which need code review, and approval from maintainers. | +| `under-review` | [search][search-libhttpserver-repo-label-under-review] | Pull requests being reviewed by maintainers. | +| `requires-changes` | [search][search-libhttpserver-repo-label-requires-changes] | Pull requests which need to be updated based on review comments and then reviewed again. | +| `needs-testing` | [search][search-libhttpserver-repo-label-needs-testing] | Pull requests which need manual testing. | + +[search-libhttpserver-repo-label-feature-request]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Afeature-request +[search-libhttpserver-repo-label-bug]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Abug +[search-libhttpserver-repo-label-question]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Aquestion +[search-libhttpserver-repo-label-feedback]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Afeedback +[search-libhttpserver-repo-label-help-wanted]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Ahelp-wanted +[search-libhttpserver-repo-label-beginner]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Abeginner +[search-libhttpserver-repo-label-more-information-needed]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Amore-information-needed +[search-libhttpserver-repo-label-needs-reproduction]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Aneeds-reproduction +[search-libhttpserver-repo-label-triage-help-needed]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Atriage-help-needed +[search-libhttpserver-repo-label-windows]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Awindows +[search-libhttpserver-repo-label-linux]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Alinux +[search-libhttpserver-repo-label-mac]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Amac +[search-libhttpserver-repo-label-travis]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Atravis +[search-libhttpserver-repo-label-tests]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Atests +[search-libhttpserver-repo-label-documentation]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Adocumentation +[search-libhttpserver-repo-label-performance]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Aperformance +[search-libhttpserver-repo-label-security]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Asecurity +[search-libhttpserver-repo-label-api]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Aapi +[search-libhttpserver-repo-label-git]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Agit +[search-libhttpserver-repo-label-blocked]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Ablocked +[search-libhttpserver-repo-label-duplicate]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Aduplicate +[search-libhttpserver-repo-label-wontfix]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Awontfix +[search-libhttpserver-repo-label-invalid]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Ainvalid +[search-libhttpserver-repo-label-build-error]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Abuild-error +[search-libhttpserver-repo-label-installer]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Ainstaller +[search-libhttpserver-repo-label-deprecation-help]: https://github.com/search?q=is%3Aopen+is%3Aissue+repo%3Aetr%23Flibhttpserver+label%3Adeprecation-help +[search-libhttpserver-repo-label-work-in-progress]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3Aetr%23Flibhttpserver+label%3Awork-in-progress +[search-libhttpserver-repo-label-needs-review]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3Aetr%23Flibhttpserver+label%3Aneeds-review +[search-libhttpserver-repo-label-under-review]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3Aetr%23Flibhttpserver+label%3Aunder-review +[search-libhttpserver-repo-label-requires-changes]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3Aetr%23Flibhttpserver+label%3Arequires-changes +[search-libhttpserver-repo-label-needs-testing]: https://github.com/search?q=is%3Aopen+is%3Apr+repo%3Aetr%23Flibhttpserver+label%3Aneeds-testing + +[beginner]:https://github.com/search?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3Abeginner+label%3Ahelp-wanted+user%3Aetr+sort%3Acomments-desc +[help-wanted]:https://github.com/search?q=is%3Aopen+is%3Aissue+label%3Ahelp-wanted+user%3Aetr+sort%3Acomments-desc+-label%3Abeginner diff --git a/CPPLINT.cfg b/CPPLINT.cfg new file mode 100644 index 00000000..9add2a08 --- /dev/null +++ b/CPPLINT.cfg @@ -0,0 +1,4 @@ +linelength=200 +headers=hpp +extensions=cpp,hpp +filter=-test/littletest.hpp diff --git a/ChangeLog b/ChangeLog index 4bd29c93..6e9532e3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,220 +1,209 @@ -Fri Jul 17 21:38:54 2015 +0000 - Removed build dependency on pkg-config +Version 0.20.0 + + Added example and documentation for serving binary data from memory + using string_response (addresses PR #368). + Added conditional compilation for basic auth (HAVE_BAUTH), mirroring + existing HAVE_DAUTH pattern for digest auth. Basic auth support + is auto-detected via AC_CHECK_LIB and can be disabled at build time. + Fixed path traversal vulnerability in file uploads when + generate_random_filename_on_upload is disabled. + Fixed TOCTOU race in file_response by replacing stat-then-open with + open-then-fstat; added O_NOFOLLOW on non-Windows. + Fixed file descriptor leaks in file_response on lseek failure and + zero-size file paths. + Fixed NULL pointer dereference when MHD_get_connection_info returns + nullptr for TCP_NODELAY. + Fixed uninitialized _file_size in file_info. + Fixed auth skip path bypass via path traversal (e.g. /public/../protected). + Fixed use of free() instead of MHD_free() for digest auth username. + Fixed unchecked write error during file upload. + +Version 0.19.0 - 2023-06-15 + + Considering family_url as part of the priority when selecting a URL to match. + More explicit selection of C++ version. + Ability to handle multiple parameters with the same name on the URL. + Enable error_log to handle params to interpolate in the string. + Reduced memory footprint of requests using of string_view. + Configurable management of upload files that allows to load files on disk only, memory only or both disk and memory. + Fixed error codes. + Method not allowed now returns 'allows' header. + Added method to access the gnutls_session struct. + Code cleanups. + Better use of RAII. + Improved test coverage. + Cleaned code to support cpplint and extra warnings. + Use pointers in place of non-const references. + Simplified dependency management for libmicrohttpd. + Added support on build for CodeQL security checks. + Moved builds to travis.com. + Added IWYU checks as part of build and cleaned-up accordingly. + Introduced dual-stack support. + Added OS specific tips, and cleaned up some compiler warnings. + Updates to readme and documentation. + Slight performances improvement by allowing to skip a copy in + string_response constructor. + Moved windows builds to AppVeyor. + Made the library compatible with libmicrohttpd v0.9.71 and above. + +Version 0.18.1 - 2020-06-06 + + Prevent use of regex in http_endpoint outside of registration which could + allow DOS attacks. + +Version 0.18.0 - 2020-05-16 + + General performance improvements (reduced use of regex, lazy-building of + post-processor). + General code cleanup. + General fixes to the documentation. + Fixed support on FreeBSD (added missing headers). + Fixed support for Cygwin. + Removed depedency on C regex - now using C++11 regex. + Added support for TCP-NODELAY. + Changed set_path on http_request to have lazy behavior. + Added support for body parsing in DELETE requests. + Added support for PATCH method. + +Version 0.17.5 - 2019-01-28 + + libhttpserver now includes set of examples to demonstrate the main capabilities of the library. + "examples" are now optionally disabled. + Adds valgrind memcheck to the build system on travis. + Travis now tests performance with apache benchmark. + Reduced the CPU time spent in normalizing URLs (thus saving ~15% on average per request). + All classes now implement move constructor and move assignment operator. + The library now avoids collecting connection properties (headers, arguments, footers, cookies, etc...) unless explicitly asked by the client code. + Removed the support for integrated COMET logic. + Removed the support for caching logic. + Added integ tests. + Changed http_resource interface to use shared_ptr. + Improved interface of the http_response object. + Deprecated http_response_builder object. + +Version 0.16.0 - 2018-12-26 + + Fixed IPV6 parsing logic. + Added tests to support IP parsing, URL parsing and utilities. + +Version 0.15.0 - 2018-11-23 + + Solved problem with the server not being able to start on mac os. + +Version 0.14.0 - 2018-11-05 + + Moved http_endpoint as a sub-class of webserver. This avoids usage of friends. + +Version 0.13.0 - 2017-02-26 + + Fixed problem with segfault when copying http_response object. + +Version 0.12.0 - 2017-02-12 -Wed Apr 15 01:40:11 2015 +0000 - Support build on MacOsX - Improved support for CI on travis - Solved bug on event_supplier registering - Solved bug on standardize_url to avoid removing root - Change cycle_callback_ptr so that buffer can be modified - Moved to version 0.9.0 + Updated to libmicrohttpd 0.9.52. + +Version 0.11.1 - 2016-07-13 -Sun Jul 23 02:46:20 2014 +0100 - Support for building on MinGW/Cygwin systems - min libmicrohttpd version moved to 0.9.37 - Moved to version 0.8.0 + Fixed problems with large payloads. + Fixed memory leak in http_response_ptr. -Sat Mar 23 15:22:40 2014 +0100 - Continue the cleanup reducing webserver.cpp responsibilities - Deep work on documentation - Moved to version 0.7.2 +Version 0.11.0 - 2015-12-26 -Sat Jan 25 16:31:03 2014 +0100 - Cleaned-up webserver.cpp code to extract secondary classes - Enforced immutability of webserver class - Enabled library to compile on g++ 4.1.2 + Removed support for event supplier (badly defined, complicated and almost useless). + Eliminated custom selection logic (simplified overall code in webserver.cpp). + Changed comet to use a lock-free implementation. + Removed POLL start configuration (THREAD now defaults to POLL or EPOLL on Linux). + Use TCP_FASTOPEN on linux >= 3.6. + Changed http_resource to use classic C++ polymorphism using virtual instead of CRTP. + Removed build dependency on pkg-config. -Wed Oct 31 17:59:40 2012 +0100 - Added parameter in http_response to specify if it needs to be deleted by - WS - Sebastiano Merlino +Version 0.9.0 - 2015-04-15 -Wed Oct 31 14:23:57 2012 +0100 - Changed dependency download method - Sebastiano Merlino + Support build on MacOsX. + Improved support for CI on travis. + Solved bug on event_supplier registering. + Solved bug on standardize_url to avoid removing root. + Change cycle_callback_ptr so that buffer can be modified. -Wed Oct 31 14:13:49 2012 +0100 - Added dependency to travis - Sebastiano Merlino +Version 0.8.0 - 2014-07-23 -Wed Oct 31 14:07:30 2012 +0100 - Changed travis build path - Sebastiano Merlino + Support for building on MinGW/Cygwin systems. + min libmicrohttpd version moved to 0.9.37. -Wed Oct 31 14:02:59 2012 +0100 - Added travis conf to repo - Sebastiano Merlino +Version 0.7.2 - 2014-03-23 -Tue Oct 30 16:13:10 2012 +0100 - Changed the buggy debian changelog - Sebastiano Merlino + Continue the cleanup reducing webserver.cpp responsibilities. + Deep work on documentation. -Tue Oct 30 16:06:26 2012 +0100 - Changed version to v0.5.4 - Sebastiano Merlino +Version 0.7.0 - 2014-01-25 -Tue Oct 30 15:59:45 2012 +0100 - Adjusted debian build rules - Sebastiano Merlino + Cleaned-up webserver.cpp code to extract secondary classes. + Enforced immutability of webserver class. + Enabled library to compile on g++ 4.1.2. -Tue Oct 30 12:52:04 2012 +0100 - Changed version to 0.5.3 - Added grow method to http_request - Sebastiano Merlino +Version 0.5.4 - 2012-10-30 -Tue Oct 23 12:46:48 2012 +0200 - Changed version from 0.5.1 to 0.5.2 - Sebastiano Merlino + Added parameter in http_response to specify if it needs to be deleted by WS. + Changed dependency download method. + Added travis CI configuration. + Changed the buggy debian changelog. -Tue Oct 23 12:46:07 2012 +0200 - Changed default log behaviour to print nothing - Added getters and setters for dynamic components of WS - Sebastiano Merlino +Version 0.5.3 - 2012-10-30 -Mon Oct 22 12:13:11 2012 +0200 - Modified version number and changelog in order to prepare tag - Sebastiano Merlino + Added grow method to http_request. -Fri Oct 19 17:11:21 2012 +0200 - Added response constructor with byte - Sebastiano Merlino +Version 0.5.2 - 2012-10-23 -Mon Oct 15 11:16:22 2012 +0200 - Removed unuseful dependency from libuuid - Sebastiano Merlino + Changed default log behaviour to print nothing. + Added getters and setters for dynamic components of WS. -Fri Oct 12 15:42:21 2012 +0200 - Solved a bug that made impossible to parse post data - Sebastiano Merlino +Version 0.5.1 - 2012-10-10 -Wed Oct 10 17:19:25 2012 +0200 - Moved to version 0.5.1 - Sebastiano Merlino + Added querystring to request attributes. + Added response constructor with byte. + Removed unuseful dependency from libuuid. + Solved a bug that made impossible to parse post data. -Wed Oct 10 17:16:26 2012 +0200 - Added querystring to request attributes - Sebastiano Merlino +Version 0.5.0 - 2012-10-05 -Fri Oct 5 18:00:38 2012 +0200 - Merge branch 'master' of https://github.com/etr/libhttpserver - Conflicts: - src/webserver.cpp - Sebastiano Merlino - -Fri Oct 5 17:55:42 2012 +0200 Added -D_REENTRANT to configuration. Aligned debian changelog. - Added comet capabilities to the server. - Sebastiano Merlino - -Tue Sep 25 00:50:45 2012 +0200 - Solved a bug with print in debug mode - Sebastiano Merlino - -Mon Sep 24 15:29:28 2012 +0200 - Modified webserver in order to accept comet calls - Added ignored patters in gitignore - Sebastiano Merlino - -Sun Sep 23 19:10:28 2012 +0200 - Partially solved undefined symbol in wrappers - Sebastiano Merlino - -Sun Sep 23 19:09:54 2012 +0200 - Avoided the usage of the sole option MHD_USE_POLL - Sebastiano Merlino - -Thu Sep 20 08:47:24 2012 +0200 - Added forgotten modded_request.hpp file - Sebastiano Merlino - -Thu Sep 20 08:46:33 2012 +0200 - Added .gitignore file - Sebastiano Merlino - -Sat Sep 15 13:02:52 2012 +0200 - Moved http_endpoint to details namespace - Sebastiano Merlino - -Sat Sep 15 02:39:47 2012 -0700 - Merge pull request #35 from etr/cflags_for_swig_in_pcfile - add -I${includedir}/httpserver to CFLAGS - Sebastiano Merlino + Added comet capabilities to the server. + Solved a bug with print in debug mode. + Modified webserver in order to accept comet calls. + Partially solved undefined symbol in wrappers. + Avoided the usage of the sole option MHD_USE_POLL. + Added forgotten modded_request.hpp file. + Added .gitignore file. + Moved http_endpoint to details namespace. -Tue Aug 28 16:33:45 2012 +0200 - add -I${includedir}/httpserver to CFLAGS - This make swig file generation easier because HTTPSERVER_CFLAGS can be - directly used in swig file generation. - This fix affect only clients that use swing on their code. - Dario Mazza +Version 0.4.0 - 2012-08-26 -Sun Aug 26 19:03:44 2012 +0200 - Changed version. - Aligned version and dependencies in pc and debian files - Updated debian changelog. - Sebastiano Merlino - -Sun Aug 26 18:55:05 2012 +0200 Changed visibility of http_endpoint methods to avoid them to be called - by external applications. + by external applications. Avoided explicit usage of MHD constants in classes interface. Changed http_resource interface in order to avoid copy-constructor calls - and improve performances. + and improve performances. Changed answer_to_connection method in order to avoid multiple checking - on methods and thus improve performances. - Added a way to register personalized error pages. - Sebastiano Merlino - -Wed Aug 8 17:33:39 2012 +0200 - Removed code repetition in handle_request method - Sebastiano Merlino - -Wed Aug 8 12:31:44 2012 +0200 - Added capability to compile with gcov - Changed infinite loop in ws to use wait conditions - Removed a bug from GET-like method handling - Sebastiano Merlino - -Sun Aug 5 18:26:25 2012 +0200 - Modified in order to parse qs in POST/PUT cases - Sebastiano Merlino - -Fri Aug 3 23:36:14 2012 +0200 - Avoid inclusion of internal headers - Sebastiano Merlino - -Thu Aug 2 00:43:02 2012 +0200 - Changed in order to find libmicrohttpd in system - Sebastiano Merlino - -Thu Jul 26 14:08:47 2012 +0200 - Solved some performance and style issues - Sebastiano Merlino - -Wed Jul 25 18:42:48 2012 +0200 - Merge branch 'master' of github.com:etr/libhttpserver - Sebastiano Merlino - -Wed Jul 25 18:41:45 2012 +0200 - Added some comments to http_endpoint and http_request - Sebastiano Merlino - -Wed Jul 25 08:58:04 2012 -0700 - Merge pull request #29 from etr/libtool_version_number - using m4 to define major,minor and revision number in configure.ac - Sebastiano Merlino - -Wed Jul 25 17:50:05 2012 +0200 - using m4 to define major,minor and revision number in configure.ac and send version number to libtool and AC_INIT - Dario Mazza - -Wed Jul 25 17:10:49 2012 +0200 - Changed in order to solve some problems with deb package and rpm package - Sebastiano Merlino - -Tue Jul 24 16:55:51 2012 -0700 - Merge pull request #28 from etr/debpkg_patch_deps - added parameter used to ignore dependecies during debpkg creation - Sebastiano Merlino - -Wed Jul 25 01:51:52 2012 +0200 - added parameter used to ignore dependecies during debpkg creation - Dario Mazza - -Wed Jul 25 00:42:25 2012 +0200 - Adjusted errors in debian rules - Sebastiano Merlino - -Tue Jul 24 16:37:07 2012 +0200 - Modified rpm build in order to compile it - Lowered required version of libmicrohttpd to 0.9.7 - Sebastiano Merlino - -Tue Jul 24 13:28:38 2012 +0200 - Changed also build default directory for debs - Sebastiano Merlino - -Tue Jul 24 13:22:59 2012 +0200 - Changed rules.in in order to avoid relative paths in deb compile - Sebastiano Merlino - -Mon Jul 23 15:42:33 2012 +0200 - Solved a logical error in http_resource route - Added some debug prints - Sebastiano Merlino - -Sun Jul 22 00:24:04 2012 +0200 - Changed in order to add optional optimizations on ws - Sebastiano Merlino - -Sat Jul 21 17:46:03 2012 +0200 - Changed in order to enhance deb packages generation - Added rpm packages generation - Sebastiano Merlino - -Sat Jul 21 00:43:39 2012 +0200 - adjusted error in changelog - Sebastiano Merlino - -Sat Jul 21 00:41:43 2012 +0200 - Changed in order to include debian package creation to makefile - Sebastiano Merlino - -Fri Jul 20 12:11:30 2012 -0700 - Merge pull request #26 from etr/debpackage - project debianized - Sebastiano Merlino - -Fri Jul 20 21:03:43 2012 +0200 - Merge branch 'master' of github.com:etr/libhttpserver - Sebastiano Merlino - -Fri Jul 20 21:03:24 2012 +0200 - Changed version - Sebastiano Merlino - + on methods and thus improve performances. + Added a way to register personalized error pages. + Removed code repetition in handle_request method. + Added capability to compile with gcov. + Changed infinite loop in ws to use wait conditions. + Removed a bug from GET-like method handling. + Modified in order to parse qs in POST/PUT cases. + Avoid inclusion of internal headers. + Changed in order to find libmicrohttpd in system. + Solved some performance and style issues. + Added some comments to http_endpoint and http_request. + using m4 to define major, minor and revision number in configure.ac. + Changed in order to solve some problems with deb package and rpm package. + Added parameter used to ignore dependecies during debpkg creation. + Adjusted errors in debian rules. + Modified rpm build in order to compile it. + Lowered required version of libmicrohttpd to 0.9.7. + Solved a logical error in http_resource route. + Changed in order to add optional optimizations on ws. + Changed in order to enhance deb packages generation. + Added rpm packages generation. + Changed in order to include debian package creation to makefile. diff --git a/INSTALL b/INSTALL deleted file mode 100644 index 97a25409..00000000 --- a/INSTALL +++ /dev/null @@ -1,367 +0,0 @@ -Installation Instructions -************************* - -Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, -2006, 2007, 2008, 2009 Free Software Foundation, Inc. - - Copying and distribution of this file, with or without modification, -are permitted in any medium without royalty provided the copyright -notice and this notice are preserved. This file is offered as-is, -without warranty of any kind. - -Basic Installation -================== - - Briefly, the shell commands `./configure; make; make install' should -configure, build, and install this package. The following -more-detailed instructions are generic; see the `README' file for -instructions specific to this package. Some packages provide this -`INSTALL' file but do not implement all of the features documented -below. The lack of an optional feature in a given package is not -necessarily a bug. More recommendations for GNU packages can be found -in *note Makefile Conventions: (standards)Makefile Conventions. - - The `configure' shell script attempts to guess correct values for -various system-dependent variables used during compilation. It uses -those values to create a `Makefile' in each directory of the package. -It may also create one or more `.h' files containing system-dependent -definitions. Finally, it creates a shell script `config.status' that -you can run in the future to recreate the current configuration, and a -file `config.log' containing compiler output (useful mainly for -debugging `configure'). - - It can also use an optional file (typically called `config.cache' -and enabled with `--cache-file=config.cache' or simply `-C') that saves -the results of its tests to speed up reconfiguring. Caching is -disabled by default to prevent problems with accidental use of stale -cache files. - - If you need to do unusual things to compile the package, please try -to figure out how `configure' could check whether to do them, and mail -diffs or instructions to the address given in the `README' so they can -be considered for the next release. If you are using the cache, and at -some point `config.cache' contains results you don't want to keep, you -may remove or edit it. - - The file `configure.ac' (or `configure.in') is used to create -`configure' by a program called `autoconf'. You need `configure.ac' if -you want to change it or regenerate `configure' using a newer version -of `autoconf'. - - The simplest way to compile this package is: - - 1. `cd' to the directory containing the package's source code and type - `make -f Makefile.cvs` to create configure file. - - 2. `./configure' to configure the package for your system. - - Running `configure' might take a while. While running, it prints - some messages telling which features it is checking for. - - 3. Type `make' to compile the package. - - 4. Optionally, type `make check' to run any self-tests that come with - the package, generally using the just-built uninstalled binaries. - - 5. Type `make install' to install the programs and any data files and - documentation. When installing into a prefix owned by root, it is - recommended that the package be configured and built as a regular - user, and only the `make install' phase executed with root - privileges. - - 6. Optionally, type `make installcheck' to repeat any self-tests, but - this time using the binaries in their final installed location. - This target does not install anything. Running this target as a - regular user, particularly if the prior `make install' required - root privileges, verifies that the installation completed - correctly. - - 7. You can remove the program binaries and object files from the - source code directory by typing `make clean'. To also remove the - files that `configure' created (so you can compile the package for - a different kind of computer), type `make distclean'. There is - also a `make maintainer-clean' target, but that is intended mainly - for the package's developers. If you use it, you may have to get - all sorts of other programs in order to regenerate files that came - with the distribution. - - 8. Often, you can also type `make uninstall' to remove the installed - files again. In practice, not all packages have tested that - uninstallation works correctly, even though it is required by the - GNU Coding Standards. - - 9. Some packages, particularly those that use Automake, provide `make - distcheck', which can by used by developers to test that all other - targets like `make install' and `make uninstall' work correctly. - This target is generally not run by end users. - -Compilers and Options -===================== - - Some systems require unusual options for compilation or linking that -the `configure' script does not know about. Run `./configure --help' -for details on some of the pertinent environment variables. - - You can give `configure' initial values for configuration parameters -by setting variables in the command line or in the environment. Here -is an example: - - ./configure CC=c99 CFLAGS=-g LIBS=-lposix - - *Note Defining Variables::, for more details. - -Compiling For Multiple Architectures -==================================== - - You can compile the package for more than one kind of computer at the -same time, by placing the object files for each architecture in their -own directory. To do this, you can use GNU `make'. `cd' to the -directory where you want the object files and executables to go and run -the `configure' script. `configure' automatically checks for the -source code in the directory that `configure' is in and in `..'. This -is known as a "VPATH" build. - - With a non-GNU `make', it is safer to compile the package for one -architecture at a time in the source code directory. After you have -installed the package for one architecture, use `make distclean' before -reconfiguring for another architecture. - - On MacOS X 10.5 and later systems, you can create libraries and -executables that work on multiple system types--known as "fat" or -"universal" binaries--by specifying multiple `-arch' options to the -compiler but only a single `-arch' option to the preprocessor. Like -this: - - ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ - CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ - CPP="gcc -E" CXXCPP="g++ -E" - - This is not guaranteed to produce working output in all cases, you -may have to build one architecture at a time and combine the results -using the `lipo' tool if you have problems. - -Installation Names -================== - - By default, `make install' installs the package's commands under -`/usr/local/bin', include files under `/usr/local/include', etc. You -can specify an installation prefix other than `/usr/local' by giving -`configure' the option `--prefix=PREFIX', where PREFIX must be an -absolute file name. - - You can specify separate installation prefixes for -architecture-specific files and architecture-independent files. If you -pass the option `--exec-prefix=PREFIX' to `configure', the package uses -PREFIX as the prefix for installing programs and libraries. -Documentation and other data files still use the regular prefix. - - In addition, if you use an unusual directory layout you can give -options like `--bindir=DIR' to specify different values for particular -kinds of files. Run `configure --help' for a list of the directories -you can set and what kinds of files go in them. In general, the -default for these options is expressed in terms of `${prefix}', so that -specifying just `--prefix' will affect all of the other directory -specifications that were not explicitly provided. - - The most portable way to affect installation locations is to pass the -correct locations to `configure'; however, many packages provide one or -both of the following shortcuts of passing variable assignments to the -`make install' command line to change installation locations without -having to reconfigure or recompile. - - The first method involves providing an override variable for each -affected directory. For example, `make install -prefix=/alternate/directory' will choose an alternate location for all -directory configuration variables that were expressed in terms of -`${prefix}'. Any directories that were specified during `configure', -but not in terms of `${prefix}', must each be overridden at install -time for the entire installation to be relocated. The approach of -makefile variable overrides for each directory variable is required by -the GNU Coding Standards, and ideally causes no recompilation. -However, some platforms have known limitations with the semantics of -shared libraries that end up requiring recompilation when using this -method, particularly noticeable in packages that use GNU Libtool. - - The second method involves providing the `DESTDIR' variable. For -example, `make install DESTDIR=/alternate/directory' will prepend -`/alternate/directory' before all installation names. The approach of -`DESTDIR' overrides is not required by the GNU Coding Standards, and -does not work on platforms that have drive letters. On the other hand, -it does better at avoiding recompilation issues, and works well even -when some directory options were not specified in terms of `${prefix}' -at `configure' time. - -Optional Features -================= - - If the package supports it, you can cause programs to be installed -with an extra prefix or suffix on their names by giving `configure' the -option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. - - Some packages pay attention to `--enable-FEATURE' options to -`configure', where FEATURE indicates an optional part of the package. -They may also pay attention to `--with-PACKAGE' options, where PACKAGE -is something like `gnu-as' or `x' (for the X Window System). The -`README' should mention any `--enable-' and `--with-' options that the -package recognizes. - - For packages that use the X Window System, `configure' can usually -find the X include and library files automatically, but if it doesn't, -you can use the `configure' options `--x-includes=DIR' and -`--x-libraries=DIR' to specify their locations. - - Some packages offer the ability to configure how verbose the -execution of `make' will be. For these packages, running `./configure ---enable-silent-rules' sets the default to minimal output, which can be -overridden with `make V=1'; while running `./configure ---disable-silent-rules' sets the default to verbose, which can be -overridden with `make V=0'. - -Particular systems -================== - - On HP-UX, the default C compiler is not ANSI C compatible. If GNU -CC is not installed, it is recommended to use the following options in -order to use an ANSI C compiler: - - ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" - -and if that doesn't work, install pre-built binaries of GCC for HP-UX. - - On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot -parse its `' header file. The option `-nodtk' can be used as -a workaround. If GNU CC is not installed, it is therefore recommended -to try - - ./configure CC="cc" - -and if that doesn't work, try - - ./configure CC="cc -nodtk" - - On Solaris, don't put `/usr/ucb' early in your `PATH'. This -directory contains several dysfunctional programs; working variants of -these programs are available in `/usr/bin'. So, if you need `/usr/ucb' -in your `PATH', put it _after_ `/usr/bin'. - - On Haiku, software installed for all users goes in `/boot/common', -not `/usr/local'. It is recommended to use the following options: - - ./configure --prefix=/boot/common - -Specifying the System Type -========================== - - There may be some features `configure' cannot figure out -automatically, but needs to determine by the type of machine the package -will run on. Usually, assuming the package is built to be run on the -_same_ architectures, `configure' can figure that out, but if it prints -a message saying it cannot guess the machine type, give it the -`--build=TYPE' option. TYPE can either be a short name for the system -type, such as `sun4', or a canonical name which has the form: - - CPU-COMPANY-SYSTEM - -where SYSTEM can have one of these forms: - - OS - KERNEL-OS - - See the file `config.sub' for the possible values of each field. If -`config.sub' isn't included in this package, then this package doesn't -need to know the machine type. - - If you are _building_ compiler tools for cross-compiling, you should -use the option `--target=TYPE' to select the type of system they will -produce code for. - - If you want to _use_ a cross compiler, that generates code for a -platform different from the build platform, you should specify the -"host" platform (i.e., that on which the generated programs will -eventually be run) with `--host=TYPE'. - -Sharing Defaults -================ - - If you want to set default values for `configure' scripts to share, -you can create a site shell script called `config.site' that gives -default values for variables like `CC', `cache_file', and `prefix'. -`configure' looks for `PREFIX/share/config.site' if it exists, then -`PREFIX/etc/config.site' if it exists. Or, you can set the -`CONFIG_SITE' environment variable to the location of the site script. -A warning: not all `configure' scripts look for a site script. - -Defining Variables -================== - - Variables not defined in a site shell script can be set in the -environment passed to `configure'. However, some packages may run -configure again during the build, and the customized values of these -variables may be lost. In order to avoid this problem, you should set -them in the `configure' command line, using `VAR=value'. For example: - - ./configure CC=/usr/local2/bin/gcc - -causes the specified `gcc' to be used as the C compiler (unless it is -overridden in the site shell script). - -Unfortunately, this technique does not work for `CONFIG_SHELL' due to -an Autoconf bug. Until the bug is fixed you can use this workaround: - - CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash - -`configure' Invocation -====================== - - `configure' recognizes the following options to control how it -operates. - -`--help' -`-h' - Print a summary of all of the options to `configure', and exit. - -`--help=short' -`--help=recursive' - Print a summary of the options unique to this package's - `configure', and exit. The `short' variant lists options used - only in the top level, while the `recursive' variant lists options - also present in any nested packages. - -`--version' -`-V' - Print the version of Autoconf used to generate the `configure' - script, and exit. - -`--cache-file=FILE' - Enable the cache: use and save the results of the tests in FILE, - traditionally `config.cache'. FILE defaults to `/dev/null' to - disable caching. - -`--config-cache' -`-C' - Alias for `--cache-file=config.cache'. - -`--quiet' -`--silent' -`-q' - Do not print messages saying which checks are being made. To - suppress all normal output, redirect it to `/dev/null' (any error - messages will still be shown). - -`--srcdir=DIR' - Look for the package's source code in directory DIR. Usually - `configure' can determine that directory automatically. - -`--prefix=DIR' - Use DIR as the installation prefix. *note Installation Names:: - for more details, including other options available for fine-tuning - the installation locations. - -`--no-create' -`-n' - Run the configure checks, but stop before creating any output - files. - -`configure' also accepts some other, not widely useful, options. Run -`configure --help' for more details. - diff --git a/Makefile.am b/Makefile.am index 605e3fc6..02121fde 100644 --- a/Makefile.am +++ b/Makefile.am @@ -24,16 +24,31 @@ LIBTOOL_DEPS = @LIBTOOL_DEPS@ AUTOMAKE_OPTIONS = foreign 1.4 ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = src test examples -DIST_SUBDIRS = src test examples -EXTRA_DIST = libhttpserver.pc.in debian/changelog.in debian/control.in debian/copyright.in debian/rules.in debian/libhttpserver-dev.install.in debian/libhttpserver.install.in redhat/libhttpserver.SPEC.in $(DX_CONFIG) +SUBDIRS = src +DIST_SUBDIRS = src -MOSTLYCLEANFILES = $(DX_CLEANFILES) redhat/SOURCES/* *.gcda *.gcno *.gcov -DISTCLEANFILES = redhat/SOURCES/* redhat/SPEC/* redhat/RPMS/* redhat/SRPMS/* redhat/* debian/* DIST_REVISION +if !COND_CROSS_COMPILE +SUBDIRS += test +DIST_SUBDIRS += test + +if BUILD_EXAMPLES +SUBDIRS += examples +DIST_SUBDIRS += examples +endif + +endif + +EXTRA_DIST = libhttpserver.pc.in $(DX_CONFIG) scripts/extract-release-notes.sh scripts/validate-version.sh + +MOSTLYCLEANFILES = $(DX_CLEANFILES) *.gcda *.gcno *.gcov +DISTCLEANFILES = DIST_REVISION pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libhttpserver.pc +cmakemoduledir = $(datadir)/cmake/Modules +cmakemodule_DATA = cmakemodule/FindLibHttpServer.cmake + include $(top_srcdir)/aminclude.am # Update libtool, if needed. @@ -44,18 +59,3 @@ dist-hook: date >DIST_REVISION git branch -vv >>DIST_REVISION cp DIST_REVISION $(distdir)/ - -deb: - debuild -i -us -uc -b - mv ../libhttpserver_@VERSION@_* . - mv ../libhttpserver-dev_@VERSION@_* . - mv ../libhttpserver-dbg_@VERSION@_* . - -rpm: dist - mkdir -p redhat/SOURCES - mkdir -p redhat/BUILD - mkdir -p redhat/RPMS - mkdir -p redhat/SRPMS - cp libhttpserver-@VERSION@.tar.gz redhat/SOURCES - rpmbuild -v -bb redhat/libhttpserver.SPEC - rpmbuild -v -ba redhat/libhttpserver.SPEC diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..a4d74934 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,10 @@ +Hello there! Welcome. Please follow the steps below to tell us about your contribution. + +1. Copy the correct template for your contribution + - Are you fixing a bug? Copy the template from https://raw.githubusercontent.com/etr/libhttpserver/master/.github/PULL_REQUEST_TEMPLATE/bug_fix.md + - Are you improving performance? Copy the template https://raw.githubusercontent.com/etr/libhttpserver/master/.github/PULL_REQUEST_TEMPLATE/performance_improvement.md + - Are you updating documentation? Copy the template from https://raw.githubusercontent.com/etr/libhttpserver/master/.github/PULL_REQUEST_TEMPLATE/documentation.md + - Are you changing functionality? Copy the template from https://raw.githubusercontent.com/etr/libhttpserver/master/.github/PULL_REQUEST_TEMPLATE/feature_change.md +2. Replace this text with the contents of the template +3. Fill in all sections of the template +4. Click "Create pull request" diff --git a/README.CentOS-7 b/README.CentOS-7 new file mode 100644 index 00000000..1dfaaa70 --- /dev/null +++ b/README.CentOS-7 @@ -0,0 +1,7 @@ +## Cent OS 7 / RHEL 7 + +CentOS 7 has a lower version of gcc (4.8.7) that is barely C++11 capable and this library +needs a better compiler. We recommend at least gcc 5+ + +We recommend installing devtoolset-8 +https://www.softwarecollections.org/en/scls/rhscl/devtoolset-8/ diff --git a/README.FreeBSD b/README.FreeBSD new file mode 100644 index 00000000..853c5589 --- /dev/null +++ b/README.FreeBSD @@ -0,0 +1,9 @@ +## Building on FreeBSD (Tested on 12.0) + +# Due to the differences in the directory structures on BSD systems some minor tweaks need to occur +# Also, FreeBSD and AIX "make" command is not compatible with gmake, like Linux and Mingw are + +export MAKE=gmake +bootstrap +configure CPPFLAGS=-I/usr/local/include LDFLAGS=-L/usr/local/lib +gmake diff --git a/README.md b/README.md index 1bd46b18..b9d96f37 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -The libhttpserver (0.8.0) reference manual -========================================== +# The libhttpserver reference manual +![GA: Build Status](https://github.com/etr/libhttpserver/actions/workflows/verify-build.yml/badge.svg) +[![codecov](https://codecov.io/gh/etr/libhttpserver/branch/master/graph/badge.svg)](https://codecov.io/gh/etr/libhttpserver) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/1bd1e8c21f66400fb70e5a5ce357b525)](https://www.codacy.com/gh/etr/libhttpserver/dashboard?utm_source=github.com&utm_medium=referral&utm_content=etr/libhttpserver&utm_campaign=Badge_Grade) +[![Gitter chat](https://badges.gitter.im/etr/libhttpserver.png)](https://gitter.im/libhttpserver/community) + +[![ko-fi](https://www.ko-fi.com/img/donate_sm.png)](https://ko-fi.com/F1F5HY8B) + +## Tl;dr +libhttpserver is a C++ library for building high performance RESTful web servers. +libhttpserver is built upon [libmicrohttpd](https://www.gnu.org/software/libmicrohttpd/) to provide a simple API for developers to create HTTP services in C++. + +**Features:** +- HTTP 1.1 compatible request parser +- RESTful oriented interface +- Flexible handler API +- Cross-platform compatible +- Implementation is HTTP 1.1 compliant +- Multiple threading models +- Support for IPv6 +- Support for SHOUTcast +- Support for incremental processing of POST data (optional) +- Support for basic and digest authentication (optional) +- Support for centralized authentication with path-based skip rules +- Support for TLS (requires libgnutls, optional) + +## Table of Contents +* [Introduction](#introduction) +* [Requirements](#requirements) +* [Building](#building) +* [Getting Started](#getting-started) +* [Structures and classes type definition](#structures-and-classes-type-definition) +* [Create and work with a webserver](#create-and-work-with-a-webserver) +* [The resource object](#the-resource-object) +* [Registering resources](#registering-resources) +* [Parsing requests](#parsing-requests) +* [Building responses to requests](#building-responses-to-requests) +* [IP Blacklisting and Whitelisting](#ip-blacklisting-and-whitelisting) +* [Authentication](#authentication) +* [HTTP Utils](#http-utils) +* [Other Examples](#other-examples) + +#### Community +* [Code of Conduct (on a separate page)](https://github.com/etr/libhttpserver/blob/master/CODE_OF_CONDUCT.md) +* [Contributing (on a separate page)](https://github.com/etr/libhttpserver/blob/master/CONTRIBUTING.md) + +#### Appendices +* [Copying statement](#copying) +* [GNU-LGPL](#GNU-lesser-general-public-license): The GNU Lesser General Public License says how you can copy and share almost all of libhttpserver. +* [GNU-FDL](#GNU-free-documentation-license): The GNU Free Documentation License says how you can copy and share the documentation of libhttpserver. + +## Introduction +libhttpserver is meant to constitute an easy system to build HTTP servers with REST fashion. +libhttpserver is based on [libmicrohttpd](https://www.gnu.org/software/libmicrohttpd/) and, like this, it is a daemon library (parts of this documentation are, in fact, matching those of the wrapped library). +The mission of this library is to support all possible HTTP features directly and with a simple semantic allowing then the user to concentrate only on his application and not on HTTP request handling details. + +The library is supposed to work transparently for the client Implementing the business logic and using the library itself to realize an interface. +If the user wants it must be able to change every behavior of the library itself through the registration of callbacks. + +libhttpserver is able to decode certain body formats and automatically format them in object oriented fashion. This is true for query arguments and for *POST* and *PUT* requests bodies if *application/x-www-form-urlencoded* or *multipart/form-data* header are passed. + +All functions are guaranteed to be completely reentrant and thread-safe (unless differently specified). +Additionally, clients can specify resource limits on the overall number of connections, number of connections per IP address and memory used per connection to avoid resource exhaustion. + +[Back to TOC](#table-of-contents) + +## Requirements +libhttpserver can be used without any dependencies aside from libmicrohttpd. + +The minimum versions required are: +* g++ >= 5.5.0 or clang-3.6 +* C++17 or newer +* libmicrohttpd >= 0.9.64 +* [Optionally]: for TLS (HTTPS) support, you'll need [libgnutls](http://www.gnutls.org/). +* [Optionally]: to compile the code-reference, you'll need [doxygen](http://www.doxygen.nl/). -[![Build Status](https://travis-ci.org/etr/libhttpserver.png?branch=master)](https://travis-ci.org/etr/libhttpserver) +Additionally, for MinGW on windows you will need: +* libwinpthread (For MinGW-w64, if you use thread model posix then you have this) -This library has been originally developed under the zencoders flags and this community has always supported me all along this work so I am happy to put the logo on this readme. +For versions before 0.18.0, on MinGW, you will need: +* libgnurx >= 2.5.1 + +Furthermore, the testcases use [libcurl](http://curl.haxx.se/libcurl/) but you don't need it to compile the library. + +Please refer to the readme file for your particular distribution if there is one for important notes. + +[Back to TOC](#table-of-contents) + +## Building +libhttpserver uses the standard system where the usual build process involves running +> ./bootstrap +> mkdir build +> cd build +> \.\./configure +> make +> make install # (optionally to install on the system) + +[Back to TOC](#table-of-contents) + +### Optional parameters to configure script +A complete list of parameters can be obtained running 'configure --help'. +Here are listed the libhttpserver specific options (the canonical configure options are also supported). + +* _\-\-enable-same-directory-build:_ enable to compile in the same directory. This is heavily discouraged. (def=no) +* _\-\-enable-debug:_ enable debug data generation. (def=no) +* _\-\-disable-doxygen-doc:_ don't generate any doxygen documentation. Doxygen is automatically invoked if present on the system. Automatically disabled otherwise. +* _\-\-enable-fastopen:_ enable use of TCP_FASTOPEN (def=yes) +* _\-\-enable-static:_ enable use static linking (def=yes) + +[Back to TOC](#table-of-contents) + +### Building on Windows (MSYS2) + +MSYS2 provides multiple shell environments with different purposes. Understanding which shell to use is important: + +| Shell | Host Triplet | Runtime Dependency | Use Case | +|-------|--------------|-------------------|----------| +| **MinGW64** | `x86_64-w64-mingw32` | Native Windows | **Recommended** for native Windows apps | +| **MSYS** | `x86_64-pc-msys` | msys-2.0.dll | POSIX-style apps, build tools | + +**Recommended: Use the MinGW64 shell** for building libhttpserver to produce native Windows binaries without additional runtime dependencies. + +#### Step-by-step build instructions + +1. Install [MSYS2](https://www.msys2.org/) + +2. Open the **MINGW64** shell (not the MSYS shell) from the Start Menu + +3. Install dependencies: +```bash +pacman -S --needed mingw-w64-x86_64-{gcc,libtool,make,pkg-config,doxygen,gnutls,curl} autotools +``` + +4. Build and install [libmicrohttpd](https://www.gnu.org/software/libmicrohttpd/) (>= 0.9.64) + +5. Build libhttpserver: +```bash +./bootstrap +mkdir build && cd build +../configure --disable-fastopen +make +make check # run tests +``` + +**Important:** The `--disable-fastopen` flag is required on Windows as TCP_FASTOPEN is not supported. + +#### If you use the MSYS shell + +Building from the MSYS shell also works but the resulting binaries will depend on `msys-2.0.dll`. The configure script will display a warning when building in this environment. If you see: - When you see this tree, know that you've came across ZenCoders.org +``` +configure: WARNING: Building from MSYS environment. Binaries will depend on msys-2.0.dll. +``` + +Consider switching to the MinGW64 shell for native Windows binaries. + +#### Library files on Windows + +When building with GCC-based toolchains (MSYS2/MinGW, Cygwin), the following library files are generated: + +| File | Purpose | +|------|---------| +| `libhttpserver.a` | Static library archive | +| `libhttpserver.dll` | Shared library (DLL) | +| `libhttpserver.dll.a` | Import library for linking against the DLL | +| `libhttpserver.la` | Libtool archive (used by libtool during linking) | + +**Note about `.lib` files:** The `.dll.a` format is the import library format used by GCC toolchains. If you're looking for `.lib` files, those are the MSVC (Microsoft Visual C++) import library format and are only generated when building with the MSVC toolchain. The `.dll.a` file serves the same purpose as `.lib` but for GCC-based compilers. + +**Linking against libhttpserver:** + +Using pkg-config (recommended): +```bash +g++ myapp.cpp $(pkg-config --cflags --libs libhttpserver) -o myapp +``` + +Manual linking: +```bash +g++ myapp.cpp -I/mingw64/include -L/mingw64/lib -lhttpserver -lmicrohttpd -o myapp +``` + +[Back to TOC](#table-of-contents) + +## Getting Started +The most basic example of creating a server and handling a requests for the path `/hello`: +```cpp + #include + + using namespace httpserver; + + class hello_world_resource : public http_resource { + public: + std::shared_ptr render(const http_request&) { + return std::shared_ptr(new string_response("Hello, World!")); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you could run the following command from a terminal: - with open('ZenCoders. - `num` in numbers synchronized - datetime d glob. sys.argv[2] . - def myclass `..` @@oscla org. . class { - displ hooks( public static void ma functor: - $myclass->method( impport sys, os.pipe ` @param name` - fcl if(system(cmd) myc. /de ` $card( array("a" srand - format lists: ++: conc ++ "my an WHERE for( == myi - `sys: myvalue(myvalue) sys.t Console.W try{ rais using - connec SELECT * FROM table mycnf acco desc and or selector::clas at - openldap string sys. print "zenc der " { 'a': `ls -l` > appe &firs - import Tkinter paste( $obh &a or it myval bro roll: :: [] require a - case `` super. +y expr say " %rooms 1 --account fb- yy - proc meth Animate => send(D, open) putd EndIf 10 whi myc` cont - and main (--) import loop $$ or end onload UNION WITH tab timer 150 *2 - end. begin True GtkLabel *label doto partition te let auto i<- (i + d ); - .mushup ``/. ^/zenc/ myclass->her flv op <> element >> 71 or - QFileDi : and .. with myc toA channel::bo myc isEmpty a not bodt; - class T public pol str mycalc d pt &&a *i fc add ^ac - ::ZenCoders::core::namespac boost::function st f = std: ;; int assert - cout << endl public genera #include "b ost ::ac myna const cast mys - ac size_t return ran int (*getNextValue)(void) ff double sa_family_t famil - pu a do puts(" ac int main(int argc, char* "%5d struct nam - cs float for typedef enum puts getchar() - if( else #define fp FILE* f char* s - i++ strcat( %s int - 31] total+= do - }do while(1) sle - getc strcpy( a for - prin scanf(%d, & get - int void myfunc(int pa retu - BEQ BNEQZ R1 10 ANDI R1 R2 SYS - XOR SYSCALL 5 SLTIU MFLO 15 SW JAL - BNE BLTZAL R1 1 LUI 001 NOOP MULTU SLLV - MOV R1 ADD R1 R2 JUMP 10 1001 BEQ R1 R2 1 ANDI - 1101 1010001100 111 001 01 1010 101100 1001 100 - 110110 100 0 01 101 01100 100 100 1000100011 - 11101001001 00 11 100 11 10100010 - 000101001001 10 1001 101000101 - 010010010010110101001010 + curl -XGET -v http://localhost:8080/hello + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/minimal_hello_world.cpp). + +[Back to TOC](#table-of-contents) + +## Structures and classes type definition +* _webserver:_ Represents the daemon listening on a socket for HTTP traffic. + * _create_webserver:_ Builder class to support the creation of a webserver. +* _http_resource:_ Represents the resource associated with a specific http endpoint. +* _http_request:_ Represents the request received by the resource that process it. +* _http_response:_ Represents the response sent by the server once the resource finished its work. + * _string_response:_ A simple string response. + * _file_response:_ A response getting content from a file. + * _basic_auth_fail_response:_ A failure in basic authentication. + * _digest_auth_fail_response:_ A failure in digest authentication. + * _deferred_response:_ A response getting content from a callback. + +[Back to TOC](#table-of-contents) + +## Create and work with a webserver +As you can see from the example above, creating a webserver with standard configuration is quite simple: +```cpp + webserver ws = create_webserver(8080); +``` +The `create_webserver` class is a supporting _builder_ class that eases the building of a webserver through chained syntax. + +### Basic Startup Options + +In this section we will explore other basic options that you can use when configuring your server. More advanced options (custom callbacks, https support, etc...) will be discussed separately. + +* _.port(**int** port):_ The port at which the server will listen. This can also be passed to the consturctor of `create_webserver`. E.g. `create_webserver(8080)`. +* _.max_connections(**int** max_conns):_ Maximum number of concurrent connections to accept. The default is `FD_SETSIZE - 4` (the maximum number of file descriptors supported by `select` minus four for `stdin`, `stdout`, `stderr` and the server socket). In other words, the default is as large as possible. Note that if you set a low connection limit, you can easily get into trouble with browsers doing request pipelining. +For example, if your connection limit is “1”, a browser may open a first connection to access your “index.html” file, keep it open but use a second connection to retrieve CSS files, images and the like. In fact, modern browsers are typically by default configured for up to 15 parallel connections to a single server. If this happens, the library will refuse to even accept the second connection until the first connection is closed — which does not happen until timeout. As a result, the browser will fail to render the page and seem to hang. If you expect your server to operate close to the connection limit, you should first consider using a lower timeout value and also possibly add a “Connection: close” header to your response to ensure that request pipelining is not used and connections are closed immediately after the request has completed. +* _.content_size_limit(**size_t** size_limit):_ Sets the maximum size of the content that a client can send over in a single block. The default is `-1 = unlimited`. +* _.connection_timeout(**int** timeout):_ Determines after how many seconds of inactivity a connection should be timed out automatically. The default timeout is `180 seconds`. +* _.memory_limit(**int** memory_limit):_ Maximum memory size per connection (followed by a `size_t`). The default is 32 kB (32*1024 bytes). Values above 128k are unlikely to result in much benefit, as half of the memory will be typically used for IO, and TCP buffers are unlikely to support window sizes above 64k on most systems. +* _.per_IP_connection_limit(**int** connection_limit):_ Limit on the number of (concurrent) connections made to the server from the same IP address. Can be used to prevent one IP from taking over all of the allowed connections. If the same IP tries to establish more than the specified number of connections, they will be immediately rejected. The default is `0`, which means no limit on the number of connections from the same IP address. +* _.bind_address(**const struct sockaddr*** address):_ Bind the server to a specific network interface by passing a pre-constructed `sockaddr` structure. This gives full control over the address configuration but requires manual socket address setup. +* _.bind_address(**const std::string&** ip):_ Bind the server to a specific network interface by IP address string (e.g., `"127.0.0.1"` for localhost only, or `"192.168.1.100"` for a specific interface). Supports both IPv4 and IPv6 addresses. When an IPv6 address is provided, IPv6 mode is automatically enabled. Example: `create_webserver(8080).bind_address("127.0.0.1")`. +* _.bind_socket(**int** socket_fd):_ Listen socket to use. Pass a listen socket for the daemon to use (systemd-style). If this option is used, the daemon will not open its own listen socket(s). The argument passed must be of type "int" and refer to an existing socket that has been bound to a port and is listening. +* _.max_thread_stack_size(**int** stack_size):_ Maximum stack size for threads created by the library. Not specifying this option or using a value of zero means using the system default (which is likely to differ based on your platform). Default is `0 (system default)`. +* _.use_ipv6() and .no_ipv6():_ Enable or disable the IPv6 protocol support (by default, libhttpserver will just support IPv4). If you specify this and the local platform does not support it, starting up the server will throw an exception. `off` by default. +* _.use_dual_stack() and .no_dual_stack():_ Enable or disable the support for both IPv6 and IPv4 protocols at the same time (by default, libhttpserver will just support IPv4). If you specify this and the local platform does not support it, starting up the server will throw an exception. Note that this will mean that IPv4 addresses are returned in the IPv6-mapped format (the ’structsockaddrin6’ format will be used for IPv4 and IPv6). `off` by default. +* _.pedantic() and .no_pedantic():_ Enables pedantic checks about the protocol (as opposed to as tolerant as possible). Specifically, at the moment, this flag causes the library to reject HTTP 1.1 connections without a `Host` header. This is required by the standard, but of course in violation of the “be as liberal as possible in what you accept” norm. It is recommended to turn this **off** if you are testing clients against the library, and **on** in production. `off` by default. +* _.debug() and .no_debug():_ Enables debug messages from the library. `off` by default. +* _.regex_checking() and .no_regex_checking():_ Enables pattern matching for endpoints. Read more [here](#registering-resources). `on` by default. +* _.post_process() and .no_post_process():_ Enables/Disables the library to automatically parse the body of the http request as arguments if in querystring format. Read more [here](#parsing-requests). `on` by default. +* _.put_processed_data_to_content() and .no_put_processed_data_to_content():_ Enables/Disables the library to copy parsed body data to the content or to only store it in the arguments map. `on` by default. +* _.file_upload_target(**file_upload_target_T** file_upload_target):_ Controls, how the library stores uploaded files. Default value is `FILE_UPLOAD_MEMORY_ONLY`. + * `FILE_UPLOAD_MEMORY_ONLY`: The content of the file is only stored in memory. Depending on `put_processed_data_to_content` only as part of the arguments map or additionally in the content. + * `FILE_UPLOAD_DISK_ONLY`: The content of the file is stored only in the file system. The path is created from `file_upload_dir` and either a random name (if `generate_random_filename_on_upload` is true) or the actually uploaded file name. + * `FILE_UPLOAD_MEMORY_AND_DISK`: The content of the file is stored in memory and on the file system. +* _.file_upload_dir(**const std::string&** file_upload_dir):_ Specifies the directory to store all uploaded files. Default value is `/tmp`. +* _.generate_random_filename_on_upload() and .no_generate_random_filename_on_upload():_ Enables/Disables the library to generate a unique and unused filename to store the uploaded file to. Otherwise the actually uploaded file name is used. `off` by default. +* _.file_cleanup_callback(**file_cleanup_callback_ptr** callback):_ Sets a callback function to control what happens to uploaded files when the request completes. By default (when no callback is set), all uploaded files are automatically deleted. The callback signature is `bool(const std::string& key, const std::string& filename, const http::file_info& info)` where `key` is the form field name, `filename` is the original uploaded filename, and `info` contains file metadata including the filesystem path. Return `true` to delete the file (default behavior) or `false` to keep it (e.g., after moving it to permanent storage). If the callback throws an exception, the file will be deleted as a safety measure. +* _.deferred()_ and _.no_deferred():_ Enables/Disables the ability for the server to suspend and resume connections. Simply put, it enables/disables the ability to use `deferred_response`. Read more [here](#building-responses-to-requests). `on` by default. +* _.single_resource() and .no_single_resource:_ Sets or unsets the server in single resource mode. This limits all endpoints to be served from a single resource. The resultant is that the webserver will process the request matching to the endpoint skipping any complex semantic. Because of this, the option is incompatible with `regex_checking` and requires the resource to be registered against an empty endpoint or the root endpoint (`"/"`). The resource will also have to be registered as family. (For more information on resource registration, read more [here](#registering-resources)). `off` by default. + +### Threading Models +* _.start_method(**const http::http_utils::start_method_T&** start_method):_ libhttpserver can operate with two different threading models that can be selected through this method. Default value is `INTERNAL_SELECT`. + * `http::http_utils::INTERNAL_SELECT`: In this mode, libhttpserver uses only a single thread to handle listening on the port and processing of requests. This mode is preferable if spawning a thread for each connection would be costly. If the HTTP server is able to quickly produce responses without much computational overhead for each connection, this mode can be a great choice. Note that libhttpserver will still start a single thread for itself -- this way, the main program can continue with its operations after calling the start method. Naturally, if the HTTP server needs to interact with shared state in the main application, synchronization will be required. If such synchronization in code providing a response results in blocking, all HTTP server operations on all connections will stall. This mode is a bad choice if response data cannot always be provided instantly. The reason is that the code generating responses should not block (since that would block all other connections) and on the other hand, if response data is not available immediately, libhttpserver will start to busy wait on it. If you need to scale along the number of concurrent connection and scale on multiple thread you can specify a value for `max_threads` (see below) thus enabling a thread pool - this is different from `THREAD_PER_CONNECTION` below where a new thread is spawned for each connection. + * `http::http_utils::THREAD_PER_CONNECTION`: In this mode, libhttpserver starts one thread to listen on the port for new connections and then spawns a new thread to handle each connection. This mode is great if the HTTP server has hardly any state that is shared between connections (no synchronization issues!) and may need to perform blocking operations (such as extensive IO or running of code) to handle an individual connection. +* _.max_threads(**int** max_threads):_ A thread pool can be combined with the `INTERNAL_SELECT` mode to benefit implementations that require scalability. As said before, by default this mode only uses a single thread. When combined with the thread pool option, it is possible to handle multiple connections with multiple threads. Any value greater than one for this option will activate the use of the thread pool. In contrast to the `THREAD_PER_CONNECTION` mode (where each thread handles one and only one connection), threads in the pool can handle a large number of concurrent connections. Using `INTERNAL_SELECT` in combination with a thread pool is typically the most scalable (but also hardest to debug) mode of operation for libhttpserver. Default value is `1`. This option is incompatible with `THREAD_PER_CONNECTION`. + +### Custom defaulted error messages +libhttpserver allows to override internal error retrieving functions to provide custom messages to the HTTP client. There are only 3 cases in which implementing logic (an http_resource) cannot be invoked: (1) a not found resource, where the library is not being able to match the URL requested by the client to any implementing http_resource object; (2) a not allowed method, when the HTTP client is requesting a method explicitly marked as not allowed (more info [here](#allowing-and-disallowing-methods-on-a-resource)) by the implementation; (3) an exception being thrown. +In all these 3 cases libhttpserver would provide a standard HTTP response to the client with the correct error code; respectively a `404`, a `405` and a `500`. The library allows its user to specify custom callbacks that will be called to replace the default behavior. +* _.not_found_resource(**const shared_ptr(*render_ptr)(const http_request&)** resource):_ Specifies a function to handle a request when no matching registered endpoint exist for the URL requested by the client. +* _.method_not_allowed_resource(**const shared_ptr(*render_ptr)(const http_request&)** resource):_ Specifies a function to handle a request that is asking for a method marked as not allowed on the matching http_resource. +* _.internal_error_resource(**const shared_ptr(*render_ptr)(const http_request&)** resource):_ Specifies a function to handle a request that is causing an uncaught exception during its execution. **REMEMBER:** is this callback is causing an exception itself, the standard default response from libhttpserver will be reported to the HTTP client. + +#### Example of custom errors: +```cpp + #include + + using namespace httpserver; + + std::shared_ptr not_found_custom(const http_request& req) { + return std::shared_ptr(new string_response("Not found custom", 404, "text/plain")); + } + + std::shared_ptr not_allowed_custom(const http_request& req) { + return std::shared_ptr(new string_response("Not allowed custom", 405, "text/plain")); + } + + class hello_world_resource : public http_resource { + public: + std::shared_ptr render(const http_request&) { + return std::shared_ptr(new string_response("Hello, World!")); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080) + .not_found_resource(not_found_custom) + .method_not_allowed_resource(not_allowed_custom); + + hello_world_resource hwr; + hwr.disallow_all(); + hwr.set_allowing("GET", true); + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you can run the following command from a terminal: + + curl -XGET -v http://localhost:8080/hello -For further information: -visit our website www.zencoders.org +If you try to run either of the two following commands, you'll see your custom errors: +* `curl -XGET -v http://localhost:8080/morning`: will return your custom `not found` error. +* `curl -XPOST -v http://localhost:8080/hello`: will return your custom `not allowed` error. + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/custom_error.cpp). + +### Custom logging callbacks +* _.log_access(**void(*log_access_ptr)(const std::string&)** functor):_ Specifies a function used to log accesses (requests) to the server. +* _.log_error(**void(*log_error_ptr)(const std::string&)** functor):_ Specifies a function used to log errors generating from the server. + +#### Example of custom logging callback +```cpp + #include + #include + + using namespace httpserver; + + void custom_access_log(const std::string& url) { + std::cout << "ACCESSING: " << url << std::endl; + } + + class hello_world_resource : public http_resource { + public: + std::shared_ptr render(const http_request&) { + return std::shared_ptr(new string_response("Hello, World!")); + } + }; -Author: Sebastiano Merlino + int main(int argc, char** argv) { + webserver ws = create_webserver(8080) + .log_access(custom_access_log); -Copying -======= -This manual is for libhttpserver, C++ library for creating an -embedded Rest HTTP server (and more). + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you can run the following command from a terminal: + + curl -XGET -v http://localhost:8080/hello + +You'll notice how, on the terminal runing your server, the logs will now be printed in output for each request received. + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/custom_access_log.cpp). + +### TLS/HTTPS +* _.use_ssl() and .no_ssl():_ Determines whether to run in HTTPS-mode or not. If you set this as on and libhttpserver was compiled without SSL support, the library will throw an exception at start of the server. `off` by default. +* _.cred_type(**const http::http_utils::cred_type_T&** cred_type):_ Daemon credentials type. Either certificate or anonymous. Acceptable values are: + * `NONE`: No credentials. + * `CERTIFICATE`: Certificate credential. + * `ANON`: Anonymous credential. + * `SRP`: SRP credential. + * `PSK`: PSK credential. + * `IA`: IA credential. +* _.https_mem_key(**const std::string&** filename):_ String representing the path to a file containing the private key to be used by the HTTPS daemon. This must be used in conjunction with `https_mem_cert`. +* _.https_mem_cert(**const std::string&** filename):_ String representing the path to a file containing the certificate to be used by the HTTPS daemon. This must be used in conjunction with `https_mem_key`. +* _.https_mem_trust(**const std::string&** filename):_ String representing the path to a file containing the CA certificate to be used by the HTTPS daemon to authenticate and trust clients certificates. The presence of this option activates the request of certificate to the client. The request to the client is marked optional, and it is the responsibility of the server to check the presence of the certificate if needed. Note that most browsers will only present a client certificate only if they have one matching the specified CA, not sending any certificate otherwise. +* _.https_priorities(**const std::string&** priority_string):_ SSL/TLS protocol version and ciphers. Must be followed by a string specifying the SSL/TLS protocol versions and ciphers that are acceptable for the application. The string is passed unchanged to gnutls_priority_init. If this option is not specified, `"NORMAL"` is used. +* _.psk_cred_handler(**psk_cred_handler_callback** handler):_ Sets a callback function for TLS-PSK (Pre-Shared Key) authentication. The callback receives a username and should return the corresponding hex-encoded PSK, or an empty string if the user is unknown. This option requires `use_ssl()`, `cred_type(http::http_utils::PSK)`, and an appropriate `https_priorities()` string that enables PSK cipher suites. PSK authentication allows TLS without certificates by using a shared secret key. +* _.sni_callback(**sni_callback_t** callback):_ Sets a callback function for SNI (Server Name Indication) support. The callback receives the server name requested by the client and should return a `std::pair` containing the PEM-encoded certificate and key for that server name. Return empty strings to use the default certificate. Requires libmicrohttpd 0.9.71+ with GnuTLS. + +#### Minimal example using HTTPS +```cpp + #include + + using namespace httpserver; + + class hello_world_resource : public http_resource { + public: + std::shared_ptr render(const http_request&) { + return std::shared_ptr(new string_response("Hello, World!")); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080) + .use_ssl() + .https_mem_key("key.pem") + .https_mem_cert("cert.pem"); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you can run the following command from a terminal: + + curl -XGET -v -k 'https://localhost:8080/hello' + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/minimal_https.cpp). + +#### Minimal example using TLS-PSK +```cpp + #include + #include + #include + + using namespace httpserver; + + // Simple PSK database - in production, use secure storage + std::map psk_database = { + {"client1", "0123456789abcdef0123456789abcdef"}, + {"client2", "fedcba9876543210fedcba9876543210"} + }; + + // PSK credential handler callback + std::string psk_handler(const std::string& username) { + auto it = psk_database.find(username); + if (it != psk_database.end()) { + return it->second; + } + return ""; // Return empty string for unknown users + } + + class hello_world_resource : public http_resource { + public: + std::shared_ptr render(const http_request&) { + return std::shared_ptr( + new string_response("Hello, World (via TLS-PSK)!")); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080) + .use_ssl() + .cred_type(http::http_utils::PSK) + .psk_cred_handler(psk_handler) + .https_priorities("NORMAL:-VERS-TLS-ALL:+VERS-TLS1.2:+PSK:+DHE-PSK"); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you can run the following command from a terminal using gnutls-cli: + + gnutls-cli --pskusername=client1 --pskkey=0123456789abcdef0123456789abcdef -p 8080 localhost + +Then type `GET /hello HTTP/1.1` followed by `Host: localhost` and two newlines. + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/minimal_https_psk.cpp). + +### IP Blacklisting/Whitelisting +libhttpserver supports IP blacklisting and whitelisting as an internal feature. This section explains the startup options related with IP blacklisting/whitelisting. See the [specific section](#ip-blacklisting-and-whitelisting) to read more about the topic. +* _.ban_system() and .no_ban_system:_ Can be used to enable/disable the ban system. `on` by default. +* _.default_policy(**const http::http_utils::policy_T&** default_policy):_ Specifies what should be the default behavior when receiving a request. Possible values are `ACCEPT` and `REJECT`. Default is `ACCEPT`. + +### Authentication Parameters +* _.basic_auth() and .no_basic_auth:_ Can be used to enable/disable parsing of the basic authorization header sent by the client. `on` by default. +* _.digest_auth() and .no_digest_auth:_ Can be used to enable/disable parsing of the digested authentication data sent by the client. `on` by default. +* _.nonce_nc_size(**int** nonce_size):_ Size of an array of nonce and nonce counter map. This option represents the size (number of elements) of a map of a nonce and a nonce-counter. If this option is not specified, a default value of 4 will be used (which might be too small for servers handling many requests). +You should calculate the value of NC_SIZE based on the number of connections per second multiplied by your expected session duration plus a factor of about two for hash table collisions. For example, if you expect 100 digest-authenticated connections per second and the average user to stay on your site for 5 minutes, then you likely need a value of about 60000. On the other hand, if you can only expect only 10 digest-authenticated connections per second, tolerate browsers getting a fresh nonce for each request and expect a HTTP request latency of 250 ms, then a value of about 5 should be fine. +* _.digest_auth_random(**const std::string&** nonce_seed):_ Digest Authentication nonce’s seed. For security, you SHOULD provide a fresh random nonce when actually using Digest Authentication with libhttpserver in production. + +### Examples of chaining syntax to create a webserver +```cpp + webserver ws = create_webserver(8080) + .no_ssl() + .no_ipv6() + .no_debug() + .no_pedantic() + .no_basic_auth() + .no_digest_auth() + .no_comet() + .no_regex_checking() + .no_ban_system() + .no_post_process(); +``` +## +```cpp + webserver ws = create_webserver(8080) + .use_ssl() + .https_mem_key("key.pem") + .https_mem_cert("cert.pem"); +``` +### Starting and stopping a webserver +Once a webserver is created, you can manage its execution through the following methods on the `webserver` class: +* _**void** webserver::start(**bool** blocking):_ Allows to start a server. If the `blocking` flag is passed as `true`, it will block the execution of the current thread until a call to stop on the same webserver object is performed. +* _**void** webserver::stop():_ Allows to stop a server. It immediately stops it. +* _**bool** webserver::is_running():_ Checks if a server is running +* _**void** webserver::sweet_kill():_ Allows to stop a server. It doesn't guarantee an immediate halt to allow for thread termination and connection closure. + +[Back to TOC](#table-of-contents) + +## The Resource Object +The `http_resource` class represents a logical collection of HTTP methods that will be associated to a URL when registered on the webserver. The class is **designed for extension** and it is where most of your code should ideally live. When the webserver matches a request against a resource (see: [resource registration](#registering-resources)), the method correspondent to the one in the request (GET, POST, etc..) (see below) is called on the resource. + +Given this, the `http_resource` class contains the following extensible methods (also called `handlers` or `render methods`): +* _**std::shared_ptr** http_resource::render_GET(**const http_request&** req):_ Invoked on an HTTP GET request. +* _**std::shared_ptr** http_resource::render_POST(**const http_request&** req):_ Invoked on an HTTP POST request. +* _**std::shared_ptr** http_resource::render_PUT(**const http_request&** req):_ Invoked on an HTTP PUT request. +* _**std::shared_ptr** http_resource::render_HEAD(**const http_request&** req):_ Invoked on an HTTP HEAD request. +* _**std::shared_ptr** http_resource::render_DELETE(**const http_request&** req):_ Invoked on an HTTP DELETE request. +* _**std::shared_ptr** http_resource::render_TRACE(**const http_request&** req):_ Invoked on an HTTP TRACE request. +* _**std::shared_ptr** http_resource::render_OPTIONS(**const http_request&** req):_ Invoked on an HTTP OPTIONS request. +* _**std::shared_ptr** http_resource::render_CONNECT(**const http_request&** req):_ Invoked on an HTTP CONNECT request. +* _**std::shared_ptr** http_resource::render(**const http_request&** req):_ Invoked as a backup method if the matching method is not implemented. It can be used whenever you want all the invocations on a URL to activate the same behavior regardless of the HTTP method requested. The default implementation of the `render` method returns an empty response with a `404`. + +#### Example of implementation of render methods +```cpp + #include + + using namespace httpserver; + + class hello_world_resource : public http_resource { + public: + std::shared_ptr render_GET(const http_request&) { + return std::shared_ptr(new string_response("GET: Hello, World!")); + } + + std::shared_ptr render(const http_request&) { + return std::shared_ptr(new string_response("OTHER: Hello, World!")); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you can run the following commands from a terminal: + * `curl -XGET -v http://localhost:8080/hello`: will return `GET: Hello, World!`. + * `curl -XPOST -v http://localhost:8080/hello`: will return `OTHER: Hello, World!`. You can try requesting other methods beside `POST` to verify how the same message will be returned. + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/handlers.cpp). + +### Allowing and disallowing methods on a resource +By default, all methods an a resource are allowed, meaning that an HTTP request with that method will be invoked. It is possible to mark methods as `not allowed` on a resource. When a method not allowed is requested on a resource, the default `method_not_allowed` method is invoked - the default can be overriden as explain in the section [Custom defaulted error messages](custom-defaulted-error-messages). +The base `http_resource` class has a set of methods that can be used to allow and disallow HTTP methods. +* _**void** http_resource::set_allowing(**const std::string&** method, **bool** allowed):_ Used to allow or disallow a method. The `method` parameter is a string representing an HTTP method (GET, POST, PUT, etc...). +* _**void** http_resource::allow_all():_ Marks all HTTP methods as allowed. +* _**void** http_resource::disallow_all():_ Marks all HTTP methods as not allowed. + +#### Example of methods allowed/disallowed +```cpp + #include + + using namespace httpserver; + + class hello_world_resource : public http_resource { + public: + std::shared_ptr render(const http_request&) { + return std::shared_ptr(new string_response("Hello, World!")); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080); + + hello_world_resource hwr; + hwr.disallow_all(); + hwr.set_allowing("GET", true); + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you can run the following command from a terminal: + + curl -XGET -v http://localhost:8080/hello + +If you try to run the following command, you'll see a `method_not_allowed` error: +* `curl -XPOST -v http://localhost:8080/hello`. + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/allowing_disallowing_methods.cpp). + +[Back to TOC](#table-of-contents) + +## Registering resources +Once you have created your resource and extended its methods, you'll have to register the resource on the webserver. Registering a resource will associate it with an endpoint and allows the webserver to route it. +The `webserver` class offers a method to register a resource: +* _**bool** register_resource(**const std::string&** endpoint, **http_resource*** resource, **bool** family = `false`):_ Registers the `resource` to an `endpoint`. The endpoint is a string representing the path on your webserver from where you want your resource to be served from (e.g. `"/path/to/resource"`). The optional `family` parameter allows to register a resource as a "family" resource that will match any path nested into the one specified. For example, if family is set to `true` and endpoint is set to `"/path"`, the webserver will route to the resource not only the requests against `"/path"` but also everything in its nested path `"/path/on/the/previous/one"`. + +### Specifying endpoints +There are essentially four ways to specify an endpoint string: +* **A simple path (e.g. `"/path/to/resource"`).** In this case, the webserver will try to match exactly the value of the endpoint. +* **A regular exception.** In this case, the webserver will try to match the URL of the request with the regex passed. For example, if passing `"/path/as/decimal/[0-9]+`, requests on URLs like `"/path/as/decimal/5"` or `"/path/as/decimal/42"` will be matched; instead, URLs like `"/path/as/decimal/three"` will not. +* **A parametrized path. (e.g. `"/path/to/resource/with/{arg1}/{arg2}/in/url"`)**. In this case, the webserver will match the argument with any value passed. In addition to this, the arguments will be passed to the resource as part of the arguments (readable from the `http_request::get_arg` method - see [here](#parsing-requests)). For example, if passing `"/path/to/resource/with/{arg1}/{arg2}/in/url"` will match any request on URL with any value in place of `{arg1}` and `{arg2}`. +* **A parametrized path with custom parameters.** This is the same of a normal parametrized path, but allows to specify a regular expression for the argument (e.g. `"/path/to/resource/with/{arg1|[0-9]+}/{arg2|[a-z]+}/in/url"`. In this case, the webserver will match the arguments with any value passed that satisfies the regex. In addition to this, as above, the arguments will be passed to the resource as part of the arguments (readable from the `http_request::get_arg` method - see [here](#parsing-requests)). For example, if passing `"/path/to/resource/with/{arg1|[0-9]+}/{arg2|[a-z]+}/in/url"` will match requests on URLs like `"/path/to/resource/with/10/AA/in/url"` but not like `""/path/to/resource/with/BB/10/in/url""` +* Any of the above marked as `family`. Will match any request on URLs having path that is prefixed by the path passed. For example, if family is set to `true` and endpoint is set to `"/path"`, the webserver will route to the resource not only the requests against `"/path"` but also everything in its nested path `"/path/on/the/previous/one"`. +```cpp + #include + + using namespace httpserver; + + class hello_world_resource : public http_resource { + public: + std::shared_ptr render(const http_request&) { + return std::shared_ptr(new string_response("Hello, World!")); + } + }; + + class handling_multiple_resource : public http_resource { + public: + std::shared_ptr render(const http_request& req) { + return std::shared_ptr(new string_response("Your URL: " + req.get_path())); + } + }; + + class url_args_resource : public http_resource { + public: + std::shared_ptr render(const http_request& req) { + std::string arg1(req.get_arg("arg1")); + std::string arg2(req.get_arg("arg2")); + return std::shared_ptr(new string_response("ARGS: " + arg1 + " and " + arg2)); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + + handling_multiple_resource hmr; + ws.register_resource("/family", &hmr, true); + ws.register_resource("/with_regex_[0-9]+", &hmr); + + url_args_resource uar; + ws.register_resource("/url/with/{arg1}/and/{arg2}", &uar); + ws.register_resource("/url/with/parametric/args/{arg1|[0-9]+}/and/{arg2|[A-Z]+}", &uar); + + ws.start(true); + + return 0; + } +``` +To test the above example, you can run the following commands from a terminal: + +* `curl -XGET -v http://localhost:8080/hello`: will return the `Hello, World!` message. +* `curl -XGET -v http://localhost:8080/family`: will return the `Your URL: /family` message. +* `curl -XGET -v http://localhost:8080/family/with/suffix`: will return the `Your URL: /family/with/suffix` message. +* `curl -XGET -v http://localhost:8080/with_regex_10`: will return the `Your URL: /with_regex_10` message. +* `curl -XGET -v http://localhost:8080/url/with/AA/and/BB`: will return the `ARGS: AA and BB` message. You can change `AA` and `BB` with any value and observe how the URL is still matched and parameters are read. +* `curl -XGET -v http://localhost:8080/url/with/parametric/args/10/and/AA`: will return the `ARGS: 10 and AA` message. You can change `10` and `AA` with any value matching the regexes and observe how the URL is still matched and parameters are read. + +Conversely, you can observe how these URL will not be matched (al the following will give you a `not found` message): +* `curl -XGET -v http://localhost:8080/with_regex_A` +* `curl -XGET -v http://localhost:8080/url/with/parametric/args/AA/and/BB` + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/url_registration.cpp). + +[Back to TOC](#table-of-contents) + +## Parsing requests +As seen in the documentation of [http_resource](#the-resource-object), every extensible method takes in input a `http_request` object. The webserver takes the responsibility to extract the data from the HTTP request on the network and does all the heavy lifting to build the instance of `http_request`. + +The `http_request` class has a set of methods you will have access to when implementing your handlers: +* _**const std::string&** get_path() **const**:_ Returns the path as requested from the HTTP client. +* _**const std::vector\&** get_path_pieces() **const**:_ Returns the components of the path requested by the HTTP client (each piece of the path split by `'/'`. +* _**const std::string&** get_path_piece(int index) **const**:_ Returns one piece of the path requested by the HTTP client. The piece is selected through the `index` parameter (0-indexed). +* _**const std::string&** get_method() **const**:_ Returns the method requested by the HTTP client. +* _**std::string_view** get_header(**std::string_view** key) **const**:_ Returns the header with name equal to `key` if present in the HTTP request. Returns an `empty string` otherwise. +* _**std::string_view** get_cookie(**std::string_view** key) **const**:_ Returns the cookie with name equal to `key` if present in the HTTP request. Returns an `empty string` otherwise. +* _**std::string_view** get_footer(**std::string_view** key) **const**:_ Returns the footer with name equal to `key` if present in the HTTP request (only for http 1.1 chunked encodings). Returns an `empty string` otherwise. +* _**http_arg_value** get_arg(**std::string_view** key) **const**:_ Returns the argument with name equal to `key` if present in the HTTP request. Arguments can be (1) querystring parameters, (2) path argument (in case of parametric endpoint, (3) parameters parsed from the HTTP request body if the body is in `application/x-www-form-urlencoded` or `multipart/form-data` formats and the postprocessor is enabled in the webserver (enabled by default). Arguments are collected in a domain object that allows to collect multiple arguments with the same key while keeping the access transparent in the most common case of a single value provided per key. +* _**std::string_view** get_arg_flat(**std::string_view** key) **const**_ Returns the argument in the same way as `get_arg` but as a string. If multiple values are provided with the same key, this method only returns the first value provided. +* _**const std::map** get_headers() **const**:_ Returns a map containing all the headers present in the HTTP request. +* _**const std::map** get_cookies() **const**:_ Returns a map containing all the cookies present in the HTTP request. +* _**const std::map** get_footers() **const**:_ Returns a map containing all the footers present in the HTTP request (only for http 1.1 chunked encodings). +* _**const std::map** get_args() **const**:_ Returns all the arguments present in the HTTP request. Arguments can be (1) querystring parameters, (2) path argument (in case of parametric endpoint, (3) parameters parsed from the HTTP request body if the body is in `application/x-www-form-urlencoded` or `multipart/form-data` formats and the postprocessor is enabled in the webserver (enabled by default). For each key, arguments are collected in a domain object that allows to collect multiple arguments with the same key while keeping the access transparent in the most common case of a single value provided per key. +* _**const std::map** get_args_flat() **const**:_ Returns all the arguments as the `get_args` method but as strings. If multiple values are provided with the same key, this method will only return the first value provided. +* _**const std::map>** get_files() **const**:_ Returns information about all the uploaded files (if the files are stored to disk). This information includes the key (as identifier of the outer map), the original file name (as identifier of the inner map) and a class `file_info`, which includes the size of the file and the path to the file in the file system. +* _**const std::string&** get_content() **const**:_ Returns the body of the HTTP request. +* _**bool** content_too_large() **const**:_ Returns `true` if the body length of the HTTP request sent by the client is longer than the max allowed on the server. +* _**const std::string** get_querystring() **const**:_ Returns the `querystring` of the HTTP request. +* _**const std::string&** get_version() **const**:_ Returns the HTTP version of the client request. +* _**const std::string** get_requestor() **const**:_ Returns the IP from which the client is sending the request. +* _**unsigned short** get_requestor_port() **const**:_ Returns the port from which the client is sending the request. +* _**const std::string** get_user() **const**:_ Returns the `user` as self-identified through basic authentication. The content of the user header will be parsed only if basic authentication is enabled on the server (enabled by default). +* _**const std::string** get_pass() **const**:_ Returns the `password` as self-identified through basic authentication. The content of the password header will be parsed only if basic authentication is enabled on the server (enabled by default). +* _**const std::string** get_digested_user() **const**:_ Returns the `digested user` as self-identified through digest authentication. The content of the user header will be parsed only if digest authentication is enabled on the server (enabled by default). +* _**bool** check_digest_auth(**const std::string&** realm, **const std::string&** password, **int** nonce_timeout, **bool*** reload_nonce) **const**:_ Allows to check the validity of the authentication token sent through digest authentication (if the provided values in the WWW-Authenticate header are valid and sound according to RFC2716). Takes in input the `realm` of validity of the authentication, the `password` as known to the server to compare against, the `nonce_timeout` to indicate how long the nonce is valid and `reload_nonce` a boolean that will be set by the method to indicate a nonce being reloaded. The method returns `true` if the authentication is valid, `false` otherwise. +* _**bool** has_tls_session() **const**:_ Tests if there is an underlying TLS state of the current request. +* _**gnutls_session_t** get_tls_session() **const**:_ Returns the underlying TLS state of the current request for inspection. (It is an error to call this if the state does not exist.) +* _**bool** has_client_certificate() **const**:_ Returns `true` if the client presented a certificate during the TLS handshake. Requires GnuTLS support. +* _**std::string** get_client_cert_dn() **const**:_ Returns the Distinguished Name (DN) from the client certificate's subject field (e.g., "CN=John Doe,O=Example Corp"). Returns empty string if no client certificate. +* _**std::string** get_client_cert_issuer_dn() **const**:_ Returns the Distinguished Name of the certificate issuer. Returns empty string if no client certificate. +* _**std::string** get_client_cert_cn() **const**:_ Returns the Common Name (CN) from the client certificate's subject. Returns empty string if no client certificate or no CN field. +* _**bool** is_client_cert_verified() **const**:_ Returns `true` if the client certificate was verified against the trust store configured via `https_mem_trust()`. Returns `false` if verification failed or no TLS session. +* _**std::string** get_client_cert_fingerprint_sha256() **const**:_ Returns the SHA-256 fingerprint of the client certificate as a lowercase hex string (64 characters). Returns empty string if no client certificate. +* _**time_t** get_client_cert_not_before() **const**:_ Returns the start of the certificate validity period. Returns -1 if no client certificate. +* _**time_t** get_client_cert_not_after() **const**:_ Returns the end of the certificate validity period. Returns -1 if no client certificate. + +Details on the `http::file_info` structure. + +* _**size_t** get_file_size() **const**:_ Returns the size of the file uploaded through the HTTP request. +* _**const std::string** get_file_system_file_name() **const**:_ Returns the name of the file uploaded through the HTTP request as stored on the filesystem. +* _**const std::string** get_content_type() **const**:_ Returns the content type of the file uploaded through the HTTP request. +* _**const std::string** get_transfer_encoding() **const**:_ Returns the transfer encoding of the file uploaded through the HTTP request. + +#### Example of keeping uploaded files +By default, uploaded files are automatically deleted when the request completes. To keep files (e.g., move them to permanent storage), use the `file_cleanup_callback`: + +```cpp +#include +#include + +using namespace httpserver; + +int main() { + webserver ws = create_webserver(8080) + .file_upload_target(FILE_UPLOAD_DISK_ONLY) + .file_upload_dir("/tmp/uploads") + .file_cleanup_callback([](const std::string& key, + const std::string& filename, + const http::file_info& info) { + // Move file to permanent storage + std::string dest = "/var/uploads/" + filename; + std::rename(info.get_file_system_file_name().c_str(), dest.c_str()); + return false; // Don't delete - we moved it + }); + + // ... register resources and start server +} +``` +To test file uploads, you can run the following command from a terminal: + + curl -XPOST -F "file=@/path/to/your/file.txt" 'http://localhost:8080/upload' + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/file_upload_with_callback.cpp). + +Details on the `http_arg_value` structure. + +* _**std::string_view** get_flat_value() **const**:_ Returns only the first value provided for the key. +* _**std::vector\** get_all_values() **const**:_ Returns all the values provided for the key. +* _**operator** std::string() **const**:_ Converts the http_arg_value to a string with the same logic as `get_flat_value`. +* _**operator** std::string_view() **const**:_ Converts the http_arg_value to a string_view with the same logic as `get_flat_value`. +* _**operator** std::vector\() **const**:_ Converts the http_arg_value to a std::vector with the same logic as `get_value`. + +#### Example of handler reading arguments from a request +```cpp + #include + + using namespace httpserver; + + class hello_world_resource : public http_resource { + public: + std::shared_ptr render(const http_request& req) { + return std::shared_ptr(new string_response("Hello: " + std::string(req.get_arg("name")))); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you can run the following command from a terminal: + + curl -XGET -v "http://localhost:8080/hello?name=John" + +You will receive the message `Hello: John` in reply. Given that the body post processing is enabled, you can also run `curl -d "name=John" -X POST http://localhost:8080/hello` to obtain the same result. + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/hello_with_get_arg.cpp). + +[Back to TOC](#table-of-contents) + +## Building responses to requests +As seen in the documentation of [http_resource](#the-resource-object), every extensible method returns in output a `http_response` object. The webserver takes the responsibility to convert the `http_response` object you create into a response on the network. + +There are 5 types of response that you can create - we will describe them here through their constructors: +* _string_response(**const std::string&** content, **int** response_code = `200`, **const std::string&** content_type = `"text/plain"`):_ The most basic type of response. It uses the `content` string passed in construction as body of the HTTP response. The other two optional parameters are the `response_code` and the `content_type`. You can find constant definition for the various response codes within the [http_utils](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp) library file. Note that `std::string` can hold arbitrary binary data (including null bytes), so `string_response` is also the right choice for serving binary content such as images directly from memory — simply set an appropriate `content_type` (e.g., `"image/png"`). +* _file_response(**const std::string&** filename, **int** response_code = `200`, **const std::string&** content_type = `"text/plain"`):_ Uses the `filename` passed in construction as pointer to a file on disk. The body of the HTTP response will be set using the content of the file. The file must be a regular file and exist on disk. Otherwise libhttpserver will return an error 500 (Internal Server Error). The other two optional parameters are the `response_code` and the `content_type`. You can find constant definition for the various response codes within the [http_utils](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp) library file. +* _basic_auth_fail_response(**const std::string&** content, **const std::string&** realm = `""`, **int** response_code = `200`, **const std::string&** content_type = `"text/plain"`):_ A response in return to a failure during basic authentication. It allows to specify a `content` string as a message to send back to the client. The `realm` parameter should contain your realm of authentication (if any). The other two optional parameters are the `response_code` and the `content_type`. You can find constant definition for the various response codes within the [http_utils](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp) library file. +* _digest_auth_fail_response(**const std::string&** content, **const std::string&** realm = `""`, **const std::string&** opaque = `""`, **bool** reload_nonce = `false`, **int** response_code = `200`, **const std::string&** content_type = `"text/plain"`):_ A response in return to a failure during digest authentication. It allows to specify a `content` string as a message to send back to the client. The `realm` parameter should contain your realm of authentication (if any). The `opaque` represents a value that gets passed to the client and expected to be passed again to the server as-is. This value can be a hexadecimal or base64 string. The `reload_nonce` parameter tells the server to reload the nonce (you should use the value returned by the `check_digest_auth` method on the `http_request`. The other two optional parameters are the `response_code` and the `content_type`. You can find constant definition for the various response codes within the [http_utils](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp) library file. +* _deferred_response(**ssize_t(*cycle_callback_ptr)(shared_ptr<T>, char*, size_t)** cycle_callback, **const std::string&** content = `""`, **int** response_code = `200`, **const std::string&** content_type = `"text/plain"`):_ A response that obtains additional content from a callback executed in a deferred way. It leaves the client in pending state (returning a `100 CONTINUE` message) and suspends the connection. Besides the callback, optionally, you can provide a `content` parameter that sets the initial message sent immediately to the client. The other two optional parameters are the `response_code` and the `content_type`. You can find constant definition for the various response codes within the [http_utils](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp) library file. To use `deferred_response` you need to have the `deferred` option active on your webserver (enabled by default). + * The `cycle_callback_ptr` has this shape: + _**ssize_t** cycle_callback(**shared_ptr<T> closure_data, char*** buf, **size_t** max_size)_. + You are supposed to implement a function in this shape and provide it to the `deferred_repsonse` method. The webserver will provide a `char*` to the function. It is responsibility of the function to allocate it and fill its content. The method is supposed to respect the `max_size` parameter passed in input. The function must return a `ssize_t` value representing the actual size you filled the `buf` with. Any value different from `-1` will keep the resume the connection, deliver the content and suspend it again (with a `100 CONTINUE`). If the method returns `-1`, the webserver will complete the communication with the client and close the connection. You can also pass a `shared_ptr` pointing to a data object of your choice (this will be templetized with a class of your choice). The server will guarantee that this object is passed at each invocation of the method allowing the client code to use it as a memory buffer during computation. + +### Setting additional properties of the response +The `http_response` class offers an additional set of methods to "decorate" your responses. This set of methods is: +* _**void** with_header(**const std::string&** key, **const std::string&** value):_ Sets an HTTP header with name set to `key` and value set to `value`. +* _**void** with_footer(**const std::string&** key, **const std::string&** value):_ Sets an HTTP footer with name set to `key` and value set to `value`. +* _**void** with_cookie(**const std::string&** key, **const std::string&** value):_ Sets an HTTP cookie with name set to `key` and value set to `value` (only for http 1.1 chunked encodings). +* _**void** shoutCAST():_ Mark the response as a `shoutCAST` one. + +### Example of response setting headers +```cpp + #include + + using namespace httpserver; + + class hello_world_resource : public http_resource { + public: + std::shared_ptr render(const http_request&) { + std::shared_ptr response = std::shared_ptr(new string_response("Hello, World!")); + response->with_header("MyHeader", "MyValue"); + return response; + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you could run the following command from a terminal: + + curl -XGET -v "http://localhost:8080/hello" + +You will receive the message custom header in reply. + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/setting_headers.cpp). + +### Serving binary data from memory +`string_response` is not limited to text — it can serve arbitrary binary content directly from memory. This is useful when you have data in a buffer at runtime (e.g., from a camera, an image processing library, or a database) and want to serve it without writing to disk. + +```cpp + #include + + using namespace httpserver; + + class image_resource : public http_resource { + public: + std::shared_ptr render_GET(const http_request&) { + // binary_data could come from a camera capture, image library, etc. + std::string binary_data = get_image_bytes_from_camera(); + + return std::make_shared( + std::move(binary_data), 200, "image/jpeg"); + } + }; + + int main() { + webserver ws = create_webserver(8080); + + image_resource ir; + ws.register_resource("/image", &ir); + ws.start(true); + + return 0; + } +``` +To test the above example, you could run the following command from a terminal: + + curl -o image.jpg http://localhost:8080/image + +You can also check the complete example on [github](https://github.com/etr/libhttpserver/blob/master/examples/binary_buffer_response.cpp). + +[Back to TOC](#table-of-contents) + +## IP Blacklisting and Whitelisting +libhttpserver provides natively a system to blacklist and whitelist IP addresses. To enable/disable the system, it is possible to use the `ban_system` and `no_ban_system` methods on the `create_webserver` class. In the same way, you can specify what you want to be your "default behavior" (allow by default or disallow by default) by using the `default_policy` method (see [here](#create-and-work-with-a-webserver)). + +The system supports both IPV4 and IPV6 and manages them transparently. The only requirement is for ipv6 to be enabled on your server - you'll have to enable this by using the `use_ipv6` method on `create_webserver`. + +You can explicitly ban or allow an IP address using the following methods on the `webserver` class: +* _**void** ban_ip(**const std::string&** ip):_ Adds one IP (or a range of IPs) to the list of the banned ones. Takes in input a `string` that contains the IP (or range of IPs) to ban. To use when the `default_policy` is `ACCEPT`. +* _**void** allow_ip(**const std::string&** ip):_ Adds one IP (or a range of IPs) to the list of the allowed ones. Takes in input a `string` that contains the IP (or range of IPs) to allow. To use when the `default_policy` is `REJECT`. +* _**void** unban_ip(**const std::string&** ip):_ Removes one IP (or a range of IPs) from the list of the banned ones. Takes in input a `string` that contains the IP (or range of IPs) to remove from the list. To use when the `default_policy` is `ACCEPT`. +* _**void** disallow_ip(**const std::string&** ip):_ Removes one IP (or a range of IPs) from the list of the allowed ones. Takes in input a `string` that contains the IP (or range of IPs) to remove from the list. To use when the `default_policy` is `REJECT`. + +### IP String Format +The IP string format can represent both IPV4 and IPV6. Addresses will be normalized by the webserver to operate in the same sapce. Any valid IPV4 or IPV6 textual representation works. +It is also possible to specify ranges of IPs. To do so, omit the octect you want to express as a range and specify a `'*'` in its place. +Examples of valid IPs include: +* `"192.168.5.5"`: standard IPV4 +* `"192.168.*.*"`: range of IPV4 addresses. In the example, everything between `192.168.0.0` and `192.168.255.255`. +* `"2001:db8:8714:3a90::12"`: standard IPV6 - clustered empty ranges are fully supported. +* `"2001:db8:8714:3a90:*:*"`: range of IPV6 addresses. +* `"::ffff:192.0.2.128"`: IPV4 IPs nested into IPV6. +* `"::192.0.2.128"`: IPV4 IPs nested into IPV6 (without `'ffff'` prefix) +* `"::ffff:192.0.*.*"`: ranges of IPV4 IPs nested into IPV6. + +#### Example of IP Whitelisting/Blacklisting +```cpp + #include + + using namespace httpserver; + + class hello_world_resource : public http_resource { + public: + std::shared_ptr render(const http_request&) { + return std::shared_ptr(new string_response("Hello, World!")); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080) + .default_policy(http::http_utils::REJECT); + + ws.allow_ip("127.0.0.1"); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you could run the following command from a terminal: + + curl -XGET -v "http://localhost:8080/hello" + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/minimal_ip_ban.cpp). + +[Back to TOC](#table-of-contents) + +## Authentication +libhttpserver support three types of client authentication. + +Basic authentication uses a simple authentication method based on BASE64 algorithm. Username and password are exchanged in clear between the client and the server, so this method must only be used for non-sensitive content or when the session is protected with https. When using basic authentication libhttpserver will have access to the clear password, possibly allowing to create a chained authentication toward an external authentication server. You can enable/disable support for Basic authentication through the `basic_auth` and `no_basic_auth` methods of the `create_webserver` class. + +Digest authentication uses a one-way authentication method based on MD5 hash algorithm. Only the hash will transit over the network, hence protecting the user password. The nonce will prevent replay attacks. This method is appropriate for general use, especially when https is not used to encrypt the session. You can enable/disable support for Digest authentication through the `digest_auth` and `no_digest_auth` methods of the `create_webserver` class. + +Client certificate authentication uses a X.509 certificate from the client. This is the strongest authentication mechanism but it requires the use of HTTPS. Client certificate authentication can be used simultaneously with Basic or Digest Authentication in order to provide a two levels authentication (like for instance separate machine and user authentication). You can enable/disable support for Certificate authentication through the `use_ssl` and `no_ssl` methods of the `create_webserver` class. + +### Using Basic Authentication +```cpp + #include + + using namespace httpserver; + + class user_pass_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const http_request& req) { + if (req.get_user() != "myuser" || req.get_pass() != "mypass") { + return std::shared_ptr(new basic_auth_fail_response("FAIL", "test@example.com")); + } + return std::shared_ptr(new string_response(req.get_user() + " " + req.get_pass(), 200, "text/plain")); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080); + + user_pass_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you can run the following command from a terminal: + + curl -XGET -v -u myuser:mypass "http://localhost:8080/hello" + +You will receive back the user and password you passed in input. Try to pass the wrong credentials to see the failure. + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/basic_authentication.cpp). + +### Using Digest Authentication +```cpp + #include + + #define MY_OPAQUE "11733b200778ce33060f31c9af70a870ba96ddd4" + + using namespace httpserver; + + class digest_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const http_request& req) { + if (req.get_digested_user() == "") { + return std::shared_ptr(new digest_auth_fail_response("FAIL", "test@example.com", MY_OPAQUE, true)); + } + else { + bool reload_nonce = false; + if(!req.check_digest_auth("test@example.com", "mypass", 300, reload_nonce)) { + return std::shared_ptr(new digest_auth_fail_response("FAIL", "test@example.com", MY_OPAQUE, reload_nonce)); + } + } + return std::shared_ptr(new string_response("SUCCESS", 200, "text/plain")); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080); + + digest_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you can run the following command from a terminal: + + curl -XGET -v --digest --user myuser:mypass localhost:8080/hello + +You will receive a `SUCCESS` in response (observe the response message from the server in detail and you'll see the full interaction). Try to pass the wrong credentials or send a request without `digest` active to see the failure. + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/digest_authentication.cpp). + +### Using Centralized Authentication +The examples above show authentication handled within each resource's `render_*` method. This approach requires duplicating authentication logic in every resource, which is error-prone and violates DRY (Don't Repeat Yourself) principles. + +libhttpserver provides a centralized authentication mechanism that runs a single authentication handler before any resource's render method is called. This allows you to: +- Define authentication logic once for all resources +- Automatically protect all endpoints by default +- Specify paths that should bypass authentication (e.g., health checks, public APIs) + +```cpp + #include + + using namespace httpserver; + + // Resources no longer need authentication logic + class hello_resource : public http_resource { + public: + std::shared_ptr render_GET(const http_request&) { + return std::make_shared("Hello, authenticated user!", 200, "text/plain"); + } + }; + + class health_resource : public http_resource { + public: + std::shared_ptr render_GET(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } + }; + + // Centralized authentication handler + // Return nullptr to allow the request, or an http_response to reject it + std::shared_ptr my_auth_handler(const http_request& req) { + if (req.get_user() != "admin" || req.get_pass() != "secret") { + return std::make_shared("Unauthorized", "MyRealm"); + } + return nullptr; // Allow request to proceed to resource + } + + int main() { + webserver ws = create_webserver(8080) + .auth_handler(my_auth_handler) + .auth_skip_paths({"/health", "/public/*"}); + + hello_resource hello; + health_resource health; + + ws.register_resource("/api", &hello); + ws.register_resource("/health", &health); + + ws.start(true); + return 0; + } +``` + +The `auth_handler` callback is called for every request before the resource's render method. It receives the `http_request` and can: +- Return `nullptr` to allow the request to proceed normally +- Return an `http_response` (e.g., `basic_auth_fail_response` or `digest_auth_fail_response`) to reject the request + +The `auth_skip_paths` method accepts a vector of paths that should bypass authentication: +- Exact matches: `"/health"` matches only `/health` +- Wildcard suffixes: `"/public/*"` matches `/public/`, `/public/info`, `/public/docs/api`, etc. + +To test the above example: + + # Without auth - returns 401 Unauthorized + curl -v http://localhost:8080/api + + # With valid auth - returns 200 OK + curl -u admin:secret http://localhost:8080/api + + # Health endpoint (skip path) - works without auth + curl http://localhost:8080/health + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/centralized_authentication.cpp). + +### Using Client Certificate Authentication (mTLS) +Client certificate authentication (also known as mutual TLS or mTLS) provides strong authentication by requiring clients to present X.509 certificates during the TLS handshake. This is the most secure authentication method as it verifies client identity cryptographically. + +To enable client certificate authentication, configure your webserver with: +1. `use_ssl()` - Enable TLS +2. `https_mem_key()` and `https_mem_cert()` - Server certificate +3. `https_mem_trust()` - CA certificate(s) to verify client certificates + +```cpp + #include + + using namespace httpserver; + + class secure_resource : public http_resource { + public: + std::shared_ptr render_GET(const http_request& req) { + // Check if client provided a certificate + if (!req.has_client_certificate()) { + return std::make_shared( + "Client certificate required", 401, "text/plain"); + } + + // Check if certificate is verified by our CA + if (!req.is_client_cert_verified()) { + return std::make_shared( + "Certificate not verified", 403, "text/plain"); + } + + // Extract certificate information + std::string cn = req.get_client_cert_cn(); // Common Name + std::string dn = req.get_client_cert_dn(); // Subject DN + std::string issuer = req.get_client_cert_issuer_dn(); // Issuer DN + std::string fingerprint = req.get_client_cert_fingerprint_sha256(); + time_t not_before = req.get_client_cert_not_before(); + time_t not_after = req.get_client_cert_not_after(); + + return std::make_shared( + "Welcome, " + cn + "!", 200, "text/plain"); + } + }; + + int main() { + webserver ws = create_webserver(8443) + .use_ssl() + .https_mem_key("server_key.pem") + .https_mem_cert("server_cert.pem") + .https_mem_trust("ca_cert.pem"); // CA for client certs + + secure_resource sr; + ws.register_resource("/secure", &sr); + ws.start(true); + + return 0; + } +``` + +Available client certificate methods (require GnuTLS support): +- `has_client_certificate()` - Check if client presented a certificate +- `get_client_cert_dn()` - Get the subject Distinguished Name +- `get_client_cert_issuer_dn()` - Get the issuer Distinguished Name +- `get_client_cert_cn()` - Get the Common Name from the subject +- `is_client_cert_verified()` - Check if the certificate chain is verified +- `get_client_cert_fingerprint_sha256()` - Get hex-encoded SHA-256 fingerprint +- `get_client_cert_not_before()` - Get certificate validity start time +- `get_client_cert_not_after()` - Get certificate validity end time + +To test with curl: + + # With client certificate + curl -k --cert client_cert.pem --key client_key.pem https://localhost:8443/secure + + # Without client certificate (will be rejected) + curl -k https://localhost:8443/secure + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/client_cert_auth.cpp). + +### Server Name Indication (SNI) Callback +SNI allows a server to host multiple TLS certificates on a single IP address. The client indicates which hostname it's connecting to during the TLS handshake, and the server can select the appropriate certificate. + +To use SNI with libhttpserver, configure an SNI callback that returns the certificate/key pair for each server name: + +```cpp + #include + #include + + using namespace httpserver; + + // Map of server names to cert/key pairs + std::map> certs; + + // SNI callback - returns (cert_pem, key_pem) for the requested server name + std::pair sni_callback(const std::string& server_name) { + auto it = certs.find(server_name); + if (it != certs.end()) { + return it->second; + } + return {"", ""}; // Use default certificate + } + + int main() { + // Load certificates for different hostnames + certs["www.example.com"] = {load_file("www_cert.pem"), load_file("www_key.pem")}; + certs["api.example.com"] = {load_file("api_cert.pem"), load_file("api_key.pem")}; + + webserver ws = create_webserver(443) + .use_ssl() + .https_mem_key("default_key.pem") // Default certificate + .https_mem_cert("default_cert.pem") + .sni_callback(sni_callback); // SNI callback + + // ... register resources and start + ws.start(true); + return 0; + } +``` + +Note: SNI support requires libmicrohttpd 0.9.71 or later compiled with GnuTLS. + +[Back to TOC](#table-of-contents) + +## HTTP Utils +libhttpserver provides a set of constants to help you develop your HTTP server. It would be redundant to list them here; so, please, consult the list directly [here](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp). + +[Back to TOC](#table-of-contents) + +## Other Examples + +#### Example of returning a response from a file +```cpp + #include + + using namespace httpserver; + + class file_response_resource : public http_resource { + public: + std::shared_ptr render_GET(const http_request& req) { + return std::shared_ptr(new file_response("test_content", 200, "text/plain")); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080); + + file_response_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you can run the following command from a terminal: + + curl -XGET -v localhost:8080/hello + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/minimal_file_response.cpp). + +#### Example of a deferred response through callback +```cpp + #include + + using namespace httpserver; + + static int counter = 0; + + ssize_t test_callback (std::shared_ptr closure_data, char* buf, size_t max) { + if (counter == 2) { + return -1; + } + else { + memset(buf, 0, max); + strcat(buf, " test "); + counter++; + return std::string(buf).size(); + } + } + + class deferred_resource : public http_resource { + public: + std::shared_ptr render_GET(const http_request& req) { + return std::shared_ptr >(new deferred_response(test_callback, nullptr, "cycle callback response")); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080); + + deferred_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you can run the following command from a terminal: + + curl -XGET -v localhost:8080/hello + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/minimal_deferred.cpp). + +#### Example of a deferred response through callback (passing additional data along) +```cpp + #include + #include + + using namespace httpserver; + + std::atomic counter; + + ssize_t test_callback (std::shared_ptr > closure_data, char* buf, size_t max) { + int reqid; + if (closure_data == nullptr) { + reqid = -1; + } else { + reqid = *closure_data; + } + + // only first 5 connections can be established + if (reqid >= 5) { + return -1; + } else { + // respond corresponding request IDs to the clients + std::string str = ""; + str += std::to_string(reqid) + " "; + memset(buf, 0, max); + std::copy(str.begin(), str.end(), buf); + + // keep sending reqid + sleep(1); + + return (ssize_t)max; + } + } + + class deferred_resource : public http_resource { + public: + std::shared_ptr render_GET(const http_request& req) { + std::shared_ptr > closure_data(new std::atomic(counter++)); + return std::shared_ptr > >(new deferred_response >(test_callback, closure_data, "cycle callback response")); + } + }; + + int main(int argc, char** argv) { + webserver ws = create_webserver(8080); + + deferred_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; + } +``` +To test the above example, you can run the following command from a terminal: + + curl -XGET -v localhost:8080/hello + +You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/deferred_with_accumulator.cpp). + +[Back to TOC](#table-of-contents) + +## Copying +This manual is for libhttpserver, C++ library for creating an embedded Rest HTTP server (and more). > Permission is granted to copy, distribute and/or modify this document > under the terms of the GNU Free Documentation License, Version 1.3 @@ -75,116 +1378,9 @@ embedded Rest HTTP server (and more). > Texts. A copy of the license is included in the section entitled GNU > Free Documentation License. -Contents -======== -* Introduction. -* Requirements. -* Compilation. -* Constants. -* Structures and classes type definition. -* Callback functions definition. -* Create and work with server. -* Registering resources. -* Building responses to requests. -* Whitelists and Blacklists. -* Simple comet semantics. -* Utilizing Authentication. -* Obtaining and modifying status information. - -Appendices ----------- -* GNU-LGPL: The GNU Lesser General Public License says how you can copy and share almost all of libhttpserver. -* GNU-FDL: The GNU Free Documentation License says how you can copy and share the documentation of libhttpserver. - -Introduction -============ -libhttpserver is meant to constitute an easy system to build HTTP -servers with REST fashion. -libhttpserver is based on libmicrohttpd and, like this, it is a -daemon library. -The mission of this library is to support all possible HTTP features -directly and with a simple semantic allowing then the user to concentrate -only on his application and not on HTTP request handling details. - -The library is supposed to work transparently for the client Implementing -the business logic and using the library itself to realize an interface. -If the user wants it must be able to change every behavior of the library -itself through the registration of callbacks. - -Like the api is based on (libmicrohttpd), libhttpserver is able to decode -certain body format a and automatically format them in object oriented -fashion. This is true for query arguments and for *POST* and *PUT* -requests bodies if *application/x-www-form-urlencoded* or -*multipart/form-data* header are passed. - -The header reproduce all the constants defined by libhttpserver. -These maps various constant used by the HTTP protocol that are exported -as a convenience for users of the library. Is is possible for the user -to define their own extensions of the HTTP standard and use those with -libhttpserver. - -All functions are guaranteed to be completely reentrant and -thread-safe (unless differently specified). -Additionally, clients can specify resource limits on the overall -number of connections, number of connections per IP address and memory -used per connection to avoid resource exhaustion. - -Requirements -============ -* g++ >= 4.1.2 -* libmicrohttpd >= 0.9.37 -* doxygen (if you want to build code reference) - -Additionally, for MinGW on windows you will need: -* libwinpthread (For MinGW-w64, if you use thread model posix then you have this) -* libgnurx >= 2.5.1 - -Compilation -=========== -libhttpserver uses the standard system where the usual build process -involves running -> ./bootstrap -> mkdir build -> cd build -> ../configure -> make -> make install -> make doxygen-doc # if you want to build the code reference - -Optional parameters to configure script ---------------------------------------- -A complete list of parameters can be obtained running 'configure --help'. -Here are listed the libhttpserver specific options (the canonical configure options are also supported). - -* --enable-same-directory-build: enable to compile in the same directory. This is heavily discouraged. (def=no) -* --enable-debug: enable debug data generation (def=no) -* --enable-cpp11: enable c++11 std classes (def=no) -* --disable-doxygen-doc: don't generate any doxygen documentation -* --disable-doxygen-dot: don't generate graphics for doxygen documentation -* --disable-doxygen-man: don't generate doxygen manual pages -* --enable-doxygen-rtf: generate doxygen RTF documentation -* --enable-doxygen-xml: generate doxygen XML documentation -* --enable-doxygen-chm: generate doxygen compressed HTML help documentation -* --enable-doxygen-chi: generate doxygen seperate compressed HTML help index file -* --disable-doxygen-html: don't generate doxygen plain HTML documentation -* --enable-doxygen-ps: generate doxygen PostScript documentation -* --enable-doxygen-pdf: generate doxygen PDF documentation - -Constants -========= -W.I.P. - -Structures and classes type definition -====================================== -* http_resource (CPP class): Represents the resource associated with a specific http endpoint. -* http_request (CPP class): Represents the request received by the resource that process it. -* http_response (CPP class): Represents the response sent by the server once the resource finished its work. -* event_supplier (CPP class): Represents a class that supplies events to the webserver. It can be used to trigger the internal select of it. -* webserver (CPP class): Represents the daemon listening on a socket for HTTP traffic. - -GNU Lesser General Public License -================================= +[Back to TOC](#table-of-contents) +## GNU Lesser General Public License Version 2.1, February 1999 Copyright © 1991, 1999 Free Software Foundation, Inc. @@ -677,8 +1873,9 @@ necessary. Here is a sample; alter the names: That's all there is to it! -GNU Free Documentation License -============================== +[Back to TOC](#table-of-contents) + +## GNU Free Documentation License Version 1.3, 3 November 2008 @@ -1128,3 +2325,59 @@ If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software. + +[Back to TOC](#table-of-contents) + +## Thanks + +This library has been originally developed under the zencoders flags and this community has always supported me all along this work so I am happy to put the logo on this readme. + + When you see this tree, know that you've came across ZenCoders + + with open('ZenCoders. + `num` in numbers synchronized + datetime d glob. sys.argv[2] . + def myclass `..` @@oscla org. . class { + displ hooks( public static void ma functor: + $myclass->method( impport sys, os.pipe ` @param name` + fcl if(system(cmd) myc. /de ` $card( array("a" srand + format lists: ++: conc ++ "my an WHERE for( == myi + `sys: myvalue(myvalue) sys.t Console.W try{ rais using + connec SELECT * FROM table mycnf acco desc and or selector::clas at + openldap string sys. print "zenc der " { 'a': `ls -l` > appe &firs + import Tkinter paste( $obh &a or it myval bro roll: :: [] require a + case `` super. +y expr say " %rooms 1 --account fb- yy + proc meth Animate => send(D, open) putd EndIf 10 whi myc` cont + and main (--) import loop $$ or end onload UNION WITH tab timer 150 *2 + end. begin True GtkLabel *label doto partition te let auto i<- (i + d ); + .mushup ``/. ^/zenc/ myclass->her flv op <> element >> 71 or + QFileDi : and .. with myc toA channel::bo myc isEmpty a not bodt; + class T public pol str mycalc d pt &&a *i fc add ^ac + ::ZenCoders::core::namespac boost::function st f = std: ;; int assert + cout << endl public genera #include "b ost ::ac myna const cast mys + ac size_t return ran int (*getNextValue)(void) ff double sa_family_t famil + pu a do puts(" ac int main(int argc, char* "%5d struct nam + cs float for typedef enum puts getchar() + if( else #define fp FILE* f char* s + i++ strcat( %s int + 31] total+= do + }do while(1) sle + getc strcpy( a for + prin scanf(%d, & get + int void myfunc(int pa retu + BEQ BNEQZ R1 10 ANDI R1 R2 SYS + XOR SYSCALL 5 SLTIU MFLO 15 SW JAL + BNE BLTZAL R1 1 LUI 001 NOOP MULTU SLLV + MOV R1 ADD R1 R2 JUMP 10 1001 BEQ R1 R2 1 ANDI + 1101 1010001100 111 001 01 1010 101100 1001 100 + 110110 100 0 01 101 01100 100 100 1000100011 + 11101001001 00 11 100 11 10100010 + 000101001001 10 1001 101000101 + 010010010010110101001010 + +For further information: +visit our website https://zencoders.github.io + +**Author:** Sebastiano Merlino + +[Back to TOC](#table-of-contents) diff --git a/bootstrap b/bootstrap index 67425d32..7de7c1be 100755 --- a/bootstrap +++ b/bootstrap @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # This file is part of libhttpserver # Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino @@ -26,11 +26,3 @@ else fi automake --add-missing autoconf - -# Optionally do the build as well. -if [ "$1" = "-build" -o "$1" = "--build" ] ; then - shift - ./configure "$@" - make - make check -fi diff --git a/ci-report-coverage b/ci-report-coverage index 1b017d6c..d8f72e16 100755 --- a/ci-report-coverage +++ b/ci-report-coverage @@ -10,5 +10,10 @@ cp -R ../test/* ./test/ cp -R ../test/integ/* ./test/integ/ cp -R ../test/unit/* ./test/unit/ -echo "Sending coveralls json report" -coveralls --gcov-options '\-lp' +echo "Sending json report" +for filename in `find . | egrep '\.cpp'`; +do + gcov -n -o . $filename > /dev/null; +done + +codecov diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..97a08e6a --- /dev/null +++ b/codecov.yml @@ -0,0 +1,23 @@ +codecov: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "70...100" + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "reach,diff,flags,files,footer" + behavior: default + require_changes: no + +ignore: + - "test" diff --git a/configure.ac b/configure.ac index 1beb4292..003170c6 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # # This file is part of libhttpserver -# Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino +# Copyright (C) 2011-2021 Sebastiano Merlino # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -21,8 +21,8 @@ AC_PREREQ(2.57) m4_define([libhttpserver_MAJOR_VERSION],[0])dnl -m4_define([libhttpserver_MINOR_VERSION],[9])dnl -m4_define([libhttpserver_REVISION],[1])dnl +m4_define([libhttpserver_MINOR_VERSION],[20])dnl +m4_define([libhttpserver_REVISION],[0])dnl m4_define([libhttpserver_PKG_VERSION],[libhttpserver_MAJOR_VERSION.libhttpserver_MINOR_VERSION.libhttpserver_REVISION])dnl m4_define([libhttpserver_LDF_VERSION],[libhttpserver_MAJOR_VERSION:libhttpserver_MINOR_VERSION:libhttpserver_REVISION])dnl AC_INIT([libhttpserver], libhttpserver_PKG_VERSION, [electrictwister2000@gmail.com]) @@ -31,15 +31,21 @@ AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIR([m4]) AC_CANONICAL_HOST +AX_VALGRIND_DFLT +AX_VALGRIND_CHECK + OLD_CXXFLAGS=$CXXFLAGS LT_INIT AC_PROG_CC -AC_PROG_CXX([clang++]) +AC_PROG_CXX() AC_PROG_LN_S CXXFLAGS=$OLD_CXXFLAGS AC_LANG([C++]) AC_SYS_LARGEFILE +# Minimal feature-set required +AX_CXX_COMPILE_STDCXX([17]) + native_srcdir=$srcdir AC_MSG_CHECKING([whether it is possible to compile in the same directory]) @@ -51,35 +57,47 @@ AC_ARG_ENABLE([same-directory-build], AC_MSG_RESULT([$samedirectory]) if test x"$samedirectory" = x"no"; then - if test "`cd $srcdir; /bin/pwd`" = "`/bin/pwd`"; then + if test "`cd $srcdir; /bin/pwd`" = "`/bin/pwd`"; then AC_MSG_ERROR("you must configure in a separate build directory") fi fi +is_windows=yes; +NETWORK_LIBS="" case "$host" in *-mingw*) - NETWORK_HEADER="winsock2.h" - REGEX_LIBS="-lregex -lpthread -no-undefined" + NETWORK_HEADER="winsock2.h" + ADDITIONAL_LIBS="-lpthread -no-undefined" + NETWORK_LIBS="-lws2_32" + native_srcdir=$(cd $srcdir; pwd -W) + ;; + *-msys*) + AC_MSG_WARN([ +Building from MSYS environment. Binaries will depend on msys-2.0.dll. +For native Windows binaries, use the MinGW64 shell instead. +]) + NETWORK_HEADER="winsock2.h" + ADDITIONAL_LIBS="-lpthread -no-undefined" + NETWORK_LIBS="-lws2_32" native_srcdir=$(cd $srcdir; pwd -W) ;; *-cygwin*) - NETWORK_HEADER="winsock2.h" - REGEX_LIBS="-lregex -lpthread -no-undefined" + NETWORK_HEADER="arpa/inet.h" + ADDITIONAL_LIBS="-lpthread -no-undefined" ;; *) - NETWORK_HEADER="arpa/inet.h" - REGEX_LIBS="" + NETWORK_HEADER="arpa/inet.h" + ADDITIONAL_LIBS="" + is_windows=no ;; esac # Checks for header files. -AC_HEADER_STDC AC_CHECK_HEADER([stdint.h],[],[AC_MSG_ERROR("stdint.h not found")]) AC_CHECK_HEADER([inttypes.h],[],[AC_MSG_ERROR("inttypes.h not found")]) AC_CHECK_HEADER([errno.h],[],[AC_MSG_ERROR("errno.h not found")]) AC_CHECK_HEADER([unistd.h],[],[AC_MSG_ERROR("unistd.h not found")]) AC_CHECK_HEADER([ctype.h],[],[AC_MSG_ERROR("cctype not found")]) -AC_CHECK_HEADER([regex.h],[],[AC_MSG_ERROR("regex.h not found")]) AC_CHECK_HEADER([sys/stat.h],[],[AC_MSG_ERROR("sys/stat.h not found")]) AC_CHECK_HEADER([sys/types.h],[],[AC_MSG_ERROR("sys/types.h not found")]) AC_CHECK_HEADER([$NETWORK_HEADER],[],[AC_MSG_ERROR("$NETWORK_HEADER not found")]) @@ -88,28 +106,88 @@ AC_CHECK_HEADER([signal.h],[],[AC_MSG_ERROR("signal.h not found")]) AC_CHECK_HEADER([gnutls/gnutls.h],[have_gnutls="yes"],[AC_MSG_WARN("gnutls/gnutls.h not found. TLS will be disabled"); have_gnutls="no"]) # Checks for libmicrohttpd -AC_CHECK_HEADER([microhttpd.h], - AC_CHECK_LIB([microhttpd], [MHD_get_fdset2], - [AC_MSG_CHECKING([for libmicrohttpd >= 0.9.37]) - AC_COMPILE_IFELSE( - [AC_LANG_SOURCE([ - #include - #if (MHD_VERSION < 0x0093700) - #error needs at least version 0.9.37 - #endif - int main () { return 0; } - ])], +if test x"$host" = x"$build"; then + AC_CHECK_HEADER([microhttpd.h], + AC_CHECK_LIB([microhttpd], [MHD_get_fdset2], + [AC_MSG_CHECKING([for libmicrohttpd >= 0.9.64]) + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE([ + #include + #if (MHD_VERSION < 0x00096400) + #error needs at least version 0.9.64 + #endif + int main () { return 0; } + ])], + [], + [AC_MSG_ERROR("libmicrohttpd is too old - install libmicrohttpd >= 0.9.64")] + ) + ], + [AC_MSG_ERROR(["libmicrohttpd not found"])] + ), + [AC_MSG_ERROR(["microhttpd.h not found"])] + ) + + CXXFLAGS="-DHTTPSERVER_COMPILATION -D_REENTRANT $LIBMICROHTTPD_CFLAGS $CXXFLAGS" + LDFLAGS="$LIBMICROHTTPD_LIBS $NETWORK_LIBS $ADDITIONAL_LIBS $LDFLAGS" + + cond_cross_compile="no" +else + AC_CHECK_HEADER([microhttpd.h], + AC_CHECK_LIB([microhttpd], [MHD_get_fdset2], [], - [AC_MSG_ERROR("libmicrohttpd is too old - install libmicrohttpd >= 0.9.37")] - ) - ], - [AC_MSG_ERROR(["libmicrohttpd not found"])] - ), - [AC_MSG_ERROR(["microhttpd.h not found"])] -) + [AC_MSG_ERROR(["libmicrohttpd not found"])] + ), + [AC_MSG_ERROR(["microhttpd.h not found"])] + ) + + CXXFLAGS="-DHTTPSERVER_COMPILATION -D_REENTRANT $CXXFLAGS" + LDFLAGS="$NETWORK_LIBS $ADDITIONAL_LIBS $LDFLAGS" + + cond_cross_compile="yes" +fi -CXXFLAGS="-DHTTPSERVER_COMPILATION -D_REENTRANT $LIBMICROHTTPD_CFLAGS $CXXFLAGS" -LDFLAGS="$LIBMICROHTTPD_LIBS $REGEX_LIBS $LD_FLAGS" +AM_CONDITIONAL([COND_CROSS_COMPILE],[test x"$cond_cross_compile" = x"yes"]) +AC_SUBST(COND_CROSS_COMPILE) + +# Check for basic auth support in libmicrohttpd +AC_CHECK_LIB([microhttpd], [MHD_queue_basic_auth_fail_response], + [have_bauth="yes"], + [have_bauth="no"; AC_MSG_WARN("libmicrohttpd basic auth support not found. Basic auth will be disabled")]) + +# Check for digest auth support in libmicrohttpd +AC_CHECK_LIB([microhttpd], [MHD_queue_auth_fail_response], + [have_dauth="yes"], + [have_dauth="no"; AC_MSG_WARN("libmicrohttpd digest auth support not found. Digest auth will be disabled")]) + +AC_MSG_CHECKING([whether to build with TCP_FASTOPEN support]) +AC_ARG_ENABLE([fastopen], + [AS_HELP_STRING([--enable-fastopen], + [enable use of TCP_FASTOPEN (def=yes)])], + [fastopen="$enableval"], + [fastopen=yes]) +AC_MSG_RESULT([$fastopen]) + +is_fastopen_supported=no; +if test x"$fastopen" = x"yes" && test x"$is_windows" = x"no"; then + major=`uname -r | cut -d. -f1` + minor=`uname -r | cut -d. -f2` + if test "$major" -ge 4 || { test "$major" -eq 3 && test "$minor" -ge 7; }; then + CXXFLAGS="-DUSE_FASTOPEN $CXXFLAGS"; + is_fastopen_supported=yes; + fi +fi + +AC_MSG_CHECKING([whether to link statically]) +AC_ARG_ENABLE([static], + [AS_HELP_STRING([--enable-static], + [enable use static linking (def=yes)])], + [static="$enableval"], + [static=yes]) +AC_MSG_RESULT([$static]) + +if test x"$static" = x"$yes"; then + LDFLAGS="-static $LDFLAGS"; +fi m4_pattern_allow([AC_TYPE_SIZE_T]) m4_pattern_allow([AC_TYPE_UINT16_T]) @@ -135,6 +213,34 @@ AC_MSG_RESULT([$debugit]) AM_LDFLAGS="-lstdc++" if test x"$debugit" = x"yes"; then + AC_DEFINE([DEBUG],[],[Debug Mode]) + AM_CXXFLAGS="$AM_CXXFLAGS -DDEBUG -g -Wall -Wextra -Werror -pedantic -std=c++17 -Wno-unused-command-line-argument -O0" + AM_CFLAGS="$AM_CXXFLAGS -DDEBUG -g -Wall -Wextra -Werror -pedantic -Wno-unused-command-line-argument -O0" +else + AC_DEFINE([NDEBUG],[],[No-debug Mode]) + AM_CXXFLAGS="$AM_CXXFLAGS -O3" + AM_CFLAGS="$AM_CXXFLAGS -O3" +fi + +case $host_os in + darwin* ) + AM_CXXFLAGS="$AM_CXXFLAGS -DDARWIN" + AM_CFLAGS="$AM_CFLAGS -DDARWIN" + ;; + freebsd* ) + AM_LDFLAGS="" + ;; +esac + +AC_MSG_CHECKING([whether to build with coverage information]) +AC_ARG_ENABLE([coverage], + [AS_HELP_STRING([--enable-coverage], + [enable coverage data generation (def=no)])], + [coverit="$enableval"], + [coverit=no]) +AC_MSG_RESULT([$coverit]) + +if test x"$coverit" = x"yes"; then case $host_os in darwin* ) echo "Coverage not supported on OSX" @@ -145,24 +251,38 @@ if test x"$debugit" = x"yes"; then cond_gcov="yes" ;; esac - - AC_DEFINE([DEBUG],[],[Debug Mode]) - AM_CXXFLAGS="$AM_CXXFLAGS -DDEBUG -g -Wall -Wno-uninitialized -O0" - AM_CFLAGS="$AM_CXXFLAGS -DDEBUG -g -Wall -Wno-uninitialized -O0" -else - AC_DEFINE([NDEBUG],[],[No-debug Mode]) - AM_CXXFLAGS="$AM_CXXFLAGS -O3" - AM_CFLAGS="$AM_CXXFLAGS -O3" fi +AC_ARG_ENABLE([[examples]], + [AS_HELP_STRING([[--disable-examples]], [do not build any examples])], , + [enable_examples=yes]) +test "x$enable_examples" = "xno" || enable_examples=yes +AM_CONDITIONAL([BUILD_EXAMPLES], [test "x$enable_examples" = "xyes"]) + AM_CONDITIONAL([COND_GCOV],[test x"$cond_gcov" = x"yes"]) AC_SUBST(COND_GCOV) -if test x"have_gnutls" = x"yes"; then +if test x"$have_gnutls" = x"yes"; then AM_CXXFLAGS="$AM_CXXFLAGS -DHAVE_GNUTLS" AM_CFLAGS="$AM_CXXFLAGS -DHAVE_GNUTLS" fi +AM_CONDITIONAL([HAVE_GNUTLS],[test x"$have_gnutls" = x"yes"]) + +if test x"$have_bauth" = x"yes"; then + AM_CXXFLAGS="$AM_CXXFLAGS -DHAVE_BAUTH" + AM_CFLAGS="$AM_CXXFLAGS -DHAVE_BAUTH" +fi + +AM_CONDITIONAL([HAVE_BAUTH],[test x"$have_bauth" = x"yes"]) + +if test x"$have_dauth" = x"yes"; then + AM_CXXFLAGS="$AM_CXXFLAGS -DHAVE_DAUTH" + AM_CFLAGS="$AM_CXXFLAGS -DHAVE_DAUTH" +fi + +AM_CONDITIONAL([HAVE_DAUTH],[test x"$have_dauth" = x"yes"]) + DX_HTML_FEATURE(ON) DX_CHM_FEATURE(OFF) DX_CHI_FEATURE(OFF) @@ -185,6 +305,24 @@ AC_SUBST(LDFLAGS) AC_SUBST(EXT_LIB_PATH) AC_SUBST(EXT_LIBS) +AC_CONFIG_FILES([test/test_content:test/test_content]) +AC_CONFIG_FILES([test/test_content_2:test/test_content_2]) +AC_CONFIG_FILES([test/test_content_empty:test/test_content_empty]) +AC_CONFIG_FILES([test/test_content_large:test/test_content_large]) +AC_CONFIG_FILES([test/cert.pem:test/cert.pem]) +AC_CONFIG_FILES([test/key.pem:test/key.pem]) +AC_CONFIG_FILES([test/test_root_ca.pem:test/test_root_ca.pem]) +AC_CONFIG_FILES([test/client_cert.pem:test/client_cert.pem]) +AC_CONFIG_FILES([test/client_key.pem:test/client_key.pem]) +AC_CONFIG_FILES([test/client_cert_no_cn.pem:test/client_cert_no_cn.pem]) +AC_CONFIG_FILES([test/client_key_no_cn.pem:test/client_key_no_cn.pem]) +AC_CONFIG_FILES([test/client_cert_untrusted.pem:test/client_cert_untrusted.pem]) +AC_CONFIG_FILES([test/client_key_untrusted.pem:test/client_key_untrusted.pem]) +AC_CONFIG_FILES([test/libhttpserver.supp:test/libhttpserver.supp]) +AC_CONFIG_FILES([examples/cert.pem:examples/cert.pem]) +AC_CONFIG_FILES([examples/key.pem:examples/key.pem]) +AC_CONFIG_FILES([examples/test_content:examples/test_content]) + AC_OUTPUT( libhttpserver.pc Makefile @@ -192,19 +330,19 @@ AC_OUTPUT( src/Makefile test/Makefile examples/Makefile - debian/changelog - debian/copyright - debian/control - debian/libhttpserver-dev.install - debian/libhttpserver.install - debian/rules - redhat/libhttpserver.SPEC ) -AC_MSG_NOTICE([Configuration Summary: +AC_MSG_NOTICE([Configuration Summary: Operating System: ${host_os} + Host triplet : ${host} Target directory: ${prefix} License : LGPL only Debug : ${debugit} TLS Enabled : ${have_gnutls} + Basic Auth : ${have_bauth} + Digest Auth : ${have_dauth} + TCP_FASTOPEN : ${is_fastopen_supported} + Static : ${static} + Windows build : ${is_windows} + Build examples : ${enable_examples} ]) diff --git a/coveralls-debug b/coveralls-debug deleted file mode 100755 index 11a32b1d..00000000 --- a/coveralls-debug +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; -use JSON; -use File::Slurp; -my $json = JSON->new; -my $file = read_file('coveralls.json', { binmode => ':utf8' }); -my $rv = $json->decode($file); -my $sources = $rv->{'source_files'}; -print STDERR join ", ", keys %{$rv}, "\n"; -foreach my $source (sort { - $a->{'name'} cmp $b->{'name'} -} @{$sources}) -{ - my $sum = 0; - my $undefs = 0; - my $coverages = $source->{'coverage'}; - foreach my $coverage (@{$coverages}) - { - if (defined $coverage) - { $sum += $coverage } - else { $undefs ++; } - } - if ($sum > 0) - { - print STDERR $source->{'name'}; - print STDERR " [sum: $sum]"; - print STDERR " [undefs: $undefs]"; - print STDERR "\n"; - } -} diff --git a/custom_iwyu.imp b/custom_iwyu.imp new file mode 100644 index 00000000..e814a310 --- /dev/null +++ b/custom_iwyu.imp @@ -0,0 +1,17 @@ +[ + { include: ["\"microhttpd.h\"", "private", "", "public"] }, + { include: ["", "private", "", "public"] }, + + { symbol: ["std::exception", "private", "", "public"]}, + { symbol: ["std::shared_ptr", "private", "", "public"]}, + { symbol: ["std::uint16_t", "private", "", "public"]}, + { symbol: ["std::uint64_t", "private", "", "public"]}, + { symbol: ["std::istringstream", "private", "", "public"]}, + { symbol: ["std::stringstream", "private", "", "public"]}, + { symbol: ["std::ifstream", "private", "", "public"]}, + + { symbol: ["uint16_t", "private", "", "public"]}, + { symbol: ["uint64_t", "private", "", "public"]}, + + { symbol: ["MHD_Connection", "private", "", "public"]}, +] diff --git a/debian/changelog.in b/debian/changelog.in deleted file mode 100644 index 6f63bcda..00000000 --- a/debian/changelog.in +++ /dev/null @@ -1,125 +0,0 @@ -libhttpserver (0.9.1) unstable; urgency=low - * Eliminated build dependency on pkg-config - - -- Sebastiano Merlino Fri, 17 Jul 2015 21:38:54 +0000 - -libhttpserver (0.9.0) unstable; urgency=low - * Support build on MacOsX - * Improved support for CI on travis - * Solved bug on event_supplier registering - * Solved bug on standardize_url to avoid removing root - * Change cycle_callback_ptr so that buffer can be modified - * Moved to version 0.9.0 - - -- Sebastiano Merlino Wed, 15 Apr 2015 01:40:11 +0000 - -libhttpserver (0.8.0) unstable; urgency=low - * Support for building on MinGW/Cygwin systems - * min libmicrohttpd version moved to 0.9.37 - * Moved to version 0.8.0 - - -- Sebastiano Merlino Sat, 23 Jul 2014 02:46:20 +0100 - -libhttpserver (0.7.2) unstable; urgency=low - * Documentation updates - * Reduced responsibilities of webserver class - - -- Sebastiano Merlino Sat, 23 Mar 2014 15:23:40 +0100 - -libhttpserver (0.7.1) unstable; urgency=low - * Improved methods constness - - -- Sebastiano Merlino Sat, 22 Feb 2014 10:58:02 +0100 - -libhttpserver (0.7.0) unstable; urgency=low - * Cleaned-up webserver.cpp code to extract secondary classes - * Enforced immutability of webserver class - * Enabled library to compile on g++ 4.1.2 - - -- Sebastiano Merlino Sat, 25 Jan 2014 16:31:03 +0100 - -libhttpserver (0.6.3) unstable; urgency=low - * Fixed some bugs in exception management - - -- Sebastiano Merlino Wed, 28 Nov 2012 10:17:03 +0100 - -libhttpserver (0.6.2) unstable; urgency=low - * Cookie management in http_response.hpp; - - -- Sebastiano Merlino Fri, 16 Nov 2012 13:01:23 +0100 - -libhttpserver (0.6.1) unstable; urgency=low - * Solved problems in cache management; - - -- Sebastiano Merlino Wed, 08 Nov 2012 17:25:55 +0100 - -libhttpserver (0.6.0) unstable; urgency=low - * Added support for caching; - - -- Sebastiano Merlino Wed, 06 Nov 2012 17:05:23 +0100 - -libhttpserver (0.5.5) unstable; urgency=low - * Added a parameter to avoid http_response deletion by WS; - - -- Sebastiano Merlino Wed, 31 Oct 2012 18:02:40 +0100 - -libhttpserver (0.5.4) unstable; urgency=low - * Solved some problems with debian package building - - -- Sebastiano Merlino Tue, 30 Oct 2012 16:12:00 +0100 - -libhttpserver (0.5.3) unstable; urgency=low - * Added grow method to http_response - - -- Sebastiano Merlino Tue, 30 Oct 2012 12:49:42 +0100 - -libhttpserver (0.5.2) unstable; urgency=low - * Changed default log behaviour to print nothing - * Added getters and setters for webserver external components - - -- Sebastiano Merlino Tue, 23 Oct 2012 12:45:31 +0200 - -libhttpserver (0.5.1) unstable; urgency=low - * Stabilized comet support - * Added features to manage requests easily - * Added response constructor with byte initializer - - -- Sebastiano Merlino Tue, 10 Oct 2012 17:18:42 +0200 - -libhttpserver (0.4.0) unstable; urgency=low - * Added support to Comet - - -- Sebastiano Merlino Tue, 25 Sep 2012 11:42:23 +0200 - -libhttpserver (0.3.0) unstable; urgency=low - * Improved performaces - * Added capability to register default error pages - - -- Sebastiano Merlino Sun, 26 Aug 2012 19:00:42 +0200 - -libhttpserver (0.2.0) unstable; urgency=low - - * Deb target now seems to work really - * Rpm target added - - -- Sebastiano Merlino Wed, 25 Jul 2012 17:05:11 +0200 - -libhttpserver (0.1.2) unstable; urgency=low - - * Added deb target to makefile. - * Added cmakemodule in order to help people who want to include the library - in a cmake installation - - -- Sebastiano Merlino Sat, 21 Jul 2012 00:40:05 +0200 - -libhttpserver (0.1.2) unstable; urgency=low - - * First debian release. - - -- Andrea Nicotra Thu, 19 Jul 2012 13:37:44 +0200 - -libhttpserver (0.1.1) unstable; urgency=low - - * First tag. - - -- Sebastiano Merlino Sun, 15 Jul 2012 11:15:44 +0200 diff --git a/debian/control.in b/debian/control.in deleted file mode 100644 index 110d0cc5..00000000 --- a/debian/control.in +++ /dev/null @@ -1,43 +0,0 @@ -Source: libhttpserver -Section: libs -Priority: optional -Maintainer: Sebastiano Merlino -Build-Depends: cdbs -Depends: libmicrohttpd10(>= 0.9.7), libstdc++6 -Standards-Version: @VERSION@ -Vcs-Git: git://github.com/etr/libhttpserver.git -Vcs-browser: https://github.com/etr/libhttpserver.git -Homepage: https://github.com/etr/libhttpserver - -Package: libhttpserver -Architecture: any -Installed-Size: -Depends: ${misc:Depends}, ${shlibs:Depends} -Description: - library embedding RESTful HTTP server functionality - libhttpserver is a small C++ library for embedding RESTful HTTP server functionality into - applications. - . - This package includes libraries. - -Package: libhttpserver-dbg -Section: debug -Architecture: any -Installed-Size: -Depends: ${misc:Depends}, ${shlibs:Depends}, libmicrohttpd-dbg(>= 0.9.7), libstdc++6 -Description: - library embedding RESTful HTTP server functionality (debug) - libhttpserver is a small C++ library for embedding RESTful HTTP server functionality into - applications. - . - This package contains the debugging symbols. - -Package: libhttpserver-dev -Section: libdevel -Architecture: any -Installed-Size: -Depends: ${misc:Depends}, ${shlibs:Depends}, libmicrohttpd-dev(>= 0.9.7), libstdc++6 -Description: - The libhttpserver library . - . - This package contains the development files. diff --git a/debian/copyright.in b/debian/copyright.in deleted file mode 100644 index 89a1f7a3..00000000 --- a/debian/copyright.in +++ /dev/null @@ -1,33 +0,0 @@ -Files: * -Copyright: (C) 2011-2015 Sebastiano Merlino -License: LGPL-2.1+ - -Files: doc/* -Copyright: (C) 2011-2015 Sebastiano Merlino - -License: GFDL-1.3+ - Permission is granted to copy, distribute and/or modify this document - under the terms of the GNU Free Documentation License, Version 1.3 - or any later version published by the Free Software Foundation; - with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. - . - The complete text of the GNU Free Documentation License - can be found in /usr/share/common-licenses/GFDL-1.3 file. - -License: LGPL-2.1+ - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - . - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - . - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - . - The complete text of the GNU Library General Public License - can be found in /usr/share/common-licenses/LGPL-2.1 file. diff --git a/debian/libhttpserver-dev.install.in b/debian/libhttpserver-dev.install.in deleted file mode 100644 index 934451bf..00000000 --- a/debian/libhttpserver-dev.install.in +++ /dev/null @@ -1,4 +0,0 @@ -usr/include -usr/lib/*.a -usr/lib/*.so -usr/lib/pkgconfig \ No newline at end of file diff --git a/debian/libhttpserver.install.in b/debian/libhttpserver.install.in deleted file mode 100644 index 49513a14..00000000 --- a/debian/libhttpserver.install.in +++ /dev/null @@ -1 +0,0 @@ -usr/lib/*.so.* diff --git a/debian/rules.in b/debian/rules.in deleted file mode 100755 index 78981fe3..00000000 --- a/debian/rules.in +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/make -f - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -include /usr/share/cdbs/1/rules/debhelper.mk - -DEB_CONFIGURE_EXTRA_FLAGS += --libdir=\$${prefix}/lib/$(DEB_HOST_MULTIARCH) -CURDIR := -DEB_SRCDIR := @abs_top_srcdir@ -DEB_DESTDIR := @abs_top_builddir@/debian/tmp -DEB_DH_INSTALL_SOURCEDIR := @abs_top_builddir@/debian/tmp -DEB_BUILDDIR := @abs_top_builddir@/deb_build - -# compile jobs -MAKE += -j1 - -include /usr/share/cdbs/1/class/autotools.mk - -#makebuilddir/libhttpserver:: -# autoreconf --install - diff --git a/examples/Makefile.am b/examples/Makefile.am index e0c44ac6..148fa944 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -19,8 +19,44 @@ LDADD = $(top_builddir)/src/libhttpserver.la AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/ METASOURCES = AUTO -noinst_PROGRAMS = hello_world service +noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg args_processing setting_headers custom_access_log minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator file_upload file_upload_with_callback binary_buffer_response hello_world_SOURCES = hello_world.cpp service_SOURCES = service.cpp +minimal_hello_world_SOURCES = minimal_hello_world.cpp +custom_error_SOURCES = custom_error.cpp +allowing_disallowing_methods_SOURCES = allowing_disallowing_methods.cpp +handlers_SOURCES = handlers.cpp +hello_with_get_arg_SOURCES = hello_with_get_arg.cpp +args_processing_SOURCES = args_processing.cpp +setting_headers_SOURCES = setting_headers.cpp +custom_access_log_SOURCES = custom_access_log.cpp +minimal_https_SOURCES = minimal_https.cpp +minimal_file_response_SOURCES = minimal_file_response.cpp +minimal_deferred_SOURCES = minimal_deferred.cpp +deferred_with_accumulator_SOURCES = deferred_with_accumulator.cpp +url_registration_SOURCES = url_registration.cpp +minimal_ip_ban_SOURCES = minimal_ip_ban.cpp +benchmark_select_SOURCES = benchmark_select.cpp +benchmark_threads_SOURCES = benchmark_threads.cpp +benchmark_nodelay_SOURCES = benchmark_nodelay.cpp +file_upload_SOURCES = file_upload.cpp +file_upload_with_callback_SOURCES = file_upload_with_callback.cpp +binary_buffer_response_SOURCES = binary_buffer_response.cpp +if HAVE_BAUTH +noinst_PROGRAMS += basic_authentication centralized_authentication +basic_authentication_SOURCES = basic_authentication.cpp +centralized_authentication_SOURCES = centralized_authentication.cpp +endif + +if HAVE_GNUTLS +LDADD += -lgnutls +noinst_PROGRAMS += minimal_https_psk +minimal_https_psk_SOURCES = minimal_https_psk.cpp +endif + +if HAVE_DAUTH +noinst_PROGRAMS += digest_authentication +digest_authentication_SOURCES = digest_authentication.cpp +endif diff --git a/examples/allowing_disallowing_methods.cpp b/examples/allowing_disallowing_methods.cpp new file mode 100644 index 00000000..50efa4fd --- /dev/null +++ b/examples/allowing_disallowing_methods.cpp @@ -0,0 +1,42 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include + +#include + +class hello_world_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request&) { + return std::shared_ptr(new httpserver::string_response("Hello, World!")); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080); + + hello_world_resource hwr; + hwr.disallow_all(); + hwr.set_allowing("GET", true); + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} diff --git a/examples/args_processing.cpp b/examples/args_processing.cpp new file mode 100644 index 00000000..ddf41c4e --- /dev/null +++ b/examples/args_processing.cpp @@ -0,0 +1,100 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include +#include +#include + +#include + +// This example demonstrates how to use get_args() and get_args_flat() to +// process all query string and body arguments from an HTTP request. +// +// Try these URLs: +// http://localhost:8080/args?name=john&age=30 +// http://localhost:8080/args?id=1&id=2&id=3 (multiple values for same key) +// http://localhost:8080/args?colors=red&colors=green&colors=blue + +class args_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request& req) { + std::stringstream response_body; + + response_body << "=== Using get_args() (supports multiple values per key) ===\n\n"; + + // get_args() returns a map where each key maps to an http_arg_value. + // http_arg_value contains a vector of values for parameters like "?id=1&id=2&id=3" + auto args = req.get_args(); + for (const auto& [key, arg_value] : args) { + response_body << "Key: " << key << "\n"; + // Use get_all_values() to get all values for this key + auto all_values = arg_value.get_all_values(); + if (all_values.size() > 1) { + response_body << " Values (" << all_values.size() << "):\n"; + for (const auto& v : all_values) { + response_body << " - " << v << "\n"; + } + } else { + // For single values, http_arg_value converts to string_view + response_body << " Value: " << std::string_view(arg_value) << "\n"; + } + } + + response_body << "\n=== Using get_args_flat() (one value per key) ===\n\n"; + + // get_args_flat() returns a simple map with one value per key. + // If a key has multiple values, only the first value is returned. + auto args_flat = req.get_args_flat(); + for (const auto& [key, value] : args_flat) { + response_body << key << " = " << value << "\n"; + } + + response_body << "\n=== Accessing individual arguments ===\n\n"; + + // You can also access individual arguments directly + auto name = req.get_arg("name"); // Returns http_arg_value (may have multiple values) + auto name_flat = req.get_arg_flat("name"); // Returns string_view (first value only) + + if (!name.get_flat_value().empty()) { + response_body << "name (via get_arg): " << std::string_view(name) << "\n"; + } + if (!name_flat.empty()) { + response_body << "name (via get_arg_flat): " << name_flat << "\n"; + } + + return std::make_shared(response_body.str(), 200, "text/plain"); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080); + + args_resource ar; + ws.register_resource("/args", &ar); + + std::cout << "Server running on http://localhost:8080/args\n"; + std::cout << "Try: http://localhost:8080/args?name=john&age=30\n"; + std::cout << "Or: http://localhost:8080/args?id=1&id=2&id=3\n"; + + ws.start(true); + + return 0; +} diff --git a/examples/basic_authentication.cpp b/examples/basic_authentication.cpp new file mode 100644 index 00000000..661bbb3c --- /dev/null +++ b/examples/basic_authentication.cpp @@ -0,0 +1,45 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include + +#include + +class user_pass_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const httpserver::http_request& req) { + if (req.get_user() != "myuser" || req.get_pass() != "mypass") { + return std::shared_ptr(new httpserver::basic_auth_fail_response("FAIL", "test@example.com")); + } + + return std::shared_ptr(new httpserver::string_response(std::string(req.get_user()) + " " + std::string(req.get_pass()), 200, "text/plain")); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080); + + user_pass_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} diff --git a/examples/benchmark_nodelay.cpp b/examples/benchmark_nodelay.cpp new file mode 100755 index 00000000..96c2f570 --- /dev/null +++ b/examples/benchmark_nodelay.cpp @@ -0,0 +1,60 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include + +#include + +#define PATH "/plaintext" +#define BODY "Hello, World!" + +class hello_world_resource : public httpserver::http_resource { + public: + explicit hello_world_resource(const std::shared_ptr& resp): + resp(resp) { + } + + std::shared_ptr render(const httpserver::http_request&) { + return resp; + } + + private: + std::shared_ptr resp; +}; + +int main(int argc, char** argv) { + std::ignore = argc; + + httpserver::webserver ws = httpserver::create_webserver(atoi(argv[1])) + .start_method(httpserver::http::http_utils::INTERNAL_SELECT) + .tcp_nodelay() + .max_threads(atoi(argv[2])); + + std::shared_ptr hello = std::shared_ptr(new httpserver::string_response(BODY, 200)); + hello->with_header("Server", "libhttpserver"); + + hello_world_resource hwr(hello); + ws.register_resource(PATH, &hwr, false); + + ws.start(true); + + return 0; +} diff --git a/examples/benchmark_select.cpp b/examples/benchmark_select.cpp new file mode 100755 index 00000000..ef5cd089 --- /dev/null +++ b/examples/benchmark_select.cpp @@ -0,0 +1,59 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include + +#include + +#define PATH "/plaintext" +#define BODY "Hello, World!" + +class hello_world_resource : public httpserver::http_resource { + public: + explicit hello_world_resource(const std::shared_ptr& resp): + resp(resp) { + } + + std::shared_ptr render(const httpserver::http_request&) { + return resp; + } + + private: + std::shared_ptr resp; +}; + +int main(int argc, char** argv) { + std::ignore = argc; + + httpserver::webserver ws = httpserver::create_webserver(atoi(argv[1])) + .start_method(httpserver::http::http_utils::INTERNAL_SELECT) + .max_threads(atoi(argv[2])); + + std::shared_ptr hello = std::shared_ptr(new httpserver::string_response(BODY, 200)); + hello->with_header("Server", "libhttpserver"); + + hello_world_resource hwr(hello); + ws.register_resource(PATH, &hwr, false); + + ws.start(true); + + return 0; +} diff --git a/examples/benchmark_threads.cpp b/examples/benchmark_threads.cpp new file mode 100755 index 00000000..db376168 --- /dev/null +++ b/examples/benchmark_threads.cpp @@ -0,0 +1,58 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include + +#include + +#define PATH "/plaintext" +#define BODY "Hello, World!" + +class hello_world_resource : public httpserver::http_resource { + public: + explicit hello_world_resource(const std::shared_ptr& resp): + resp(resp) { + } + + std::shared_ptr render(const httpserver::http_request&) { + return resp; + } + + private: + std::shared_ptr resp; +}; + +int main(int argc, char** argv) { + std::ignore = argc; + + httpserver::webserver ws = httpserver::create_webserver(atoi(argv[1])) + .start_method(httpserver::http::http_utils::THREAD_PER_CONNECTION); + + std::shared_ptr hello = std::shared_ptr(new httpserver::string_response(BODY, 200)); + hello->with_header("Server", "libhttpserver"); + + hello_world_resource hwr(hello); + ws.register_resource(PATH, &hwr, false); + + ws.start(true); + + return 0; +} diff --git a/examples/binary_buffer_response.cpp b/examples/binary_buffer_response.cpp new file mode 100644 index 00000000..19559cfc --- /dev/null +++ b/examples/binary_buffer_response.cpp @@ -0,0 +1,82 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +// This example demonstrates how to serve binary data (e.g., images) directly +// from an in-memory buffer using string_response. Despite its name, +// string_response works with arbitrary binary content because std::string can +// hold any bytes, including null characters. +// +// This is useful when you generate or receive binary data at runtime (e.g., +// from a camera, an image library, or a database) and want to serve it over +// HTTP without writing it to disk first. +// +// To test: +// curl -o output.png http://localhost:8080/image + +#include +#include +#include + +#include + +// Generate a minimal valid 1x1 red PNG image in memory. +// In a real application, this could come from a camera capture, image +// processing library, database blob, etc. +static std::string generate_png_data() { + // Minimal 1x1 red pixel PNG (68 bytes) + static const unsigned char png[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, // PNG signature + 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, // IHDR chunk + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, // 1x1 + 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, // 8-bit RGB + 0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, // IDAT chunk + 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xcf, 0xc0, 0x00, // compressed data + 0x00, 0x00, 0x03, 0x00, 0x01, 0x36, 0x28, 0x19, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, // IEND chunk + 0x44, 0xae, 0x42, 0x60, 0x82 + }; + + return std::string(reinterpret_cast(png), sizeof(png)); +} + +class image_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const httpserver::http_request&) { + // Build binary content as a std::string. The string can contain any + // bytes — it is not limited to printable characters or null-terminated + // C strings. The size is tracked internally by std::string::size(). + std::string image_data = generate_png_data(); + + // Use string_response with the appropriate content type. The response + // will send the exact bytes contained in the string. + return std::make_shared( + std::move(image_data), 200, "image/png"); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080); + + image_resource ir; + ws.register_resource("/image", &ir); + ws.start(true); + + return 0; +} diff --git a/examples/centralized_authentication.cpp b/examples/centralized_authentication.cpp new file mode 100644 index 00000000..0f965af6 --- /dev/null +++ b/examples/centralized_authentication.cpp @@ -0,0 +1,88 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include + +#include + +using httpserver::http_request; +using httpserver::http_response; +using httpserver::http_resource; +using httpserver::webserver; +using httpserver::create_webserver; +using httpserver::string_response; +using httpserver::basic_auth_fail_response; + +// Simple resource that doesn't need to handle auth itself +class hello_resource : public http_resource { + public: + std::shared_ptr render_GET(const http_request&) { + return std::make_shared("Hello, authenticated user!", 200, "text/plain"); + } +}; + +class health_resource : public http_resource { + public: + std::shared_ptr render_GET(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } +}; + +// Centralized authentication handler +// Returns nullptr to allow the request, or an http_response to reject it +std::shared_ptr auth_handler(const http_request& req) { + if (req.get_user() != "admin" || req.get_pass() != "secret") { + return std::make_shared("Unauthorized", "MyRealm"); + } + return nullptr; // Allow request +} + +int main() { + // Create webserver with centralized authentication + // - auth_handler: called before every resource's render method + // - auth_skip_paths: paths that bypass authentication + webserver ws = create_webserver(8080) + .auth_handler(auth_handler) + .auth_skip_paths({"/health", "/public/*"}); + + hello_resource hello; + health_resource health; + + ws.register_resource("/api", &hello); + ws.register_resource("/health", &health); + + ws.start(true); + + return 0; +} + +// Usage: +// # Start the server +// ./centralized_authentication +// +// # Without auth - should get 401 Unauthorized +// curl -v http://localhost:8080/api +// +// # With valid auth - should get 200 OK +// curl -u admin:secret http://localhost:8080/api +// +// # Health endpoint (skip path) - works without auth +// curl http://localhost:8080/health diff --git a/examples/client_cert_auth.cpp b/examples/client_cert_auth.cpp new file mode 100644 index 00000000..90a3ba84 --- /dev/null +++ b/examples/client_cert_auth.cpp @@ -0,0 +1,175 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +/** + * Example demonstrating client certificate (mTLS) authentication. + * + * This example shows how to: + * 1. Configure the server to request client certificates + * 2. Extract client certificate information in request handlers + * 3. Implement certificate-based access control + * + * To test this example: + * + * 1. Generate server certificate and key: + * openssl req -x509 -newkey rsa:2048 -keyout server_key.pem -out server_cert.pem \ + * -days 365 -nodes -subj "/CN=localhost" + * + * 2. Generate a CA certificate for client certs: + * openssl req -x509 -newkey rsa:2048 -keyout ca_key.pem -out ca_cert.pem \ + * -days 365 -nodes -subj "/CN=Test CA" + * + * 3. Generate client certificate signed by the CA: + * openssl req -newkey rsa:2048 -keyout client_key.pem -out client_csr.pem \ + * -nodes -subj "/CN=Alice/O=Engineering" + * openssl x509 -req -in client_csr.pem -CA ca_cert.pem -CAkey ca_key.pem \ + * -CAcreateserial -out client_cert.pem -days 365 + * + * 4. Run the server: + * ./client_cert_auth + * + * 5. Test with curl using client certificate: + * curl -k --cert client_cert.pem --key client_key.pem https://localhost:8443/secure + * + * Or without a certificate (will be denied): + * curl -k https://localhost:8443/secure + */ + +#include +#include +#include +#include + +#include + +// Set of allowed certificate fingerprints (SHA-256, hex-encoded) +// In a real application, this would be loaded from a database or config file +std::set allowed_fingerprints; + +// Resource that requires client certificate authentication +class secure_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const httpserver::http_request& req) { + // Check if client provided a certificate + if (!req.has_client_certificate()) { + return std::make_shared( + "Client certificate required", + httpserver::http::http_utils::http_unauthorized, "text/plain"); + } + + // Get certificate information + std::string cn = req.get_client_cert_cn(); + std::string dn = req.get_client_cert_dn(); + std::string issuer = req.get_client_cert_issuer_dn(); + std::string fingerprint = req.get_client_cert_fingerprint_sha256(); + bool verified = req.is_client_cert_verified(); + + // Check if certificate is verified by our CA + if (!verified) { + return std::make_shared( + "Certificate not verified by trusted CA", + httpserver::http::http_utils::http_forbidden, "text/plain"); + } + + // Optional: Check fingerprint against allowlist + if (!allowed_fingerprints.empty() && + allowed_fingerprints.find(fingerprint) == allowed_fingerprints.end()) { + return std::make_shared( + "Certificate not in allowlist", + httpserver::http::http_utils::http_forbidden, "text/plain"); + } + + // Check certificate validity times + time_t now = time(nullptr); + time_t not_before = req.get_client_cert_not_before(); + time_t not_after = req.get_client_cert_not_after(); + + if (now < not_before) { + return std::make_shared( + "Certificate not yet valid", + httpserver::http::http_utils::http_forbidden, "text/plain"); + } + + if (now > not_after) { + return std::make_shared( + "Certificate has expired", + httpserver::http::http_utils::http_forbidden, "text/plain"); + } + + // Build response with certificate info + std::string response = "Welcome, " + cn + "!\n\n"; + response += "Certificate Details:\n"; + response += " Subject DN: " + dn + "\n"; + response += " Issuer DN: " + issuer + "\n"; + response += " Fingerprint (SHA-256): " + fingerprint + "\n"; + response += " Verified: " + std::string(verified ? "Yes" : "No") + "\n"; + + return std::make_shared(response, 200, "text/plain"); + } +}; + +// Public resource that shows certificate info but doesn't require it +class info_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const httpserver::http_request& req) { + std::string response; + + if (req.has_client_certificate()) { + response = "Client certificate detected:\n"; + response += " Common Name: " + req.get_client_cert_cn() + "\n"; + response += " Verified: " + std::string(req.is_client_cert_verified() ? "Yes" : "No") + "\n"; + } else { + response = "No client certificate provided.\n"; + response += "Use --cert and --key with curl to provide one.\n"; + } + + return std::make_shared(response, 200, "text/plain"); + } +}; + +int main() { + std::cout << "Starting HTTPS server with client certificate authentication on port 8443...\n"; + std::cout << "\nEndpoints:\n"; + std::cout << " /info - Shows certificate info (optional cert)\n"; + std::cout << " /secure - Requires valid client certificate\n\n"; + + // Create webserver with SSL and client certificate trust store + httpserver::webserver ws = httpserver::create_webserver(8443) + .use_ssl() + .https_mem_key("server_key.pem") // Server private key + .https_mem_cert("server_cert.pem") // Server certificate + .https_mem_trust("ca_cert.pem"); // CA certificate for verifying client certs + + secure_resource secure; + info_resource info; + + ws.register_resource("/secure", &secure); + ws.register_resource("/info", &info); + + std::cout << "Server started. Press Ctrl+C to stop.\n\n"; + std::cout << "Test commands:\n"; + std::cout << " curl -k https://localhost:8443/info\n"; + std::cout << " curl -k --cert client_cert.pem --key client_key.pem https://localhost:8443/info\n"; + std::cout << " curl -k --cert client_cert.pem --key client_key.pem https://localhost:8443/secure\n"; + + ws.start(true); + + return 0; +} diff --git a/examples/custom_access_log.cpp b/examples/custom_access_log.cpp new file mode 100644 index 00000000..8f596c90 --- /dev/null +++ b/examples/custom_access_log.cpp @@ -0,0 +1,47 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include +#include + +#include + +void custom_access_log(const std::string& url) { + std::cout << "ACCESSING: " << url << std::endl; +} + +class hello_world_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request&) { + return std::shared_ptr(new httpserver::string_response("Hello, World!")); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080) + .log_access(custom_access_log); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} diff --git a/examples/custom_error.cpp b/examples/custom_error.cpp new file mode 100644 index 00000000..c38fb169 --- /dev/null +++ b/examples/custom_error.cpp @@ -0,0 +1,52 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include + +#include + +std::shared_ptr not_found_custom(const httpserver::http_request&) { + return std::shared_ptr(new httpserver::string_response("Not found custom", 404, "text/plain")); +} + +std::shared_ptr not_allowed_custom(const httpserver::http_request&) { + return std::shared_ptr(new httpserver::string_response("Not allowed custom", 405, "text/plain")); +} + +class hello_world_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request&) { + return std::shared_ptr(new httpserver::string_response("Hello, World!")); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080) + .not_found_resource(not_found_custom) + .method_not_allowed_resource(not_allowed_custom); + + hello_world_resource hwr; + hwr.disallow_all(); + hwr.set_allowing("GET", true); + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} diff --git a/examples/deferred_with_accumulator.cpp b/examples/deferred_with_accumulator.cpp new file mode 100644 index 00000000..a4367773 --- /dev/null +++ b/examples/deferred_with_accumulator.cpp @@ -0,0 +1,78 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include +#include +// cpplint errors on chrono and thread because they are replaced (in Chromium) by other google libraries. +// This is not an issue here. +#include // NOLINT [build/c++11] +#include +#include +#include // NOLINT [build/c++11] + +#include + +std::atomic counter; + +ssize_t test_callback(std::shared_ptr > closure_data, char* buf, size_t max) { + int reqid; + if (closure_data == nullptr) { + reqid = -1; + } else { + reqid = *closure_data; + } + + // only first 5 connections can be established + if (reqid >= 5) { + return -1; + } else { + // respond corresponding request IDs to the clients + std::string str = ""; + str += std::to_string(reqid) + " "; + memset(buf, 0, max); + std::copy(str.begin(), str.end(), buf); + + // keep sending reqid + // sleep(1); ==> adapted for C++11 on non-*Nix systems + std::this_thread::sleep_for(std::chrono::seconds(1)); + + return (ssize_t)max; + } +} + +class deferred_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const httpserver::http_request&) { + std::shared_ptr > closure_data(new std::atomic(counter++)); + return std::shared_ptr > >(new httpserver::deferred_response >(test_callback, closure_data, "cycle callback response")); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080); + + deferred_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} + diff --git a/examples/digest_authentication.cpp b/examples/digest_authentication.cpp new file mode 100644 index 00000000..fb87cd4b --- /dev/null +++ b/examples/digest_authentication.cpp @@ -0,0 +1,50 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include + +#include + +#define MY_OPAQUE "11733b200778ce33060f31c9af70a870ba96ddd4" + +class digest_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const httpserver::http_request& req) { + if (req.get_digested_user() == "") { + return std::shared_ptr(new httpserver::digest_auth_fail_response("FAIL", "test@example.com", MY_OPAQUE, true)); + } else { + bool reload_nonce = false; + if (!req.check_digest_auth("test@example.com", "mypass", 300, &reload_nonce)) { + return std::shared_ptr(new httpserver::digest_auth_fail_response("FAIL", "test@example.com", MY_OPAQUE, reload_nonce)); + } + } + return std::shared_ptr(new httpserver::string_response("SUCCESS", 200, "text/plain")); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080); + + digest_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} diff --git a/examples/file_upload.cpp b/examples/file_upload.cpp new file mode 100644 index 00000000..0916a4fc --- /dev/null +++ b/examples/file_upload.cpp @@ -0,0 +1,119 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include +#include + +#include + +class file_upload_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const httpserver::http_request&) { + std::string get_response = "\n"; + get_response += " \n"; + get_response += "
\n"; + get_response += "

Upload 1 (key is 'files', multiple files can be selected)


\n"; + get_response += " \n"; + get_response += "

\n"; + get_response += "

Upload 2 (key is 'files2', multiple files can be selected)


\n"; + get_response += "

\n"; + get_response += " \n"; + get_response += "
\n"; + get_response += " \n"; + get_response += "\n"; + + return std::shared_ptr(new httpserver::string_response(get_response, 200, "text/html")); + } + + std::shared_ptr render_POST(const httpserver::http_request& req) { + std::string post_response = "\n"; + post_response += "\n"; + post_response += " \n"; + post_response += "\n"; + post_response += "\n"; + post_response += " Uploaded files:\n"; + post_response += "

\n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + post_response += " \n"; + + for (auto &file_key : req.get_files()) { + for (auto &files : file_key.second) { + post_response += " \n"; + } + } + + post_response += "
KeyUploaded filenameFile system pathFile sizeContent typeTransfer encoding
"; + post_response += file_key.first; + post_response += ""; + post_response += files.first; + post_response += ""; + post_response += files.second.get_file_system_file_name(); + post_response += ""; + post_response += std::to_string(files.second.get_file_size()); + post_response += ""; + post_response += files.second.get_content_type(); + post_response += ""; + post_response += files.second.get_transfer_encoding(); + post_response += "


\n"; + post_response += " back\n"; + post_response += "\n"; + return std::shared_ptr(new httpserver::string_response(post_response, 201, "text/html")); + } +}; + +int main(int argc, char** argv) { + // this example needs a directory as parameter + if (2 != argc) { + std::cout << "Usage: file_upload " << std::endl; + std::cout << std::endl; + std::cout << " file_upload: writeable directory where uploaded files will be stored" << std::endl; + return -1; + } + + std::cout << "CAUTION: this example will create files in the directory " << std::string(argv[1]) << std::endl; + std::cout << "These files won't be deleted at termination" << std::endl; + std::cout << "Please make sure, that the given directory exists and is writeable" << std::endl; + + httpserver::webserver ws = httpserver::create_webserver(8080) + .no_put_processed_data_to_content() + .file_upload_dir(std::string(argv[1])) + .generate_random_filename_on_upload() + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY); + + file_upload_resource fur; + ws.register_resource("/", &fur); + ws.start(true); + + return 0; +} + diff --git a/examples/file_upload_with_callback.cpp b/examples/file_upload_with_callback.cpp new file mode 100644 index 00000000..edc5338f --- /dev/null +++ b/examples/file_upload_with_callback.cpp @@ -0,0 +1,111 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include +#include +#include + +#include + +class file_upload_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const httpserver::http_request&) { + std::string get_response = "\n"; + get_response += " \n"; + get_response += "

File Upload with Cleanup Callback Demo

\n"; + get_response += "

Uploaded files will be moved to the permanent directory.

\n"; + get_response += "
\n"; + get_response += " \n"; + get_response += "

\n"; + get_response += " \n"; + get_response += "
\n"; + get_response += " \n"; + get_response += "\n"; + + return std::shared_ptr(new httpserver::string_response(get_response, 200, "text/html")); + } + + std::shared_ptr render_POST(const httpserver::http_request& req) { + std::string post_response = "\n"; + post_response += "\n"; + post_response += "

Upload Complete

\n"; + post_response += "

Files have been moved to permanent storage:

\n"; + post_response += "
    \n"; + + for (auto &file_key : req.get_files()) { + for (auto &files : file_key.second) { + post_response += "
  • " + files.first + " (" + + std::to_string(files.second.get_file_size()) + " bytes)
  • \n"; + } + } + + post_response += "
\n"; + post_response += " Upload more\n"; + post_response += "\n"; + return std::shared_ptr(new httpserver::string_response(post_response, 201, "text/html")); + } +}; + +int main(int argc, char** argv) { + if (3 != argc) { + std::cout << "Usage: file_upload_with_callback " << std::endl; + std::cout << std::endl; + std::cout << " temp_dir: directory for temporary upload storage" << std::endl; + std::cout << " permanent_dir: directory where files will be moved after upload" << std::endl; + return -1; + } + + std::string temp_dir = argv[1]; + std::string permanent_dir = argv[2]; + + std::cout << "Starting file upload server on port 8080..." << std::endl; + std::cout << " Temporary directory: " << temp_dir << std::endl; + std::cout << " Permanent directory: " << permanent_dir << std::endl; + std::cout << std::endl; + std::cout << "Open http://localhost:8080 in your browser to upload files." << std::endl; + + httpserver::webserver ws = httpserver::create_webserver(8080) + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY) + .file_upload_dir(temp_dir) + .generate_random_filename_on_upload() + .file_cleanup_callback([&permanent_dir](const std::string& key, + const std::string& filename, + const httpserver::http::file_info& info) { + (void)key; // Unused in this example + // Move the uploaded file to permanent storage + std::string dest = permanent_dir + "/" + filename; + int result = std::rename(info.get_file_system_file_name().c_str(), dest.c_str()); + + if (result == 0) { + std::cout << "Moved: " << filename << " -> " << dest << std::endl; + return false; // Don't delete - we moved it + } else { + std::cerr << "Failed to move " << filename << ", will be deleted" << std::endl; + return true; // Delete the temp file on failure + } + }); + + file_upload_resource fur; + ws.register_resource("/", &fur); + ws.start(true); + + return 0; +} diff --git a/examples/handlers.cpp b/examples/handlers.cpp new file mode 100644 index 00000000..4fc70303 --- /dev/null +++ b/examples/handlers.cpp @@ -0,0 +1,45 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include + +#include + +class hello_world_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const httpserver::http_request&) { + return std::shared_ptr(new httpserver::string_response("GET: Hello, World!")); + } + + std::shared_ptr render(const httpserver::http_request&) { + return std::shared_ptr(new httpserver::string_response("OTHER: Hello, World!")); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} + diff --git a/examples/hello_with_get_arg.cpp b/examples/hello_with_get_arg.cpp new file mode 100644 index 00000000..41829a4d --- /dev/null +++ b/examples/hello_with_get_arg.cpp @@ -0,0 +1,41 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include + +#include + +class hello_world_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request& req) { + return std::shared_ptr(new httpserver::string_response("Hello: " + std::string(req.get_arg("name")))); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} diff --git a/examples/hello_world.cpp b/examples/hello_world.cpp index e5d37d9a..9c06f87a 100755 --- a/examples/hello_world.cpp +++ b/examples/hello_world.cpp @@ -18,49 +18,44 @@ USA */ -#include #include +#include +#include -using namespace httpserver; +#include -class hello_world_resource : public http_resource { - public: - void render(const http_request&, http_response**); - void set_some_data(const std::string &s) {data = s;} - std::string data; +class hello_world_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request&); + void set_some_data(const std::string &s) {data = s;} + std::string data; }; -//using the render method you are able to catch each type of request you receive -void hello_world_resource::render(const http_request& req, http_response** res) -{ - //it is possible to store data inside the resource object that can be altered - //through the requests +// Using the render method you are able to catch each type of request you receive +std::shared_ptr hello_world_resource::render(const httpserver::http_request& req) { + // It is possible to store data inside the resource object that can be altered through the requests std::cout << "Data was: " << data << std::endl; - std::string datapar = req.get_arg("data"); - set_some_data(datapar == "" ? "no data passed!!!" : datapar); + std::string_view datapar = req.get_arg("data"); + set_some_data(datapar == "" ? "no data passed!!!" : std::string(datapar)); std::cout << "Now data is:" << data << std::endl; - //it is possible to send a response initializing an http_string_response - //that reads the content to send in response from a string. - *res = new http_response(http_response_builder("Hello World!!!", 200).string_response()); + // It is possible to send a response initializing an http_string_response that reads the content to send in response from a string. + return std::shared_ptr(new httpserver::string_response("Hello World!!!", 200)); } -int main() -{ - //it is possible to create a webserver passing a great number of parameters. - //In this case we are just passing the port and the number of thread running. - webserver ws = create_webserver(8080).max_threads(5); +int main() { + // It is possible to create a webserver passing a great number of parameters. In this case we are just passing the port and the number of thread running. + httpserver::webserver ws = httpserver::create_webserver(8080).start_method(httpserver::http::http_utils::INTERNAL_SELECT).max_threads(5); hello_world_resource hwr; - //this way we are registering the hello_world_resource to answer for the endpoint - //"/hello". The requested method is called (if the request is a GET we call the render_GET - //method. In case that the specific render method is not implemented, the generic "render" - //method is called. + // This way we are registering the hello_world_resource to answer for the endpoint + // "/hello". The requested method is called (if the request is a GET we call the render_GET + // method. In case that the specific render method is not implemented, the generic "render" + // method is called. ws.register_resource("/hello", &hwr, true); - //This way we are putting the created webserver in listen. We pass true in order to have - //a blocking call; if we want the call to be non-blocking we can just pass false to the - //method. + // This way we are putting the created webserver in listen. We pass true in order to have + // a blocking call; if we want the call to be non-blocking we can just pass false to the method. ws.start(true); - return 0; + return 0; } diff --git a/examples/minimal_deferred.cpp b/examples/minimal_deferred.cpp new file mode 100644 index 00000000..d7a61d90 --- /dev/null +++ b/examples/minimal_deferred.cpp @@ -0,0 +1,59 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include +#include +#include + +#include + +static int counter = 0; + +ssize_t test_callback(std::shared_ptr closure_data, char* buf, size_t max) { + std::ignore = closure_data; + + if (counter == 2) { + return -1; + } else { + memset(buf, 0, max); + snprintf(buf, max, "%s", " test "); + counter++; + return std::string(buf).size(); + } +} + +class deferred_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const httpserver::http_request&) { + return std::shared_ptr >(new httpserver::deferred_response(test_callback, nullptr, "cycle callback response")); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080); + + deferred_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} + diff --git a/examples/minimal_file_response.cpp b/examples/minimal_file_response.cpp new file mode 100644 index 00000000..34776993 --- /dev/null +++ b/examples/minimal_file_response.cpp @@ -0,0 +1,40 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include + +#include + +class file_response_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const httpserver::http_request&) { + return std::shared_ptr(new httpserver::file_response("test_content", 200, "text/plain")); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080); + + file_response_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} diff --git a/examples/minimal_hello_world.cpp b/examples/minimal_hello_world.cpp new file mode 100644 index 00000000..fc166535 --- /dev/null +++ b/examples/minimal_hello_world.cpp @@ -0,0 +1,40 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include + +#include + +class hello_world_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request&) { + return std::shared_ptr(new httpserver::string_response("Hello, World!")); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} diff --git a/examples/minimal_https.cpp b/examples/minimal_https.cpp new file mode 100644 index 00000000..79cd710c --- /dev/null +++ b/examples/minimal_https.cpp @@ -0,0 +1,43 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include + +#include + +class hello_world_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request&) { + return std::shared_ptr(new httpserver::string_response("Hello, World!")); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080) + .use_ssl() + .https_mem_key("key.pem") + .https_mem_cert("cert.pem"); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} diff --git a/examples/minimal_https_psk.cpp b/examples/minimal_https_psk.cpp new file mode 100644 index 00000000..9bb02ef6 --- /dev/null +++ b/examples/minimal_https_psk.cpp @@ -0,0 +1,63 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2024 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include +#include + +#include + +// Simple PSK database - in production, use secure storage +std::map psk_database = { + {"client1", "0123456789abcdef0123456789abcdef"}, + {"client2", "fedcba9876543210fedcba9876543210"} +}; + +// PSK credential handler callback +// Returns the hex-encoded PSK for the given username, or empty string if not found +std::string psk_handler(const std::string& username) { + auto it = psk_database.find(username); + if (it != psk_database.end()) { + return it->second; + } + return ""; // Return empty string for unknown users +} + +class hello_world_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request&) { + return std::shared_ptr( + new httpserver::string_response("Hello, World (via TLS-PSK)!")); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080) + .use_ssl() + .cred_type(httpserver::http::http_utils::PSK) + .psk_cred_handler(psk_handler) + .https_priorities("NORMAL:-VERS-TLS-ALL:+VERS-TLS1.2:+PSK:+DHE-PSK"); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} diff --git a/examples/minimal_ip_ban.cpp b/examples/minimal_ip_ban.cpp new file mode 100644 index 00000000..4b95b5f0 --- /dev/null +++ b/examples/minimal_ip_ban.cpp @@ -0,0 +1,42 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include + +#include + +class hello_world_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request&) { + return std::shared_ptr(new httpserver::string_response("Hello, World!")); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080).default_policy(httpserver::http::http_utils::REJECT); + + ws.allow_ip("127.0.0.1"); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} diff --git a/examples/service.cpp b/examples/service.cpp index 537ebf97..309628bc 100644 --- a/examples/service.cpp +++ b/examples/service.cpp @@ -18,185 +18,169 @@ USA */ -#include -#include #include + #include +#include +#include -using namespace httpserver; +#include + +bool verbose = false; -bool verbose=false; +class service_resource: public httpserver::http_resource { + public: + service_resource(); -class service_resource: public http_resource { -public: - service_resource(); + ~service_resource(); - ~service_resource(); - - void render_GET(const http_request &req, http_response** res); + std::shared_ptr render_GET(const httpserver::http_request &req); + std::shared_ptr render_PUT(const httpserver::http_request &req); + std::shared_ptr render_POST(const httpserver::http_request &req); + std::shared_ptr render(const httpserver::http_request &req); + std::shared_ptr render_HEAD(const httpserver::http_request &req); + std::shared_ptr render_OPTIONS(const httpserver::http_request &req); + std::shared_ptr render_CONNECT(const httpserver::http_request &req); + std::shared_ptr render_DELETE(const httpserver::http_request &req); +}; - void render_PUT(const http_request &req, http_response** res); +service_resource::service_resource() { } - void render_POST(const http_request &req, http_response** res); +service_resource::~service_resource() { } - void render(const http_request &req, http_response** res); +std::shared_ptr service_resource::render_GET(const httpserver::http_request &req) { + std::cout << "service_resource::render_GET()" << std::endl; - void render_HEAD(const http_request &req, http_response** res); + if (verbose) std::cout << req; + httpserver::string_response* res = new httpserver::string_response("GET response", 200); - void render_OPTIONS(const http_request &req, http_response** res); + if (verbose) std::cout << *res; - void render_CONNECT(const http_request &req, http_response** res); + return std::shared_ptr(res); +} - void render_DELETE(const http_request &req, http_response** res); -private: +std::shared_ptr service_resource::render_PUT(const httpserver::http_request &req) { + std::cout << "service_resource::render_PUT()" << std::endl; + if (verbose) std::cout << req; -}; + httpserver::string_response* res = new httpserver::string_response("PUT response", 200); -service_resource::service_resource() -{} + if (verbose) std::cout << *res; -service_resource::~service_resource() -{} + return std::shared_ptr(res); +} -void -service_resource::render_GET(const http_request &req, http_response** res) -{ - std::cout << "service_resource::render_GET()" << std::endl; +std::shared_ptr service_resource::render_POST(const httpserver::http_request &req) { + std::cout << "service_resource::render_POST()" << std::endl; if (verbose) std::cout << req; - *res = new http_response(http_response_builder("GET response", 200).string_response()); + httpserver::string_response* res = new httpserver::string_response("POST response", 200); - if (verbose) std::cout << **res; -} + if (verbose) std::cout << *res; + return std::shared_ptr(res); +} -void -service_resource::render_PUT(const http_request &req, http_response** res) -{ - std::cout << "service_resource::render_PUT()" << std::endl; +std::shared_ptr service_resource::render(const httpserver::http_request &req) { + std::cout << "service_resource::render()" << std::endl; if (verbose) std::cout << req; - - *res = new http_response(http_response_builder("PUT response", 200).string_response()); - - if (verbose) std::cout << **res; -} + httpserver::string_response* res = new httpserver::string_response("generic response", 200); -void -service_resource::render_POST(const http_request &req, http_response** res) -{ - std::cout << "service_resource::render_POST()" << std::endl; + if (verbose) std::cout << *res; - if (verbose) std::cout << req; - - *res = new http_response(http_response_builder("POST response", 200).string_response()); - - if (verbose) std::cout << **res; + return std::shared_ptr(res); } -void -service_resource::render(const http_request &req, http_response** res) -{ - std::cout << "service_resource::render()" << std::endl; + +std::shared_ptr service_resource::render_HEAD(const httpserver::http_request &req) { + std::cout << "service_resource::render_HEAD()" << std::endl; if (verbose) std::cout << req; - *res = new http_response(http_response_builder("generic response", 200).string_response()); + httpserver::string_response* res = new httpserver::string_response("HEAD response", 200); - if (verbose) std::cout << **res; -} + if (verbose) std::cout << *res; + return std::shared_ptr(res); +} -void -service_resource::render_HEAD(const http_request &req, http_response** res) -{ - std::cout << "service_resource::render_HEAD()" << std::endl; +std::shared_ptr service_resource::render_OPTIONS(const httpserver::http_request &req) { + std::cout << "service_resource::render_OPTIONS()" << std::endl; if (verbose) std::cout << req; - - *res = new http_response(http_response_builder("HEAD response", 200).string_response()); - - if (verbose) std::cout << **res; -} -void -service_resource::render_OPTIONS(const http_request &req, http_response** res) -{ - std::cout << "service_resource::render_OPTIONS()" << std::endl; + httpserver::string_response* res = new httpserver::string_response("OPTIONS response", 200); - if (verbose) std::cout << req; - - *res = new http_response(http_response_builder("OPTIONS response", 200).string_response()); + if (verbose) std::cout << *res; - if (verbose) std::cout << **res; + return std::shared_ptr(res); } -void -service_resource::render_CONNECT(const http_request &req, http_response** res) -{ - std::cout << "service_resource::render_CONNECT()" << std::endl; +std::shared_ptr service_resource::render_CONNECT(const httpserver::http_request &req) { + std::cout << "service_resource::render_CONNECT()" << std::endl; if (verbose) std::cout << req; - - *res = new http_response(http_response_builder("CONNECT response", 200).string_response()); - if (verbose) std::cout << **res; + httpserver::string_response* res = new httpserver::string_response("CONNECT response", 200); + + if (verbose) std::cout << *res; + + return std::shared_ptr(res); } -void -service_resource::render_DELETE(const http_request &req, http_response** res) -{ - std::cout << "service_resource::render_DELETE()" << std::endl; +std::shared_ptr service_resource::render_DELETE(const httpserver::http_request &req) { + std::cout << "service_resource::render_DELETE()" << std::endl; if (verbose) std::cout << req; - - *res = new http_response(http_response_builder("DELETE response", 200).string_response()); - if (verbose) std::cout << **res; + httpserver::string_response* res = new httpserver::string_response("DELETE response", 200); + + if (verbose) std::cout << *res; + + return std::shared_ptr(res); } -void usage() -{ +void usage() { std::cout << "Usage:" << std::endl << "service [-p ][-s [-k ][-c ]][-v]" << std::endl; } -int main(int argc, char **argv) -{ - uint16_t port=8080; - int c; - const char *key="key.pem"; - const char *cert="cert.pem"; - bool secure=false; - - while ((c = getopt(argc,argv,"p:k:c:sv?")) != EOF) { - switch (c) { - case 'p': - port=strtoul(optarg,NULL,10); - break; +int main(int argc, char **argv) { + uint16_t port = 8080; + int c; + const char *key = "key.pem"; + const char *cert = "cert.pem"; + bool secure = false; + + while ((c = getopt(argc, argv, "p:k:c:sv?")) != EOF) { + switch (c) { + case 'p': + port = strtoul(optarg, nullptr, 10); + break; case 'k': key = optarg; break; case 'c': - cert=optarg; + cert = optarg; break; case 's': - secure=true; + secure = true; break; case 'v': - verbose=true; + verbose = true; break; - default: + default: usage(); exit(1); - break; - } - } + break; + } + } - std::cout << "Using port " << port << std::endl; + std::cout << "Using port " << port << std::endl; if (secure) { std::cout << "Key: " << key << " Certificate: " << cert << std::endl; @@ -205,7 +189,7 @@ int main(int argc, char **argv) // // Use builder to define webserver configuration options // - create_webserver cw = create_webserver(port).max_threads(5); + httpserver::create_webserver cw = httpserver::create_webserver(port).max_threads(5); if (secure) { cw.use_ssl().https_mem_key(key).https_mem_cert(cert); @@ -214,18 +198,18 @@ int main(int argc, char **argv) // // Create webserver using the configured options // - webserver ws = cw; + httpserver::webserver ws = cw; // // Create and register service resource available at /service // - service_resource res; - ws.register_resource("/service",&res,true); + service_resource res; + ws.register_resource("/service", &res, true); // // Start and block the webserver // - ws.start(true); + ws.start(true); - return 0; + return 0; } diff --git a/examples/setting_headers.cpp b/examples/setting_headers.cpp new file mode 100644 index 00000000..f92b76c1 --- /dev/null +++ b/examples/setting_headers.cpp @@ -0,0 +1,42 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include + +#include + +class hello_world_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request&) { + std::shared_ptr response = std::shared_ptr(new httpserver::string_response("Hello, World!")); + response->with_header("MyHeader", "MyValue"); + return response; + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + ws.start(true); + + return 0; +} diff --git a/examples/test_content b/examples/test_content new file mode 100755 index 00000000..5f643138 --- /dev/null +++ b/examples/test_content @@ -0,0 +1 @@ +test content of file diff --git a/examples/url_registration.cpp b/examples/url_registration.cpp new file mode 100644 index 00000000..e6eef458 --- /dev/null +++ b/examples/url_registration.cpp @@ -0,0 +1,64 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include + +#include + +class hello_world_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request&) { + return std::shared_ptr(new httpserver::string_response("Hello, World!")); + } +}; + +class handling_multiple_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request& req) { + return std::shared_ptr(new httpserver::string_response("Your URL: " + std::string(req.get_path()))); + } +}; + +class url_args_resource : public httpserver::http_resource { + public: + std::shared_ptr render(const httpserver::http_request& req) { + return std::shared_ptr(new httpserver::string_response("ARGS: " + std::string(req.get_arg("arg1")) + " and " + std::string(req.get_arg("arg2")))); + } +}; + +int main() { + httpserver::webserver ws = httpserver::create_webserver(8080); + + hello_world_resource hwr; + ws.register_resource("/hello", &hwr); + + handling_multiple_resource hmr; + ws.register_resource("/family", &hmr, true); + ws.register_resource("/with_regex_[0-9]+", &hmr); + + url_args_resource uar; + ws.register_resource("/url/with/{arg1}/and/{arg2}", &uar); + ws.register_resource("/url/with/parametric/args/{arg1|[0-9]+}/and/{arg2|[A-Z]+}", &uar); + + ws.start(true); + + return 0; +} diff --git a/libhttpserver.pc.in b/libhttpserver.pc.in index 36355eea..aaf116af 100644 --- a/libhttpserver.pc.in +++ b/libhttpserver.pc.in @@ -6,8 +6,8 @@ includedir=@includedir@ Name: libhttpserver Description: A C++ library for creating an embedded Rest HTTP server Version: @VERSION@ -Requires: libmicrohttpd >= 0.9.37 +Requires: libmicrohttpd >= 0.9.52 Conflicts: -Libs: -L${libdir} -lmicrohttpd +Libs: -L${libdir} -lhttpserver Libs.private: @LHT_LIBDEPS@ Cflags: -I${includedir} -I${includedir}/httpserver diff --git a/m4/ax_cxx_compile_stdcxx.m4 b/m4/ax_cxx_compile_stdcxx.m4 new file mode 100644 index 00000000..2bb9b25e --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx.m4 @@ -0,0 +1,964 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the specified +# version of the C++ standard. If necessary, add switches to CXX and +# CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) +# or '14' (for the C++14 standard). +# +# The second argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for no added switch, and then for an extended mode. +# +# The third argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline support for the specified C++ standard is +# required and that the macro should error out if no mode with that +# support is found. If specified 'optional', then configuration proceeds +# regardless, after defining HAVE_CXX${VERSION} if and only if a +# supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# Copyright (c) 2015 Paul Norman +# Copyright (c) 2015 Moritz Klammler +# Copyright (c) 2016, 2018 Krzesimir Nowak +# Copyright (c) 2019 Enji Cooper +# Copyright (c) 2020 Jason Merrill +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 12 + +dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro +dnl (serial version number 13). + +AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl + m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], + [$1], [14], [ax_cxx_compile_alternatives="14 1y"], + [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$2], [], [], + [$2], [ext], [], + [$2], [noext], [], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], + [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], + [$3], [optional], [ax_cxx_compile_cxx$1_required=false], + [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + + m4_if([$2], [], [dnl + AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, + ax_cv_cxx_compile_cxx$1, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [ax_cv_cxx_compile_cxx$1=yes], + [ax_cv_cxx_compile_cxx$1=no])]) + if test x$ax_cv_cxx_compile_cxx$1 = xyes; then + ac_success=yes + fi]) + + m4_if([$2], [noext], [], [dnl + if test x$ac_success = xno; then + for alternative in ${ax_cxx_compile_alternatives}; do + switch="-std=gnu++${alternative}" + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + fi]) + + m4_if([$2], [ext], [], [dnl + if test x$ac_success = xno; then + dnl HP's aCC needs +std=c++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf + dnl Cray's crayCC needs "-h std=c++11" + for alternative in ${ax_cxx_compile_alternatives}; do + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + if test x$ac_success = xyes; then + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx$1_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) + fi + fi + if test x$ac_success = xno; then + HAVE_CXX$1=0 + AC_MSG_NOTICE([No compiler with C++$1 support was found]) + else + HAVE_CXX$1=1 + AC_DEFINE(HAVE_CXX$1,1, + [define if the compiler supports basic C++$1 syntax]) + fi + AC_SUBST(HAVE_CXX$1) +]) + + +dnl Test body for checking C++11 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 +) + + +dnl Test body for checking C++14 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 +) + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 +) + +dnl Tests for new features in C++11 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ + +// If the compiler admits that it is not ready for C++11, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201103L + +#error "This is not a C++11 compiler" + +#else + +namespace cxx11 +{ + + namespace test_static_assert + { + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + } + + namespace test_final_override + { + + struct Base + { + virtual ~Base() {} + virtual void f() {} + }; + + struct Derived : public Base + { + virtual ~Derived() override {} + virtual void f() override {} + }; + + } + + namespace test_double_right_angle_brackets + { + + template < typename T > + struct check {}; + + typedef check single_type; + typedef check> double_type; + typedef check>> triple_type; + typedef check>>> quadruple_type; + + } + + namespace test_decltype + { + + int + f() + { + int a = 1; + decltype(a) b = 2; + return a + b; + } + + } + + namespace test_type_deduction + { + + template < typename T1, typename T2 > + struct is_same + { + static const bool value = false; + }; + + template < typename T > + struct is_same + { + static const bool value = true; + }; + + template < typename T1, typename T2 > + auto + add(T1 a1, T2 a2) -> decltype(a1 + a2) + { + return a1 + a2; + } + + int + test(const int c, volatile int v) + { + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == false, ""); + auto ac = c; + auto av = v; + auto sumi = ac + av + 'x'; + auto sumf = ac + av + 1.0; + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == true, ""); + return (sumf > 0.0) ? sumi : add(c, v); + } + + } + + namespace test_noexcept + { + + int f() { return 0; } + int g() noexcept { return 0; } + + static_assert(noexcept(f()) == false, ""); + static_assert(noexcept(g()) == true, ""); + + } + + namespace test_constexpr + { + + template < typename CharT > + unsigned long constexpr + strlen_c_r(const CharT *const s, const unsigned long acc) noexcept + { + return *s ? strlen_c_r(s + 1, acc + 1) : acc; + } + + template < typename CharT > + unsigned long constexpr + strlen_c(const CharT *const s) noexcept + { + return strlen_c_r(s, 0UL); + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("1") == 1UL, ""); + static_assert(strlen_c("example") == 7UL, ""); + static_assert(strlen_c("another\0example") == 7UL, ""); + + } + + namespace test_rvalue_references + { + + template < int N > + struct answer + { + static constexpr int value = N; + }; + + answer<1> f(int&) { return answer<1>(); } + answer<2> f(const int&) { return answer<2>(); } + answer<3> f(int&&) { return answer<3>(); } + + void + test() + { + int i = 0; + const int c = 0; + static_assert(decltype(f(i))::value == 1, ""); + static_assert(decltype(f(c))::value == 2, ""); + static_assert(decltype(f(0))::value == 3, ""); + } + + } + + namespace test_uniform_initialization + { + + struct test + { + static const int zero {}; + static const int one {1}; + }; + + static_assert(test::zero == 0, ""); + static_assert(test::one == 1, ""); + + } + + namespace test_lambdas + { + + void + test1() + { + auto lambda1 = [](){}; + auto lambda2 = lambda1; + lambda1(); + lambda2(); + } + + int + test2() + { + auto a = [](int i, int j){ return i + j; }(1, 2); + auto b = []() -> int { return '0'; }(); + auto c = [=](){ return a + b; }(); + auto d = [&](){ return c; }(); + auto e = [a, &b](int x) mutable { + const auto identity = [](int y){ return y; }; + for (auto i = 0; i < a; ++i) + a += b--; + return x + identity(a + b); + }(0); + return a + b + c + d + e; + } + + int + test3() + { + const auto nullary = [](){ return 0; }; + const auto unary = [](int x){ return x; }; + using nullary_t = decltype(nullary); + using unary_t = decltype(unary); + const auto higher1st = [](nullary_t f){ return f(); }; + const auto higher2nd = [unary](nullary_t f1){ + return [unary, f1](unary_t f2){ return f2(unary(f1())); }; + }; + return higher1st(nullary) + higher2nd(nullary)(unary); + } + + } + + namespace test_variadic_templates + { + + template + struct sum; + + template + struct sum + { + static constexpr auto value = N0 + sum::value; + }; + + template <> + struct sum<> + { + static constexpr auto value = 0; + }; + + static_assert(sum<>::value == 0, ""); + static_assert(sum<1>::value == 1, ""); + static_assert(sum<23>::value == 23, ""); + static_assert(sum<1, 2>::value == 3, ""); + static_assert(sum<5, 5, 11>::value == 21, ""); + static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); + + } + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function + // because of this. + namespace test_template_alias_sfinae + { + + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { func(0); } + + } + +} // namespace cxx11 + +#endif // __cplusplus >= 201103L + +]]) + + +dnl Tests for new features in C++14 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ + +// If the compiler admits that it is not ready for C++14, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201402L + +#error "This is not a C++14 compiler" + +#else + +namespace cxx14 +{ + + namespace test_polymorphic_lambdas + { + + int + test() + { + const auto lambda = [](auto&&... args){ + const auto istiny = [](auto x){ + return (sizeof(x) == 1UL) ? 1 : 0; + }; + const int aretiny[] = { istiny(args)... }; + return aretiny[0]; + }; + return lambda(1, 1L, 1.0f, '1'); + } + + } + + namespace test_binary_literals + { + + constexpr auto ivii = 0b0000000000101010; + static_assert(ivii == 42, "wrong value"); + + } + + namespace test_generalized_constexpr + { + + template < typename CharT > + constexpr unsigned long + strlen_c(const CharT *const s) noexcept + { + auto length = 0UL; + for (auto p = s; *p; ++p) + ++length; + return length; + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("x") == 1UL, ""); + static_assert(strlen_c("test") == 4UL, ""); + static_assert(strlen_c("another\0test") == 7UL, ""); + + } + + namespace test_lambda_init_capture + { + + int + test() + { + auto x = 0; + const auto lambda1 = [a = x](int b){ return a + b; }; + const auto lambda2 = [a = lambda1(x)](){ return a; }; + return lambda2(); + } + + } + + namespace test_digit_separators + { + + constexpr auto ten_million = 100'000'000; + static_assert(ten_million == 100000000, ""); + + } + + namespace test_return_type_deduction + { + + auto f(int& x) { return x; } + decltype(auto) g(int& x) { return x; } + + template < typename T1, typename T2 > + struct is_same + { + static constexpr auto value = false; + }; + + template < typename T > + struct is_same + { + static constexpr auto value = true; + }; + + int + test() + { + auto x = 0; + static_assert(is_same::value, ""); + static_assert(is_same::value, ""); + return x; + } + + } + +} // namespace cxx14 + +#endif // __cplusplus >= 201402L + +]]) + + +dnl Tests for new features in C++17 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ + +// If the compiler admits that it is not ready for C++17, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201703L + +#error "This is not a C++17 compiler" + +#else + +#include +#include +#include + +namespace cxx17 +{ + + namespace test_constexpr_lambdas + { + + constexpr int foo = [](){return 42;}(); + + } + + namespace test::nested_namespace::definitions + { + + } + + namespace test_fold_expression + { + + template + int multiply(Args... args) + { + return (args * ... * 1); + } + + template + bool all(Args... args) + { + return (args && ...); + } + + } + + namespace test_extended_static_assert + { + + static_assert (true); + + } + + namespace test_auto_brace_init_list + { + + auto foo = {5}; + auto bar {5}; + + static_assert(std::is_same, decltype(foo)>::value); + static_assert(std::is_same::value); + } + + namespace test_typename_in_template_template_parameter + { + + template typename X> struct D; + + } + + namespace test_fallthrough_nodiscard_maybe_unused_attributes + { + + int f1() + { + return 42; + } + + [[nodiscard]] int f2() + { + [[maybe_unused]] auto unused = f1(); + + switch (f1()) + { + case 17: + f1(); + [[fallthrough]]; + case 42: + f1(); + } + return f1(); + } + + } + + namespace test_extended_aggregate_initialization + { + + struct base1 + { + int b1, b2 = 42; + }; + + struct base2 + { + base2() { + b3 = 42; + } + int b3; + }; + + struct derived : base1, base2 + { + int d; + }; + + derived d1 {{1, 2}, {}, 4}; // full initialization + derived d2 {{}, {}, 4}; // value-initialized bases + + } + + namespace test_general_range_based_for_loop + { + + struct iter + { + int i; + + int& operator* () + { + return i; + } + + const int& operator* () const + { + return i; + } + + iter& operator++() + { + ++i; + return *this; + } + }; + + struct sentinel + { + int i; + }; + + bool operator== (const iter& i, const sentinel& s) + { + return i.i == s.i; + } + + bool operator!= (const iter& i, const sentinel& s) + { + return !(i == s); + } + + struct range + { + iter begin() const + { + return {0}; + } + + sentinel end() const + { + return {5}; + } + }; + + void f() + { + range r {}; + + for (auto i : r) + { + [[maybe_unused]] auto v = i; + } + } + + } + + namespace test_lambda_capture_asterisk_this_by_value + { + + struct t + { + int i; + int foo() + { + return [*this]() + { + return i; + }(); + } + }; + + } + + namespace test_enum_class_construction + { + + enum class byte : unsigned char + {}; + + byte foo {42}; + + } + + namespace test_constexpr_if + { + + template + int f () + { + if constexpr(cond) + { + return 13; + } + else + { + return 42; + } + } + + } + + namespace test_selection_statement_with_initializer + { + + int f() + { + return 13; + } + + int f2() + { + if (auto i = f(); i > 0) + { + return 3; + } + + switch (auto i = f(); i + 4) + { + case 17: + return 2; + + default: + return 1; + } + } + + } + + namespace test_template_argument_deduction_for_class_templates + { + + template + struct pair + { + pair (T1 p1, T2 p2) + : m1 {p1}, + m2 {p2} + {} + + T1 m1; + T2 m2; + }; + + void f() + { + [[maybe_unused]] auto p = pair{13, 42u}; + } + + } + + namespace test_non_type_auto_template_parameters + { + + template + struct B + {}; + + B<5> b1; + B<'a'> b2; + + } + + namespace test_structured_bindings + { + + int arr[2] = { 1, 2 }; + std::pair pr = { 1, 2 }; + + auto f1() -> int(&)[2] + { + return arr; + } + + auto f2() -> std::pair& + { + return pr; + } + + struct S + { + int x1 : 2; + volatile double y1; + }; + + S f3() + { + return {}; + } + + auto [ x1, y1 ] = f1(); + auto& [ xr1, yr1 ] = f1(); + auto [ x2, y2 ] = f2(); + auto& [ xr2, yr2 ] = f2(); + const auto [ x3, y3 ] = f3(); + + } + + namespace test_exception_spec_type_system + { + + struct Good {}; + struct Bad {}; + + void g1() noexcept; + void g2(); + + template + Bad + f(T*, T*); + + template + Good + f(T1*, T2*); + + static_assert (std::is_same_v); + + } + + namespace test_inline_variables + { + + template void f(T) + {} + + template inline T g(T) + { + return T{}; + } + + template<> inline void f<>(int) + {} + + template<> int g<>(int) + { + return 5; + } + + } + +} // namespace cxx17 + +#endif // __cplusplus < 201703L + +]]) + + diff --git a/m4/ax_have_epoll.m4 b/m4/ax_have_epoll.m4 new file mode 100644 index 00000000..9d9bc873 --- /dev/null +++ b/m4/ax_have_epoll.m4 @@ -0,0 +1,104 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_have_epoll.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_HAVE_EPOLL([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# AX_HAVE_EPOLL_PWAIT([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# +# DESCRIPTION +# +# This macro determines whether the system supports the epoll I/O event +# interface. A neat usage example would be: +# +# AX_HAVE_EPOLL( +# [AX_CONFIG_FEATURE_ENABLE(epoll)], +# [AX_CONFIG_FEATURE_DISABLE(epoll)]) +# AX_CONFIG_FEATURE( +# [epoll], [This platform supports epoll(7)], +# [HAVE_EPOLL], [This platform supports epoll(7).]) +# +# The epoll interface was added to the Linux kernel in version 2.5.45, and +# the macro verifies that a kernel newer than this is installed. This +# check is somewhat unreliable if doesn't match the +# running kernel, but it is necessary regardless, because glibc comes with +# stubs for the epoll_create(), epoll_wait(), etc. that allow programs to +# compile and link even if the kernel is too old; the problem would then +# be detected only at runtime. +# +# Linux kernel version 2.6.19 adds the epoll_pwait() call in addition to +# epoll_wait(). The availability of that function can be tested with the +# second macro. Generally speaking, it is safe to assume that +# AX_HAVE_EPOLL would succeed if AX_HAVE_EPOLL_PWAIT has, but not the +# other way round. +# +# LICENSE +# +# Copyright (c) 2008 Peter Simons +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 11 + +AC_DEFUN([AX_HAVE_EPOLL], [dnl + ax_have_epoll_cppflags="${CPPFLAGS}" + AC_CHECK_HEADER([linux/version.h], [CPPFLAGS="${CPPFLAGS} -DHAVE_LINUX_VERSION_H"]) + AC_MSG_CHECKING([for Linux epoll(7) interface]) + AC_CACHE_VAL([ax_cv_have_epoll], [dnl + AC_LINK_IFELSE([dnl + AC_LANG_PROGRAM([dnl +#include +#ifdef HAVE_LINUX_VERSION_H +# include +# if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,45) +# error linux kernel version is too old to have epoll +# endif +#endif +], [dnl +int fd, rc; +struct epoll_event ev; +fd = epoll_create(128); +rc = epoll_wait(fd, &ev, 1, 0);])], + [ax_cv_have_epoll=yes], + [ax_cv_have_epoll=no])]) + CPPFLAGS="${ax_have_epoll_cppflags}" + AS_IF([test "${ax_cv_have_epoll}" = "yes"], + [AC_MSG_RESULT([yes]) +$1],[AC_MSG_RESULT([no]) +$2]) +])dnl + +AC_DEFUN([AX_HAVE_EPOLL_PWAIT], [dnl + ax_have_epoll_cppflags="${CPPFLAGS}" + AC_CHECK_HEADER([linux/version.h], + [CPPFLAGS="${CPPFLAGS} -DHAVE_LINUX_VERSION_H"]) + AC_MSG_CHECKING([for Linux epoll(7) interface with signals extension]) + AC_CACHE_VAL([ax_cv_have_epoll_pwait], [dnl + AC_LINK_IFELSE([dnl + AC_LANG_PROGRAM([dnl +#ifdef HAVE_LINUX_VERSION_H +# include +# if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +# error linux kernel version is too old to have epoll_pwait +# endif +#endif +#include +#include +], [dnl +int fd, rc; +struct epoll_event ev; +fd = epoll_create(128); +rc = epoll_wait(fd, &ev, 1, 0); +rc = epoll_pwait(fd, &ev, 1, 0, (sigset_t const *)(0));])], + [ax_cv_have_epoll_pwait=yes], + [ax_cv_have_epoll_pwait=no])]) + CPPFLAGS="${ax_have_epoll_cppflags}" + AS_IF([test "${ax_cv_have_epoll_pwait}" = "yes"], + [AC_MSG_RESULT([yes]) +$1],[AC_MSG_RESULT([no]) +$2]) +])dnl diff --git a/m4/ax_valgrind_check.m4 b/m4/ax_valgrind_check.m4 new file mode 100644 index 00000000..70337981 --- /dev/null +++ b/m4/ax_valgrind_check.m4 @@ -0,0 +1,239 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_valgrind_check.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_VALGRIND_DFLT(memcheck|helgrind|drd|sgcheck, on|off) +# AX_VALGRIND_CHECK() +# +# DESCRIPTION +# +# AX_VALGRIND_CHECK checks whether Valgrind is present and, if so, allows +# running `make check` under a variety of Valgrind tools to check for +# memory and threading errors. +# +# Defines VALGRIND_CHECK_RULES which should be substituted in your +# Makefile; and $enable_valgrind which can be used in subsequent configure +# output. VALGRIND_ENABLED is defined and substituted, and corresponds to +# the value of the --enable-valgrind option, which defaults to being +# enabled if Valgrind is installed and disabled otherwise. Individual +# Valgrind tools can be disabled via --disable-valgrind-, the +# default is configurable via the AX_VALGRIND_DFLT command or is to use +# all commands not disabled via AX_VALGRIND_DFLT. All AX_VALGRIND_DFLT +# calls must be made before the call to AX_VALGRIND_CHECK. +# +# If unit tests are written using a shell script and automake's +# LOG_COMPILER system, the $(VALGRIND) variable can be used within the +# shell scripts to enable Valgrind, as described here: +# +# https://www.gnu.org/software/gnulib/manual/html_node/Running-self_002dtests-under-valgrind.html +# +# Usage example: +# +# configure.ac: +# +# AX_VALGRIND_DFLT([sgcheck], [off]) +# AX_VALGRIND_CHECK +# +# in each Makefile.am with tests: +# +# @VALGRIND_CHECK_RULES@ +# VALGRIND_SUPPRESSIONS_FILES = my-project.supp +# EXTRA_DIST = my-project.supp +# +# This results in a "check-valgrind" rule being added. Running `make +# check-valgrind` in that directory will recursively run the module's test +# suite (`make check`) once for each of the available Valgrind tools (out +# of memcheck, helgrind and drd) while the sgcheck will be skipped unless +# enabled again on the commandline with --enable-valgrind-sgcheck. The +# results for each check will be output to test-suite-$toolname.log. The +# target will succeed if there are zero errors and fail otherwise. +# +# Alternatively, a "check-valgrind-$TOOL" rule will be added, for $TOOL in +# memcheck, helgrind, drd and sgcheck. These are useful because often only +# some of those tools can be ran cleanly on a codebase. +# +# The macro supports running with and without libtool. +# +# LICENSE +# +# Copyright (c) 2014, 2015, 2016 Philip Withnall +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 17 + +dnl Configured tools +m4_define([valgrind_tool_list], [[memcheck], [helgrind], [drd], [sgcheck]]) +m4_set_add_all([valgrind_exp_tool_set], [sgcheck]) +m4_foreach([vgtool], [valgrind_tool_list], + [m4_define([en_dflt_valgrind_]vgtool, [on])]) + +AC_DEFUN([AX_VALGRIND_DFLT],[ + m4_define([en_dflt_valgrind_$1], [$2]) +])dnl + +AM_EXTRA_RECURSIVE_TARGETS([check-valgrind]) +m4_foreach([vgtool], [valgrind_tool_list], + [AM_EXTRA_RECURSIVE_TARGETS([check-valgrind-]vgtool)]) + +AC_DEFUN([AX_VALGRIND_CHECK],[ + dnl Check for --enable-valgrind + AC_ARG_ENABLE([valgrind], + [AS_HELP_STRING([--enable-valgrind], [Whether to enable Valgrind on the unit tests])], + [enable_valgrind=$enableval],[enable_valgrind=]) + + AS_IF([test "$enable_valgrind" != "no"],[ + # Check for Valgrind. + AC_CHECK_PROG([VALGRIND],[valgrind],[valgrind]) + AS_IF([test "$VALGRIND" = ""],[ + AS_IF([test "$enable_valgrind" = "yes"],[ + AC_MSG_ERROR([Could not find valgrind; either install it or reconfigure with --disable-valgrind]) + ],[ + enable_valgrind=no + ]) + ],[ + enable_valgrind=yes + ]) + ]) + + AM_CONDITIONAL([VALGRIND_ENABLED],[test "$enable_valgrind" = "yes"]) + AC_SUBST([VALGRIND_ENABLED],[$enable_valgrind]) + + # Check for Valgrind tools we care about. + [valgrind_enabled_tools=] + m4_foreach([vgtool],[valgrind_tool_list],[ + AC_ARG_ENABLE([valgrind-]vgtool, + m4_if(m4_defn([en_dflt_valgrind_]vgtool),[off],dnl +[AS_HELP_STRING([--enable-valgrind-]vgtool, [Whether to use ]vgtool[ during the Valgrind tests])],dnl +[AS_HELP_STRING([--disable-valgrind-]vgtool, [Whether to skip ]vgtool[ during the Valgrind tests])]), + [enable_valgrind_]vgtool[=$enableval], + [enable_valgrind_]vgtool[=]) + AS_IF([test "$enable_valgrind" = "no"],[ + enable_valgrind_]vgtool[=no], + [test "$enable_valgrind_]vgtool[" ]dnl +m4_if(m4_defn([en_dflt_valgrind_]vgtool), [off], [= "yes"], [!= "no"]),[ + AC_CACHE_CHECK([for Valgrind tool ]vgtool, + [ax_cv_valgrind_tool_]vgtool,[ + ax_cv_valgrind_tool_]vgtool[=no + m4_set_contains([valgrind_exp_tool_set],vgtool, + [m4_define([vgtoolx],[exp-]vgtool)], + [m4_define([vgtoolx],vgtool)]) + AS_IF([`$VALGRIND --tool=]vgtoolx[ --help >/dev/null 2>&1`],[ + ax_cv_valgrind_tool_]vgtool[=yes + ]) + ]) + AS_IF([test "$ax_cv_valgrind_tool_]vgtool[" = "no"],[ + AS_IF([test "$enable_valgrind_]vgtool[" = "yes"],[ + AC_MSG_ERROR([Valgrind does not support ]vgtool[; reconfigure with --disable-valgrind-]vgtool) + ],[ + enable_valgrind_]vgtool[=no + ]) + ],[ + enable_valgrind_]vgtool[=yes + ]) + ]) + AS_IF([test "$enable_valgrind_]vgtool[" = "yes"],[ + valgrind_enabled_tools="$valgrind_enabled_tools ]m4_bpatsubst(vgtool,[^exp-])[" + ]) + AC_SUBST([ENABLE_VALGRIND_]vgtool,[$enable_valgrind_]vgtool) + ]) + AC_SUBST([valgrind_tools],["]m4_join([ ], valgrind_tool_list)["]) + AC_SUBST([valgrind_enabled_tools],[$valgrind_enabled_tools]) + +[VALGRIND_CHECK_RULES=' +# Valgrind check +# +# Optional: +# - VALGRIND_SUPPRESSIONS_FILES: Space-separated list of Valgrind suppressions +# files to load. (Default: empty) +# - VALGRIND_FLAGS: General flags to pass to all Valgrind tools. +# (Default: --num-callers=30) +# - VALGRIND_$toolname_FLAGS: Flags to pass to Valgrind $toolname (one of: +# memcheck, helgrind, drd, sgcheck). (Default: various) + +# Optional variables +VALGRIND_SUPPRESSIONS ?= $(addprefix --suppressions=,$(VALGRIND_SUPPRESSIONS_FILES)) +VALGRIND_FLAGS ?= --num-callers=30 +VALGRIND_memcheck_FLAGS ?= --leak-check=full --show-reachable=no +VALGRIND_helgrind_FLAGS ?= --history-level=approx +VALGRIND_drd_FLAGS ?= +VALGRIND_sgcheck_FLAGS ?= + +# Internal use +valgrind_log_files = $(addprefix test-suite-,$(addsuffix .log,$(valgrind_tools))) + +valgrind_memcheck_flags = --tool=memcheck $(VALGRIND_memcheck_FLAGS) +valgrind_helgrind_flags = --tool=helgrind $(VALGRIND_helgrind_FLAGS) +valgrind_drd_flags = --tool=drd $(VALGRIND_drd_FLAGS) +valgrind_sgcheck_flags = --tool=exp-sgcheck $(VALGRIND_sgcheck_FLAGS) + +valgrind_quiet = $(valgrind_quiet_$(V)) +valgrind_quiet_ = $(valgrind_quiet_$(AM_DEFAULT_VERBOSITY)) +valgrind_quiet_0 = --quiet +valgrind_v_use = $(valgrind_v_use_$(V)) +valgrind_v_use_ = $(valgrind_v_use_$(AM_DEFAULT_VERBOSITY)) +valgrind_v_use_0 = @echo " USE " $(patsubst check-valgrind-%-am,%,$''@):; + +# Support running with and without libtool. +ifneq ($(LIBTOOL),) +valgrind_lt = $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=execute +else +valgrind_lt = +endif + +# Use recursive makes in order to ignore errors during check +check-valgrind-am: +ifeq ($(VALGRIND_ENABLED),yes) + $(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) -k \ + $(foreach tool, $(valgrind_enabled_tools), check-valgrind-$(tool)) +else + @echo "Need to reconfigure with --enable-valgrind" +endif + +# Valgrind running +VALGRIND_TESTS_ENVIRONMENT = \ + $(TESTS_ENVIRONMENT) \ + env VALGRIND=$(VALGRIND) \ + G_SLICE=always-malloc,debug-blocks \ + G_DEBUG=fatal-warnings,fatal-criticals,gc-friendly + +VALGRIND_LOG_COMPILER = \ + $(valgrind_lt) \ + $(VALGRIND) $(VALGRIND_SUPPRESSIONS) --error-exitcode=1 $(VALGRIND_FLAGS) + +define valgrind_tool_rule +check-valgrind-$(1)-am: +ifeq ($$(VALGRIND_ENABLED)-$$(ENABLE_VALGRIND_$(1)),yes-yes) +ifneq ($$(TESTS),) + $$(valgrind_v_use)$$(MAKE) check-TESTS \ + TESTS_ENVIRONMENT="$$(VALGRIND_TESTS_ENVIRONMENT)" \ + LOG_COMPILER="$$(VALGRIND_LOG_COMPILER)" \ + LOG_FLAGS="$$(valgrind_$(1)_flags)" \ + TEST_SUITE_LOG=test-suite-$(1).log +endif +else ifeq ($$(VALGRIND_ENABLED),yes) + @echo "Need to reconfigure with --enable-valgrind-$(1)" +else + @echo "Need to reconfigure with --enable-valgrind" +endif +endef + +$(foreach tool,$(valgrind_tools),$(eval $(call valgrind_tool_rule,$(tool)))) + +A''M_DISTCHECK_CONFIGURE_FLAGS ?= +A''M_DISTCHECK_CONFIGURE_FLAGS += --disable-valgrind + +MOSTLYCLEANFILES ?= +MOSTLYCLEANFILES += $(valgrind_log_files) + +.PHONY: check-valgrind $(add-prefix check-valgrind-,$(valgrind_tools)) +'] + + AC_SUBST([VALGRIND_CHECK_RULES]) + m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([VALGRIND_CHECK_RULES])]) +]) diff --git a/msan_ignorelist.txt b/msan_ignorelist.txt new file mode 100644 index 00000000..e435d8be --- /dev/null +++ b/msan_ignorelist.txt @@ -0,0 +1,3 @@ +fun:__interceptor_strlen +fun:__interceptor_fopen64 +fun:__interceptor_memcmp \ No newline at end of file diff --git a/redhat/libhttpserver.SPEC.in b/redhat/libhttpserver.SPEC.in deleted file mode 100644 index e06c5139..00000000 --- a/redhat/libhttpserver.SPEC.in +++ /dev/null @@ -1,56 +0,0 @@ -%define _topdir @abs_builddir@ -%define name libhttpserver -%define version @VERSION@ -%define buildroot %{_topdir}/%{name}-%{version}-root - -Name: %{name} -Release: 1 -Version: %{version} -Summary: library embedding RESTful HTTP server functionality -Group: Development/Libraries -License: LGPL 2.1 -URL: https://github.com/etr/libhttpserver -Source: @abs_builddir@/%{name}-%{version}.tar.gz -Vendor: Zencoders -Prefix: /usr -BuildRoot: %{buildroot} -Packager: Sebastiano Merlino -BuildRequires: libmicrohttpd >= 0.9.7 -Requires: libmicrohttpd >= 0.9.7 -%description -libhttpserver is a small C++ library for embedding RESTful HTTP server functionality into applications. - -%package devel -Summary: Development files -BuildRequires: libmicrohttpd >= 0.9.7, libmicrohttpd-devel >= 0.9.7 -Group: Development/Libraries -Requires: libmicrohttpd >= 0.9.7, libmicrohttpd-devel >= 0.9.7 -%description devel -libhttpserver is a small C++ library for embedding RESTful HTTP server functionality into applications. -This package contains development files and headers. - - -%prep -%setup - -%build -mkdir build -cd build -../configure --prefix=$RPM_BUILD_ROOT/usr -make - -%install -cd build -make install -rm $RPM_BUILD_ROOT/usr/lib/libhttpserver.la - -%files -%defattr(-,root,root) -/usr/lib/*.so.* - -%files devel -%defattr(-,root,root) -/usr/include -/usr/lib/*.a -/usr/lib/*.so -/usr/lib/pkgconfig diff --git a/scripts/extract-release-notes.sh b/scripts/extract-release-notes.sh new file mode 100755 index 00000000..ddc129d5 --- /dev/null +++ b/scripts/extract-release-notes.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Extract release notes for a given version from the ChangeLog. +# Usage: extract-release-notes.sh [VERSION] +# If VERSION is omitted, extracts the first (most recent) section. + +set -euo pipefail + +VERSION="${1:-}" + +# Strip leading 'v' if present +VERSION="${VERSION#v}" + +CHANGELOG="${CHANGELOG:-ChangeLog}" + +if [ ! -f "$CHANGELOG" ]; then + echo "Error: $CHANGELOG not found" >&2 + exit 1 +fi + +if [ -z "$VERSION" ]; then + # Extract the first version section (everything between the first and second headers) + awk ' + /^Version [0-9]+\.[0-9]+\.[0-9]+/ { + if (found) exit + found = 1 + next + } + found && /^$/ && !started { next } + found { started = 1; print } + ' "$CHANGELOG" | sed -e :a -e '/^[[:space:]]*$/{ $d; N; ba; }' +else + # Extract notes for the specific version + awk -v ver="$VERSION" ' + /^Version [0-9]+\.[0-9]+\.[0-9]+/ { + if (found) exit + if (index($0, "Version " ver " ") == 1 || $0 == "Version " ver) { + found = 1 + next + } + } + found && /^$/ && !started { next } + found { started = 1; print } + ' "$CHANGELOG" | sed -e :a -e '/^[[:space:]]*$/{ $d; N; ba; }' +fi diff --git a/scripts/validate-version.sh b/scripts/validate-version.sh new file mode 100755 index 00000000..583fdb36 --- /dev/null +++ b/scripts/validate-version.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Validate version consistency between a tag, configure.ac, and ChangeLog. +# Usage: validate-version.sh VERSION +# VERSION should NOT have a 'v' prefix (e.g., "0.19.0"). + +set -euo pipefail + +VERSION="${1:-}" + +if [ -z "$VERSION" ]; then + echo "Usage: validate-version.sh VERSION" >&2 + exit 1 +fi + +# Strip leading 'v' if present +VERSION="${VERSION#v}" + +CONFIGURE_AC="${CONFIGURE_AC:-configure.ac}" +CHANGELOG="${CHANGELOG:-ChangeLog}" + +errors=0 + +# Parse expected major.minor.revision +IFS='.' read -r expected_major expected_minor expected_revision <<< "${VERSION%%-*}" + +if [ -z "$expected_major" ] || [ -z "$expected_minor" ] || [ -z "$expected_revision" ]; then + echo "Error: VERSION must be in X.Y.Z format (got: $VERSION)" >&2 + exit 1 +fi + +# Check configure.ac +if [ ! -f "$CONFIGURE_AC" ]; then + echo "Error: $CONFIGURE_AC not found" >&2 + exit 1 +fi + +actual_major=$(grep 'm4_define(\[libhttpserver_MAJOR_VERSION\]' "$CONFIGURE_AC" | sed 's/.*\[\([0-9]*\)\].*/\1/') +actual_minor=$(grep 'm4_define(\[libhttpserver_MINOR_VERSION\]' "$CONFIGURE_AC" | sed 's/.*\[\([0-9]*\)\].*/\1/') +actual_revision=$(grep 'm4_define(\[libhttpserver_REVISION\]' "$CONFIGURE_AC" | sed 's/.*\[\([0-9]*\)\].*/\1/') + +if [ "$actual_major" != "$expected_major" ] || [ "$actual_minor" != "$expected_minor" ] || [ "$actual_revision" != "$expected_revision" ]; then + echo "Error: configure.ac version ($actual_major.$actual_minor.$actual_revision) does not match tag ($VERSION)" >&2 + errors=$((errors + 1)) +else + echo "OK: configure.ac version matches ($actual_major.$actual_minor.$actual_revision)" +fi + +# Check ChangeLog has a Version header for this version +if [ ! -f "$CHANGELOG" ]; then + echo "Error: $CHANGELOG not found" >&2 + exit 1 +fi + +# Match "Version X.Y.Z" at start of line (allowing trailing date or text) +base_version="${VERSION%%-*}" +if grep -q "^Version ${base_version}" "$CHANGELOG"; then + echo "OK: ChangeLog contains Version ${base_version} header" +else + echo "Error: ChangeLog missing 'Version ${base_version}' header" >&2 + errors=$((errors + 1)) +fi + +if [ "$errors" -gt 0 ]; then + echo "Validation failed with $errors error(s)" >&2 + exit 1 +fi + +echo "Version validation passed for $VERSION" diff --git a/src/Makefile.am b/src/Makefile.am index 51299bf3..ed8dc8f4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,6 +1,6 @@ # # This file is part of libhttpserver -# Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino +# Copyright (C) 2011-2019 Sebastiano Merlino # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -19,9 +19,14 @@ AM_CPPFLAGS = -I../ -I$(srcdir)/httpserver/ METASOURCES = AUTO lib_LTLIBRARIES = libhttpserver.la -libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp http_request.cpp http_response.cpp http_resource.cpp details/comet_manager.cpp details/http_endpoint.cpp -noinst_HEADERS = httpserver/string_utilities.hpp httpserver/details/modded_request.hpp httpserver/details/http_response_ptr.hpp httpserver/details/cache_entry.hpp httpserver/details/comet_manager.hpp gettext.h -nobase_include_HEADERS = httpserver.hpp httpserver/create_webserver.hpp httpserver/webserver.hpp httpserver/http_utils.hpp httpserver/details/http_endpoint.hpp httpserver/http_request.hpp httpserver/http_response.hpp httpserver/http_resource.hpp httpserver/binders.hpp httpserver/event_supplier.hpp httpserver/details/event_tuple.hpp httpserver/details/http_resource_mirror.hpp httpserver/http_response_builder.hpp +libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp file_info.cpp http_request.cpp http_response.cpp string_response.cpp digest_auth_fail_response.cpp deferred_response.cpp file_response.cpp http_resource.cpp create_webserver.cpp details/http_endpoint.cpp +noinst_HEADERS = httpserver/string_utilities.hpp httpserver/details/modded_request.hpp gettext.h +nobase_include_HEADERS = httpserver.hpp httpserver/create_webserver.hpp httpserver/webserver.hpp httpserver/http_utils.hpp httpserver/file_info.hpp httpserver/details/http_endpoint.hpp httpserver/http_request.hpp httpserver/http_response.hpp httpserver/http_resource.hpp httpserver/string_response.hpp httpserver/digest_auth_fail_response.hpp httpserver/deferred_response.hpp httpserver/file_response.hpp httpserver/http_arg_value.hpp + +if HAVE_BAUTH +libhttpserver_la_SOURCES += basic_auth_fail_response.cpp +nobase_include_HEADERS += httpserver/basic_auth_fail_response.hpp +endif AM_CXXFLAGS += -fPIC -Wall @@ -31,7 +36,10 @@ AM_CXXFLAGS += -O0 --coverage --no-inline AM_LDFLAGS += -O0 --coverage -lgcov --no-inline endif +if !COND_CROSS_COMPILE libhttpserver_la_LIBADD = -lmicrohttpd +endif + libhttpserver_la_CFLAGS = $(AM_CFLAGS) libhttpserver_la_CXXFLAGS = $(AM_CXXFLAGS) libhttpserver_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined diff --git a/src/basic_auth_fail_response.cpp b/src/basic_auth_fail_response.cpp new file mode 100644 index 00000000..1e6aa0e5 --- /dev/null +++ b/src/basic_auth_fail_response.cpp @@ -0,0 +1,38 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#ifdef HAVE_BAUTH + +#include "httpserver/basic_auth_fail_response.hpp" +#include +#include + +struct MHD_Connection; +struct MHD_Response; + +namespace httpserver { + +int basic_auth_fail_response::enqueue_response(MHD_Connection* connection, MHD_Response* response) { + return MHD_queue_basic_auth_fail_response(connection, realm.c_str(), response); +} + +} // namespace httpserver + +#endif // HAVE_BAUTH diff --git a/src/create_test_request.cpp b/src/create_test_request.cpp new file mode 100644 index 00000000..985acd39 --- /dev/null +++ b/src/create_test_request.cpp @@ -0,0 +1,71 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +*/ + +#include "httpserver/create_test_request.hpp" + +#include +#include + +namespace httpserver { + +http_request create_test_request::build() { + http_request req; + + req.set_method(_method); + req.set_path(_path); + req.set_version(_version); + req.set_content(_content); + + req.headers_local = std::move(_headers); + req.footers_local = std::move(_footers); + req.cookies_local = std::move(_cookies); + + for (auto& [key, values] : _args) { + for (auto& value : values) { + req.cache->unescaped_args[key].push_back(std::move(value)); + } + } + req.cache->args_populated = true; + + if (!_querystring.empty()) { + req.cache->querystring = std::move(_querystring); + } + +#ifdef HAVE_BAUTH + req.cache->username = std::move(_user); + req.cache->password = std::move(_pass); +#endif // HAVE_BAUTH + +#ifdef HAVE_DAUTH + req.cache->digested_user = std::move(_digested_user); +#endif // HAVE_DAUTH + + req.cache->requestor_ip = std::move(_requestor); + req.requestor_port_local = _requestor_port; + +#ifdef HAVE_GNUTLS + req.tls_enabled_local = _tls_enabled; +#endif // HAVE_GNUTLS + + return req; +} + +} // namespace httpserver diff --git a/src/create_webserver.cpp b/src/create_webserver.cpp new file mode 100644 index 00000000..1a9f60dc --- /dev/null +++ b/src/create_webserver.cpp @@ -0,0 +1,68 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#if defined(_WIN32) && !defined(__CYGWIN__) +#define _WINDOWS +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x600 +#include +#include +#else +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include "httpserver/create_webserver.hpp" + +namespace httpserver { + +create_webserver& create_webserver::bind_address(const std::string& ip) { + _bind_address_storage = std::make_shared(); + std::memset(_bind_address_storage.get(), 0, sizeof(struct sockaddr_storage)); + + // Try IPv4 first + auto* addr4 = reinterpret_cast(_bind_address_storage.get()); + if (inet_pton(AF_INET, ip.c_str(), &(addr4->sin_addr)) == 1) { + addr4->sin_family = AF_INET; + addr4->sin_port = htons(_port); + _bind_address = reinterpret_cast(_bind_address_storage.get()); + return *this; + } + + // Try IPv6 + auto* addr6 = reinterpret_cast(_bind_address_storage.get()); + if (inet_pton(AF_INET6, ip.c_str(), &(addr6->sin6_addr)) == 1) { + addr6->sin6_family = AF_INET6; + addr6->sin6_port = htons(_port); + _bind_address = reinterpret_cast(_bind_address_storage.get()); + _use_ipv6 = true; + return *this; + } + + throw std::invalid_argument("Invalid IP address: " + ip); +} + +} // namespace httpserver diff --git a/src/deferred_response.cpp b/src/deferred_response.cpp new file mode 100644 index 00000000..f2764810 --- /dev/null +++ b/src/deferred_response.cpp @@ -0,0 +1,37 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include "httpserver/deferred_response.hpp" +#include +#include + +struct MHD_Response; + +namespace httpserver { + +namespace details { + +MHD_Response* get_raw_response_helper(void* cls, ssize_t (*cb)(void*, uint64_t, char*, size_t)) { + return MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 1024, cb, cls, nullptr); +} + +} // namespace details + +} // namespace httpserver diff --git a/src/details/comet_manager.cpp b/src/details/comet_manager.cpp deleted file mode 100644 index a6eb8708..00000000 --- a/src/details/comet_manager.cpp +++ /dev/null @@ -1,298 +0,0 @@ -/* - This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - USA -*/ - -#include -#include -#include "details/comet_manager.hpp" -#include - -using namespace std; - -namespace httpserver -{ - -namespace details -{ - -comet_manager::comet_manager() -{ - pthread_rwlock_init(&comet_guard, NULL); - pthread_mutex_init(&cleanmux, NULL); - pthread_cond_init(&cleancond, NULL); -} - -comet_manager::~comet_manager() -{ - pthread_rwlock_destroy(&comet_guard); - pthread_mutex_destroy(&cleanmux); - pthread_cond_destroy(&cleancond); -} - -void comet_manager::send_message_to_topic ( - const string& topic, - const string& message, - const httpserver::http::http_utils::start_method_T& start_method -) -{ - pthread_rwlock_wrlock(&comet_guard); - for(set::const_iterator it = q_waitings[topic].begin(); - it != q_waitings[topic].end(); - ++it - ) - { - q_messages[(*it)].push_back(message); - q_signal.insert((*it)); - if(start_method != http::http_utils::INTERNAL_SELECT) - { - pthread_mutex_lock(&q_blocks[(*it)].first); - pthread_cond_signal(&q_blocks[(*it)].second); - pthread_mutex_unlock(&q_blocks[(*it)].first); - } - map::const_iterator itt; - if((itt = q_keepalives.find(*it)) != q_keepalives.end()) - { - struct timeval curtime; - gettimeofday(&curtime, NULL); - q_keepalives[*it] = curtime.tv_sec; - } - } - pthread_rwlock_unlock(&comet_guard); - if(start_method != http::http_utils::INTERNAL_SELECT) - { - pthread_mutex_lock(&cleanmux); - pthread_cond_signal(&cleancond); - pthread_mutex_unlock(&cleanmux); - } -} - -void comet_manager::register_to_topics ( - const vector& topics, - const http::httpserver_ska& connection_id, - int keepalive_secs, - string keepalive_msg, - const httpserver::http::http_utils::start_method_T& start_method -) -{ - pthread_rwlock_wrlock(&comet_guard); - for(vector::const_iterator it = topics.begin(); - it != topics.end(); ++it - ) - q_waitings[*it].insert(connection_id); - if(keepalive_secs != -1) - { - struct timeval curtime; - gettimeofday(&curtime, NULL); - q_keepalives[connection_id] = curtime.tv_sec; - q_keepalives_mem[connection_id] = make_pair( - keepalive_secs, keepalive_msg - ); - } - if(start_method != http::http_utils::INTERNAL_SELECT) - { - pthread_mutex_t m; - pthread_cond_t c; - pthread_mutex_init(&m, NULL); - pthread_cond_init(&c, NULL); - q_blocks[connection_id] = - make_pair(m, c); - } - pthread_rwlock_unlock(&comet_guard); -} - -size_t comet_manager::read_message(const http::httpserver_ska& connection_id, - string& message -) -{ - pthread_rwlock_wrlock(&comet_guard); - deque& t_deq = q_messages[connection_id]; - message.assign(t_deq.front()); - t_deq.pop_front(); - pthread_rwlock_unlock(&comet_guard); - return message.size(); -} - -size_t comet_manager::get_topic_consumers( - const string& topic, - set& consumers -) -{ - pthread_rwlock_rdlock(&comet_guard); - - for(set::const_iterator it = q_waitings[topic].begin(); - it != q_waitings[topic].end(); ++it - ) - { - consumers.insert((*it)); - } - std::set::size_type size = consumers.size(); - pthread_rwlock_unlock(&comet_guard); - return size; -} - -bool comet_manager::pop_signaled(const http::httpserver_ska& consumer, - const httpserver::http::http_utils::start_method_T& start_method -) -{ - if(start_method == http::http_utils::INTERNAL_SELECT) - { - pthread_rwlock_wrlock(&comet_guard); - set::iterator it = q_signal.find(consumer); - if(it != q_signal.end()) - { - if(q_messages[consumer].empty()) - { - q_signal.erase(it); - pthread_rwlock_unlock(&comet_guard); - return false; - } - pthread_rwlock_unlock(&comet_guard); - return true; - } - else - { - pthread_rwlock_unlock(&comet_guard); - return false; - } - } - else - { - pthread_rwlock_rdlock(&comet_guard); - pthread_mutex_lock(&q_blocks[consumer].first); - struct timespec t; - struct timeval curtime; - - { - bool to_unlock = true; - while(q_signal.find(consumer) == q_signal.end()) - { - if(to_unlock) - { - pthread_rwlock_unlock(&comet_guard); - to_unlock = false; - } - gettimeofday(&curtime, NULL); - t.tv_sec = curtime.tv_sec + q_keepalives_mem[consumer].first; - t.tv_nsec = 0; - int rslt = pthread_cond_timedwait(&q_blocks[consumer].second, - &q_blocks[consumer].first, &t - ); - if(rslt == ETIMEDOUT) - { - pthread_rwlock_wrlock(&comet_guard); - send_message_to_consumer(consumer, - q_keepalives_mem[consumer].second, false, start_method - ); - pthread_rwlock_unlock(&comet_guard); - } - } - if(to_unlock) - pthread_rwlock_unlock(&comet_guard); - } - - if(q_messages[consumer].size() == 0) - { - pthread_rwlock_wrlock(&comet_guard); - q_signal.erase(consumer); - pthread_mutex_unlock(&q_blocks[consumer].first); - pthread_rwlock_unlock(&comet_guard); - return false; - } - pthread_rwlock_rdlock(&comet_guard); - pthread_mutex_unlock(&q_blocks[consumer].first); - pthread_rwlock_unlock(&comet_guard); - return true; - } - return false; -} - -void comet_manager::complete_request(const http::httpserver_ska& connection_id) -{ - pthread_rwlock_wrlock(&comet_guard); - q_messages.erase(connection_id); - q_blocks.erase(connection_id); - q_signal.erase(connection_id); - q_keepalives.erase(connection_id); - - typedef map >::iterator conn_it; - for(conn_it it = q_waitings.begin(); it != q_waitings.end(); ++it) - { - it->second.erase(connection_id); - } - pthread_rwlock_unlock(&comet_guard); -} - -void comet_manager::comet_select(unsigned long long* timeout_secs, - unsigned long long* timeout_microsecs, - const httpserver::http::http_utils::start_method_T& start_method -) -{ - pthread_rwlock_wrlock(&comet_guard); - for(map::iterator it = q_keepalives.begin(); it != q_keepalives.end(); ++it) - { - struct timeval curtime; - gettimeofday(&curtime, NULL); - int waited_time = curtime.tv_sec - (*it).second; - if(waited_time >= q_keepalives_mem[(*it).first].first) - { - send_message_to_consumer((*it).first, q_keepalives_mem[(*it).first].second, true, start_method); - } - else - { - unsigned long long to_wait_time = (q_keepalives_mem[(*it).first].first - waited_time); - if(to_wait_time < *timeout_secs) - { - *timeout_secs = to_wait_time; - *timeout_microsecs = 0; - } - } - } - pthread_rwlock_unlock(&comet_guard); -} - -void comet_manager::send_message_to_consumer( - const http::httpserver_ska& connection_id, - const std::string& message, - bool to_lock, - const httpserver::http::http_utils::start_method_T& start_method -) -{ - //This function need to be externally locked on write - q_messages[connection_id].push_back(message); - map::const_iterator it; - if((it = q_keepalives.find(connection_id)) != q_keepalives.end()) - { - struct timeval curtime; - gettimeofday(&curtime, NULL); - q_keepalives[connection_id] = curtime.tv_sec; - } - q_signal.insert(connection_id); - if(start_method != http::http_utils::INTERNAL_SELECT) - { - if(to_lock) - pthread_mutex_lock(&q_blocks[connection_id].first); - pthread_cond_signal(&q_blocks[connection_id].second); - if(to_lock) - pthread_mutex_unlock(&q_blocks[connection_id].first); - } -} - -} //details - -} //httpserver diff --git a/src/details/http_endpoint.cpp b/src/details/http_endpoint.cpp index 3e5a6431..8133f6aa 100644 --- a/src/details/http_endpoint.cpp +++ b/src/details/http_endpoint.cpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -18,42 +18,38 @@ USA */ -#include "details/http_endpoint.hpp" -#include "http_utils.hpp" -#include "string_utilities.hpp" +#include +#include +#include +#include +// Disabling lint error on regex (the only reason it errors is because the Chromium team prefers google/re2) +#include // NOLINT [build/c++11] +#include +#include +#include +#include -using namespace std; +#include "httpserver/details/http_endpoint.hpp" +#include "httpserver/http_utils.hpp" -namespace httpserver -{ +using std::string; +using std::vector; -using namespace http; +namespace httpserver { -namespace details -{ +namespace details { -http_endpoint::~http_endpoint() -{ - if(reg_compiled) - { - regfree(&(this->re_url_modded)); - } +http_endpoint::~http_endpoint() { } -http_endpoint::http_endpoint -( - const string& url, - bool family, - bool registration, - bool use_regex -): +http_endpoint::http_endpoint(const string& url, bool family, bool registration, bool use_regex): family_url(family), - reg_compiled(false) -{ - if(use_regex) - this->url_modded = "^/"; - else - this->url_modded = "/"; + reg_compiled(false) { + if (use_regex && !registration) { + throw std::invalid_argument("Cannot use regex if not during registration"); + } + + url_normalized = use_regex ? "^/" : "/"; vector parts; #ifdef CASE_INSENSITIVE @@ -62,173 +58,110 @@ http_endpoint::http_endpoint url_complete = url; #endif - if(url_complete[0] != '/') + if (url_complete[url_complete.size() - 1] == '/') { + url_complete = url_complete.substr(0, url_complete.size() - 1); + } + + if (url_complete[0] != '/') { url_complete = "/" + url_complete; + } - http_utils::tokenize_url(url, parts); + parts = httpserver::http::http_utils::tokenize_url(url); string buffered; bool first = true; - if(registration) - { - for(unsigned int i = 0; i< parts.size(); i++) - { - if((parts[i] != "") && (parts[i][0] != '{')) - { - if(first) - { - if(parts[i][0] == '^') - { - this->url_modded = parts[i]; - } - else - { - this->url_modded += parts[i]; - } - first = false; - } - else - { - this->url_modded += "/" + parts[i]; - } - } - else - { - if( - (parts[i].size() >= 3) && - (parts[i][0] == '{') && - (parts[i][parts[i].size() - 1] == '}') - ) - { - std::string::size_type bar = parts[i].find_first_of('|'); - if(bar != string::npos) - { - this->url_pars.push_back(parts[i].substr(1, bar - 1)); - if(first) - { - this->url_modded += parts[i].substr( - bar + 1, parts[i].size() - bar - 2 - ); - first = false; - } - else - { - this->url_modded += "/"+parts[i].substr( - bar + 1, parts[i].size() - bar - 2 - ); - } - } - else - { - this->url_pars.push_back( - parts[i].substr(1,parts[i].size() - 2) - ); - if(first) - { - this->url_modded += "([^\\/]+)"; - first = false; - } - else - { - this->url_modded += "/([^\\/]+)"; - } - } - this->chunk_positions.push_back(i); - } - else - { - throw bad_http_endpoint(); - } - } - this->url_pieces.push_back(parts[i]); + + for (unsigned int i = 0; i < parts.size(); i++) { + if (!registration) { + url_normalized += (first ? "" : "/") + parts[i]; + first = false; + + url_pieces.push_back(parts[i]); + + continue; } - } - else - { - for(unsigned int i = 0; i< parts.size(); i++) - { - if(first) - { - this->url_modded += parts[i]; + + if ((parts[i] != "") && (parts[i][0] != '{')) { + if (first) { + url_normalized = (parts[i][0] == '^' ? "" : url_normalized) + parts[i]; first = false; + } else { + url_normalized += "/" + parts[i]; } - else - { - this->url_modded += "/" + parts[i]; - } - this->url_pieces.push_back(parts[i]); + url_pieces.push_back(parts[i]); + + continue; } + + if ((parts[i].size() < 3) || (parts[i][0] != '{') || (parts[i][parts[i].size() - 1] != '}')) { + throw std::invalid_argument("Bad URL format"); + } + + std::string::size_type bar = parts[i].find_first_of('|'); + url_pars.push_back(parts[i].substr(1, bar != string::npos ? bar - 1 : parts[i].size() - 2)); + url_normalized += (first ? "" : "/") + (bar != string::npos ? parts[i].substr(bar + 1, parts[i].size() - bar - 2) : "([^\\/]+)"); + + first = false; + + chunk_positions.push_back(i); + + url_pieces.push_back(parts[i]); } - if(use_regex) - { - this->url_modded += "$"; - regcomp(&(this->re_url_modded), url_modded.c_str(), - REG_EXTENDED|REG_ICASE|REG_NOSUB - ); + + if (use_regex) { + url_normalized += "$"; + try { + re_url_normalized = std::regex(url_normalized, std::regex::extended | std::regex::icase | std::regex::nosubs); + } catch (std::regex_error& e) { + throw std::invalid_argument("Not a valid regex in URL: " + url_normalized); + } reg_compiled = true; } } http_endpoint::http_endpoint(const http_endpoint& h): url_complete(h.url_complete), - url_modded(h.url_modded), + url_normalized(h.url_normalized), url_pars(h.url_pars), url_pieces(h.url_pieces), chunk_positions(h.chunk_positions), + re_url_normalized(h.re_url_normalized), family_url(h.family_url), - reg_compiled(h.reg_compiled) -{ - if(this->reg_compiled) - regcomp(&(this->re_url_modded), url_modded.c_str(), - REG_EXTENDED|REG_ICASE|REG_NOSUB - ); + reg_compiled(h.reg_compiled) { } -http_endpoint& http_endpoint::operator =(const http_endpoint& h) -{ - this->url_complete = h.url_complete; - this->url_modded = h.url_modded; - this->family_url = h.family_url; - this->reg_compiled = h.reg_compiled; - if(this->reg_compiled) - regcomp(&(this->re_url_modded), url_modded.c_str(), - REG_EXTENDED|REG_ICASE|REG_NOSUB - ); - this->url_pars = h.url_pars; - this->url_pieces = h.url_pieces; - this->chunk_positions = h.chunk_positions; +http_endpoint& http_endpoint::operator =(const http_endpoint& h) { + url_complete = h.url_complete; + url_normalized = h.url_normalized; + family_url = h.family_url; + reg_compiled = h.reg_compiled; + re_url_normalized = h.re_url_normalized; + url_pars = h.url_pars; + url_pieces = h.url_pieces; + chunk_positions = h.chunk_positions; return *this; } -bool http_endpoint::operator <(const http_endpoint& b) const -{ - COMPARATOR(this->url_modded, b.url_modded, std::toupper); +bool http_endpoint::operator <(const http_endpoint& b) const { + if (family_url != b.family_url) return family_url; + COMPARATOR(url_normalized, b.url_normalized, std::toupper); } -bool http_endpoint::match(const http_endpoint& url) const -{ - if(this->family_url && (url.url_pieces.size() >= this->url_pieces.size())) - { - string nn = "/"; - bool first = true; - for(unsigned int i = 0; i < this->url_pieces.size(); i++) - { - if(first) - { - nn += url.url_pieces[i]; - first = false; - } - else - { - nn += "/" + url.url_pieces[i]; - } - } - return regexec(&(this->re_url_modded), nn.c_str(), 0, NULL, 0) == 0; +bool http_endpoint::match(const http_endpoint& url) const { + if (!reg_compiled) throw std::invalid_argument("Cannot run match. Regex suppressed."); + + if (!family_url || url.url_pieces.size() < url_pieces.size()) { + return regex_match(url.url_complete, re_url_normalized); + } + + string nn = "/"; + bool first = true; + for (unsigned int i = 0; i < url_pieces.size(); i++) { + nn += (first ? "" : "/") + url.url_pieces[i]; + first = false; } - else - return regexec(&(this->re_url_modded), - url.url_modded.c_str(), 0, NULL, 0) == 0; + return regex_match(nn, re_url_normalized); } -}; +} // namespace details -}; +} // namespace httpserver diff --git a/src/digest_auth_fail_response.cpp b/src/digest_auth_fail_response.cpp new file mode 100644 index 00000000..1fb8307c --- /dev/null +++ b/src/digest_auth_fail_response.cpp @@ -0,0 +1,44 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#ifdef HAVE_DAUTH + +#include "httpserver/digest_auth_fail_response.hpp" +#include +#include + +struct MHD_Connection; +struct MHD_Response; + +namespace httpserver { + +int digest_auth_fail_response::enqueue_response(MHD_Connection* connection, MHD_Response* response) { + return MHD_queue_auth_fail_response2( + connection, + realm.c_str(), + opaque.c_str(), + response, + reload_nonce ? MHD_YES : MHD_NO, + static_cast(algorithm)); +} + +} // namespace httpserver + +#endif // HAVE_DAUTH diff --git a/src/file_info.cpp b/src/file_info.cpp new file mode 100644 index 00000000..d37e7aeb --- /dev/null +++ b/src/file_info.cpp @@ -0,0 +1,57 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include +#include "httpserver/file_info.hpp" + +namespace httpserver { +namespace http { + +void file_info::set_file_system_file_name(const std::string& file_system_file_name) { + _file_system_file_name = file_system_file_name; +} + +void file_info::set_content_type(const std::string& content_type) { + _content_type = content_type; +} + +void file_info::set_transfer_encoding(const std::string& transfer_encoding) { + _transfer_encoding = transfer_encoding; +} + +void file_info::grow_file_size(size_t additional_file_size) { + _file_size += additional_file_size; +} +size_t file_info::get_file_size() const { + return _file_size; +} +const std::string file_info::get_file_system_file_name() const { + return _file_system_file_name; +} +const std::string file_info::get_content_type() const { + return _content_type; +} +const std::string file_info::get_transfer_encoding() const { + return _transfer_encoding; +} + +} // namespace http +} // namespace httpserver diff --git a/src/file_response.cpp b/src/file_response.cpp new file mode 100644 index 00000000..3e915413 --- /dev/null +++ b/src/file_response.cpp @@ -0,0 +1,62 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include "httpserver/file_response.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct MHD_Response; + +namespace httpserver { + +MHD_Response* file_response::get_raw_response() { +#ifndef _WIN32 + int fd = open(filename.c_str(), O_RDONLY | O_NOFOLLOW); +#else + int fd = open(filename.c_str(), O_RDONLY); +#endif + if (fd == -1) return nullptr; + + struct stat sb; + if (fstat(fd, &sb) != 0 || !S_ISREG(sb.st_mode)) { + close(fd); + return nullptr; + } + + off_t size = lseek(fd, 0, SEEK_END); + if (size == (off_t) -1) { + close(fd); + return nullptr; + } + + if (size) { + return MHD_create_response_from_fd(size, fd); + } else { + close(fd); + return MHD_create_response_from_buffer(0, nullptr, MHD_RESPMEM_PERSISTENT); + } +} + +} // namespace httpserver diff --git a/src/http_request.cpp b/src/http_request.cpp index a6e3112f..4d67bf39 100644 --- a/src/http_request.cpp +++ b/src/http_request.cpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -19,88 +19,581 @@ */ -#include "http_utils.hpp" -#include "http_request.hpp" -#include "string_utilities.hpp" +#include "httpserver/http_request.hpp" +#include +#include #include +#include +#include +#include +#include "httpserver/http_utils.hpp" +#include "httpserver/string_utilities.hpp" -using namespace std; - -namespace httpserver -{ - -void http_request::set_method(const std::string& method) -{ - string_utilities::to_upper_copy(method, this->method); -} - -bool http_request::check_digest_auth( - const std::string& realm, - const std::string& password, - int nonce_timeout, - bool& reload_nonce -) const -{ - int val = MHD_digest_auth_check( - underlying_connection, - realm.c_str(), - digested_user.c_str(), - password.c_str(), - nonce_timeout - ); - if(val == MHD_INVALID_NONCE) - { - reload_nonce = true; +#ifdef HAVE_GNUTLS +#include + +// RAII wrapper for gnutls_x509_crt_t to ensure proper cleanup +class scoped_x509_cert { + public: + scoped_x509_cert() : cert_(nullptr), valid_(false) {} + + ~scoped_x509_cert() { + if (cert_ != nullptr) { + gnutls_x509_crt_deinit(cert_); + } + } + + // Initialize from a TLS session's peer certificate + // Returns true if certificate was successfully loaded + bool init_from_session(gnutls_session_t session) { + unsigned int list_size = 0; + const gnutls_datum_t* cert_list = gnutls_certificate_get_peers(session, &list_size); + + if (cert_list == nullptr || list_size == 0) { + return false; + } + + if (gnutls_x509_crt_init(&cert_) != GNUTLS_E_SUCCESS) { + cert_ = nullptr; + return false; + } + + if (gnutls_x509_crt_import(cert_, &cert_list[0], GNUTLS_X509_FMT_DER) != GNUTLS_E_SUCCESS) { + gnutls_x509_crt_deinit(cert_); + cert_ = nullptr; + return false; + } + + valid_ = true; + return true; + } + + bool is_valid() const { return valid_; } + gnutls_x509_crt_t get() const { return cert_; } + + // Non-copyable + scoped_x509_cert(const scoped_x509_cert&) = delete; + scoped_x509_cert& operator=(const scoped_x509_cert&) = delete; + + private: + gnutls_x509_crt_t cert_; + bool valid_; +}; +#endif // HAVE_GNUTLS + +namespace httpserver { + +const char http_request::EMPTY[] = ""; + +struct arguments_accumulator { + unescaper_ptr unescaper; + std::map, http::arg_comparator>* arguments; +}; + +void http_request::set_method(const std::string& method) { + this->method = method; +} + +#ifdef HAVE_DAUTH +bool http_request::check_digest_auth(const std::string& realm, const std::string& password, int nonce_timeout, bool* reload_nonce) const { + std::string_view digested_user = get_digested_user(); + + int val = MHD_digest_auth_check(underlying_connection, realm.c_str(), digested_user.data(), password.c_str(), nonce_timeout); + + if (val == MHD_INVALID_NONCE) { + *reload_nonce = true; + return false; + } else if (val == MHD_NO) { + *reload_nonce = false; return false; } - else if(val == MHD_NO) - { - reload_nonce = false; + *reload_nonce = false; + return true; +} + +bool http_request::check_digest_auth_ha1( + const std::string& realm, + const unsigned char* digest, + size_t digest_size, + int nonce_timeout, + bool* reload_nonce, + http::http_utils::digest_algorithm algo) const { + std::string_view digested_user = get_digested_user(); + + int val = MHD_digest_auth_check_digest2( + underlying_connection, + realm.c_str(), + digested_user.data(), + digest, + digest_size, + nonce_timeout, + static_cast(algo)); + + if (val == MHD_INVALID_NONCE) { + *reload_nonce = true; + return false; + } else if (val == MHD_NO) { + *reload_nonce = false; return false; } - reload_nonce = false; + *reload_nonce = false; return true; } +#endif // HAVE_DAUTH + +std::string_view http_request::get_connection_value(std::string_view key, enum MHD_ValueKind kind) const { + const char* header_c = MHD_lookup_connection_value(underlying_connection, kind, key.data()); -size_t http_request::get_headers(std::map& result) const -{ - result = this->headers; - return result.size(); + if (header_c == nullptr) return EMPTY; + + return header_c; } -size_t http_request::get_footers(std::map& result) const -{ - result = this->footers; - return result.size(); +MHD_Result http_request::build_request_header(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { + // Parameters needed to respect MHD interface, but not used in the implementation. + std::ignore = kind; + + http::header_view_map* dhr = static_cast(cls); + (*dhr)[key] = value; + return MHD_YES; } -size_t http_request::get_cookies(std::map& result) const -{ - result = this->cookies; - return result.size(); +const http::header_view_map http_request::get_headerlike_values(enum MHD_ValueKind kind) const { + http::header_view_map headers; + + MHD_get_connection_values(underlying_connection, kind, &build_request_header, reinterpret_cast(&headers)); + + return headers; } -size_t http_request::get_args(std::map& result) const -{ - result = this->args; - return result.size(); +std::string_view http_request::get_header(std::string_view key) const { + return get_connection_value(key, MHD_HEADER_KIND); } -std::ostream &operator<< (std::ostream &os, const http_request &r) -{ - os << r.method << " Request [user:\"" << r.user << "\" pass:\"" << r.pass << "\"] path:\"" - << r.path << "\"" << std::endl; +const http::header_view_map http_request::get_headers() const { + return get_headerlike_values(MHD_HEADER_KIND); +} - http::dump_header_map(os,"Headers",r.headers); - http::dump_header_map(os,"Footers",r.footers); - http::dump_header_map(os,"Cookies",r.cookies); - http::dump_arg_map(os,"Query Args",r.args); +std::string_view http_request::get_footer(std::string_view key) const { + return get_connection_value(key, MHD_FOOTER_KIND); +} - os << " Version [ " << r.version << " ] Requestor [ " << r.requestor - << " ] Port [ " << r.requestor_port << " ]" << std::endl; +const http::header_view_map http_request::get_footers() const { + return get_headerlike_values(MHD_FOOTER_KIND); +} - return os; +std::string_view http_request::get_cookie(std::string_view key) const { + return get_connection_value(key, MHD_COOKIE_KIND); +} + +const http::header_view_map http_request::get_cookies() const { + return get_headerlike_values(MHD_COOKIE_KIND); +} + +void http_request::populate_args() const { + if (cache->args_populated) { + return; + } + arguments_accumulator aa; + aa.unescaper = unescaper; + aa.arguments = &cache->unescaped_args; + MHD_get_connection_values(underlying_connection, MHD_GET_ARGUMENT_KIND, &build_request_args, reinterpret_cast(&aa)); + + cache->args_populated = true; +} + + +void http_request::grow_last_arg(const std::string& key, const std::string& value) { + auto it = cache->unescaped_args.find(key); + + if (it != cache->unescaped_args.end()) { + if (!it->second.empty()) { + it->second.back() += value; + } else { + it->second.push_back(value); + } + } else { + cache->unescaped_args[key] = {value}; + } +} + +http_arg_value http_request::get_arg(std::string_view key) const { + populate_args(); + + auto it = cache->unescaped_args.find(key); + if (it != cache->unescaped_args.end()) { + http_arg_value arg; + arg.values.reserve(it->second.size()); + for (const auto& value : it->second) { + arg.values.push_back(value); + } + return arg; + } + return http_arg_value(); +} + +std::string_view http_request::get_arg_flat(std::string_view key) const { + auto const it = cache->unescaped_args.find(key); + + if (it != cache->unescaped_args.end()) { + return it->second[0]; + } + + return get_connection_value(key, MHD_GET_ARGUMENT_KIND); +} + +const http::arg_view_map http_request::get_args() const { + populate_args(); + + http::arg_view_map arguments; + for (const auto& [key, value] : cache->unescaped_args) { + auto& arg_values = arguments[key]; + for (const auto& v : value) { + arg_values.values.push_back(v); + } + } + return arguments; +} + +const std::map http_request::get_args_flat() const { + populate_args(); + std::map ret; + for (const auto&[key, val] : cache->unescaped_args) { + ret[key] = val[0]; + } + return ret; +} + +http::file_info& http_request::get_or_create_file_info(const std::string& key, const std::string& upload_file_name) { + return files[key][upload_file_name]; +} + +std::string_view http_request::get_querystring() const { + if (!cache->querystring.empty()) { + return cache->querystring; + } + + MHD_get_connection_values(underlying_connection, MHD_GET_ARGUMENT_KIND, &build_request_querystring, reinterpret_cast(&cache->querystring)); + + return cache->querystring; +} + +MHD_Result http_request::build_request_args(void *cls, enum MHD_ValueKind kind, const char *key, const char *arg_value) { + // Parameters needed to respect MHD interface, but not used in the implementation. + std::ignore = kind; + + arguments_accumulator* aa = static_cast(cls); + std::string value = ((arg_value == nullptr) ? "" : arg_value); + + http::base_unescaper(&value, aa->unescaper); + (*aa->arguments)[key].push_back(value); + return MHD_YES; +} + +MHD_Result http_request::build_request_querystring(void *cls, enum MHD_ValueKind kind, const char *key_value, const char *arg_value) { + // Parameters needed to respect MHD interface, but not used in the implementation. + std::ignore = kind; + + std::string* querystring = static_cast(cls); + + std::string_view key = key_value; + std::string_view value = ((arg_value == nullptr) ? "" : arg_value); + + // Limit to a single allocation. + querystring->reserve(querystring->size() + key.size() + value.size() + 3); + + *querystring += ((*querystring == "") ? "?" : "&"); + *querystring += key; + *querystring += "="; + *querystring += value; + + return MHD_YES; +} + +#ifdef HAVE_BAUTH +void http_request::fetch_user_pass() const { + char* password = nullptr; + auto* username = MHD_basic_auth_get_username_password(underlying_connection, &password); + + if (username != nullptr) { + cache->username = username; + MHD_free(username); + } + if (password != nullptr) { + cache->password = password; + MHD_free(password); + } +} + +std::string_view http_request::get_user() const { + if (!cache->username.empty()) { + return cache->username; + } + fetch_user_pass(); + return cache->username; +} + +std::string_view http_request::get_pass() const { + if (!cache->password.empty()) { + return cache->password; + } + fetch_user_pass(); + return cache->password; +} +#endif // HAVE_BAUTH + +#ifdef HAVE_DAUTH +std::string_view http_request::get_digested_user() const { + if (!cache->digested_user.empty()) { + return cache->digested_user; + } + + char* digested_user_c = MHD_digest_auth_get_username(underlying_connection); + + cache->digested_user = EMPTY; + if (digested_user_c != nullptr) { + cache->digested_user = digested_user_c; + MHD_free(digested_user_c); + } + + return cache->digested_user; +} +#endif // HAVE_DAUTH + +#ifdef HAVE_GNUTLS +bool http_request::has_tls_session() const { + const MHD_ConnectionInfo * conninfo = MHD_get_connection_info(underlying_connection, MHD_CONNECTION_INFO_GNUTLS_SESSION); + return (conninfo != nullptr); } +gnutls_session_t http_request::get_tls_session() const { + const MHD_ConnectionInfo * conninfo = MHD_get_connection_info(underlying_connection, MHD_CONNECTION_INFO_GNUTLS_SESSION); + if (conninfo == nullptr) { + return nullptr; + } + + return static_cast(conninfo->tls_session); } + +bool http_request::has_client_certificate() const { + if (!has_tls_session()) { + return false; + } + + gnutls_session_t session = get_tls_session(); + unsigned int list_size = 0; + const gnutls_datum_t* cert_list = gnutls_certificate_get_peers(session, &list_size); + + return (cert_list != nullptr && list_size > 0); +} + +std::string http_request::get_client_cert_dn() const { + if (!has_tls_session()) { + return ""; + } + + scoped_x509_cert cert; + if (!cert.init_from_session(get_tls_session())) { + return ""; + } + + size_t dn_size = 0; + gnutls_x509_crt_get_dn(cert.get(), nullptr, &dn_size); + + std::string dn(dn_size, '\0'); + if (gnutls_x509_crt_get_dn(cert.get(), &dn[0], &dn_size) != GNUTLS_E_SUCCESS) { + return ""; + } + + // Remove trailing null if present + if (!dn.empty() && dn.back() == '\0') { + dn.pop_back(); + } + + return dn; +} + +std::string http_request::get_client_cert_issuer_dn() const { + if (!has_tls_session()) { + return ""; + } + + scoped_x509_cert cert; + if (!cert.init_from_session(get_tls_session())) { + return ""; + } + + size_t dn_size = 0; + gnutls_x509_crt_get_issuer_dn(cert.get(), nullptr, &dn_size); + + std::string dn(dn_size, '\0'); + if (gnutls_x509_crt_get_issuer_dn(cert.get(), &dn[0], &dn_size) != GNUTLS_E_SUCCESS) { + return ""; + } + + // Remove trailing null if present + if (!dn.empty() && dn.back() == '\0') { + dn.pop_back(); + } + + return dn; +} + +std::string http_request::get_client_cert_cn() const { + if (!has_tls_session()) { + return ""; + } + + scoped_x509_cert cert; + if (!cert.init_from_session(get_tls_session())) { + return ""; + } + + size_t cn_size = 0; + gnutls_x509_crt_get_dn_by_oid(cert.get(), GNUTLS_OID_X520_COMMON_NAME, 0, 0, nullptr, &cn_size); + + if (cn_size == 0) { + return ""; + } + + std::string cn(cn_size, '\0'); + if (gnutls_x509_crt_get_dn_by_oid(cert.get(), GNUTLS_OID_X520_COMMON_NAME, 0, 0, &cn[0], &cn_size) != GNUTLS_E_SUCCESS) { + return ""; + } + + // Remove trailing null if present + if (!cn.empty() && cn.back() == '\0') { + cn.pop_back(); + } + + return cn; +} + +bool http_request::is_client_cert_verified() const { + if (!has_tls_session()) { + return false; + } + + gnutls_session_t session = get_tls_session(); + unsigned int status = 0; + + if (gnutls_certificate_verify_peers2(session, &status) != GNUTLS_E_SUCCESS) { + return false; + } + + return (status == 0); +} + +std::string http_request::get_client_cert_fingerprint_sha256() const { + if (!has_tls_session()) { + return ""; + } + + scoped_x509_cert cert; + if (!cert.init_from_session(get_tls_session())) { + return ""; + } + + unsigned char fingerprint[32]; // SHA-256 is 32 bytes + size_t fingerprint_size = sizeof(fingerprint); + + if (gnutls_x509_crt_get_fingerprint(cert.get(), GNUTLS_DIG_SHA256, fingerprint, &fingerprint_size) != GNUTLS_E_SUCCESS) { + return ""; + } + + // Convert to hex string + std::string hex_fingerprint; + hex_fingerprint.reserve(fingerprint_size * 2); + for (size_t i = 0; i < fingerprint_size; ++i) { + char hex[3]; + snprintf(hex, sizeof(hex), "%02x", fingerprint[i]); + hex_fingerprint += hex; + } + + return hex_fingerprint; +} + +time_t http_request::get_client_cert_not_before() const { + if (!has_tls_session()) { + return -1; + } + + scoped_x509_cert cert; + if (!cert.init_from_session(get_tls_session())) { + return -1; + } + + return gnutls_x509_crt_get_activation_time(cert.get()); +} + +time_t http_request::get_client_cert_not_after() const { + if (!has_tls_session()) { + return -1; + } + + scoped_x509_cert cert; + if (!cert.init_from_session(get_tls_session())) { + return -1; + } + + return gnutls_x509_crt_get_expiration_time(cert.get()); +} +#endif // HAVE_GNUTLS + +std::string_view http_request::get_requestor() const { + if (!cache->requestor_ip.empty()) { + return cache->requestor_ip; + } + + const MHD_ConnectionInfo * conninfo = MHD_get_connection_info(underlying_connection, MHD_CONNECTION_INFO_CLIENT_ADDRESS); + + cache->requestor_ip = http::get_ip_str(conninfo->client_addr); + return cache->requestor_ip; +} + +uint16_t http_request::get_requestor_port() const { + const MHD_ConnectionInfo * conninfo = MHD_get_connection_info(underlying_connection, MHD_CONNECTION_INFO_CLIENT_ADDRESS); + + return http::get_port(conninfo->client_addr); +} + +std::ostream &operator<< (std::ostream &os, const http_request &r) { + os << r.get_method() << " Request ["; +#ifdef HAVE_BAUTH + os << "user:\"" << r.get_user() << "\" pass:\"" << r.get_pass() << "\""; +#endif // HAVE_BAUTH + os << "] path:\"" << r.get_path() << "\"" << std::endl; + + http::dump_header_map(os, "Headers", r.get_headers()); + http::dump_header_map(os, "Footers", r.get_footers()); + http::dump_header_map(os, "Cookies", r.get_cookies()); + http::dump_arg_map(os, "Query Args", r.get_args()); + + os << " Version [ " << r.get_version() << " ] Requestor [ " << r.get_requestor() + << " ] Port [ " << r.get_requestor_port() << " ]" << std::endl; + + return os; +} + +http_request::~http_request() { + for (const auto& file_key : get_files()) { + for (const auto& files : file_key.second) { + bool should_delete = true; + if (file_cleanup_callback != nullptr) { + try { + should_delete = file_cleanup_callback(file_key.first, files.first, files.second); + } catch (...) { + // If callback throws, default to deleting the file + should_delete = true; + } + } + if (should_delete) { + // C++17 has std::filesystem::remove() + remove(files.second.get_file_system_file_name().c_str()); + } + } + } +} + +} // namespace httpserver diff --git a/src/http_resource.cpp b/src/http_resource.cpp index 693f33cf..430c4e65 100644 --- a/src/http_resource.cpp +++ b/src/http_resource.cpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -18,31 +18,37 @@ */ -#include - -#include "http_resource.hpp" -#include "http_utils.hpp" -#include "http_request.hpp" -#include "http_response.hpp" -#include "details/event_tuple.hpp" -#include "webserver.hpp" -#include "string_utilities.hpp" - -using namespace std; - -namespace httpserver -{ -//RESOURCE -void resource_init(map& allowed_methods) -{ - allowed_methods[MHD_HTTP_METHOD_GET] = true; - allowed_methods[MHD_HTTP_METHOD_POST] = true; - allowed_methods[MHD_HTTP_METHOD_PUT] = true; - allowed_methods[MHD_HTTP_METHOD_HEAD] = true; - allowed_methods[MHD_HTTP_METHOD_DELETE] = true; - allowed_methods[MHD_HTTP_METHOD_TRACE] = true; - allowed_methods[MHD_HTTP_METHOD_CONNECT] = true; - allowed_methods[MHD_HTTP_METHOD_OPTIONS] = true; +#include "httpserver/http_resource.hpp" +#include +#include +#include +#include +#include +#include "httpserver/string_response.hpp" + +namespace httpserver { class http_response; } + +namespace httpserver { + +// RESOURCE +void resource_init(std::map* method_state) { + (*method_state)[MHD_HTTP_METHOD_GET] = true; + (*method_state)[MHD_HTTP_METHOD_POST] = true; + (*method_state)[MHD_HTTP_METHOD_PUT] = true; + (*method_state)[MHD_HTTP_METHOD_HEAD] = true; + (*method_state)[MHD_HTTP_METHOD_DELETE] = true; + (*method_state)[MHD_HTTP_METHOD_TRACE] = true; + (*method_state)[MHD_HTTP_METHOD_CONNECT] = true; + (*method_state)[MHD_HTTP_METHOD_OPTIONS] = true; + (*method_state)[MHD_HTTP_METHOD_PATCH] = true; } -}; +namespace details { + +std::shared_ptr empty_render(const http_request&) { + return std::make_shared(); +} + +} // namespace details + +} // namespace httpserver diff --git a/src/http_response.cpp b/src/http_response.cpp index cddf3cbd..f12589f7 100644 --- a/src/http_response.cpp +++ b/src/http_response.cpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -18,287 +18,62 @@ USA */ -#include -#include +#include "httpserver/http_response.hpp" +#include #include -#include -#include -#include -#include "http_utils.hpp" -#include "details/http_resource_mirror.hpp" -#include "details/event_tuple.hpp" -#include "webserver.hpp" -#include "http_response.hpp" -#include "http_response_builder.hpp" +#include +#include +#include +#include "httpserver/http_utils.hpp" -using namespace std; +namespace httpserver { -namespace httpserver -{ - -http_response::http_response(const http_response_builder& builder): - content(builder._content_hook), - response_code(builder._response_code), - autodelete(builder._autodelete), - realm(builder._realm), - opaque(builder._opaque), - reload_nonce(builder._reload_nonce), - fp(-1), - filename(builder._content_hook), - headers(builder._headers), - footers(builder._footers), - cookies(builder._cookies), - topics(builder._topics), - keepalive_secs(builder._keepalive_secs), - keepalive_msg(builder._keepalive_msg), - send_topic(builder._send_topic), - underlying_connection(0x0), - ca(0x0), - closure_data(0x0), - ce(builder._ce), - cycle_callback(builder._cycle_callback), - get_raw_response(this, builder._get_raw_response), - decorate_response(this, builder._decorate_response), - enqueue_response(this, builder._enqueue_response), - completed(false), - ws(0x0), - connection_id(0x0) -{ -} - -http_response::~http_response() -{ - if(ce != 0x0) - webserver::unlock_cache_entry(ce); -} - -size_t http_response::get_headers(std::map& result) const -{ - result = this->headers; - return result.size(); -} - -size_t http_response::get_footers(std::map& result) const -{ - result = this->footers; - return result.size(); -} - -size_t http_response::get_cookies(std::map& result) const -{ - result = this->cookies; - return result.size(); -} - -//RESPONSE -void http_response::get_raw_response_str(MHD_Response** response, webserver* ws) -{ - size_t size = &(*content.end()) - &(*content.begin()); - *response = MHD_create_response_from_buffer( - size, - (void*) content.c_str(), - MHD_RESPMEM_PERSISTENT - ); +MHD_Response* http_response::get_raw_response() { + return MHD_create_response_from_buffer(0, nullptr, MHD_RESPMEM_PERSISTENT); } -void http_response::decorate_response_str(MHD_Response* response) -{ - map::iterator it; +void http_response::decorate_response(MHD_Response* response) { + std::map::iterator it; - for (it=headers.begin() ; it != headers.end(); ++it) - MHD_add_response_header( - response, - (*it).first.c_str(), - (*it).second.c_str() - ); - - for (it=footers.begin() ; it != footers.end(); ++it) - MHD_add_response_footer(response, - (*it).first.c_str(), - (*it).second.c_str() - ); - - for (it=cookies.begin(); it != cookies.end(); ++it) - MHD_add_response_header( - response, - "Set-Cookie", - ((*it).first + "=" + (*it).second).c_str() - ); -} - -int http_response::enqueue_response_str( - MHD_Connection* connection, - MHD_Response* response -) -{ - return MHD_queue_response(connection, response_code, response); -} - -void http_response::decorate_response_cache(MHD_Response* response) -{ -} - -int http_response::enqueue_response_basic( - MHD_Connection* connection, - MHD_Response* response -) -{ - return MHD_queue_basic_auth_fail_response( - connection, - realm.c_str(), - response - ); -} - -int http_response::enqueue_response_digest( - MHD_Connection* connection, - MHD_Response* response -) -{ - return MHD_queue_auth_fail_response( - connection, - realm.c_str(), - opaque.c_str(), - response, - reload_nonce ? MHD_YES : MHD_NO - ); -} - -void http_response::get_raw_response_file( - MHD_Response** response, - webserver* ws -) -{ - int fd = open(filename.c_str(), O_RDONLY); - size_t size = lseek(fd, 0, SEEK_END); - if(size) - { - *response = MHD_create_response_from_fd(size, fd); + for (it=headers.begin() ; it != headers.end(); ++it) { + MHD_add_response_header(response, (*it).first.c_str(), (*it).second.c_str()); } - else - { - *response = MHD_create_response_from_buffer( - 0, - (void*) "", - MHD_RESPMEM_PERSISTENT - ); - } -} - -void http_response::get_raw_response_cache( - MHD_Response** response, - webserver* ws -) -{ - bool valid; - http_response* r; - if(ce == 0x0) - r = ws->get_from_cache(content, &valid, &ce, true, false); - else - webserver::get_response(ce, &r); - r->get_raw_response(response, ws); - r->decorate_response(*response); //It is done here to avoid to search two times for the same element - - //TODO: Check if element is not in cache and throw exception -} - -namespace details -{ - -ssize_t cb(void* cls, uint64_t pos, char* buf, size_t max) -{ - ssize_t val = static_cast(cls)->cycle_callback(buf, max); - if(val == -1) - static_cast(cls)->completed = true; - return val; -} -} + for (it=footers.begin() ; it != footers.end(); ++it) { + MHD_add_response_footer(response, (*it).first.c_str(), (*it).second.c_str()); + } -void http_response::get_raw_response_deferred( - MHD_Response** response, - webserver* ws -) -{ - if(!completed) - *response = MHD_create_response_from_callback( - MHD_SIZE_UNKNOWN, - 1024, - &details::cb, - this, - NULL - ); - else - static_cast(this)->get_raw_response(response, ws); + for (it=cookies.begin(); it != cookies.end(); ++it) { + MHD_add_response_header(response, "Set-Cookie", ((*it).first + "=" + (*it).second).c_str()); + } } -void http_response::decorate_response_deferred(MHD_Response* response) -{ - if(completed) - static_cast(this)->decorate_response(response); +int http_response::enqueue_response(MHD_Connection* connection, MHD_Response* response) { + return MHD_queue_response(connection, response_code, response); } -void http_response::get_raw_response_lp_receive( - MHD_Response** response, - webserver* ws -) -{ - this->ws = ws; - this->connection_id = MHD_get_connection_info( - this->underlying_connection, - MHD_CONNECTION_INFO_CLIENT_ADDRESS - )->client_addr; - - *response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 80, - &http_response::data_generator, (void*) this, NULL); - - ws->register_to_topics( - topics, - connection_id, - keepalive_secs, - keepalive_msg - ); +void http_response::shoutCAST() { + response_code |= http::http_utils::shoutcast_response; } -ssize_t http_response::data_generator( - void* cls, - uint64_t pos, - char* buf, - size_t max -) -{ - http_response* _this = static_cast(cls); - - if(_this->ws->pop_signaled(_this->connection_id)) - { - string message; - size_t size = _this->ws->read_message(_this->connection_id, message); - memcpy(buf, message.c_str(), size); - return size; +namespace { +static inline http::header_view_map to_view_map(const http::header_map& hdr_map) { + http::header_view_map view_map; + for (const auto& item : hdr_map) { + view_map[std::string_view(item.first)] = std::string_view(item.second); } - else - return 0; + return view_map; } - -void http_response::get_raw_response_lp_send( - MHD_Response** response, - webserver* ws -) -{ - http_response::get_raw_response_str(response, ws); - ws->send_message_to_topic(send_topic, content); } -std::ostream &operator<< (std::ostream &os, const http_response &r) -{ +std::ostream &operator<< (std::ostream& os, const http_response& r) { os << "Response [response_code:" << r.response_code << "]" << std::endl; - http::dump_header_map(os,"Headers",r.headers); - http::dump_header_map(os,"Footers",r.footers); - http::dump_header_map(os,"Cookies",r.cookies); + http::dump_header_map(os, "Headers", to_view_map(r.headers)); + http::dump_header_map(os, "Footers", to_view_map(r.footers)); + http::dump_header_map(os, "Cookies", to_view_map(r.cookies)); return os; } - -}; +} // namespace httpserver diff --git a/src/http_utils.cpp b/src/http_utils.cpp index fec42643..695292a3 100644 --- a/src/http_utils.cpp +++ b/src/http_utils.cpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -18,37 +18,66 @@ USA */ -#include -#include -#include -#if defined(__MINGW32__) || defined(__CYGWIN32__) -#define _WINDOWS -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x600 +#include "httpserver/http_utils.hpp" + +#if defined(_WIN32) && !defined(__CYGWIN__) #include #include -#else -#include +#include +#include +#include +#include +#else // WIN32 check +#include #include -#endif -#include -#include +#include +#include +#include +#endif // WIN32 check + +#include +#include +#include #include -#include -#include "string_utilities.hpp" -#include "http_utils.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "httpserver/string_utilities.hpp" #pragma GCC diagnostic ignored "-Warray-bounds" -#define CHECK_BIT(var,pos) ((var) & (1<<(pos))) -#define SET_BIT(var,pos) ((var) |= 1 << (pos)) -#define CLEAR_BIT(var,pos) ((var) &= ~(1<<(pos))) +#define CHECK_BIT(var, pos) ((var) & (1 << (pos))) +#define SET_BIT(var, pos) ((var) |= 1 << (pos)) +#define CLEAR_BIT(var, pos) ((var) &= ~(1 << (pos))) + +#if defined (__CYGWIN__) + +#if !defined (NI_MAXHOST) +#define NI_MAXHOST 1025 +#endif // NI_MAXHOST + +#ifndef __u_char_defined +typedef unsigned char u_char; +#define __u_char_defined +#endif // __u_char_defined -using namespace std; +#endif // CYGWIN + +// libmicrohttpd deprecated some definitions with v0.9.74, and introduced new ones +#if MHD_VERSION < 0x00097314 +#define MHD_HTTP_CONTENT_TOO_LARGE MHD_HTTP_PAYLOAD_TOO_LARGE +#define MHD_HTTP_UNPROCESSABLE_CONTENT MHD_HTTP_UNPROCESSABLE_ENTITY +#endif namespace httpserver { namespace http { -/* See also: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html */ +// See also: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html const int http_utils::http_continue = MHD_HTTP_CONTINUE; const int http_utils::http_switching_protocol = MHD_HTTP_SWITCHING_PROTOCOLS; @@ -57,8 +86,7 @@ const int http_utils::http_processing = MHD_HTTP_PROCESSING; const int http_utils::http_ok = MHD_HTTP_OK; const int http_utils::http_created = MHD_HTTP_CREATED; const int http_utils::http_accepted = MHD_HTTP_ACCEPTED; -const int http_utils::http_non_authoritative_information = - MHD_HTTP_NON_AUTHORITATIVE_INFORMATION; +const int http_utils::http_non_authoritative_information = MHD_HTTP_NON_AUTHORITATIVE_INFORMATION; const int http_utils::http_no_content = MHD_HTTP_NO_CONTENT; const int http_utils::http_reset_content = MHD_HTTP_RESET_CONTENT; const int http_utils::http_partial_content = MHD_HTTP_PARTIAL_CONTENT; @@ -79,435 +107,435 @@ const int http_utils::http_payment_required = MHD_HTTP_PAYMENT_REQUIRED; const int http_utils::http_forbidden = MHD_HTTP_FORBIDDEN; const int http_utils::http_not_found = MHD_HTTP_NOT_FOUND; const int http_utils::http_method_not_allowed = MHD_HTTP_METHOD_NOT_ALLOWED; -const int http_utils::http_method_not_acceptable = - MHD_HTTP_METHOD_NOT_ACCEPTABLE; -const int http_utils::http_proxy_authentication_required = - MHD_HTTP_PROXY_AUTHENTICATION_REQUIRED; +const int http_utils::http_method_not_acceptable = MHD_HTTP_NOT_ACCEPTABLE; +const int http_utils::http_proxy_authentication_required = MHD_HTTP_PROXY_AUTHENTICATION_REQUIRED; const int http_utils::http_request_timeout = MHD_HTTP_REQUEST_TIMEOUT; const int http_utils::http_conflict = MHD_HTTP_CONFLICT; const int http_utils::http_gone = MHD_HTTP_GONE; const int http_utils::http_length_required = MHD_HTTP_LENGTH_REQUIRED; const int http_utils::http_precondition_failed = MHD_HTTP_PRECONDITION_FAILED; -const int http_utils::http_request_entity_too_large = - MHD_HTTP_REQUEST_ENTITY_TOO_LARGE; -const int http_utils::http_request_uri_too_long = MHD_HTTP_REQUEST_URI_TOO_LONG; -const int http_utils::http_unsupported_media_type = - MHD_HTTP_UNSUPPORTED_MEDIA_TYPE; -const int http_utils::http_requested_range_not_satisfiable = - MHD_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE; +const int http_utils::http_request_entity_too_large = MHD_HTTP_CONTENT_TOO_LARGE; +const int http_utils::http_request_uri_too_long = MHD_HTTP_URI_TOO_LONG; +const int http_utils::http_unsupported_media_type = MHD_HTTP_UNSUPPORTED_MEDIA_TYPE; +const int http_utils::http_requested_range_not_satisfiable = MHD_HTTP_RANGE_NOT_SATISFIABLE; const int http_utils::http_expectation_failed = MHD_HTTP_EXPECTATION_FAILED; -const int http_utils::http_unprocessable_entity = MHD_HTTP_UNPROCESSABLE_ENTITY; +const int http_utils::http_unprocessable_entity = MHD_HTTP_UNPROCESSABLE_CONTENT; const int http_utils::http_locked = MHD_HTTP_LOCKED; const int http_utils::http_failed_dependency = MHD_HTTP_FAILED_DEPENDENCY; -const int http_utils::http_unordered_collection = MHD_HTTP_UNORDERED_COLLECTION; const int http_utils::http_upgrade_required = MHD_HTTP_UPGRADE_REQUIRED; const int http_utils::http_retry_with = MHD_HTTP_RETRY_WITH; -const int http_utils::http_internal_server_error = - MHD_HTTP_INTERNAL_SERVER_ERROR; +const int http_utils::http_internal_server_error = MHD_HTTP_INTERNAL_SERVER_ERROR; const int http_utils::http_not_implemented = MHD_HTTP_NOT_IMPLEMENTED; const int http_utils::http_bad_gateway = MHD_HTTP_BAD_GATEWAY; const int http_utils::http_service_unavailable = MHD_HTTP_SERVICE_UNAVAILABLE; const int http_utils::http_gateway_timeout = MHD_HTTP_GATEWAY_TIMEOUT; -const int http_utils::http_version_not_supported = - MHD_HTTP_HTTP_VERSION_NOT_SUPPORTED; -const int http_utils::http_variant_also_negotiated = - MHD_HTTP_VARIANT_ALSO_NEGOTIATES; +const int http_utils::http_version_not_supported = MHD_HTTP_HTTP_VERSION_NOT_SUPPORTED; +const int http_utils::http_variant_also_negotiated = MHD_HTTP_VARIANT_ALSO_NEGOTIATES; const int http_utils::http_insufficient_storage = MHD_HTTP_INSUFFICIENT_STORAGE; -const int http_utils::http_bandwidth_limit_exceeded = - MHD_HTTP_BANDWIDTH_LIMIT_EXCEEDED; +const int http_utils::http_bandwidth_limit_exceeded = MHD_HTTP_BANDWIDTH_LIMIT_EXCEEDED; const int http_utils::http_not_extended = MHD_HTTP_NOT_EXTENDED; const int http_utils::shoutcast_response = MHD_ICY_FLAG; -const std::string http_utils::http_header_accept = MHD_HTTP_HEADER_ACCEPT; -const std::string http_utils::http_header_accept_charset = - MHD_HTTP_HEADER_ACCEPT_CHARSET; -const std::string http_utils::http_header_accept_encoding = - MHD_HTTP_HEADER_ACCEPT_ENCODING; -const std::string http_utils::http_header_accept_language = - MHD_HTTP_HEADER_ACCEPT_LANGUAGE; -const std::string http_utils::http_header_accept_ranges = - MHD_HTTP_HEADER_ACCEPT_RANGES; -const std::string http_utils::http_header_age = MHD_HTTP_HEADER_AGE; -const std::string http_utils::http_header_allow = MHD_HTTP_HEADER_ALLOW; -const std::string http_utils::http_header_authorization = - MHD_HTTP_HEADER_AUTHORIZATION; -const std::string http_utils::http_header_cache_control = - MHD_HTTP_HEADER_CACHE_CONTROL; -const std::string http_utils::http_header_connection = - MHD_HTTP_HEADER_CONNECTION; -const std::string http_utils::http_header_content_encoding = - MHD_HTTP_HEADER_CONTENT_ENCODING; -const std::string http_utils::http_header_content_language = - MHD_HTTP_HEADER_CONTENT_LANGUAGE; -const std::string http_utils::http_header_content_length = - MHD_HTTP_HEADER_CONTENT_LENGTH; -const std::string http_utils::http_header_content_location = - MHD_HTTP_HEADER_CONTENT_LOCATION; -const std::string http_utils::http_header_content_md5 = - MHD_HTTP_HEADER_CONTENT_MD5; -const std::string http_utils::http_header_content_range = - MHD_HTTP_HEADER_CONTENT_RANGE; -const std::string http_utils::http_header_content_type = - MHD_HTTP_HEADER_CONTENT_TYPE; -const std::string http_utils::http_header_date = MHD_HTTP_HEADER_DATE; -const std::string http_utils::http_header_etag = MHD_HTTP_HEADER_ETAG; -const std::string http_utils::http_header_expect = MHD_HTTP_HEADER_EXPECT; -const std::string http_utils::http_header_expires = MHD_HTTP_HEADER_EXPIRES; -const std::string http_utils::http_header_from = MHD_HTTP_HEADER_FROM; -const std::string http_utils::http_header_host = MHD_HTTP_HEADER_HOST; -const std::string http_utils::http_header_if_match = MHD_HTTP_HEADER_IF_MATCH; -const std::string http_utils::http_header_if_modified_since = - MHD_HTTP_HEADER_IF_MODIFIED_SINCE; -const std::string http_utils::http_header_if_none_match = - MHD_HTTP_HEADER_IF_NONE_MATCH; -const std::string http_utils::http_header_if_range = MHD_HTTP_HEADER_IF_RANGE; -const std::string http_utils::http_header_if_unmodified_since = - MHD_HTTP_HEADER_IF_UNMODIFIED_SINCE; -const std::string http_utils::http_header_last_modified = - MHD_HTTP_HEADER_LAST_MODIFIED; -const std::string http_utils::http_header_location = MHD_HTTP_HEADER_LOCATION; -const std::string http_utils::http_header_max_forwards = - MHD_HTTP_HEADER_MAX_FORWARDS; -const std::string http_utils::http_header_pragma = MHD_HTTP_HEADER_PRAGMA; -const std::string http_utils::http_header_proxy_authenticate = - MHD_HTTP_HEADER_PROXY_AUTHENTICATE; -const std::string http_utils::http_header_proxy_authentication = - MHD_HTTP_HEADER_PROXY_AUTHORIZATION; -const std::string http_utils::http_header_range = MHD_HTTP_HEADER_RANGE; -const std::string http_utils::http_header_referer = MHD_HTTP_HEADER_REFERER; -const std::string http_utils::http_header_retry_after = - MHD_HTTP_HEADER_RETRY_AFTER; -const std::string http_utils::http_header_server = MHD_HTTP_HEADER_SERVER; -const std::string http_utils::http_header_te = MHD_HTTP_HEADER_TE; -const std::string http_utils::http_header_trailer = MHD_HTTP_HEADER_TRAILER; -const std::string http_utils::http_header_transfer_encoding = - MHD_HTTP_HEADER_TRANSFER_ENCODING; -const std::string http_utils::http_header_upgrade = MHD_HTTP_HEADER_UPGRADE; -const std::string http_utils::http_header_user_agent = - MHD_HTTP_HEADER_USER_AGENT; -const std::string http_utils::http_header_vary = MHD_HTTP_HEADER_VARY; -const std::string http_utils::http_header_via = MHD_HTTP_HEADER_VIA; -const std::string http_utils::http_header_warning = MHD_HTTP_HEADER_WARNING; -const std::string http_utils::http_header_www_authenticate = - MHD_HTTP_HEADER_WWW_AUTHENTICATE; - -const std::string http_utils::http_version_1_0 = MHD_HTTP_VERSION_1_0; -const std::string http_utils::http_version_1_1 = MHD_HTTP_VERSION_1_1; - -const std::string http_utils::http_method_connect = MHD_HTTP_METHOD_CONNECT; -const std::string http_utils::http_method_delete = MHD_HTTP_METHOD_DELETE; -const std::string http_utils::http_method_get = MHD_HTTP_METHOD_GET; -const std::string http_utils::http_method_head = MHD_HTTP_METHOD_HEAD; -const std::string http_utils::http_method_options = MHD_HTTP_METHOD_OPTIONS; -const std::string http_utils::http_method_post = MHD_HTTP_METHOD_POST; -const std::string http_utils::http_method_put = MHD_HTTP_METHOD_PUT; -const std::string http_utils::http_method_trace = MHD_HTTP_METHOD_TRACE; - -const std::string http_utils::http_post_encoding_form_urlencoded = - MHD_HTTP_POST_ENCODING_FORM_URLENCODED; -const std::string http_utils::http_post_encoding_multipart_formdata = - MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA; - - -size_t http_utils::tokenize_url( - const std::string& str, - std::vector& result, - const char separator -) -{ - string_utilities::string_split(str, result, separator); - return result.size(); +const char* http_utils::http_header_accept = MHD_HTTP_HEADER_ACCEPT; +const char* http_utils::http_header_accept_charset = MHD_HTTP_HEADER_ACCEPT_CHARSET; +const char* http_utils::http_header_accept_encoding = MHD_HTTP_HEADER_ACCEPT_ENCODING; +const char* http_utils::http_header_accept_language = MHD_HTTP_HEADER_ACCEPT_LANGUAGE; +const char* http_utils::http_header_accept_ranges = MHD_HTTP_HEADER_ACCEPT_RANGES; +const char* http_utils::http_header_age = MHD_HTTP_HEADER_AGE; +const char* http_utils::http_header_allow = MHD_HTTP_HEADER_ALLOW; +const char* http_utils::http_header_authorization = MHD_HTTP_HEADER_AUTHORIZATION; +const char* http_utils::http_header_cache_control = MHD_HTTP_HEADER_CACHE_CONTROL; +const char* http_utils::http_header_connection = MHD_HTTP_HEADER_CONNECTION; +const char* http_utils::http_header_content_encoding = MHD_HTTP_HEADER_CONTENT_ENCODING; +const char* http_utils::http_header_content_language = MHD_HTTP_HEADER_CONTENT_LANGUAGE; +const char* http_utils::http_header_content_length = MHD_HTTP_HEADER_CONTENT_LENGTH; +const char* http_utils::http_header_content_location = MHD_HTTP_HEADER_CONTENT_LOCATION; +const char* http_utils::http_header_content_md5 = MHD_HTTP_HEADER_CONTENT_MD5; +const char* http_utils::http_header_content_range = MHD_HTTP_HEADER_CONTENT_RANGE; +const char* http_utils::http_header_content_type = MHD_HTTP_HEADER_CONTENT_TYPE; +const char* http_utils::http_header_date = MHD_HTTP_HEADER_DATE; +const char* http_utils::http_header_etag = MHD_HTTP_HEADER_ETAG; +const char* http_utils::http_header_expect = MHD_HTTP_HEADER_EXPECT; +const char* http_utils::http_header_expires = MHD_HTTP_HEADER_EXPIRES; +const char* http_utils::http_header_from = MHD_HTTP_HEADER_FROM; +const char* http_utils::http_header_host = MHD_HTTP_HEADER_HOST; +const char* http_utils::http_header_if_match = MHD_HTTP_HEADER_IF_MATCH; +const char* http_utils::http_header_if_modified_since = MHD_HTTP_HEADER_IF_MODIFIED_SINCE; +const char* http_utils::http_header_if_none_match = MHD_HTTP_HEADER_IF_NONE_MATCH; +const char* http_utils::http_header_if_range = MHD_HTTP_HEADER_IF_RANGE; +const char* http_utils::http_header_if_unmodified_since = MHD_HTTP_HEADER_IF_UNMODIFIED_SINCE; +const char* http_utils::http_header_last_modified = MHD_HTTP_HEADER_LAST_MODIFIED; +const char* http_utils::http_header_location = MHD_HTTP_HEADER_LOCATION; +const char* http_utils::http_header_max_forwards = MHD_HTTP_HEADER_MAX_FORWARDS; +const char* http_utils::http_header_pragma = MHD_HTTP_HEADER_PRAGMA; +const char* http_utils::http_header_proxy_authenticate = MHD_HTTP_HEADER_PROXY_AUTHENTICATE; +const char* http_utils::http_header_proxy_authentication = MHD_HTTP_HEADER_PROXY_AUTHORIZATION; +const char* http_utils::http_header_range = MHD_HTTP_HEADER_RANGE; +const char* http_utils::http_header_referer = MHD_HTTP_HEADER_REFERER; +const char* http_utils::http_header_retry_after = MHD_HTTP_HEADER_RETRY_AFTER; +const char* http_utils::http_header_server = MHD_HTTP_HEADER_SERVER; +const char* http_utils::http_header_te = MHD_HTTP_HEADER_TE; +const char* http_utils::http_header_trailer = MHD_HTTP_HEADER_TRAILER; +const char* http_utils::http_header_transfer_encoding = MHD_HTTP_HEADER_TRANSFER_ENCODING; +const char* http_utils::http_header_upgrade = MHD_HTTP_HEADER_UPGRADE; +const char* http_utils::http_header_user_agent = MHD_HTTP_HEADER_USER_AGENT; +const char* http_utils::http_header_vary = MHD_HTTP_HEADER_VARY; +const char* http_utils::http_header_via = MHD_HTTP_HEADER_VIA; +const char* http_utils::http_header_warning = MHD_HTTP_HEADER_WARNING; +const char* http_utils::http_header_www_authenticate = MHD_HTTP_HEADER_WWW_AUTHENTICATE; + +const char* http_utils::http_version_1_0 = MHD_HTTP_VERSION_1_0; +const char* http_utils::http_version_1_1 = MHD_HTTP_VERSION_1_1; + +const char* http_utils::http_method_connect = MHD_HTTP_METHOD_CONNECT; +const char* http_utils::http_method_delete = MHD_HTTP_METHOD_DELETE; +const char* http_utils::http_method_get = MHD_HTTP_METHOD_GET; +const char* http_utils::http_method_head = MHD_HTTP_METHOD_HEAD; +const char* http_utils::http_method_options = MHD_HTTP_METHOD_OPTIONS; +const char* http_utils::http_method_post = MHD_HTTP_METHOD_POST; +const char* http_utils::http_method_put = MHD_HTTP_METHOD_PUT; +const char* http_utils::http_method_trace = MHD_HTTP_METHOD_TRACE; +const char* http_utils::http_method_patch = MHD_HTTP_METHOD_PATCH; + +const char* http_utils::http_post_encoding_form_urlencoded = MHD_HTTP_POST_ENCODING_FORM_URLENCODED; +const char* http_utils::http_post_encoding_multipart_formdata = MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA; + +const char* http_utils::application_octet_stream = "application/octet-stream"; +const char* http_utils::text_plain = "text/plain"; + +const char* http_utils::upload_filename_template = "libhttpserver.XXXXXX"; + +#if defined(_WIN32) +const char http_utils::path_separator = '\\'; +#else // _WIN32 +const char http_utils::path_separator = '/'; +#endif // _WIN32 + +std::vector http_utils::tokenize_url(const std::string& str, const char separator) { + return string_utilities::string_split(str, separator); } -void http_utils::standardize_url(const std::string& url, std::string& result) -{ - std::string n_url; - string_utilities::regex_replace(url, "(\\/)+", "/", n_url); - std::string::size_type n_url_length = n_url.length(); +std::string http_utils::standardize_url(const std::string& url) { + if (url.empty()) return url; + + std::string result = url; - if (n_url_length > 1 && n_url[n_url_length - 1] == '/') - { - result = n_url.substr(0, n_url_length - 1); + auto new_end = std::unique(result.begin(), result.end(), [](char a, char b) { return (a == b) && (a == '/'); }); + result.erase(new_end, result.end()); + + if (result.length() > 1 && result.back() == '/') { + result.pop_back(); } - else - { - result = n_url; + + return result; +} + +const std::string http_utils::generate_random_upload_filename(const std::string& directory) { + std::string filename = directory + http_utils::path_separator + http_utils::upload_filename_template; + char *template_filename = strdup(filename.c_str()); + int fd = 0; + +#if defined(_WIN32) + // only function for win32 which creates unique filenames and can handle a given template including a path + // all other functions like tmpnam() always create filenames in the 'temp' directory + if (0 != _mktemp_s(template_filename, filename.size() + 1)) { + free(template_filename); + throw generateFilenameException("Failed to create unique filename"); + } + + // as no existing file should be overwritten the operation should fail if the file already exists + // fstream or ofstream classes don't feature such an option + // with the function _sopen_s this can be achieved by setting the flag _O_EXCL + if (0 != _sopen_s(&fd, template_filename, _O_CREAT | _O_EXCL | _O_NOINHERIT, _SH_DENYNO, _S_IREAD | _S_IWRITE)) { + free(template_filename); + throw generateFilenameException("Failed to create file"); + } + if (fd == -1) { + free(template_filename); + throw generateFilenameException("File descriptor after successful _sopen_s is -1"); + } + _close(fd); +#else // _WIN32 + fd = mkstemp(template_filename); + + if (fd == -1) { + free(template_filename); + throw generateFilenameException("Failed to create unique file"); } + close(fd); +#endif // _WIN32 + std::string ret_filename = template_filename; + free(template_filename); + return ret_filename; } -void get_ip_str( - const struct sockaddr *sa, - std::string& result, - socklen_t maxlen -) -{ - if(sa) - { - char to_ret[NI_MAXHOST]; - getnameinfo(sa, sizeof (struct sockaddr), to_ret, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); - result = to_ret; +std::string http_utils::sanitize_upload_filename(const std::string& filename) { + if (filename.empty()) return ""; + + // Find the basename: take everything after the last '/' or '\' + std::string::size_type pos = filename.find_last_of("/\\"); + std::string basename = (pos != std::string::npos) ? filename.substr(pos + 1) : filename; + + // Reject empty basename, ".", and ".." + if (basename.empty() || basename == "." || basename == "..") { + return ""; } + + return basename; } -std::string get_ip_str_new( - const struct sockaddr* sa, - socklen_t maxlen -) -{ - std::string to_ret; - get_ip_str(sa, to_ret, maxlen); - return to_ret; +std::string get_ip_str(const struct sockaddr *sa) { + if (!sa) throw std::invalid_argument("socket pointer is null"); + + char to_ret[NI_MAXHOST]; + if (AF_INET6 == sa->sa_family) { + inet_ntop(AF_INET6, &((reinterpret_cast(sa))->sin6_addr), to_ret, INET6_ADDRSTRLEN); + return to_ret; + } else if (AF_INET == sa->sa_family) { + inet_ntop(AF_INET, &((reinterpret_cast(sa))->sin_addr), to_ret, INET_ADDRSTRLEN); + return to_ret; + } else { + throw std::invalid_argument("IP family must be either AF_INET or AF_INET6"); + } } -short get_port(const struct sockaddr* sa) -{ - if(sa) - { - switch(sa->sa_family) - { - case AF_INET: - return ((struct sockaddr_in *)sa)->sin_port; - case AF_INET6: - return ((struct sockaddr_in *)sa)->sin_port; - default: - return 0; - } +uint16_t get_port(const struct sockaddr* sa) { + if (!sa) throw std::invalid_argument("socket pointer is null"); + + if (sa->sa_family == AF_INET) { + return (reinterpret_cast(sa))->sin_port; + } else if (sa->sa_family == AF_INET6) { + return (reinterpret_cast(sa))->sin6_port; + } else { + throw std::invalid_argument("IP family must be either AF_INET or AF_INET6"); } - return 0; } -size_t http_unescape (char *val) -{ - char *rpos = val; - char *wpos = val; - unsigned int num; +static inline int hex_digit_value(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +size_t http_unescape(std::string* val) { + if (val->empty()) return 0; - while ('\0' != *rpos) - { - switch (*rpos) - { + unsigned int rpos = 0; + unsigned int wpos = 0; + + unsigned int size = val->size(); + + while (rpos < size && (*val)[rpos] != '\0') { + switch ((*val)[rpos]) { case '+': - *wpos = ' '; + (*val)[wpos] = ' '; wpos++; rpos++; break; case '%': - if ( (1 == sscanf (&rpos[1], - "%2x", &num)) || - (1 == sscanf (&rpos[1], - "%2X", &num)) - ) - { - *wpos = (unsigned char) num; - wpos++; - rpos += 3; - break; + if (size > rpos + 2) { + int hi = hex_digit_value((*val)[rpos + 1]); + int lo = hex_digit_value((*val)[rpos + 2]); + if (hi >= 0 && lo >= 0) { + (*val)[wpos] = static_cast((hi << 4) | lo); + wpos++; + rpos += 3; + break; + } } - /* intentional fall through! */ + // intentional fall through! default: - *wpos = *rpos; + (*val)[wpos] = (*val)[rpos]; wpos++; rpos++; } } - *wpos = '\0'; /* add 0-terminator */ - return wpos - val; /* = strlen(val) */ + (*val)[wpos] = '\0'; // add 0-terminator + val->resize(wpos); + return wpos; // = strlen(val) } -ip_representation::ip_representation(const struct sockaddr* ip) -{ +ip_representation::ip_representation(const struct sockaddr* ip) { std::fill(pieces, pieces + 16, 0); - if(ip->sa_family == AF_INET) - { + if (ip->sa_family == AF_INET) { ip_version = http_utils::IPV4; - for(int i=0;i<4;i++) - { - pieces[12+i]=((u_char*)&(((struct sockaddr_in *)ip)->sin_addr))[i]; + const in_addr* sin_addr_pt = &((reinterpret_cast(ip))->sin_addr); + for (int i = 0; i < 4; i++) { + pieces[12 + i] = (reinterpret_cast(sin_addr_pt))[i]; } - } - else - { + } else { ip_version = http_utils::IPV6; - for(int i=0;i<32;i+=2) - { - pieces[i/2] = - ((u_char*)&(((struct sockaddr_in6 *)ip)->sin6_addr))[i] + - 16 * ((u_char*)&(((struct sockaddr_in6 *)ip)->sin6_addr))[i+1]; + const in6_addr* sin_addr6_pt = &((reinterpret_cast(ip))->sin6_addr); + for (int i = 0; i < 16; i++) { + pieces[i] = (reinterpret_cast(sin_addr6_pt))[i]; } } mask = DEFAULT_MASK_VALUE; } -ip_representation::ip_representation(const std::string& ip) -{ +ip_representation::ip_representation(const std::string& ip) { std::vector parts; mask = DEFAULT_MASK_VALUE; std::fill(pieces, pieces + 16, 0); - if(ip.find(':') != std::string::npos) //IPV6 - { + if (ip.find(':') != std::string::npos) { // IPV6 ip_version = http_utils::IPV6; - string_utilities::string_split(ip, parts, ':', false); + parts = string_utilities::string_split(ip, ':', false); + if (parts.size() > 8) { + throw std::invalid_argument("IP is badly formatted. Max 8 parts in IPV6."); + } + + unsigned int omitted = 8 - (parts.size() - 1); + if (omitted != 0) { + int empty_count = 0; + for (unsigned int i = 0; i < parts.size(); i++) { + if (parts[i].size() == 0) empty_count++; + } + + if (empty_count > 1) { + if (parts[parts.size() - 1].find(".") != std::string::npos) omitted -= 1; + + if (empty_count == 2 && parts[0] == "" && parts[1] == "") { + omitted += 1; + parts = std::vector(parts.begin() + 1, parts.end()); + } else { + throw std::invalid_argument("IP is badly formatted. Cannot have more than one omitted segment in IPV6."); + } + } + } + int y = 0; - for(unsigned int i = 0; i < parts.size(); i++) - { - if(parts[i] != "*" && parts[i] != "") - { - if(parts[i].size() < 4) - { - stringstream ss; - ss << setfill('0') << setw(4) << parts[i]; + for (unsigned int i = 0; i < parts.size(); i++) { + if (parts[i] != "*") { + if (parts[i].size() == 0) { + for (unsigned int omitted_idx = 0; omitted_idx < omitted; omitted_idx++) { + pieces[y] = 0; + pieces[y+1] = 0; + y += 2; + } + + continue; + } + + if (parts[i].size() < 4) { + std::stringstream ss; + ss << std::setfill('0') << std::setw(4) << parts[i]; parts[i] = ss.str(); } - if(parts[i].size() == 4) - { - pieces[y] = strtol((parts[i].substr(0,2)).c_str(),NULL,16); - pieces[y+1] = strtol( - (parts[i].substr(2,2)).c_str(), - NULL, - 16 - ); + + if (parts[i].size() == 4) { + pieces[y] = strtol((parts[i].substr(0, 2)).c_str(), nullptr, 16); + pieces[y+1] = strtol((parts[i].substr(2, 2)).c_str(), nullptr, 16); + y += 2; - } - else - { - if(y != 12) - { - throw bad_ip_format_exception(); - } - if(parts[i].find('.') != std::string::npos) - { - vector subparts; - string_utilities::string_split(parts[i], subparts, '.'); - if(subparts.size() == 4) - { - for(unsigned int ii = 0; ii < subparts.size(); ii++) - { - if(subparts[ii] != "*") - { - pieces[y+ii] = strtol( - subparts[ii].c_str(), - NULL, - 10 - ); - } - else - { - CLEAR_BIT(mask, y+11); + } else { + if (parts[i].find('.') != std::string::npos) { + if (y != 12) { + throw std::invalid_argument("IP is badly formatted. Missing parts before nested IPV4."); + } + + if (i != parts.size() - 1) { + throw std::invalid_argument("IP is badly formatted. Nested IPV4 should be at the end"); + } + + std::vector subparts = string_utilities::string_split(parts[i], '.'); + if (subparts.size() == 4) { + for (unsigned int k = 0; k < 10; k++) { + if (pieces[k] != 0) throw std::invalid_argument("IP is badly formatted. Nested IPV4 can be preceded only by 0 (and, optionally, two 255 octects)"); + } + + if ((pieces[10] != 0 && pieces[10] != 255) || (pieces[11] != 0 && pieces[11] != 255)) { + throw std::invalid_argument("IP is badly formatted. Nested IPV4 can be preceded only by 0 (and, optionally, two 255 octects)"); + } + + for (unsigned int ii = 0; ii < subparts.size(); ii++) { + if (subparts[ii] != "*") { + pieces[y+ii] = strtol(subparts[ii].c_str(), nullptr, 10); + if (pieces[y+ii] > 255) throw std::invalid_argument("IP is badly formatted. 255 is max value for ip part."); + } else { + CLEAR_BIT(mask, y+ii); } - y++; } + } else { + throw std::invalid_argument("IP is badly formatted. Nested IPV4 can have max 4 parts."); } - else - { - throw bad_ip_format_exception(); - } - } - else - { - throw bad_ip_format_exception(); + } else { + throw std::invalid_argument("IP is badly formatted. IPV6 parts can have max 4 characters (or nest an IPV4)"); } } - } - else if(parts[i] == "*") - { + } else { CLEAR_BIT(mask, y); - y++; - } - else - { - if(parts.size() <= 8) - { - int covered_pieces = 1 + (8 - parts.size()); - if(parts[parts.size() - 1].find('.') != std::string::npos) - { - covered_pieces -= 2; - } - for(int k = 0; k < covered_pieces; k++) - { - pieces[y] = 0; - y++; - } - } - else - { - throw bad_ip_format_exception(); - } + CLEAR_BIT(mask, y+1); + y+=2; } } - } - else //IPV4 - { + } else { // IPV4 ip_version = http_utils::IPV4; - string_utilities::string_split(ip, parts, '.'); - if(parts.size() == 4) - { - for(unsigned int i = 0; i < parts.size(); i++) - { - if(parts[i] != "*") - { - pieces[12+i] = strtol(parts[i].c_str(), NULL, 10); - } - else - { + parts = string_utilities::string_split(ip, '.'); + if (parts.size() == 4) { + for (unsigned int i = 0; i < parts.size(); i++) { + if (parts[i] != "*") { + pieces[12+i] = strtol(parts[i].c_str(), nullptr, 10); + if (pieces[12+i] > 255) throw std::invalid_argument("IP is badly formatted. 255 is max value for ip part."); + } else { CLEAR_BIT(mask, 12+i); } } - } - else - { - throw bad_ip_format_exception(); + } else { + throw std::invalid_argument("IP is badly formatted. Max 4 parts in IPV4."); } } } -bool ip_representation::operator <(const ip_representation& b) const -{ - int VAL = 16; - if(this->ip_version == http_utils::IPV4 && this->ip_version == b.ip_version) - { - VAL = this->ip_version; +bool ip_representation::operator <(const ip_representation& b) const { + int64_t this_score = 0; + int64_t b_score = 0; + for (int i = 0; i < 16; i++) { + if (i == 10 || i == 11) continue; + + if (CHECK_BIT(mask, i) && CHECK_BIT(b.mask, i)) { + this_score += (16 - i) * pieces[i]; + b_score += (16 - i) * b.pieces[i]; + } } - for(int i = 16 - VAL; i < 16; i++) - { - if(CHECK_BIT(this->mask,i) && - CHECK_BIT(b.mask,i) && - this->pieces[i] < b.pieces[i] - ) - return true; + + if (this_score == b_score && + ((pieces[10] == 0x00 || pieces[10] == 0xFF) && (b.pieces[10] == 0x00 || b.pieces[10] == 0xFF)) && + ((pieces[11] == 0x00 || pieces[11] == 0xFF) && (b.pieces[11] == 0x00 || b.pieces[11] == 0xFF))) { + return false; } - return false; -} -size_t load_file (const char* filename, char** content) -{ - ifstream fp(filename, ios::in | ios::binary | ios::ate); - if(fp.is_open()) - { - int size = fp.tellg(); - *content = (char*) malloc(size * sizeof(char)); - fp.seekg(0, ios::beg); - fp.read(*content, size); - fp.close(); - return size; + for (int i = 10; i < 12; i++) { + if (CHECK_BIT(mask, i) && CHECK_BIT(b.mask, i)) { + this_score += (16 - i) * pieces[i]; + b_score += (16 - i) * b.pieces[i]; + } } - else - throw file_access_exception(); + + return this_score < b_score; } -char* load_file (const char *filename) -{ - char* content = NULL; - load_file(filename, &content); - return content; +const std::string load_file(const std::string& filename) { + std::ifstream fp(filename.c_str(), std::ios::in | std::ios::binary | std::ios::ate); + if (fp.is_open()) { + std::string content; + + fp.seekg(0, fp.end); + content.reserve(fp.tellg()); + fp.seekg(0, fp.beg); + + content.assign((std::istreambuf_iterator(fp)), std::istreambuf_iterator()); + return content; + } else { + throw std::invalid_argument("Unable to open file"); + } } -void dump_header_map(std::ostream &os, const std::string &prefix, - const std::map &map) -{ - std::map::const_iterator it = map.begin(); - std::map::const_iterator end = map.end(); +void dump_header_map(std::ostream& os, const std::string& prefix, const http::header_view_map &map) { + auto it = map.begin(); + auto end = map.end(); if (map.size()) { os << " " << prefix << " ["; @@ -518,21 +546,35 @@ void dump_header_map(std::ostream &os, const std::string &prefix, } } -void dump_arg_map(std::ostream &os, const std::string &prefix, - const std::map &map) -{ - std::map::const_iterator it = map.begin(); - std::map::const_iterator end = map.end(); +void dump_arg_map(std::ostream& os, const std::string& prefix, const http::arg_view_map &map) { + auto it = map.begin(); + auto end = map.end(); if (map.size()) { os << " " << prefix << " ["; for (; it != end; ++it) { - os << (*it).first << ":\"" << (*it).second << "\" "; + os << (*it).first << ":["; + std::string sep = ""; + for (const auto& v : it->second.values) { + os << sep << "\"" << v << "\""; + sep = ", "; + } + os << "] "; } os << "]" << std::endl; } } +size_t base_unescaper(std::string* s, unescaper_ptr unescaper) { + if ((*s)[0] == 0) return 0; + + if (unescaper != nullptr) { + unescaper(*s); + return s->size(); + } + + return http_unescape(s); +} -}; -}; +} // namespace http +} // namespace httpserver diff --git a/src/httpserver.hpp b/src/httpserver.hpp index 74470d37..b2bba186 100644 --- a/src/httpserver.hpp +++ b/src/httpserver.hpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -18,20 +18,30 @@ USA */ -#ifndef _HTTPSERVER_HPP_ -#define _HTTPSERVER_HPP_ +#ifndef SRC_HTTPSERVER_HPP_ +#define SRC_HTTPSERVER_HPP_ + +#if __cplusplus < 201703L +# error("libhttpserver requires C++17 or later.") +#endif #define _HTTPSERVER_HPP_INSIDE_ -#include "httpserver/http_utils.hpp" -#include "httpserver/details/http_endpoint.hpp" +#ifdef HAVE_BAUTH +#include "httpserver/basic_auth_fail_response.hpp" +#endif // HAVE_BAUTH +#include "httpserver/deferred_response.hpp" +#ifdef HAVE_DAUTH +#include "httpserver/digest_auth_fail_response.hpp" +#endif // HAVE_DAUTH +#include "httpserver/file_response.hpp" +#include "httpserver/http_arg_value.hpp" +#include "httpserver/http_request.hpp" #include "httpserver/http_resource.hpp" #include "httpserver/http_response.hpp" -#include "httpserver/http_response_builder.hpp" -#include "httpserver/http_request.hpp" -#include "httpserver/event_supplier.hpp" -#include "httpserver/details/event_tuple.hpp" -#include "httpserver/details/http_resource_mirror.hpp" +#include "httpserver/http_utils.hpp" +#include "httpserver/file_info.hpp" +#include "httpserver/string_response.hpp" #include "httpserver/webserver.hpp" -#endif +#endif // SRC_HTTPSERVER_HPP_ diff --git a/src/httpserver/basic_auth_fail_response.hpp b/src/httpserver/basic_auth_fail_response.hpp new file mode 100644 index 00000000..d88bbbff --- /dev/null +++ b/src/httpserver/basic_auth_fail_response.hpp @@ -0,0 +1,68 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) +#error "Only or can be included directly." +#endif + +#ifndef SRC_HTTPSERVER_BASIC_AUTH_FAIL_RESPONSE_HPP_ +#define SRC_HTTPSERVER_BASIC_AUTH_FAIL_RESPONSE_HPP_ + +#ifdef HAVE_BAUTH + +#include +#include "httpserver/http_utils.hpp" +#include "httpserver/string_response.hpp" + +struct MHD_Connection; +struct MHD_Response; + +namespace httpserver { + +class basic_auth_fail_response : public string_response { + public: + basic_auth_fail_response() = default; + + explicit basic_auth_fail_response( + const std::string& content, + const std::string& realm = "", + int response_code = http::http_utils::http_ok, + const std::string& content_type = http::http_utils::text_plain): + string_response(content, response_code, content_type), + realm(realm) { } + + basic_auth_fail_response(const basic_auth_fail_response& other) = default; + basic_auth_fail_response(basic_auth_fail_response&& other) noexcept = default; + basic_auth_fail_response& operator=(const basic_auth_fail_response& b) = default; + basic_auth_fail_response& operator=(basic_auth_fail_response&& b) = default; + + ~basic_auth_fail_response() = default; + + int enqueue_response(MHD_Connection* connection, MHD_Response* response); + + private: + std::string realm = ""; +}; + +} // namespace httpserver + +#endif // HAVE_BAUTH + +#endif // SRC_HTTPSERVER_BASIC_AUTH_FAIL_RESPONSE_HPP_ diff --git a/src/httpserver/binders.hpp b/src/httpserver/binders.hpp deleted file mode 100644 index c1ce3007..00000000 --- a/src/httpserver/binders.hpp +++ /dev/null @@ -1,374 +0,0 @@ -/* - This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - USA -*/ - -#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) -#error "Only or can be included directly." -#endif - -#ifndef _BINDERS_HPP_ -#define _BINDERS_HPP_ - -namespace httpserver { -namespace details -{ - namespace binders - { - class generic_class; - const int MEMFUNC_SIZE = sizeof(void (generic_class::*)()); - - template - struct converter - { - template - inline static generic_class* convert( - X* pmem, - func_type func, - generic_mem_func_type &bound - ) - { - return 0; - } - }; - - template<> - struct converter - { - template - inline static generic_class* convert( - X* pmem, - func_type func, - generic_mem_func_type &bound - ) - { - bound = reinterpret_cast(func); - return reinterpret_cast(pmem); - } - }; - - template - class binder - { - private: - typedef void (generic_class::*generic_mem_fun)(); - typedef void (*generic_mem_ptr)(); - - generic_class *pmem; - generic_mem_fun _pfunc; - generic_mem_ptr _spfunc; - - public: - binder() - { - } - - binder(const binder& o): - pmem(o.pmem), - _pfunc(o._pfunc), - _spfunc(o._spfunc) - { - } - - template - binder(X* pmem, Y fun): - pmem(converter::convert(pmem, fun, _pfunc)), - _spfunc(0) - { - } - - template - binder(DC* pp, parent_invoker invoker, static_function fun): - pmem(converter::convert(pp, invoker, _pfunc)), - _spfunc(reinterpret_cast(fun)) - { - } - - inline generic_class* exec() const - { - return pmem; - } - inline generic_mem get_mem_ptr() const - { - return reinterpret_cast(_pfunc); - } - inline void_static_function get_static_func() const - { - return reinterpret_cast(_spfunc); - } - }; - - template - class functor_zero - { - private: - typedef RET_TYPE (*static_function)(); - typedef RET_TYPE (*void_static_function)(); - typedef RET_TYPE (generic_class::*generic_mem)(); - typedef binder binder_type; - binder_type _binder; - - RET_TYPE exec_static() const - { - return (*(_binder.get_static_func()))(); - } - - functor_zero& operator=(const functor_zero&) - { - return *this; - } - public: - typedef functor_zero type; - functor_zero() { } - - template - functor_zero(Y* pmem, RET_TYPE(X::*func)()): - _binder(reinterpret_cast(pmem), func) - { - } - - template - functor_zero(Y* pmem, RET_TYPE(X::*func)() const ): - _binder(reinterpret_cast(pmem), func) - { - } - - functor_zero(RET_TYPE(*func)() ): - _binder(this, &functor_zero::exec_static, func) - { - } - - RET_TYPE operator() () const - { - return (_binder.exec()->*(_binder.get_mem_ptr()))(); - } - }; - - template - class functor_one - { - private: - typedef RET_TYPE (*static_function)(PAR1 p1); - typedef RET_TYPE (*void_static_function)(PAR1 p1); - typedef RET_TYPE (generic_class::*generic_mem)(PAR1 p1); - typedef binder binder_type; - binder_type _binder; - - RET_TYPE exec_static(PAR1 p1) const - { - return (*(_binder.get_static_func()))(p1); - } - - functor_one& operator=(const functor_one&) - { - return *this; - } - public: - typedef functor_one type; - functor_one() { } - - template - functor_one(Y* pmem, RET_TYPE(X::*func)(PAR1 p1) ): - _binder(reinterpret_cast(pmem), func) - { - } - - template - functor_one(Y* pmem, RET_TYPE(X::*func)(PAR1 p1) const ): - _binder(reinterpret_cast(pmem), func) - { - } - - functor_one(RET_TYPE(*func)(PAR1 p1) ): - _binder(this, &functor_one::exec_static, func) - { - } - - RET_TYPE operator() (PAR1 p1) const - { - return (_binder.exec()->*(_binder.get_mem_ptr()))(p1); - } - }; - - template - class functor_two - { - private: - typedef RET_TYPE (*static_function)(PAR1 p1, PAR2 p2); - typedef RET_TYPE (*void_static_function)(PAR1 p1, PAR2 p2); - - typedef RET_TYPE - (generic_class::*generic_mem)(PAR1 p1, PAR2 p2); - - typedef binder< - generic_mem, static_function, void_static_function - > binder_type; - - binder_type _binder; - - RET_TYPE exec_static(PAR1 p1, PAR2 p2) const - { - return (*(_binder.get_static_func()))(p1, p2); - } - public: - typedef functor_two type; - functor_two() { } - - functor_two(const functor_two& o): - _binder(o._binder) - { - } - - template - functor_two(Y* pmem, RET_TYPE(X::*func)(PAR1 p1, PAR2 p2) const ): - _binder(reinterpret_cast(pmem), func) - { - } - - template - functor_two(Y* pmem, RET_TYPE(X::*func)(PAR1 p1, PAR2 p2) ): - _binder(reinterpret_cast(pmem), func) - { - } - - functor_two(RET_TYPE(*func)(PAR1 p1, PAR2 p2) ): - _binder(this, &functor_two::exec_static, func) - { - } - - RET_TYPE operator() (PAR1 p1, PAR2 p2) const - { - return (_binder.exec()->*(_binder.get_mem_ptr()))(p1, p2); - } - }; - - template - class functor_three - { - private: - typedef RET_TYPE (*static_function)(PAR1 p1, PAR2 p2, PAR3 p3); - typedef RET_TYPE (*void_static_function)(PAR1 p1, PAR2 p2, PAR3 p3); - - typedef RET_TYPE - (generic_class::*generic_mem)(PAR1 p1, PAR2 p2, PAR3 p3); - - typedef binder< - generic_mem, static_function, void_static_function - > binder_type; - - binder_type _binder; - - RET_TYPE exec_static(PAR1 p1, PAR2 p2, PAR3 p3) const - { - return (*(_binder.get_static_func()))(p1, p2, p3); - } - public: - typedef functor_three type; - functor_three() { } - - functor_three(const functor_three& o): - _binder(o._binder) - { - } - - template - functor_three(Y* pmem, RET_TYPE(X::*func)(PAR1 p1, PAR2 p2, PAR3 p3) const ): - _binder(reinterpret_cast(pmem), func) - { - } - - template - functor_three(Y* pmem, RET_TYPE(X::*func)(PAR1 p1, PAR2 p2, PAR3 p3) ): - _binder(reinterpret_cast(pmem), func) - { - } - - functor_three(RET_TYPE(*func)(PAR1 p1, PAR2 p2, PAR3 p3) ): - _binder(this, &functor_three::exec_static, func) - { - } - - RET_TYPE operator() (PAR1 p1, PAR2 p2, PAR3 p3) const - { - return (_binder.exec()->*(_binder.get_mem_ptr()))(p1, p2, p3); - } - }; - - template - class functor_four - { - private: - typedef RET_TYPE (*static_function)(PAR1 p1, PAR2 p2, PAR3 p3, PAR4 p4); - typedef RET_TYPE (*void_static_function)(PAR1 p1, PAR2 p2, PAR3 p3, PAR4 p4); - - typedef RET_TYPE - (generic_class::*generic_mem)(PAR1 p1, PAR2 p2, PAR3 p3, PAR4 p4); - - typedef binder< - generic_mem, static_function, void_static_function - > binder_type; - - binder_type _binder; - - RET_TYPE exec_static(PAR1 p1, PAR2 p2, PAR3 p3, PAR4 p4) const - { - return (*(_binder.get_static_func()))(p1, p2, p3, p4); - } - public: - typedef functor_four type; - functor_four() { } - - functor_four(const functor_four& o): - _binder(o._binder) - { - } - - template - functor_four(Y* pmem, RET_TYPE(X::*func)(PAR1 p1, PAR2 p2, PAR3 p3, PAR4 p4) const ): - _binder(reinterpret_cast(pmem), func) - { - } - - template - functor_four(Y* pmem, RET_TYPE(X::*func)(PAR1 p1, PAR2 p2, PAR3 p3, PAR4 p4) ): - _binder(reinterpret_cast(pmem), func) - { - } - - functor_four(RET_TYPE(*func)(PAR1 p1, PAR2 p2, PAR3 p3, PAR4 p4) ): - _binder(this, &functor_four::exec_static, func) - { - } - - RET_TYPE operator() (PAR1 p1, PAR2 p2, PAR3 p3, PAR4 p4) const - { - return (_binder.exec()->*(_binder.get_mem_ptr()))(p1, p2, p3, p4); - } - }; - } -}} - -#endif diff --git a/src/httpserver/create_test_request.hpp b/src/httpserver/create_test_request.hpp new file mode 100644 index 00000000..a1f193d0 --- /dev/null +++ b/src/httpserver/create_test_request.hpp @@ -0,0 +1,149 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) +#error "Only or can be included directly." +#endif + +#ifndef SRC_HTTPSERVER_CREATE_TEST_REQUEST_HPP_ +#define SRC_HTTPSERVER_CREATE_TEST_REQUEST_HPP_ + +#include +#include +#include + +#include "httpserver/http_request.hpp" +#include "httpserver/http_utils.hpp" + +namespace httpserver { + +class create_test_request { + public: + create_test_request() = default; + + create_test_request& method(const std::string& method) { + _method = method; + return *this; + } + + create_test_request& path(const std::string& path) { + _path = path; + return *this; + } + + create_test_request& version(const std::string& version) { + _version = version; + return *this; + } + + create_test_request& content(const std::string& content) { + _content = content; + return *this; + } + + create_test_request& header(const std::string& key, const std::string& value) { + _headers[key] = value; + return *this; + } + + create_test_request& footer(const std::string& key, const std::string& value) { + _footers[key] = value; + return *this; + } + + create_test_request& cookie(const std::string& key, const std::string& value) { + _cookies[key] = value; + return *this; + } + + create_test_request& arg(const std::string& key, const std::string& value) { + _args[key].push_back(value); + return *this; + } + + create_test_request& querystring(const std::string& querystring) { + _querystring = querystring; + return *this; + } + +#ifdef HAVE_BAUTH + create_test_request& user(const std::string& user) { + _user = user; + return *this; + } + + create_test_request& pass(const std::string& pass) { + _pass = pass; + return *this; + } +#endif // HAVE_BAUTH + +#ifdef HAVE_DAUTH + create_test_request& digested_user(const std::string& digested_user) { + _digested_user = digested_user; + return *this; + } +#endif // HAVE_DAUTH + + create_test_request& requestor(const std::string& requestor) { + _requestor = requestor; + return *this; + } + + create_test_request& requestor_port(uint16_t port) { + _requestor_port = port; + return *this; + } + +#ifdef HAVE_GNUTLS + create_test_request& tls_enabled(bool enabled = true) { + _tls_enabled = enabled; + return *this; + } +#endif // HAVE_GNUTLS + + http_request build(); + + private: + std::string _method = "GET"; + std::string _path = "/"; + std::string _version = "HTTP/1.1"; + std::string _content; + http::header_map _headers; + http::header_map _footers; + http::header_map _cookies; + std::map, http::arg_comparator> _args; + std::string _querystring; +#ifdef HAVE_BAUTH + std::string _user; + std::string _pass; +#endif // HAVE_BAUTH +#ifdef HAVE_DAUTH + std::string _digested_user; +#endif // HAVE_DAUTH + std::string _requestor = "127.0.0.1"; + uint16_t _requestor_port = 0; +#ifdef HAVE_GNUTLS + bool _tls_enabled = false; +#endif // HAVE_GNUTLS +}; + +} // namespace httpserver +#endif // SRC_HTTPSERVER_CREATE_TEST_REQUEST_HPP_ diff --git a/src/httpserver/create_webserver.hpp b/src/httpserver/create_webserver.hpp index 02d2ae8a..991b8501 100644 --- a/src/httpserver/create_webserver.hpp +++ b/src/httpserver/create_webserver.hpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -22,10 +22,18 @@ #error "Only or can be included directly." #endif -#ifndef _CREATE_WEBSERVER_HPP_ -#define _CREATE_WEBSERVER_HPP_ +#ifndef SRC_HTTPSERVER_CREATE_WEBSERVER_HPP_ +#define SRC_HTTPSERVER_CREATE_WEBSERVER_HPP_ #include +#include +#include +#include +#include +#include +#include + +#include "httpserver/http_response.hpp" #include "httpserver/http_utils.hpp" #define DEFAULT_WS_TIMEOUT 180 @@ -35,329 +43,428 @@ namespace httpserver { class webserver; class http_request; -class http_response; - -typedef void(*render_ptr)(const http_request&, http_response**); -typedef bool(*validator_ptr)(const std::string&); -typedef void(*unescaper_ptr)(char*); -typedef void(*log_access_ptr)(const std::string&); -typedef void(*log_error_ptr)(const std::string&); - -class create_webserver -{ - public: - create_webserver(): - _port(DEFAULT_WS_PORT), - _start_method(http::http_utils::INTERNAL_SELECT), - _max_threads(0), - _max_connections(0), - _memory_limit(0), - _connection_timeout(DEFAULT_WS_TIMEOUT), - _per_IP_connection_limit(0), - _log_access(0x0), - _log_error(0x0), - _validator(0x0), - _unescaper(0x0), - _bind_address(0x0), - _bind_socket(0), - _max_thread_stack_size(0), - _use_ssl(false), - _use_ipv6(false), - _debug(false), - _pedantic(false), - _https_mem_key(""), - _https_mem_cert(""), - _https_mem_trust(""), - _https_priorities(""), - _cred_type(http::http_utils::NONE), - _digest_auth_random(""), - _nonce_nc_size(0), - _default_policy(http::http_utils::ACCEPT), - _basic_auth_enabled(true), - _digest_auth_enabled(true), - _regex_checking(true), - _ban_system_enabled(true), - _post_process_enabled(true), - _single_resource(0x0), - _not_found_resource(0x0), - _method_not_allowed_resource(0x0), - _method_not_acceptable_resource(0x0), - _internal_error_resource(0x0) - { - } - - explicit create_webserver(uint16_t port): - _port(port), - _start_method(http::http_utils::INTERNAL_SELECT), - _max_threads(0), - _max_connections(0), - _memory_limit(0), - _connection_timeout(DEFAULT_WS_TIMEOUT), - _per_IP_connection_limit(0), - _log_access(0x0), - _log_error(0x0), - _validator(0x0), - _unescaper(0x0), - _bind_address(0x0), - _bind_socket(0), - _max_thread_stack_size(0), - _use_ssl(false), - _use_ipv6(false), - _debug(false), - _pedantic(false), - _https_mem_key(""), - _https_mem_cert(""), - _https_mem_trust(""), - _https_priorities(""), - _cred_type(http::http_utils::NONE), - _digest_auth_random(""), - _nonce_nc_size(0), - _default_policy(http::http_utils::ACCEPT), - _basic_auth_enabled(true), - _digest_auth_enabled(true), - _regex_checking(true), - _ban_system_enabled(true), - _post_process_enabled(true), - _single_resource(0x0), - _not_found_resource(0x0), - _method_not_allowed_resource(0x0), - _method_not_acceptable_resource(0x0), - _internal_error_resource(0x0) - { - } - - create_webserver& port(uint16_t port) { _port = port; return *this; } - create_webserver& start_method( - const http::http_utils::start_method_T& start_method - ) - { - _start_method = start_method; return *this; - } - create_webserver& max_threads(int max_threads) - { - _max_threads = max_threads; return *this; - } - create_webserver& max_connections(int max_connections) - { - _max_connections = max_connections; return *this; - } - create_webserver& memory_limit(int memory_limit) - { - _memory_limit = memory_limit; return *this; - } - create_webserver& connection_timeout(int connection_timeout) - { - _connection_timeout = connection_timeout; return *this; - } - create_webserver& per_IP_connection_limit(int per_IP_connection_limit) - { - _per_IP_connection_limit = per_IP_connection_limit; return *this; - } - create_webserver& log_access(log_access_ptr log_access) - { - _log_access = log_access; return *this; - } - create_webserver& log_error(log_error_ptr log_error) - { - _log_error = log_error; return *this; - } - create_webserver& validator(validator_ptr validator) - { - _validator = validator; return *this; - } - create_webserver& unescaper(unescaper_ptr unescaper) - { - _unescaper = unescaper; return *this; - } - create_webserver& bind_address(const struct sockaddr* bind_address) - { - _bind_address = bind_address; return *this; - } - create_webserver& bind_socket(int bind_socket) - { - _bind_socket = bind_socket; return *this; - } - create_webserver& max_thread_stack_size(int max_thread_stack_size) - { - _max_thread_stack_size = max_thread_stack_size; return *this; - } - create_webserver& use_ssl() { _use_ssl = true; return *this; } - create_webserver& no_ssl() { _use_ssl = false; return *this; } - create_webserver& use_ipv6() { _use_ipv6 = true; return *this; } - create_webserver& no_ipv6() { _use_ipv6 = false; return *this; } - create_webserver& debug() { _debug = true; return *this; } - create_webserver& no_debug() { _debug = false; return *this; } - create_webserver& pedantic() { _pedantic = true; return *this; } - create_webserver& no_pedantic() { _pedantic = false; return *this; } - create_webserver& https_mem_key(const std::string& https_mem_key) - { - char* _https_mem_key_pt = http::load_file(https_mem_key.c_str()); - _https_mem_key = _https_mem_key_pt; - free(_https_mem_key_pt); - return *this; - } - create_webserver& https_mem_cert(const std::string& https_mem_cert) - { - char* _https_mem_cert_pt = http::load_file(https_mem_cert.c_str()); - _https_mem_cert = _https_mem_cert_pt; - free(_https_mem_cert_pt); - return *this; - } - create_webserver& https_mem_trust(const std::string& https_mem_trust) - { - char* _https_mem_trust_pt = http::load_file(https_mem_trust.c_str()); - _https_mem_trust = _https_mem_trust_pt; - free(_https_mem_trust_pt); - return *this; - } - create_webserver& raw_https_mem_key(const std::string& https_mem_key) - { - _https_mem_key = https_mem_key; return *this; - } - create_webserver& raw_https_mem_cert(const std::string& https_mem_cert) - { - _https_mem_cert = https_mem_cert; return *this; - } - create_webserver& raw_https_mem_trust( - const std::string& https_mem_trust - ) - { - _https_mem_trust = https_mem_trust; return *this; - } - create_webserver& https_priorities(const std::string& https_priorities) - { - _https_priorities = https_priorities; return *this; - } - create_webserver& cred_type(const http::http_utils::cred_type_T& cred_type) - { - _cred_type = cred_type; return *this; - } - create_webserver& digest_auth_random( - const std::string& digest_auth_random - ) - { - _digest_auth_random = digest_auth_random; return *this; - } - create_webserver& nonce_nc_size(int nonce_nc_size) - { - _nonce_nc_size = nonce_nc_size; return *this; - } - create_webserver& default_policy( - const http::http_utils::policy_T& default_policy - ) - { - _default_policy = default_policy; return *this; - } - create_webserver& basic_auth() - { - _basic_auth_enabled = true; return *this; - } - create_webserver& no_basic_auth() - { - _basic_auth_enabled = false; return *this; - } - create_webserver& digest_auth() - { - _digest_auth_enabled = true; return *this; - } - create_webserver& no_digest_auth() - { - _digest_auth_enabled = false; return *this; - } - create_webserver& regex_checking() - { - _regex_checking = true; return *this; - } - create_webserver& no_regex_checking() - { - _regex_checking = false; return *this; - } - create_webserver& ban_system() - { - _ban_system_enabled = true; return *this; - } - create_webserver& no_ban_system() - { - _ban_system_enabled = false; return *this; - } - create_webserver& post_process() - { - _post_process_enabled = true; return *this; - } - create_webserver& no_post_process() - { - _post_process_enabled = false; return *this; - } - create_webserver& single_resource(render_ptr single_resource) - { - _single_resource = single_resource; return *this; - } - create_webserver& not_found_resource(render_ptr not_found_resource) - { - _not_found_resource = not_found_resource; return *this; - } - create_webserver& method_not_allowed_resource( - render_ptr method_not_allowed_resource - ) - { - _method_not_allowed_resource = method_not_allowed_resource; - return *this; - } - create_webserver& method_not_acceptable_resource( - render_ptr method_not_acceptable_resource - ) - { - _method_not_acceptable_resource = method_not_acceptable_resource; - return *this; - } - create_webserver& internal_error_resource( - render_ptr internal_error_resource - ) - { - _internal_error_resource = internal_error_resource; return *this; - } - - private: - uint16_t _port; - http::http_utils::start_method_T _start_method; - int _max_threads; - int _max_connections; - int _memory_limit; - int _connection_timeout; - int _per_IP_connection_limit; - log_access_ptr _log_access; - log_error_ptr _log_error; - validator_ptr _validator; - unescaper_ptr _unescaper; - const struct sockaddr* _bind_address; - int _bind_socket; - int _max_thread_stack_size; - bool _use_ssl; - bool _use_ipv6; - bool _debug; - bool _pedantic; - std::string _https_mem_key; - std::string _https_mem_cert; - std::string _https_mem_trust; - std::string _https_priorities; - http::http_utils::cred_type_T _cred_type; - std::string _digest_auth_random; - int _nonce_nc_size; - http::http_utils::policy_T _default_policy; - bool _basic_auth_enabled; - bool _digest_auth_enabled; - bool _regex_checking; - bool _ban_system_enabled; - bool _post_process_enabled; - render_ptr _single_resource; - render_ptr _not_found_resource; - render_ptr _method_not_allowed_resource; - render_ptr _method_not_acceptable_resource; - render_ptr _internal_error_resource; - - friend class webserver; + +typedef std::function(const http_request&)> render_ptr; +typedef std::function validator_ptr; +typedef std::function log_access_ptr; +typedef std::function log_error_ptr; +typedef std::function psk_cred_handler_callback; + +/** + * SNI (Server Name Indication) callback type. + * The callback receives the server name from the TLS ClientHello. + * It should return a pair of (certificate_pem, key_pem) for the requested server name, + * or empty strings to use the default certificate. + */ +typedef std::function(const std::string& server_name)> sni_callback_t; + +namespace http { class file_info; } + +typedef std::function file_cleanup_callback_ptr; +typedef std::function(const http_request&)> auth_handler_ptr; + +class create_webserver { + public: + create_webserver() = default; + create_webserver(const create_webserver& b) = default; + create_webserver(create_webserver&& b) noexcept = default; + create_webserver& operator=(const create_webserver& b) = default; + create_webserver& operator=(create_webserver&& b) = default; + + explicit create_webserver(uint16_t port): + _port(port) { } + + create_webserver& port(uint16_t port) { + _port = port; + return *this; + } + + create_webserver& start_method(const http::http_utils::start_method_T& start_method) { + _start_method = start_method; + return *this; + } + + create_webserver& max_threads(int max_threads) { + _max_threads = max_threads; + return *this; + } + + create_webserver& max_connections(int max_connections) { + _max_connections = max_connections; + return *this; + } + + create_webserver& memory_limit(int memory_limit) { + _memory_limit = memory_limit; + return *this; + } + + create_webserver& content_size_limit(size_t content_size_limit) { + _content_size_limit = content_size_limit; + return *this; + } + + create_webserver& connection_timeout(int connection_timeout) { + _connection_timeout = connection_timeout; + return *this; + } + + create_webserver& per_IP_connection_limit(int per_IP_connection_limit) { + _per_IP_connection_limit = per_IP_connection_limit; + return *this; + } + + create_webserver& log_access(log_access_ptr log_access) { + _log_access = log_access; + return *this; + } + + create_webserver& log_error(log_error_ptr log_error) { + _log_error = log_error; + return *this; + } + + create_webserver& validator(validator_ptr validator) { + _validator = validator; + return *this; + } + + create_webserver& unescaper(unescaper_ptr unescaper) { + _unescaper = unescaper; + return *this; + } + + create_webserver& bind_address(const struct sockaddr* bind_address) { + _bind_address = bind_address; + return *this; + } + + create_webserver& bind_address(const std::string& ip); + + create_webserver& bind_socket(int bind_socket) { + _bind_socket = bind_socket; + return *this; + } + + create_webserver& max_thread_stack_size(int max_thread_stack_size) { + _max_thread_stack_size = max_thread_stack_size; + return *this; + } + + create_webserver& use_ssl() { + _use_ssl = true; + return *this; + } + + create_webserver& no_ssl() { + _use_ssl = false; + return *this; + } + + create_webserver& use_ipv6() { + _use_ipv6 = true; + return *this; + } + + create_webserver& no_ipv6() { + _use_ipv6 = false; + return *this; + } + + create_webserver& use_dual_stack() { + _use_dual_stack = true; + return *this; + } + + create_webserver& no_dual_stack() { + _use_dual_stack = false; + return *this; + } + + create_webserver& debug() { + _debug = true; + return *this; + } + + create_webserver& no_debug() { + _debug = false; + return *this; + } + + create_webserver& pedantic() { + _pedantic = true; + return *this; + } + + create_webserver& no_pedantic() { + _pedantic = false; + return *this; + } + + create_webserver& https_mem_key(const std::string& https_mem_key) { + _https_mem_key = http::load_file(https_mem_key); + return *this; + } + + create_webserver& https_mem_cert(const std::string& https_mem_cert) { + _https_mem_cert = http::load_file(https_mem_cert); + return *this; + } + + create_webserver& https_mem_trust(const std::string& https_mem_trust) { + _https_mem_trust = http::load_file(https_mem_trust); + return *this; + } + + create_webserver& raw_https_mem_key(const std::string& https_mem_key) { + _https_mem_key = https_mem_key; + return *this; + } + + create_webserver& raw_https_mem_cert(const std::string& https_mem_cert) { + _https_mem_cert = https_mem_cert; + return *this; + } + + create_webserver& raw_https_mem_trust(const std::string& https_mem_trust) { + _https_mem_trust = https_mem_trust; + return *this; + } + + create_webserver& https_priorities(const std::string& https_priorities) { + _https_priorities = https_priorities; + return *this; + } + + create_webserver& cred_type(const http::http_utils::cred_type_T& cred_type) { + _cred_type = cred_type; + return *this; + } + + create_webserver& psk_cred_handler(psk_cred_handler_callback handler) { + _psk_cred_handler = handler; + return *this; + } + + create_webserver& digest_auth_random(const std::string& digest_auth_random) { + _digest_auth_random = digest_auth_random; + return *this; + } + + create_webserver& nonce_nc_size(int nonce_nc_size) { + _nonce_nc_size = nonce_nc_size; + return *this; + } + + create_webserver& default_policy(const http::http_utils::policy_T& default_policy) { + _default_policy = default_policy; + return *this; + } + +#ifdef HAVE_BAUTH + create_webserver& basic_auth() { + _basic_auth_enabled = true; + return *this; + } + + create_webserver& no_basic_auth() { + _basic_auth_enabled = false; + return *this; + } +#endif // HAVE_BAUTH + + create_webserver& digest_auth() { + _digest_auth_enabled = true; + return *this; + } + + create_webserver& no_digest_auth() { + _digest_auth_enabled = false; + return *this; + } + + create_webserver& deferred() { + _deferred_enabled = true; + return *this; + } + + create_webserver& no_deferred() { + _deferred_enabled = false; + return *this; + } + + create_webserver& regex_checking() { + _regex_checking = true; + return *this; + } + + create_webserver& no_regex_checking() { + _regex_checking = false; + return *this; + } + + create_webserver& ban_system() { + _ban_system_enabled = true; + return *this; + } + + create_webserver& no_ban_system() { + _ban_system_enabled = false; + return *this; + } + + create_webserver& post_process() { + _post_process_enabled = true; + return *this; + } + + create_webserver& no_post_process() { + _post_process_enabled = false; + return *this; + } + + create_webserver& no_put_processed_data_to_content() { + _put_processed_data_to_content = false; + return *this; + } + + create_webserver& put_processed_data_to_content() { + _put_processed_data_to_content = true; + return *this; + } + + create_webserver& file_upload_target(const file_upload_target_T& file_upload_target) { + _file_upload_target = file_upload_target; + return *this; + } + + create_webserver& file_upload_dir(const std::string& file_upload_dir) { + _file_upload_dir = file_upload_dir; + return *this; + } + + create_webserver& no_generate_random_filename_on_upload() { + _generate_random_filename_on_upload = false; + return *this; + } + + create_webserver& generate_random_filename_on_upload() { + _generate_random_filename_on_upload = true; + return *this; + } + + create_webserver& single_resource() { + _single_resource = true; + return *this; + } + + create_webserver& no_single_resource() { + _single_resource = false; + return *this; + } + + create_webserver& tcp_nodelay() { + _tcp_nodelay = true; + return *this; + } + + create_webserver& not_found_resource(render_ptr not_found_resource) { + _not_found_resource = not_found_resource; + return *this; + } + + create_webserver& method_not_allowed_resource(render_ptr method_not_allowed_resource) { + _method_not_allowed_resource = method_not_allowed_resource; + return *this; + } + + create_webserver& internal_error_resource(render_ptr internal_error_resource) { + _internal_error_resource = internal_error_resource; + return *this; + } + + create_webserver& file_cleanup_callback(file_cleanup_callback_ptr callback) { + _file_cleanup_callback = callback; + return *this; + } + + create_webserver& auth_handler(auth_handler_ptr handler) { + _auth_handler = handler; + return *this; + } + + create_webserver& auth_skip_paths(const std::vector& paths) { + _auth_skip_paths = paths; + return *this; + } + + /** + * Set the SNI (Server Name Indication) callback. + * The callback is invoked during TLS handshake with the server name from ClientHello. + * @param callback The SNI callback function + * @return reference to this for method chaining + */ + create_webserver& sni_callback(sni_callback_t callback) { + _sni_callback = callback; + return *this; + } + + private: + uint16_t _port = DEFAULT_WS_PORT; + http::http_utils::start_method_T _start_method = http::http_utils::INTERNAL_SELECT; + int _max_threads = 0; + int _max_connections = 0; + int _memory_limit = 0; + size_t _content_size_limit = std::numeric_limits::max(); + int _connection_timeout = DEFAULT_WS_TIMEOUT; + int _per_IP_connection_limit = 0; + log_access_ptr _log_access = nullptr; + log_error_ptr _log_error = nullptr; + validator_ptr _validator = nullptr; + unescaper_ptr _unescaper = nullptr; + const struct sockaddr* _bind_address = nullptr; + std::shared_ptr _bind_address_storage; + int _bind_socket = 0; + int _max_thread_stack_size = 0; + bool _use_ssl = false; + bool _use_ipv6 = false; + bool _use_dual_stack = false; + bool _debug = false; + bool _pedantic = false; + std::string _https_mem_key = ""; + std::string _https_mem_cert = ""; + std::string _https_mem_trust = ""; + std::string _https_priorities = ""; + http::http_utils::cred_type_T _cred_type = http::http_utils::NONE; + psk_cred_handler_callback _psk_cred_handler = nullptr; + std::string _digest_auth_random = ""; + int _nonce_nc_size = 0; + http::http_utils::policy_T _default_policy = http::http_utils::ACCEPT; +#ifdef HAVE_BAUTH + bool _basic_auth_enabled = true; +#endif // HAVE_BAUTH + bool _digest_auth_enabled = true; + bool _regex_checking = true; + bool _ban_system_enabled = true; + bool _post_process_enabled = true; + bool _put_processed_data_to_content = true; + file_upload_target_T _file_upload_target = FILE_UPLOAD_MEMORY_ONLY; + std::string _file_upload_dir = "/tmp"; + bool _generate_random_filename_on_upload = false; + bool _deferred_enabled = false; + bool _single_resource = false; + bool _tcp_nodelay = false; + render_ptr _not_found_resource = nullptr; + render_ptr _method_not_allowed_resource = nullptr; + render_ptr _internal_error_resource = nullptr; + file_cleanup_callback_ptr _file_cleanup_callback = nullptr; + auth_handler_ptr _auth_handler = nullptr; + std::vector _auth_skip_paths; + sni_callback_t _sni_callback = nullptr; + + friend class webserver; }; -} //httpserver +} // namespace httpserver -#endif //_CREATE_WEBSERVER_HPP_ +#endif // SRC_HTTPSERVER_CREATE_WEBSERVER_HPP_ diff --git a/src/httpserver/deferred_response.hpp b/src/httpserver/deferred_response.hpp new file mode 100644 index 00000000..d1fc1e22 --- /dev/null +++ b/src/httpserver/deferred_response.hpp @@ -0,0 +1,96 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) +#error "Only or can be included directly." +#endif + +#ifndef SRC_HTTPSERVER_DEFERRED_RESPONSE_HPP_ +#define SRC_HTTPSERVER_DEFERRED_RESPONSE_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include "httpserver/http_utils.hpp" +#include "httpserver/string_response.hpp" + +struct MHD_Response; + +namespace httpserver { + +namespace details { +MHD_Response* get_raw_response_helper(void* cls, ssize_t (*cb)(void*, uint64_t, char*, size_t)); +} // namespace details + +template +class deferred_response : public string_response { + public: + explicit deferred_response( + ssize_t(*cycle_callback)(std::shared_ptr, char*, size_t), + std::shared_ptr closure_data, + const std::string& content = "", + int response_code = http::http_utils::http_ok, + const std::string& content_type = http::http_utils::text_plain): + string_response("", response_code, content_type), + cycle_callback(cycle_callback), + closure_data(closure_data), + initial_content(content), + content_offset(0) { } + + deferred_response(const deferred_response& other) = default; + deferred_response(deferred_response&& other) noexcept = default; + deferred_response& operator=(const deferred_response& b) = default; + deferred_response& operator=(deferred_response&& b) = default; + + ~deferred_response() = default; + + MHD_Response* get_raw_response() { + return details::get_raw_response_helper(reinterpret_cast(this), &cb); + } + + private: + ssize_t (*cycle_callback)(std::shared_ptr, char*, size_t); + std::shared_ptr closure_data; + std::string initial_content; + size_t content_offset; + + static ssize_t cb(void* cls, uint64_t, char* buf, size_t max) { + deferred_response* dfr = static_cast*>(cls); + + // First, send any remaining initial content + if (dfr->content_offset < dfr->initial_content.size()) { + size_t remaining = dfr->initial_content.size() - dfr->content_offset; + size_t to_copy = std::min(remaining, max); + std::memcpy(buf, dfr->initial_content.data() + dfr->content_offset, to_copy); + dfr->content_offset += to_copy; + return static_cast(to_copy); + } + + // Then call user's callback + return dfr->cycle_callback(dfr->closure_data, buf, max); + } +}; + +} // namespace httpserver +#endif // SRC_HTTPSERVER_DEFERRED_RESPONSE_HPP_ diff --git a/src/httpserver/details/cache_entry.hpp b/src/httpserver/details/cache_entry.hpp deleted file mode 100644 index 3bf18816..00000000 --- a/src/httpserver/details/cache_entry.hpp +++ /dev/null @@ -1,142 +0,0 @@ -/* - This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - USA -*/ - -#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) -#error "Only or can be included directly." -#endif - -#ifndef _CACHE_ENTRY_HPP_ -#define _CACHE_ENTRY_HPP_ - -#include -#include -#include "httpserver/details/http_response_ptr.hpp" -#include "httpserver/http_response.hpp" - -namespace httpserver -{ -namespace details -{ - -struct pthread_t_comparator -{ - bool operator()(const pthread_t& t1, const pthread_t& t2) const - { - return pthread_equal(t1, t2); - } -}; - -struct cache_entry -{ - long ts; - int validity; - details::http_response_ptr response; - pthread_rwlock_t elem_guard; - pthread_mutex_t lock_guard; - std::set lockers; - - cache_entry(): - ts(-1), - validity(-1) - { - pthread_rwlock_init(&elem_guard, NULL); - pthread_mutex_init(&lock_guard, NULL); - } - - ~cache_entry() - { - pthread_rwlock_destroy(&elem_guard); - pthread_mutex_destroy(&lock_guard); - } - - cache_entry(const cache_entry& b): - ts(b.ts), - validity(b.validity), - response(b.response), - elem_guard(b.elem_guard), - lock_guard(b.lock_guard) - { - } - - void operator= (const cache_entry& b) - { - ts = b.ts; - validity = b.validity; - response = b.response; - pthread_rwlock_destroy(&elem_guard); - pthread_mutex_destroy(&lock_guard); - elem_guard = b.elem_guard; - } - - cache_entry( - details::http_response_ptr response, - long ts = -1, - int validity = -1 - ): - ts(ts), - validity(validity), - response(response) - { - pthread_rwlock_init(&elem_guard, NULL); - pthread_mutex_init(&lock_guard, NULL); - } - - void lock(bool write = false) - { - pthread_mutex_lock(&lock_guard); - pthread_t tid = pthread_self(); - if(!lockers.count(tid)) - { - if(write) - { - lockers.insert(tid); - pthread_mutex_unlock(&lock_guard); - pthread_rwlock_wrlock(&elem_guard); - } - else - { - lockers.insert(tid); - pthread_mutex_unlock(&lock_guard); - pthread_rwlock_rdlock(&elem_guard); - } - } - else - pthread_mutex_unlock(&lock_guard); - } - - void unlock() - { - pthread_mutex_lock(&lock_guard); - { - pthread_t tid = pthread_self(); - if(lockers.count(tid)) - { - lockers.erase(tid); - pthread_rwlock_unlock(&elem_guard); - } - } - pthread_mutex_unlock(&lock_guard); - } -}; - -} //details -} //httpserver - -#endif //_CACHE_ENTRY_HPP_ diff --git a/src/httpserver/details/comet_manager.hpp b/src/httpserver/details/comet_manager.hpp deleted file mode 100644 index 936b5484..00000000 --- a/src/httpserver/details/comet_manager.hpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - USA -*/ - -#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) -#error "Only or can be included directly." -#endif - -#ifndef _COMET_MANAGER_HPP_ -#define _COMET_MANAGER_HPP_ - -#include -#include -#include -#include -#include -#include -#include "http_utils.hpp" - -namespace httpserver -{ - -class webserver; - -namespace http -{ -struct httpserver_ska; -}; - -namespace details -{ - -class comet_manager -{ - private: - comet_manager(); - - ~comet_manager(); - - void send_message_to_topic(const std::string& topic, - const std::string& message, const httpserver::http::http_utils::start_method_T& start_method - ); - - void send_message_to_consumer(const http::httpserver_ska& connection_id, - const std::string& message, bool to_lock, - const httpserver::http::http_utils::start_method_T& start_method - ); - - void register_to_topics(const std::vector& topics, - const http::httpserver_ska& connection_id, int keepalive_secs, - std::string keepalive_msg, const httpserver::http::http_utils::start_method_T& start_method - ); - - size_t read_message(const http::httpserver_ska& connection_id, - std::string& message - ); - - size_t get_topic_consumers(const std::string& topic, - std::set& consumers - ); - - bool pop_signaled(const http::httpserver_ska& consumer, const httpserver::http::http_utils::start_method_T& start_method); - - void complete_request(const http::httpserver_ska& connection_id); - - void comet_select(unsigned long long* timeout_secs, - unsigned long long* timeout_microsecs, - const httpserver::http::http_utils::start_method_T& start_method - ); - - comet_manager(const comet_manager&) - { - } - - std::map > q_messages; - std::map > q_waitings; - std::map > q_blocks; - std::set q_signal; - std::map q_keepalives; - std::map > q_keepalives_mem; - pthread_rwlock_t comet_guard; - pthread_mutex_t cleanmux; - pthread_cond_t cleancond; - friend class httpserver::webserver; -}; - -} //details - -} //httpserver - -#endif //_COMET_MANAGER_HPP_ diff --git a/src/httpserver/details/event_tuple.hpp b/src/httpserver/details/event_tuple.hpp deleted file mode 100644 index 8a005c6e..00000000 --- a/src/httpserver/details/event_tuple.hpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - USA -*/ - -#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) -#error "Only or can be included directly." -#endif - -#ifndef _EVENT_TUPLE_HPP_ -#define _EVENT_TUPLE_HPP_ - -#include "httpserver/event_supplier.hpp" -#include "httpserver/binders.hpp" - -namespace httpserver { - -namespace details -{ - class http_endpoint; - struct modded_request; - struct daemon_item; - - class event_tuple - { - private: - typedef void(*supply_events_ptr)( - fd_set*, - fd_set*, - fd_set*, - MHD_socket* - ); - - binders::functor_four supply_events; - binders::functor_zero get_timeout; - binders::functor_zero dispatch_events; - - event_tuple(); - - friend class ::httpserver::webserver; - public: - template - event_tuple(event_supplier* es): - supply_events(binders::functor_four(es, &T::supply_events)), - get_timeout(binders::functor_zero(es, &T::get_timeout)), - dispatch_events(binders::functor_zero(es, &T::dispatch_events)) - { - } - }; -} //details - -} //httpserver - -#endif //_EVENT_TUPLE_HPP_ diff --git a/src/httpserver/details/http_endpoint.hpp b/src/httpserver/details/http_endpoint.hpp index a514f7e7..2fcfc81b 100644 --- a/src/httpserver/details/http_endpoint.hpp +++ b/src/httpserver/details/http_endpoint.hpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -22,229 +22,175 @@ #error "Only or can be included directly." #endif -#ifndef _HTTP_ENDPOINT_HPP_ -#define _HTTP_ENDPOINT_HPP_ +#ifndef SRC_HTTPSERVER_DETAILS_HTTP_ENDPOINT_HPP_ +#define SRC_HTTPSERVER_DETAILS_HTTP_ENDPOINT_HPP_ -#include -#include -#include +// cpplint errors on regex because it is replaced (in Chromium) by re2 google library. +// We don't have that alternative here (and we are actively avoiding dependencies). +#include // NOLINT [build/c++11] +#include #include +#include +#include -namespace httpserver -{ - -class webserver; - -namespace details -{ +namespace httpserver { -class http_resource_mirror; +namespace details { -/** - * Exception class throwed when a bad formatted http url is used -**/ -class bad_http_endpoint : public std::exception -{ - /** - * Method used to see error details - * @return a const char* containing the error message - **/ - virtual const char* what() const throw() - { - return "Bad url format!"; - } -}; +class http_resource; /** * Class representing an Http Endpoint. It is an abstraction used by the APIs. **/ -class http_endpoint -{ - private: - /** - * Copy constructor. It is useful expecially to copy regex_t structure that contains dinamically allocated data. - * @param h The http_endpoint to copy - **/ - http_endpoint(const http_endpoint& h); - /** - * Class Destructor - **/ - ~http_endpoint(); //if inlined it causes problems during ruby wrapper compiling - /** - * Operator overload for "less than operator". It is used to order endpoints in maps. - * @param b The http_endpoint to compare to - * @return boolean indicating if this is less than b. - **/ - bool operator <(const http_endpoint& b) const; - /** - * Operator overload for "assignment operator". It is used to copy endpoints to existing objects. - * Is is functional expecially to copy regex_t structure that contains dinamically allocated data. - * @param h The http_endpoint to copy - * @return a reference to the http_endpoint obtained - **/ - http_endpoint& operator =(const http_endpoint& h); - /** - * Method indicating if this endpoint 'matches' with the one passed. A passed endpoint matches a registered endpoint if - * the regex represented by the registered endpoint matches the passed one. - * @param url The endpoint to match - * @return true if the passed endpoint matches this. - **/ - bool match(const http_endpoint& url) const; - /** - * Method used to get the complete endpoint url - * @return a string representing the url - **/ - const std::string get_url_complete() const - { - return this->url_complete; - } - /** - * Method used to get the complete endpoint url - * @param result a string reference that will be filled with the url - **/ - void get_url_complete(std::string& result) const - { - result = this->url_complete; - } - /** - * Method used to find the size of the complete endpoint url - * @return the size - **/ - size_t get_url_complete_size() const - { - return this->url_complete.size(); - } - /** - * Method used to get all pars defined inside an url. - * @return a vector of strings representing all found pars. - **/ - const std::vector get_url_pars() const - { - return this->url_pars; - } - size_t get_url_pars(std::vector& result) const - { - result = this->url_pars; - return result.size(); - } - /** - * Method used to get all pieces of an url; considering an url splitted by '/'. - * @return a vector of strings representing all found pieces. - **/ - const std::vector get_url_pieces() const - { - return this->url_pieces; - } - /** - * Method used to get all pieces of an url; considering an url splittet by '/'. - * @param result a vector of strings to fill with url pieces. - * @return the size of the vector in output - **/ - size_t get_url_pieces(std::vector& result) const - { - result = this->url_pieces; - return result.size(); - } - /** - * Method used to get the number of pieces the url is composed of - * @return the number of pieces - **/ - size_t get_url_pieces_num() const - { - return this->url_pieces.size(); - } - /** - * Method used to get indexes of all parameters inside url - * @return a vector of int indicating all positions. - **/ - const std::vector get_chunk_positions() const - { - return this->chunk_positions; - } - /** - * Method used to get indexes of all parameters inside url - * @param result a vector to fill with ints indicating chunk positions - * @return the size of the vector filled - **/ - size_t get_chunk_positions(std::vector& result) const - { - result = this->chunk_positions; - return result.size(); - } - /** - * Default constructor of the class. - * @param family boolean that indicates if the endpoint is a family endpoint. - * A family endpoint is an endpoint that identifies a root and all its child like the same resource. - * For example, if I identify "/path/" like a family endpoint and I associate to it the resource "A", also - * "/path/to/res/" is automatically associated to resource "A". - **/ - http_endpoint(bool family = false): - url_complete("/"), - url_modded("/"), - family_url(family), - reg_compiled(false) - { - } - /** - * Constructor of the class http_endpoint. It is used to initialize an http_endpoint starting from a string form URL. - * @param url The string representation of the endpoint. All endpoints are in the form "/path/to/resource". - * @param family boolean that indicates if the endpoint is a family endpoint. - * A family endpoint is an endpoint that identifies a root and all its child like the same resource. - * For example, if I identify "/path/" like a family endpoint and I associate to it the resource "A", also - * "/path/to/res/" is automatically associated to resource "A". Default is false. - * @param registration boolean that indicates to the system if this is an endpoint that need to be registered to a webserver - * or it is simply an endpoint to be used for comparisons. Default is false. - * @param use_regex boolean that indicates if regexes are checked or not. Default is true. - **/ - http_endpoint(const std::string& url, - bool family = false, - bool registration = false, - bool use_regex = true - ); - /** - * The complete url extracted - **/ - std::string url_complete; - /** - * The url standardized in order to use standard comparisons or regexes - **/ - std::string url_modded; - /** - * Vector containing parameters extracted from url - **/ - std::vector url_pars; - /** - * Pieces the url can be splitted into (consider '/' as separator) - **/ - std::vector url_pieces; - /** - * Position of url pieces representing parameters - **/ - std::vector chunk_positions; - /** - * Regex used in comparisons - **/ - regex_t re_url_modded; - /** - * Boolean indicating wheter the endpoint represents a family - **/ - bool family_url; - /** - * Boolean indicating if the regex is compiled - **/ - bool reg_compiled; - friend class httpserver::webserver; - friend void _register_resource( - webserver*, - const std::string&, - details::http_resource_mirror&, - bool - ); - template friend struct std::pair; - template friend struct std::less; -}; +class http_endpoint { + public: + /** + * Copy constructor. It is useful expecially to copy regex_t structure that contains dinamically allocated data. + * @param h The http_endpoint to copy + **/ + http_endpoint(const http_endpoint& h); -}; + /** + * Class Destructor + **/ + ~http_endpoint(); // if inlined it causes problems during ruby wrapper compiling + + /** + * Operator overload for "less than operator". It is used to order endpoints in maps. + * @param b The http_endpoint to compare to + * @return boolean indicating if this is less than b. + **/ + bool operator <(const http_endpoint& b) const; + + /** + * Operator overload for "assignment operator". It is used to copy endpoints to existing objects. + * Is is functional expecially to copy regex_t structure that contains dinamically allocated data. + * @param h The http_endpoint to copy + * @return a reference to the http_endpoint obtained + **/ + http_endpoint& operator =(const http_endpoint& h); + + /** + * Method indicating if this endpoint 'matches' with the one passed. A passed endpoint matches a registered endpoint if + * the regex represented by the registered endpoint matches the passed one. + * @param url The endpoint to match + * @return true if the passed endpoint matches this. + **/ + bool match(const http_endpoint& url) const; + + /** + * Method used to get the complete endpoint url + * @return a string representing the url + **/ + const std::string& get_url_complete() const { + return url_complete; + } + + const std::string& get_url_normalized() const { + return url_normalized; + } + + /** + * Method used to get all pars defined inside an url. + * @return a vector of strings representing all found pars. + **/ + const std::vector& get_url_pars() const { + return url_pars; + } + /** + * Method used to get all pieces of an url; considering an url splitted by '/'. + * @return a vector of strings representing all found pieces. + **/ + const std::vector& get_url_pieces() const { + return url_pieces; + } + + /** + * Method used to get indexes of all parameters inside url + * @return a vector of int indicating all positions. + **/ + const std::vector& get_chunk_positions() const { + return chunk_positions; + } + + bool is_family_url() const { + return family_url; + } + + bool is_regex_compiled() const { + return reg_compiled; + } + + /** + * Default constructor of the class. + **/ + http_endpoint(): + url_complete("/"), + url_normalized("/"), + re_url_normalized(std::regex("")), // initialize empty + family_url(false), + reg_compiled(false) { } + + /** + * Constructor of the class http_endpoint. It is used to initialize an http_endpoint starting from a string form URL. + * @param url The string representation of the endpoint. All endpoints are in the form "/path/to/resource". + * @param family boolean that indicates if the endpoint is a family endpoint. + * A family endpoint is an endpoint that identifies a root and all its child like the same resource. + * For example, if I identify "/path/" like a family endpoint and I associate to it the resource "A", also + * "/path/to/res/" is automatically associated to resource "A". Default is false. + * @param registration boolean that indicates to the system if this is an endpoint that need to be registered to a webserver + * or it is simply an endpoint to be used for comparisons. Default is false. + * @param use_regex boolean that indicates if regexes are checked or not. Default is true. + **/ + http_endpoint(const std::string& url, + bool family = false, + bool registration = false, + bool use_regex = false); + + private: + /** + * The complete url extracted + **/ + std::string url_complete; + + /** + * The url standardized in order to use standard comparisons or regexes + **/ + std::string url_normalized; + + /** + * Vector containing parameters extracted from url + **/ + std::vector url_pars; + + /** + * Pieces the url can be splitted into (consider '/' as separator) + **/ + std::vector url_pieces; + + /** + * Position of url pieces representing parameters + **/ + std::vector chunk_positions; + + /** + * Regex used in comparisons + **/ + std::regex re_url_normalized; + + /** + * Boolean indicating wheter the endpoint represents a family + **/ + bool family_url; + + /** + * Boolean indicating if the regex is compiled + **/ + bool reg_compiled; }; -#endif + +} // namespace details + +} // namespace httpserver +#endif // SRC_HTTPSERVER_DETAILS_HTTP_ENDPOINT_HPP_ diff --git a/src/httpserver/details/http_resource_mirror.hpp b/src/httpserver/details/http_resource_mirror.hpp deleted file mode 100644 index e8b9d12a..00000000 --- a/src/httpserver/details/http_resource_mirror.hpp +++ /dev/null @@ -1,206 +0,0 @@ -/* - This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - USA -*/ - -#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) -#error "Only or can be included directly." -#endif - -#ifndef _HTTP_RESOURCE_MIRROR_HPP_ -#define _HTTP_RESOURCE_MIRROR_HPP_ - -#include "httpserver/binders.hpp" - -#define CREATE_METHOD_DETECTOR(X) \ - template \ - class has_##X \ - { \ - template struct Check; \ - template static char func(Check *); \ - template static int func(...); \ - public: \ - enum { value = sizeof(func(0)) == sizeof(char) }; \ - }; - -#define HAS_METHOD(X, T, RESULT, ARG1, ARG2) \ - has_##X::value - -namespace httpserver { - -template class http_resource; -class http_request; -class http_response; -class webserver; - -namespace details -{ - class http_endpoint; - struct modded_request; - struct daemon_item; - typedef bool(*is_allowed_ptr)(const std::string&); - - CREATE_METHOD_DETECTOR(render); - CREATE_METHOD_DETECTOR(render_GET); - CREATE_METHOD_DETECTOR(render_POST); - CREATE_METHOD_DETECTOR(render_PUT); - CREATE_METHOD_DETECTOR(render_HEAD); - CREATE_METHOD_DETECTOR(render_DELETE); - CREATE_METHOD_DETECTOR(render_TRACE); - CREATE_METHOD_DETECTOR(render_OPTIONS); - CREATE_METHOD_DETECTOR(render_CONNECT); - CREATE_METHOD_DETECTOR(render_not_acceptable); - - void empty_render(const http_request& r, http_response** res); - void empty_not_acceptable_render( - const http_request& r, http_response** res - ); - bool empty_is_allowed(const std::string& method); - - class http_resource_mirror - { - public: - http_resource_mirror() - { - } - - ~http_resource_mirror() - { - } - private: - typedef binders::functor_two functor; - - typedef binders::functor_one functor_allowed; - - const functor render; - const functor render_GET; - const functor render_POST; - const functor render_PUT; - const functor render_HEAD; - const functor render_DELETE; - const functor render_TRACE; - const functor render_OPTIONS; - const functor render_CONNECT; - const functor_allowed is_allowed; - - functor method_not_acceptable_resource; - - http_resource_mirror& operator= (const http_resource_mirror& o) - { - return *this; - } - - template - http_resource_mirror(http_resource* res): - render( - HAS_METHOD(render, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render) : functor(&empty_render) - ), - render_GET( - HAS_METHOD(render_GET, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render_GET) : - ( - HAS_METHOD(render, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render) : functor(&empty_render) - ) - ), - render_POST( - HAS_METHOD(render_POST, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render_POST) : - ( - HAS_METHOD(render, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render) : functor(&empty_render) - ) - ), - render_PUT( - HAS_METHOD(render_PUT, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render_PUT) : - ( - HAS_METHOD(render, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render) : functor(&empty_render) - ) - ), - render_HEAD( - HAS_METHOD(render_HEAD, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render_HEAD) : - ( - HAS_METHOD(render, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render) : functor(&empty_render) - ) - ), - render_DELETE( - HAS_METHOD(render_DELETE, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render_DELETE) : - ( - HAS_METHOD(render, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render) : functor(&empty_render) - ) - ), - render_TRACE( - HAS_METHOD(render_TRACE, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render_TRACE) : - ( - HAS_METHOD(render, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render) : functor(&empty_render) - ) - ), - render_OPTIONS( - HAS_METHOD(render_OPTIONS, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render_OPTIONS) : - ( - HAS_METHOD(render, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render) : functor(&empty_render) - ) - ), - render_CONNECT( - HAS_METHOD(render_CONNECT, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render_CONNECT) : - ( - HAS_METHOD(render, T, void, - const http_request&, http_response** - ) ? functor(res, &T::render) : functor(&empty_render) - ) - ), - is_allowed(res, &T::is_allowed) - { - } - - friend class ::httpserver::webserver; - }; -} //details -} //httpserver - -#endif //_HTTP_RESOURCE_MIRROR_HPP_ diff --git a/src/httpserver/details/http_response_ptr.hpp b/src/httpserver/details/http_response_ptr.hpp deleted file mode 100644 index 6435c449..00000000 --- a/src/httpserver/details/http_response_ptr.hpp +++ /dev/null @@ -1,122 +0,0 @@ -/* - This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - USA -*/ - -#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) -#error "Only or can be included directly." -#endif - -#ifndef _HTTP_RESPONSE_PTR_HPP_ -#define _HTTP_RESPONSE_PTR_HPP_ - -#include "httpserver/http_response.hpp" - -namespace httpserver -{ - -class webserver; - -namespace details -{ - -struct http_response_ptr -{ - public: - http_response_ptr(): - res(0x0), - num_references(0x0) - { - num_references = new int(0); - } - http_response_ptr(http_response* res): - res(res), - num_references(0x0) - { - num_references = new int(0); - } - http_response_ptr(const http_response_ptr& b): - res(b.res), - num_references(b.num_references) - { - (*num_references)++; - } - ~http_response_ptr() - { - if(num_references) - { - if((*num_references) == 0) - { - if(res && res->is_autodelete()) - { - delete res; - res = 0x0; - } - delete num_references; - } - else - (*num_references)--; - } - } - http_response& operator* () - { - return *res; - } - http_response* operator-> () - { - return res; - } - http_response* ptr() - { - return res; - } - http_response_ptr& operator= (const http_response_ptr& b) - { - if( this != &b) - { - if(num_references) - { - if((*num_references) == 0) - { - if(res && res->autodelete) - { - delete res; - res = 0x0; - } - delete num_references; - } - else - (*num_references)--; - } - - res = b.res; - num_references = b.num_references; - (*num_references)++; - } - return *this; - } - private: - http_response* res; - int* num_references; - friend class ::httpserver::webserver; -}; - -} //details -} //httpserver - -#endif //_HTTP_RESPONSE_PTR_HPP_ diff --git a/src/httpserver/details/modded_request.hpp b/src/httpserver/details/modded_request.hpp index a3850dd2..49aae1d3 100644 --- a/src/httpserver/details/modded_request.hpp +++ b/src/httpserver/details/modded_request.hpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -22,59 +22,52 @@ #error "Only or can be included directly." #endif -#ifndef _MODDED_REQUEST_HPP_ -#define _MODDED_REQUEST_HPP_ - -#include "binders.hpp" -#include "details/http_response_ptr.hpp" - -namespace httpserver -{ - -namespace details -{ - -struct modded_request -{ - struct MHD_PostProcessor *pp; - std::string* complete_uri; - std::string* standardized_url; - webserver* ws; - - const binders::functor_two< - const http_request&, http_response**, void - > http_resource_mirror::*callback; - - http_request* dhr; - http_response_ptr dhrs; - bool second; - - modded_request(): - pp(0x0), - complete_uri(0x0), - standardized_url(0x0), - ws(0x0), - dhr(0x0), - dhrs(0x0), - second(false) - { - } - ~modded_request() - { - if (NULL != pp) - { - MHD_destroy_post_processor (pp); +#ifndef SRC_HTTPSERVER_DETAILS_MODDED_REQUEST_HPP_ +#define SRC_HTTPSERVER_DETAILS_MODDED_REQUEST_HPP_ + +#include +#include +#include + +#include "httpserver/http_request.hpp" + +namespace httpserver { + +namespace details { + +struct modded_request { + struct MHD_PostProcessor *pp = nullptr; + std::string complete_uri; + std::string standardized_url; + webserver* ws = nullptr; + + std::shared_ptr (httpserver::http_resource::*callback)(const httpserver::http_request&); + + std::unique_ptr dhr = nullptr; + std::shared_ptr dhrs; + bool has_body = false; + + std::string upload_key; + std::string upload_filename; + std::unique_ptr upload_ostrm; + + modded_request() = default; + + modded_request(const modded_request& b) = delete; + modded_request(modded_request&& b) = default; + + modded_request& operator=(const modded_request& b) = delete; + modded_request& operator=(modded_request&& b) = default; + + ~modded_request() { + if (nullptr != pp) { + MHD_destroy_post_processor(pp); } - if(second) - delete dhr; //TODO: verify. It could be an error - delete complete_uri; - delete standardized_url; } - }; -} //details +} // namespace details -} //httpserver +} // namespace httpserver -#endif //_MODDED_REQUEST_HPP_ +#endif // SRC_HTTPSERVER_DETAILS_MODDED_REQUEST_HPP_ diff --git a/src/httpserver/digest_auth_fail_response.hpp b/src/httpserver/digest_auth_fail_response.hpp new file mode 100644 index 00000000..2eb044dc --- /dev/null +++ b/src/httpserver/digest_auth_fail_response.hpp @@ -0,0 +1,78 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) +#error "Only or can be included directly." +#endif + +#ifndef SRC_HTTPSERVER_DIGEST_AUTH_FAIL_RESPONSE_HPP_ +#define SRC_HTTPSERVER_DIGEST_AUTH_FAIL_RESPONSE_HPP_ + +#ifdef HAVE_DAUTH + +#include +#include "httpserver/http_utils.hpp" +#include "httpserver/string_response.hpp" + +struct MHD_Connection; +struct MHD_Response; + +namespace httpserver { + +class digest_auth_fail_response : public string_response { + public: + digest_auth_fail_response() = default; + + digest_auth_fail_response(const std::string& content, + const std::string& realm = "", + const std::string& opaque = "", + bool reload_nonce = false, + int response_code = http::http_utils::http_ok, + const std::string& content_type = http::http_utils::text_plain, + http::http_utils::digest_algorithm algorithm = + http::http_utils::digest_algorithm::MD5): + string_response(content, response_code, content_type), + realm(realm), + opaque(opaque), + reload_nonce(reload_nonce), + algorithm(algorithm) { } + + digest_auth_fail_response(const digest_auth_fail_response& other) = default; + digest_auth_fail_response(digest_auth_fail_response&& other) noexcept = default; + digest_auth_fail_response& operator=(const digest_auth_fail_response& b) = default; + digest_auth_fail_response& operator=(digest_auth_fail_response&& b) = default; + + ~digest_auth_fail_response() = default; + + int enqueue_response(MHD_Connection* connection, MHD_Response* response); + + private: + std::string realm = ""; + std::string opaque = ""; + bool reload_nonce = false; + http::http_utils::digest_algorithm algorithm = + http::http_utils::digest_algorithm::MD5; +}; + +} // namespace httpserver + +#endif // HAVE_DAUTH + +#endif // SRC_HTTPSERVER_DIGEST_AUTH_FAIL_RESPONSE_HPP_ diff --git a/src/httpserver/file_info.hpp b/src/httpserver/file_info.hpp new file mode 100644 index 00000000..8fd4f9e9 --- /dev/null +++ b/src/httpserver/file_info.hpp @@ -0,0 +1,61 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) +#error "Only or can be included directly." +#endif + +#ifndef SRC_HTTPSERVER_FILE_INFO_HPP_ +#define SRC_HTTPSERVER_FILE_INFO_HPP_ + +#include + +namespace httpserver { +class webserver; + +namespace http { + +class file_info { + public: + size_t get_file_size() const; + const std::string get_file_system_file_name() const; + const std::string get_content_type() const; + const std::string get_transfer_encoding() const; + + file_info() = default; + + private: + size_t _file_size = 0; + std::string _file_system_file_name; + std::string _content_type; + std::string _transfer_encoding; + + void set_file_system_file_name(const std::string& file_system_file_name); + void set_content_type(const std::string& content_type); + void set_transfer_encoding(const std::string& transfer_encoding); + void grow_file_size(size_t additional_file_size); + + friend class httpserver::webserver; +}; + +} // namespace http +} // namespace httpserver +#endif // SRC_HTTPSERVER_FILE_INFO_HPP_ + diff --git a/src/httpserver/file_response.hpp b/src/httpserver/file_response.hpp new file mode 100644 index 00000000..c85978ef --- /dev/null +++ b/src/httpserver/file_response.hpp @@ -0,0 +1,75 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) +#error "Only or can be included directly." +#endif + +#ifndef SRC_HTTPSERVER_FILE_RESPONSE_HPP_ +#define SRC_HTTPSERVER_FILE_RESPONSE_HPP_ + +#include +#include "httpserver/http_utils.hpp" +#include "httpserver/http_response.hpp" + +struct MHD_Response; + +namespace httpserver { + +class file_response : public http_response { + public: + file_response() = default; + + /** + * Constructor of the class file_response. You usually use this to pass a + * filename to the instance. + * @param filename Name of the file which content should be sent with the + * response. User must make sure file exists and is a + * regular file, otherwise libhttpserver will return a + * generic response with HTTP status 500 (Internal Server + * Error). + * @param response_code HTTP response code in good case, optional, + * default is 200 (OK). + * @param content_type Mime type of the file content, e.g. "text/html", + * optional, default is "application/octet-stream". + **/ + explicit file_response( + const std::string& filename, + int response_code = http::http_utils::http_ok, + const std::string& content_type = http::http_utils::application_octet_stream): + http_response(response_code, content_type), + filename(filename) { } + + file_response(const file_response& other) = default; + file_response(file_response&& other) noexcept = default; + + file_response& operator=(const file_response& b) = default; + file_response& operator=(file_response&& b) = default; + + ~file_response() = default; + + MHD_Response* get_raw_response(); + + private: + std::string filename = ""; +}; + +} // namespace httpserver +#endif // SRC_HTTPSERVER_FILE_RESPONSE_HPP_ diff --git a/src/httpserver/event_supplier.hpp b/src/httpserver/http_arg_value.hpp similarity index 52% rename from src/httpserver/event_supplier.hpp rename to src/httpserver/http_arg_value.hpp index 64a0bba6..e2111081 100644 --- a/src/httpserver/event_supplier.hpp +++ b/src/httpserver/http_arg_value.hpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -22,46 +22,44 @@ #error "Only or can be included directly." #endif -#ifndef _EVENT_SUPPLIER_HPP_ -#define _EVENT_SUPPLIER_HPP_ +#ifndef SRC_HTTPSERVER_HTTP_ARG_VALUE_HPP_ +#define SRC_HTTPSERVER_HTTP_ARG_VALUE_HPP_ + +#include +#include +#include namespace httpserver { -template -class event_supplier -{ - public: - event_supplier() - { - } +class http_arg_value { + public: + std::string_view get_flat_value() const { + return values.empty() ? "" : values[0]; + } - ~event_supplier() - { - } + std::vector get_all_values() const { + return values; + } - void supply_events( - fd_set* read_fdset, - fd_set* write_fdset, - fd_set* exc_fdset, - MHD_socket* max - ) const - { - static_cast(this)->supply_events( - read_fdset, write_fdset, exc_fdset, max - ); - } + operator std::string() const { + return std::string(get_flat_value()); + } - struct timeval get_timeout() const - { - return static_cast(this)->get_timeout(); - } + operator std::string_view() const { + return get_flat_value(); + } - void dispatch_events() const - { - static_cast(this)->dispatch_events(); + operator std::vector() const { + std::vector result; + for (auto const & value : values) { + result.push_back(std::string(value)); } + return result; + } + + std::vector values; }; -} //event_supplier +} // end namespace httpserver -#endif //_EVENT_SUPPLIER_HPP_ +#endif // SRC_HTTPSERVER_HTTP_ARG_VALUE_HPP_ diff --git a/src/httpserver/http_request.hpp b/src/httpserver/http_request.hpp index a217356d..2b621b11 100644 --- a/src/httpserver/http_request.hpp +++ b/src/httpserver/http_request.hpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -22,571 +22,512 @@ #error "Only or can be included directly." #endif -#ifndef _HTTP_REQUEST_HPP_ -#define _HTTP_REQUEST_HPP_ +#ifndef SRC_HTTPSERVER_HTTP_REQUEST_HPP_ +#define SRC_HTTPSERVER_HTTP_REQUEST_HPP_ +#include + +#ifdef HAVE_GNUTLS +#include +#endif // HAVE_GNUTLS + +#include +#include +#include +#include +#include #include -#include +#include #include #include -#include +#include -struct MHD_Connection; +#include "httpserver/http_arg_value.hpp" +#include "httpserver/http_utils.hpp" +#include "httpserver/file_info.hpp" +#include "httpserver/create_webserver.hpp" -namespace httpserver -{ +struct MHD_Connection; -class webserver; +namespace httpserver { -namespace http -{ - class header_comparator; - class arg_comparator; -}; +namespace details { struct modded_request; } /** * Class representing an abstraction for an Http Request. It is used from classes using these apis to receive information through http protocol. **/ -class http_request -{ - public: - - /** - * Method used to get the username eventually passed through basic authentication. - * @return string representation of the username. - **/ - const std::string get_user() const - { - return this->user; - } - /** - * Method used to get the username eventually passed through basic authentication. - * @param result string that will be filled with the username - **/ - void get_user(std::string& result) const - { - result = this->user; - } - /** - * Method used to get the username extracted from a digest authentication - * @return the username - **/ - const std::string get_digested_user() const - { - return this->digested_user; - } - /** - * Method used to get the username extracted from a digest authentication - * @param result string that will be filled with the username - **/ - void get_digested_user(std::string& result) const - { - result = this->digested_user; - } - /** - * Method used to get the password eventually passed through basic authentication. - * @return string representation of the password. - **/ - const std::string get_pass() const - { - return this->pass; - } - /** - * Method used to get the password eventually passed through basic authentication. - * @param result string that will be filled with the password. - **/ - void get_pass(std::string& result) const - { - result = this->pass; - } - /** - * Method used to get the path requested - * @return string representing the path requested. - **/ - const std::string get_path() const - { - return this->path; - } - /** - * Method used to get the path requested - * @param result string that will be filled with the path. - **/ - void get_path(std::string& result) const - { - result = this->path; - } - /** - * Method used to get all pieces of the path requested; considering an url splitted by '/'. - * @return a vector of strings containing all pieces - **/ - const std::vector get_path_pieces() const - { - return this->post_path; - } - /** - * Method used to get all pieces of the path requested; considering an url splitted by '/'. - * @param result vector of strings containing the path - * @return the size of the vector filled - **/ - size_t get_path_pieces(std::vector& result) const - { - result = this->post_path; - return result.size(); - } - /** - * Method used to obtain the size of path in terms of pieces; considering an url splitted by '/'. - * @return an integer representing the number of pieces - **/ - size_t get_path_pieces_size() const - { - return this->post_path.size(); - } - /** - * Method used to obtain a specified piece of the path; considering an url splitted by '/'. - * @param index the index of the piece selected - * @return the selected piece in form of string - **/ - const std::string get_path_piece(int index) const - { - if(((int)(this->post_path.size())) > index) - return this->post_path[index]; - return ""; - } - /** - * Method used to obtain a specified piece of the path; considering an url splitted by '/'. - * @param index the index of the piece selected - * @param result a string that will be filled with the piece found - * @return the length of the piece found - **/ - size_t get_path_piece(int index, std::string& result) const - { - if(((int)(this->post_path.size())) > index) - { - result = this->post_path[index]; - return result.size(); - } - else - { - result = ""; - return result.size(); - } - } - /** - * Method used to get the METHOD used to make the request. - * @return string representing the method. - **/ - const std::string get_method() const - { - return this->method; - } - /** - * Method used to get the METHOD used to make the request. - * @param result string that will be filled with the method. - **/ - void get_method(std::string& result) const - { - result = this->method; - } - /** - * Method used to get all headers passed with the request. - * @param result a map > that will be filled with all headers - * @result the size of the map - **/ - size_t get_headers(std::map& result) const; - /** - * Method used to get all footers passed with the request. - * @param result a map > that will be filled with all footers - * @result the size of the map - **/ - size_t get_footers(std::map& result) const; - /** - * Method used to get all cookies passed with the request. - * @param result a map > that will be filled with all cookies - * @result the size of the map - **/ - size_t get_cookies(std::map& result) const; - /** - * Method used to get all args passed with the request. - * @param result a map > that will be filled with all args - * @result the size of the map - **/ - size_t get_args(std::map& result) const; - /** - * Method used to get a specific header passed with the request. - * @param key the specific header to get the value from - * @return the value of the header. - **/ - const std::string get_header(const std::string& key) const - { - std::map::const_iterator it = - this->headers.find(key); - if(it != this->headers.end()) - return it->second; - else - return ""; - } - void get_header(const std::string& key, std::string& result) const - { - std::map::const_iterator it = - this->headers.find(key); - if(it != this->headers.end()) - result = it->second; - else - result = ""; - } - const std::string get_cookie(const std::string& key) const - { - std::map::const_iterator it = - this->cookies.find(key); - if(it != this->cookies.end()) - return it->second; - else - return ""; - } - void get_cookie(const std::string& key, std::string& result) const - { - std::map::const_iterator it = - this->cookies.find(key); - if(it != this->cookies.end()) - result = it->second; - else - result = ""; - } - /** - * Method used to get a specific footer passed with the request. - * @param key the specific footer to get the value from - * @return the value of the footer. - **/ - const std::string get_footer(const std::string& key) const - { - std::map::const_iterator it = - this->footers.find(key); - if(it != this->footers.end()) - return it->second; - else - return ""; - } - void get_footer(const std::string& key, std::string& result) const - { - std::map::const_iterator it = - this->footers.find(key); - if(it != this->footers.end()) - result = it->second; - else - result = ""; - } - /** - * Method used to get a specific argument passed with the request. - * @param ket the specific argument to get the value from - * @return the value of the arg. - **/ - const std::string get_arg(const std::string& key) const - { - std::map::const_iterator it = - this->args.find(key); - if(it != this->args.end()) - return it->second; - else - return ""; - } - void get_arg(const std::string& key, std::string& result) const - { - std::map::const_iterator it = - this->args.find(key); - if(it != this->args.end()) - result = it->second; - else - result = ""; - } - /** - * Method used to get the content of the request. - * @return the content in string representation - **/ - const std::string get_content() const - { - return this->content; - } - void get_content(std::string& result) const - { - result = this->content; - } - const std::string get_querystring() const - { - return this->querystring; - } - void get_querystring(std::string& result) const - { - result = this->querystring; - } - /** - * Method used to get the version of the request. - * @return the version in string representation - **/ - const std::string get_version() const - { - return this->version; - } - void get_version(std::string& result) const - { - result = this->version; - } - /** - * Method used to get the requestor. - * @return the requestor - **/ - const std::string get_requestor() const - { - return this->requestor; - } - void get_requestor(std::string& result) const - { - result = this->requestor; - } - /** - * Method used to get the requestor port used. - * @return the requestor port - **/ - short get_requestor_port() const - { - return this->requestor_port; - } - bool check_digest_auth(const std::string& realm, - const std::string& password, - int nonce_timeout, bool& reload_nonce - ) const; - - friend std::ostream &operator<< (std::ostream &os, const http_request &r); - private: - /** - * Default constructor of the class. It is a specific responsibility of apis to initialize this type of objects. - **/ - http_request(): - content("") - { - } - /** - * Copy constructor. - * @param b http_request b to copy attributes from. - **/ - http_request(const http_request& b): - user(b.user), - pass(b.pass), - path(b.path), - digested_user(b.digested_user), - method(b.method), - post_path(b.post_path), - headers(b.headers), - footers(b.footers), - cookies(b.cookies), - args(b.args), - querystring(b.querystring), - content(b.content), - version(b.version), - requestor(b.requestor), - underlying_connection(b.underlying_connection) - { - } - std::string user; - std::string pass; - std::string path; - std::string digested_user; - std::string method; - std::vector post_path; - std::map headers; - std::map footers; - std::map cookies; - std::map args; +class http_request { + public: + static const char EMPTY[]; + +#ifdef HAVE_BAUTH + /** + * Method used to get the username eventually passed through basic authentication. + * @return string representation of the username. + **/ + std::string_view get_user() const; +#endif // HAVE_BAUTH + +#ifdef HAVE_DAUTH + /** + * Method used to get the username extracted from a digest authentication + * @return the username + **/ + std::string_view get_digested_user() const; +#endif // HAVE_DAUTH + +#ifdef HAVE_BAUTH + /** + * Method used to get the password eventually passed through basic authentication. + * @return string representation of the password. + **/ + std::string_view get_pass() const; +#endif // HAVE_BAUTH + + /** + * Method used to get the path requested + * @return string representing the path requested. + **/ + std::string_view get_path() const { + return path; + } + + /** + * Method used to get all pieces of the path requested; considering an url splitted by '/'. + * @return a vector of strings containing all pieces + **/ + const std::vector get_path_pieces() const { + ensure_path_pieces_cached(); + return cache->path_pieces; + } + + /** + * Method used to obtain a specified piece of the path; considering an url splitted by '/'. + * @param index the index of the piece selected + * @return the selected piece in form of string + **/ + const std::string get_path_piece(int index) const { + ensure_path_pieces_cached(); + if (static_cast(cache->path_pieces.size()) > index) { + return cache->path_pieces[index]; + } + return EMPTY; + } + + /** + * Method used to get the METHOD used to make the request. + * @return string representing the method. + **/ + std::string_view get_method() const { + return method; + } + + /** + * Method used to get all headers passed with the request. + * @param result a map > that will be filled with all headers + * @result the size of the map + **/ + const http::header_view_map get_headers() const; + + /** + * Method used to get all footers passed with the request. + * @param result a map > that will be filled with all footers + * @result the size of the map + **/ + const http::header_view_map get_footers() const; + + /** + * Method used to get all cookies passed with the request. + * @param result a map > that will be filled with all cookies + * @result the size of the map + **/ + const http::header_view_map get_cookies() const; + + /** + * Method used to get all args passed with the request. + * @result the size of the map + **/ + const http::arg_view_map get_args() const; + + /** + * Method used to get all args passed with the request. If any key has multiple + * values, one value is chosen and returned. + * @result the size of the map + **/ + const std::map get_args_flat() const; + + /** + * Method to get or create a file info struct in the map if the provided filename is already in the map + * return the exiting file info struct, otherwise create one in the map and return it. + * @param upload_file_name the file name the user uploaded (this is the identifier for the map entry) + * @result a http::file_info + **/ + http::file_info& get_or_create_file_info(const std::string& key, const std::string& upload_file_name); + + /** + * Method used to get all files passed with the request. + * @result result a map > that will be filled with all files + **/ + const std::map> get_files() const { + return files; + } + + /** + * Method used to get a specific header passed with the request. + * @param key the specific header to get the value from + * @return the value of the header. + **/ + std::string_view get_header(std::string_view key) const; + + std::string_view get_cookie(std::string_view key) const; + + /** + * Method used to get a specific footer passed with the request. + * @param key the specific footer to get the value from + * @return the value of the footer. + **/ + std::string_view get_footer(std::string_view key) const; + + /** + * Method used to get a specific argument passed with the request. + * @param key the specific argument to get the value from + * @return the value(s) of the arg. + **/ + http_arg_value get_arg(std::string_view key) const; + + /** + * Method used to get a specific argument passed with the request. + * If the arg key has more than one value, only one is returned. + * @param key the specific argument to get the value from + * @return the value of the arg. + **/ + std::string_view get_arg_flat(std::string_view key) const; + + /** + * Method used to get the content of the request. + * @return the content in string representation + **/ + std::string_view get_content() const { + return content; + } + + /** + * Method to check whether the size of the content reached or exceeded content_size_limit. + * @return boolean + **/ + bool content_too_large() const { + return content.size() >= content_size_limit; + } + /** + * Method used to get the content of the query string.. + * @return the query string in string representation + **/ + std::string_view get_querystring() const; + + /** + * Method used to get the version of the request. + * @return the version in string representation + **/ + std::string_view get_version() const { + return version; + } + +#ifdef HAVE_GNUTLS + /** + * Method used to check if there is a TLS session. + * @return the TLS session + **/ + bool has_tls_session() const; + + /** + * Method used to get the TLS session. + * @return the TLS session + **/ + gnutls_session_t get_tls_session() const; + + /** + * Check if a client certificate is present in the TLS session. + * @return true if client certificate is present + **/ + bool has_client_certificate() const; + + /** + * Get the Subject Distinguished Name from the client certificate. + * @return the subject DN as a string, empty if not available + **/ + std::string get_client_cert_dn() const; + + /** + * Get the Issuer Distinguished Name from the client certificate. + * @return the issuer DN as a string, empty if not available + **/ + std::string get_client_cert_issuer_dn() const; + + /** + * Get the Common Name (CN) from the client certificate subject. + * @return the CN as a string, empty if not available + **/ + std::string get_client_cert_cn() const; + + /** + * Check if the client certificate chain has been verified. + * @return true if certificate verification passed + **/ + bool is_client_cert_verified() const; + + /** + * Get the SHA-256 fingerprint of the client certificate. + * @return hex-encoded SHA-256 fingerprint, empty if not available + **/ + std::string get_client_cert_fingerprint_sha256() const; + + /** + * Get the not-before (validity start) time of the client certificate. + * @return validity start time as time_t, -1 if not available + **/ + time_t get_client_cert_not_before() const; + + /** + * Get the not-after (validity end) time of the client certificate. + * @return validity end time as time_t, -1 if not available + **/ + time_t get_client_cert_not_after() const; +#endif // HAVE_GNUTLS + + /** + * Method used to get the requestor. + * @return the requestor + **/ + std::string_view get_requestor() const; + + /** + * Method used to get the requestor port used. + * @return the requestor port + **/ + uint16_t get_requestor_port() const; + +#ifdef HAVE_DAUTH + bool check_digest_auth(const std::string& realm, const std::string& password, int nonce_timeout, bool* reload_nonce) const; + + /** + * Check digest authentication using a pre-computed HA1 hash. + * The HA1 hash is computed as: hash(username:realm:password) using the specified algorithm. + * @param realm The authentication realm. + * @param digest Pointer to the pre-computed HA1 hash bytes. + * @param digest_size Size of the digest (16 for MD5, 32 for SHA-256). + * @param nonce_timeout Nonce validity timeout in seconds. + * @param reload_nonce Output: set to true if nonce should be regenerated. + * @param algo The digest algorithm (defaults to MD5). + * @return true if authenticated, false otherwise. + */ + bool check_digest_auth_ha1( + const std::string& realm, + const unsigned char* digest, + size_t digest_size, + int nonce_timeout, + bool* reload_nonce, + http::http_utils::digest_algorithm algo = http::http_utils::digest_algorithm::MD5) const; +#endif // HAVE_DAUTH + + friend std::ostream &operator<< (std::ostream &os, http_request &r); + + ~http_request(); + + private: + /** + * Default constructor of the class. It is a specific responsibility of apis to initialize this type of objects. + **/ + http_request() = default; + + http_request(MHD_Connection* underlying_connection, unescaper_ptr unescaper): + underlying_connection(underlying_connection), + unescaper(unescaper) {} + + /** + * Copy constructor. Deleted to make class move-only. The class is move-only for several reasons: + * - Internal cache structure is expensive to copy + * - Various string members are expensive to copy + * - The destructor removes transient files from disk, which must only happen once. + * - unique_ptr members are not copyable. + **/ + http_request(const http_request& b) = delete; + /** + * Move constructor. + * @param b http_request b to move attributes from. + **/ + http_request(http_request&& b) noexcept = default; + + /** + * Copy-assign. Deleted to make class move-only. The class is move-only for several reasons: + * - Internal cache structure is expensive to copy + * - Various string members are expensive to copy + * - The destructor removes transient files from disk, which must only happen once. + * - unique_ptr members are not copyable. + **/ + http_request& operator=(const http_request& b) = delete; + http_request& operator=(http_request&& b) = default; + + std::string path; + std::string method; + std::map> files; + std::string content = ""; + size_t content_size_limit = std::numeric_limits::max(); + std::string version; + + struct MHD_Connection* underlying_connection = nullptr; + + unescaper_ptr unescaper = nullptr; + + static MHD_Result build_request_header(void *cls, enum MHD_ValueKind kind, const char *key, const char *value); + + static MHD_Result build_request_args(void *cls, enum MHD_ValueKind kind, const char *key, const char *value); + + static MHD_Result build_request_querystring(void *cls, enum MHD_ValueKind kind, const char *key, const char *value); + +#ifdef HAVE_BAUTH + void fetch_user_pass() const; +#endif // HAVE_BAUTH + + /** + * Method used to set an argument value by key. + * @param key The name identifying the argument + * @param value The value assumed by the argument + **/ + void set_arg(const std::string& key, const std::string& value) { + cache->unescaped_args[key].push_back(value.substr(0, content_size_limit)); + } + + /** + * Method used to set an argument value by key. + * @param key The name identifying the argument + * @param value The value assumed by the argument + * @param size The size in number of char of the value parameter. + **/ + void set_arg(const char* key, const char* value, size_t size) { + cache->unescaped_args[key].push_back(std::string(value, std::min(size, content_size_limit))); + } + + /** + * Method used to set an argument value by key. If a key already exists, overwrites it. + * @param key The name identifying the argument + * @param value The value assumed by the argument + **/ + void set_arg_flat(const std::string& key, const std::string& value) { + cache->unescaped_args[key] = { (value.substr(0, content_size_limit)) }; + } + + void grow_last_arg(const std::string& key, const std::string& value); + + /** + * Method used to set the content of the request + * @param content The content to set. + **/ + void set_content(const std::string& content) { + this->content = content.substr(0, content_size_limit); + } + + /** + * Method used to set the maximum size of the content + * @param content_size_limit The limit on the maximum size of the content and arg's. + **/ + void set_content_size_limit(size_t content_size_limit) { + this->content_size_limit = content_size_limit; + } + + /** + * Method used to append content to the request preserving the previous inserted content + * @param content The content to append. + * @param size The size of the data to append. + **/ + void grow_content(const char* content, size_t size) { + this->content.append(content, size); + if (this->content.size() > content_size_limit) { + this->content.resize(content_size_limit); + } + } + + /** + * Method used to set the path requested. + * @param path The path searched by the request. + **/ + void set_path(const std::string& path) { + this->path = path; + } + + /** + * Method used to set the request METHOD + * @param method The method to set for the request + **/ + void set_method(const std::string& method); + + /** + * Method used to set the request http version (ie http 1.1) + * @param version The version to set in form of string + **/ + void set_version(const std::string& version) { + this->version = version; + } + + /** + * Method used to set all arguments of the request. + * @param args The args key-value map to set for the request. + **/ + void set_args(const std::map& args) { + for (auto const& [key, value] : args) { + cache->unescaped_args[key].push_back(value.substr(0, content_size_limit)); + } + } + + std::string_view get_connection_value(std::string_view key, enum MHD_ValueKind kind) const; + const http::header_view_map get_headerlike_values(enum MHD_ValueKind kind) const; + + // http_request objects are owned by a single connection and are not + // shared across threads. Lazy caching (path_pieces, args, etc.) is + // safe without synchronization under this invariant. + + // Cache certain data items on demand so we can consistently return views + // over the data. Some things we transform before returning to the user for + // simplicity (e.g. query_str, requestor), others out of necessity (arg unescaping). + // Others (username, password, digested_user) MHD returns as char* that we need + // to make a copy of and free anyway. + struct http_request_data_cache { +#ifdef HAVE_BAUTH + std::string username; + std::string password; +#endif // HAVE_BAUTH std::string querystring; - std::string content; - std::string version; - std::string requestor; - - short requestor_port; - struct MHD_Connection* underlying_connection; - - void set_underlying_connection(struct MHD_Connection* conn) - { - this->underlying_connection = conn; - } - /** - * Method used to set an header value by key. - * @param key The name identifying the header - * @param value The value assumed by the header - **/ - void set_header(const std::string& key, const std::string& value) - { - this->headers[key] = value; - } - /** - * Method used to set a footer value by key. - * @param key The name identifying the footer - * @param value The value assumed by the footer - **/ - void set_footer(const std::string& key, const std::string& value) - { - this->footers[key] = value; - } - /** - * Method used to set a cookie value by key. - * @param key The name identifying the cookie - * @param value The value assumed by the cookie - **/ - void set_cookie(const std::string& key, const std::string& value) - { - this->cookies[key] = value; - } - /** - * Method used to set an argument value by key. - * @param key The name identifying the argument - * @param value The value assumed by the argument - **/ - void set_arg(const std::string& key, const std::string& value) - { - this->args[key] = value; - } - /** - * Method used to set an argument value by key. - * @param key The name identifying the argument - * @param value The value assumed by the argument - * @param size The size in number of char of the value parameter. - **/ - void set_arg(const char* key, const char* value, size_t size) - { - this->args[key] = std::string(value, size); - } - /** - * Method used to set the content of the request - * @param content The content to set. - **/ - void set_content(const std::string& content) - { - this->content = content; - } - /** - * Method used to append content to the request preserving the previous inserted content - * @param content The content to append. - * @param size The size of the data to append. - **/ - void grow_content(const char* content, size_t size) - { - this->content.append(content, size); - } - /** - * Method used to set the path requested. - * @param path The path searched by the request. - **/ - void set_path(const std::string& path) - { - this->path = path; - std::vector complete_path; - http::http_utils::tokenize_url(this->path, complete_path); - for(unsigned int i = 0; i < complete_path.size(); i++) - { - this->post_path.push_back(complete_path[i]); - } - } - /** - * Method used to set the request METHOD - * @param method The method to set for the request - **/ - void set_method(const std::string& method); - /** - * Method used to set the request http version (ie http 1.1) - * @param version The version to set in form of string - **/ - void set_version(const std::string& version) - { - this->version = version; - } - /** - * Method used to set the requestor - * @param requestor The requestor to set - **/ - void set_requestor(const std::string& requestor) - { - this->requestor = requestor; - } - /** - * Method used to set the requestor port - * @param requestor The requestor port to set - **/ - void set_requestor_port(short requestor_port) - { - this->requestor_port = requestor_port; - } - /** - * Method used to remove an header previously inserted - * @param key The key identifying the header to remove. - **/ - void remove_header(const std::string& key) - { - this->headers.erase(key); - } - /** - * Method used to set all headers of the request. - * @param headers The headers key-value map to set for the request. - **/ - void set_headers(const std::map& headers) - { - std::map::const_iterator it; - for(it = headers.begin(); it != headers.end(); ++it) - this->headers[it->first] = it->second; - } - /** - * Method used to set all footers of the request. - * @param footers The footers key-value map to set for the request. - **/ - void set_footers(const std::map& footers) - { - std::map::const_iterator it; - for(it = footers.begin(); it != footers.end(); ++it) - this->footers[it->first] = it->second; - } - /** - * Method used to set all cookies of the request. - * @param cookies The cookies key-value map to set for the request. - **/ - void set_cookies(const std::map& cookies) - { - std::map::const_iterator it; - for(it = cookies.begin(); it != cookies.end(); ++it) - this->cookies[it->first] = it->second; - } - /** - * Method used to set all arguments of the request. - * @param args The args key-value map to set for the request. - **/ - void set_args(const std::map& args) - { - std::map::const_iterator it; - for(it = args.begin(); it != args.end(); ++it) - this->args[it->first] = it->second; - } - /** - * Method used to set the username of the request. - * @param user The username to set. - **/ - void set_user(const std::string& user) - { - this->user = user; - } - void set_digested_user(const std::string& digested_user) - { - this->digested_user = digested_user; - } - /** - * Method used to set the password of the request. - * @param pass The password to set. - **/ - void set_pass(const std::string& pass) - { - this->pass = pass; - } - - friend class webserver; + std::string requestor_ip; +#ifdef HAVE_DAUTH + std::string digested_user; +#endif // HAVE_DAUTH + std::map, http::arg_comparator> unescaped_args; + std::vector path_pieces; + + bool args_populated = false; + bool path_pieces_cached = false; + }; + std::unique_ptr cache = std::make_unique(); + void ensure_path_pieces_cached() const { + if (!cache->path_pieces_cached) { + cache->path_pieces = http::http_utils::tokenize_url(path); + cache->path_pieces_cached = true; + } + } + + // Populate the data cache unescaped_args + void populate_args() const; + + file_cleanup_callback_ptr file_cleanup_callback = nullptr; + + void set_file_cleanup_callback(file_cleanup_callback_ptr callback) { + file_cleanup_callback = callback; + } + + friend class webserver; + friend struct details::modded_request; }; std::ostream &operator<< (std::ostream &os, const http_request &r); -}; -#endif +} // namespace httpserver +#endif // SRC_HTTPSERVER_HTTP_REQUEST_HPP_ diff --git a/src/httpserver/http_resource.hpp b/src/httpserver/http_resource.hpp index 3755c32d..7b4bb576 100644 --- a/src/httpserver/http_resource.hpp +++ b/src/httpserver/http_resource.hpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -22,197 +22,215 @@ #error "Only or can be included directly." #endif -#ifndef _http_resource_hpp_ -#define _http_resource_hpp_ -#include -#include +#ifndef SRC_HTTPSERVER_HTTP_RESOURCE_HPP_ +#define SRC_HTTPSERVER_HTTP_RESOURCE_HPP_ #ifdef DEBUG #include #endif -namespace httpserver -{ +#include +#include +#include +#include +#include + +namespace httpserver { class http_request; } +namespace httpserver { class http_response; } -class webserver; -class http_request; -class http_response; +namespace httpserver { + +namespace details { std::shared_ptr empty_render(const http_request& r); } + +void resource_init(std::map* res); /** * Class representing a callable http resource. **/ - -void resource_init(std::map& res); - -template -class http_resource -{ - public: - /** - * Method used to answer to a generic request - * @param req Request passed through http - * @return A http_response object - **/ - void render(const http_request& r, http_response** res) - { - static_cast(this)->render(r, res); - } - /** - * Method used to answer to a GET request - * @param req Request passed through http - * @return A http_response object - **/ - void render_GET(const http_request& req, http_response** res) - { - static_cast(this)->render_GET(req, res); - } - /** - * Method used to answer to a POST request - * @param req Request passed through http - * @return A http_response object - **/ - void render_POST(const http_request& req, http_response** res) - { - static_cast(this)->render_POST(req, res); - } - /** - * Method used to answer to a PUT request - * @param req Request passed through http - * @return A http_response object - **/ - void render_PUT(const http_request& req, http_response** res) - { - static_cast(this)->render_PUT(req, res); - } - /** - * Method used to answer to a HEAD request - * @param req Request passed through http - * @return A http_response object - **/ - void render_HEAD(const http_request& req, http_response** res) - { - static_cast(this)->render_HEAD(req, res); - } - /** - * Method used to answer to a DELETE request - * @param req Request passed through http - * @return A http_response object - **/ - void render_DELETE(const http_request& req, http_response** res) - { - static_cast(this)->render_DELETE(req, res); - } - /** - * Method used to answer to a TRACE request - * @param req Request passed through http - * @return A http_response object - **/ - void render_TRACE(const http_request& req, http_response** res) - { - static_cast(this)->render_TRACE(req, res); - } - /** - * Method used to answer to a OPTIONS request - * @param req Request passed through http - * @return A http_response object - **/ - void render_OPTIONS(const http_request& req, http_response** res) - { - static_cast(this)->render_OPTIONS(req, res); - } - /** - * Method used to answer to a CONNECT request - * @param req Request passed through http - * @return A http_response object - **/ - void render_CONNECT(const http_request& req, http_response** res) - { - static_cast(this)->render_CONNECT(req, res); - } - /** - * Method used to set if a specific method is allowed or not on this request - * @param method method to set permission on - * @param allowed boolean indicating if the method is allowed or not - **/ - void set_allowing(const std::string& method, bool allowed) - { - if(this->allowed_methods.count(method)) - { - this->allowed_methods[method] = allowed; - } - } - /** - * Method used to implicitly allow all methods - **/ - void allow_all() - { - std::map::iterator it; - for ( it=this->allowed_methods.begin() ; it != this->allowed_methods.end(); ++it ) - this->allowed_methods[(*it).first] = true; - } - /** - * Method used to implicitly disallow all methods - **/ - void disallow_all() - { - std::map::iterator it; - for ( it=this->allowed_methods.begin() ; it != this->allowed_methods.end(); ++it ) - this->allowed_methods[(*it).first] = false; - } - /** - * Method used to discover if an http method is allowed or not for this resource - * @param method Method to discover allowings - * @return true if the method is allowed - **/ - bool is_allowed(const std::string& method) - { - if(this->allowed_methods.count(method)) - { - return this->allowed_methods[method]; - } - else - { +class http_resource { + public: + /** + * Class destructor + **/ + virtual ~http_resource() = default; + + /** + * Method used to answer to a generic request + * @param req Request passed through http + * @return A http_response object + **/ + virtual std::shared_ptr render(const http_request& req) { + return details::empty_render(req); + } + + /** + * Method used to answer to a GET request + * @param req Request passed through http + * @return A http_response object + **/ + virtual std::shared_ptr render_GET(const http_request& req) { + return render(req); + } + + /** + * Method used to answer to a POST request + * @param req Request passed through http + * @return A http_response object + **/ + virtual std::shared_ptr render_POST(const http_request& req) { + return render(req); + } + + /** + * Method used to answer to a PUT request + * @param req Request passed through http + * @return A http_response object + **/ + virtual std::shared_ptr render_PUT(const http_request& req) { + return render(req); + } + + /** + * Method used to answer to a HEAD request + * @param req Request passed through http + * @return A http_response object + **/ + virtual std::shared_ptr render_HEAD(const http_request& req) { + return render(req); + } + + /** + * Method used to answer to a DELETE request + * @param req Request passed through http + * @return A http_response object + **/ + virtual std::shared_ptr render_DELETE(const http_request& req) { + return render(req); + } + + /** + * Method used to answer to a TRACE request + * @param req Request passed through http + * @return A http_response object + **/ + virtual std::shared_ptr render_TRACE(const http_request& req) { + return render(req); + } + + /** + * Method used to answer to a OPTIONS request + * @param req Request passed through http + * @return A http_response object + **/ + virtual std::shared_ptr render_OPTIONS(const http_request& req) { + return render(req); + } + + /** + * Method used to answer to a PATCH request + * @param req Request passed through http + * @return A http_response object + **/ + virtual std::shared_ptr render_PATCH(const http_request& req) { + return render(req); + } + + /** + * Method used to answer to a CONNECT request + * @param req Request passed through http + * @return A http_response object + **/ + virtual std::shared_ptr render_CONNECT(const http_request& req) { + return render(req); + } + + /** + * Method used to set if a specific method is allowed or not on this request + * @param method method to set permission on + * @param allowed boolean indicating if the method is allowed or not + **/ + void set_allowing(const std::string& method, bool allowed) { + if (method_state.count(method)) { + method_state[method] = allowed; + } + } + + /** + * Method used to implicitly allow all methods + **/ + void allow_all() { + std::map::iterator it; + for (it=method_state.begin(); it != method_state.end(); ++it) { + method_state[(*it).first] = true; + } + } + + /** + * Method used to implicitly disallow all methods + **/ + void disallow_all() { + std::map::iterator it; + for (it=method_state.begin(); it != method_state.end(); ++it) { + method_state[(*it).first] = false; + } + } + + /** + * Method used to discover if an http method is allowed or not for this resource + * @param method Method to discover allowings + * @return true if the method is allowed + **/ + bool is_allowed(const std::string& method) { + if (method_state.count(method)) { + return method_state[method]; + } else { #ifdef DEBUG - std::map::iterator it; - for(it = allowed_methods.begin(); it != allowed_methods.end(); ++it) - { - std::cout << (*it).first << " -> " << (*it).second << std::endl; - } -#endif //DEBUG - return false; - } - } - protected: - /** - * Constructor of the class - **/ - http_resource() - { - resource_init(allowed_methods); - } - /** - * Copy constructor - **/ - http_resource(const http_resource& b) : allowed_methods(b.allowed_methods) { } - - http_resource& operator = (const http_resource& b) - { - allowed_methods = b.allowed_methods; - return (*this); - } - - /** - * Class destructor - **/ - ~http_resource() - { - } - - private: - friend class webserver; - friend void resource_init(std::map& res); - std::map allowed_methods; + std::map::iterator it; + for (it = method_state.begin(); it != method_state.end(); ++it) { + std::cout << (*it).first << " -> " << (*it).second << std::endl; + } +#endif // DEBUG + return false; + } + } + + /** + * Method used to return a list of currently allowed HTTP methods for this resource + * @return vector of strings + **/ + std::vector get_allowed_methods() { + std::vector allowed_methods; + + for (auto it = method_state.cbegin(); it != method_state.cend(); ++it) { + if ( (*it).second ) { + allowed_methods.push_back((*it).first); + } + } + + return allowed_methods; + } + + protected: + /** + * Constructor of the class + **/ + http_resource() { + resource_init(&method_state); + } + + /** + * Copy constructor + **/ + http_resource(const http_resource& b) = default; + http_resource(http_resource&& b) noexcept = default; + http_resource& operator=(const http_resource& b) = default; + http_resource& operator=(http_resource&& b) = default; + + private: + friend class webserver; + friend void resource_init(std::map* res); + std::map method_state; }; -}; -#endif +} // namespace httpserver +#endif // SRC_HTTPSERVER_HTTP_RESOURCE_HPP_ diff --git a/src/httpserver/http_response.hpp b/src/httpserver/http_response.hpp index cc55e2de..81593b36 100644 --- a/src/httpserver/http_response.hpp +++ b/src/httpserver/http_response.hpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -22,288 +22,124 @@ #error "Only or can be included directly." #endif -#ifndef _HTTP_RESPONSE_HPP_ -#define _HTTP_RESPONSE_HPP_ +#ifndef SRC_HTTPSERVER_HTTP_RESPONSE_HPP_ +#define SRC_HTTPSERVER_HTTP_RESPONSE_HPP_ + +#include #include -#include #include -#include - -#include "httpserver/details/http_resource_mirror.hpp" +#include "httpserver/http_arg_value.hpp" +#include "httpserver/http_utils.hpp" struct MHD_Connection; +struct MHD_Response; -namespace httpserver -{ - -class webserver; -class http_response_builder; - -namespace http -{ - class header_comparator; - class arg_comparator; -}; - -namespace details -{ - struct http_response_ptr; - ssize_t cb(void*, uint64_t, char*, size_t); - struct cache_entry; -}; - -class bad_caching_attempt: public std::exception -{ - virtual const char* what() const throw() - { - return "You cannot pass ce = 0x0 without key!"; - } -}; - -typedef ssize_t(*cycle_callback_ptr)(char*, size_t); +namespace httpserver { /** * Class representing an abstraction for an Http Response. It is used from classes using these apis to send information through http protocol. **/ -class http_response -{ - public: - - http_response(const http_response_builder& builder); - - /** - * Copy constructor - * @param b The http_response object to copy attributes value from. - **/ - http_response(const http_response& b): - content(b.content), - response_code(b.response_code), - autodelete(b.autodelete), - realm(b.realm), - opaque(b.opaque), - reload_nonce(b.reload_nonce), - fp(b.fp), - filename(b.filename), - headers(b.headers), - footers(b.footers), - cookies(b.cookies), - topics(b.topics), - keepalive_secs(b.keepalive_secs), - keepalive_msg(b.keepalive_msg), - send_topic(b.send_topic), - underlying_connection(b.underlying_connection), - ca(0x0), - closure_data(0x0), - ce(b.ce), - cycle_callback(b.cycle_callback), - get_raw_response(b.get_raw_response), - decorate_response(b.decorate_response), - enqueue_response(b.enqueue_response), - completed(b.completed), - ws(b.ws), - connection_id(b.connection_id) - { - } - - ~http_response(); - /** - * Method used to get the content from the response. - * @return the content in string form - **/ - const std::string get_content() - { - return this->content; - } - - void get_content(std::string& result) - { - result = this->content; - } - - /** - * Method used to get a specified header defined for the response - * @param key The header identification - * @return a string representing the value assumed by the header - **/ - const std::string get_header(const std::string& key) - { - return this->headers[key]; - } - - void get_header(const std::string& key, std::string& result) - { - result = this->headers[key]; - } - - /** - * Method used to get a specified footer defined for the response - * @param key The footer identification - * @return a string representing the value assumed by the footer - **/ - const std::string get_footer(const std::string& key) - { - return this->footers[key]; - } - - void get_footer(const std::string& key, std::string& result) - { - result = this->footers[key]; - } - - const std::string get_cookie(const std::string& key) - { - return this->cookies[key]; - } - - void get_cookie(const std::string& key, std::string& result) - { - result = this->cookies[key]; - } - - /** - * Method used to get all headers passed with the request. - * @return a map containing all headers. - **/ - size_t get_headers( - std::map& result - ) const; - - /** - * Method used to get all footers passed with the request. - * @return a map containing all footers. - **/ - size_t get_footers( - std::map& result - ) const; - - size_t get_cookies( - std::map& result - ) const; - - /** - * Method used to get the response code from the response - * @return The response code - **/ - int get_response_code() - { - return this->response_code; - } - - const std::string get_realm() const - { - return this->realm; - } - - void get_realm(std::string& result) const - { - result = this->realm; - } - - const std::string get_opaque() const - { - return this->opaque; - } - - void get_opaque(std::string& result) const - { - result = this->opaque; - } - - const bool need_nonce_reload() const - { - return this->reload_nonce; - } - - int get_switch_callback() const - { - return 0; - } - - bool is_autodelete() const - { - return autodelete; - } - - size_t get_topics(std::vector& topics) const - { - typedef std::vector::const_iterator topics_it; - for(topics_it it=this->topics.begin();it != this->topics.end();++it) - topics.push_back(*it); - return topics.size(); - } - protected: - typedef details::binders::functor_two get_raw_response_t; - - typedef details::binders::functor_one decorate_response_t; - - typedef details::binders::functor_two enqueue_response_t; - - std::string content; - int response_code; - bool autodelete; - std::string realm; - std::string opaque; - bool reload_nonce; - int fp; - std::string filename; - std::map headers; - std::map footers; - std::map cookies; - std::vector topics; - int keepalive_secs; - std::string keepalive_msg; - std::string send_topic; - struct MHD_Connection* underlying_connection; - void(*ca)(void*); - void* closure_data; - details::cache_entry* ce; - cycle_callback_ptr cycle_callback; - - const get_raw_response_t get_raw_response; - const decorate_response_t decorate_response; - const enqueue_response_t enqueue_response; - - bool completed; - - webserver* ws; - struct http::httpserver_ska connection_id; - - void get_raw_response_str(MHD_Response** res, webserver* ws = 0x0); - void get_raw_response_file(MHD_Response** res, webserver* ws = 0x0); - void get_raw_response_switch_r(MHD_Response** res, webserver* ws = 0x0); - - void get_raw_response_lp_receive(MHD_Response** res, - webserver* ws = 0x0); - - void get_raw_response_lp_send(MHD_Response** res, webserver* ws = 0x0); - void get_raw_response_cache(MHD_Response** res, webserver* ws = 0x0); - void get_raw_response_deferred(MHD_Response** res, webserver* ws = 0x0); - void decorate_response_str(MHD_Response* res); - void decorate_response_cache(MHD_Response* res); - void decorate_response_deferred(MHD_Response* res); - int enqueue_response_str(MHD_Connection* connection, MHD_Response* res); - - int enqueue_response_basic(MHD_Connection* connection, - MHD_Response* res - ); - - int enqueue_response_digest(MHD_Connection* connection, - MHD_Response* res - ); - - friend class webserver; - friend struct details::http_response_ptr; - friend class http_response_builder; - friend void clone_response(const http_response& hr, http_response** dhr); - friend ssize_t details::cb(void* cls, uint64_t pos, char* buf, size_t max); - friend std::ostream &operator<< (std::ostream &os, const http_response &r); - private: - http_response& operator=(const http_response& b); - - static ssize_t data_generator (void* cls, uint64_t pos, char* buf, size_t max); +class http_response { + public: + http_response() = default; + + explicit http_response(int response_code, const std::string& content_type): + response_code(response_code) { + headers[http::http_utils::http_header_content_type] = content_type; + } + + /** + * Copy constructor + * @param b The http_response object to copy attributes value from. + **/ + http_response(const http_response& b) = default; + http_response(http_response&& b) noexcept = default; + + http_response& operator=(const http_response& b) = default; + http_response& operator=(http_response&& b) noexcept = default; + + virtual ~http_response() = default; + + /** + * Method used to get a specified header defined for the response + * @param key The header identification + * @return a string representing the value assumed by the header + **/ + const std::string& get_header(const std::string& key) { + return headers[key]; + } + + /** + * Method used to get a specified footer defined for the response + * @param key The footer identification + * @return a string representing the value assumed by the footer + **/ + const std::string& get_footer(const std::string& key) { + return footers[key]; + } + + const std::string& get_cookie(const std::string& key) { + return cookies[key]; + } + + /** + * Method used to get all headers passed with the request. + * @return a map containing all headers. + **/ + const std::map& get_headers() const { + return headers; + } + + /** + * Method used to get all footers passed with the request. + * @return a map containing all footers. + **/ + const std::map& get_footers() const { + return footers; + } + + const std::map& get_cookies() const { + return cookies; + } + + /** + * Method used to get the response code from the response + * @return The response code + **/ + int get_response_code() const { + return response_code; + } + + void with_header(const std::string& key, const std::string& value) { + headers[key] = value; + } + + void with_footer(const std::string& key, const std::string& value) { + footers[key] = value; + } + + void with_cookie(const std::string& key, const std::string& value) { + cookies[key] = value; + } + + void shoutCAST(); + + virtual MHD_Response* get_raw_response(); + virtual void decorate_response(MHD_Response* response); + virtual int enqueue_response(MHD_Connection* connection, MHD_Response* response); + + private: + int response_code = -1; + + http::header_map headers; + http::header_map footers; + http::header_map cookies; + + protected: + friend std::ostream &operator<< (std::ostream &os, const http_response &r); }; -std::ostream &operator<< (std::ostream &os, const http_response &r); +std::ostream &operator<<(std::ostream &os, const http_response &r); -}; -#endif +} // namespace httpserver +#endif // SRC_HTTPSERVER_HTTP_RESPONSE_HPP_ diff --git a/src/httpserver/http_response_builder.hpp b/src/httpserver/http_response_builder.hpp deleted file mode 100644 index e1bc9108..00000000 --- a/src/httpserver/http_response_builder.hpp +++ /dev/null @@ -1,296 +0,0 @@ -/* - This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014 Sebastiano Merlino - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - USA -*/ - -#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) -#error "Only or can be included directly." -#endif - -#ifndef _HTTP_RESPONSE_BUILDER_HPP_ -#define _HTTP_RESPONSE_BUILDER_HPP_ -#include -#include -#include "httpserver/http_response.hpp" - -struct MHD_Connection; - -namespace httpserver -{ - -class webserver; - -namespace http -{ - class header_comparator; -}; - -namespace details -{ - struct cache_entry; -}; - -struct byte_string -{ - public: - byte_string(const char* content_hook, size_t content_length): - content_hook(content_hook), - content_length(content_length) - { - } - - const char* get_content_hook() const - { - return content_hook; - } - - size_t get_content_length() const - { - return content_length; - } - - private: - const char* content_hook; - size_t content_length; -}; - -class http_response_builder -{ - public: - explicit http_response_builder( - const std::string& content_hook, - int response_code = 200, - const std::string& content_type = "text/plain", - bool autodelete = true - ): - _content_hook(content_hook), - _response_code(response_code), - _autodelete(autodelete), - _realm(""), - _opaque(""), - _reload_nonce(false), - _headers(std::map()), - _footers(std::map()), - _cookies(std::map()), - _topics(std::vector()), - _keepalive_secs(-1), - _keepalive_msg(""), - _send_topic(""), - _ce(0x0), - _get_raw_response(&http_response::get_raw_response_str), - _decorate_response(&http_response::decorate_response_str), - _enqueue_response(&http_response::enqueue_response_str) - { - _headers[http::http_utils::http_header_content_type] = content_type; - } - - http_response_builder( - const byte_string& content_hook, - int response_code = 200, - const std::string& content_type = "text/plain", - bool autodelete = true - ): - _content_hook(std::string(content_hook.get_content_hook(), content_hook.get_content_length())), - _response_code(response_code), - _autodelete(autodelete), - _realm(""), - _opaque(""), - _reload_nonce(false), - _headers(std::map()), - _footers(std::map()), - _cookies(std::map()), - _topics(std::vector()), - _keepalive_secs(-1), - _keepalive_msg(""), - _send_topic(""), - _ce(0x0), - _get_raw_response(&http_response::get_raw_response_str), - _decorate_response(&http_response::decorate_response_str), - _enqueue_response(&http_response::enqueue_response_str) - { - _headers[http::http_utils::http_header_content_type] = content_type; - } - - http_response_builder(const http_response_builder& b): - _content_hook(b._content_hook), - _response_code(b._response_code), - _autodelete(b._autodelete), - _realm(b._realm), - _opaque(b._opaque), - _reload_nonce(b._reload_nonce), - _headers(b._headers), - _footers(b._footers), - _cookies(b._cookies), - _topics(b._topics), - _keepalive_secs(b._keepalive_secs), - _keepalive_msg(b._keepalive_msg), - _send_topic(b._send_topic), - _ce(b._ce), - _get_raw_response(b._get_raw_response), - _decorate_response(b._decorate_response), - _enqueue_response(b._enqueue_response) - { - } - - http_response_builder& operator=(const http_response_builder& b) - { - _content_hook = b._content_hook; - _response_code = b._response_code; - _autodelete = b._autodelete; - _realm = b._realm; - _opaque = b._opaque; - _reload_nonce = b._reload_nonce; - _headers = b._headers; - _footers = b._footers; - _cookies = b._cookies; - _topics = b._topics; - _keepalive_secs = b._keepalive_secs; - _keepalive_msg = b._keepalive_msg; - _send_topic = b._send_topic; - _ce = b._ce; - _get_raw_response = b._get_raw_response; - _decorate_response = b._decorate_response; - _enqueue_response = b._enqueue_response; - return *this; - } - - ~http_response_builder() - { - } - - http_response_builder& string_response() - { - return *this; - } - - http_response_builder& file_response() - { - _get_raw_response = &http_response::get_raw_response_file; - return *this; - } - - http_response_builder& basic_auth_fail_response(const std::string& realm = "") - { - _realm = realm; - _enqueue_response = &http_response::enqueue_response_basic; - return *this; - } - - http_response_builder& digest_auth_fail_response( - const std::string& realm = "", - const std::string& opaque = "", - bool reload_nonce = false - ) - { - _realm = realm; - _opaque = opaque; - _reload_nonce = reload_nonce; - _enqueue_response = &http_response::enqueue_response_digest; - return *this; - } - - http_response_builder& long_polling_receive_response( - const std::vector& topics, - int keepalive_secs = -1, - std::string keepalive_msg = "" - ) - { - _topics = topics; - _keepalive_secs = keepalive_secs; - _keepalive_msg = keepalive_msg; - _get_raw_response = &http_response::get_raw_response_lp_receive; - return *this; - } - - http_response_builder& long_polling_send_response(const std::string& send_topic) - { - _send_topic = send_topic; - _get_raw_response = &http_response::get_raw_response_lp_send; - return *this; - } - - http_response_builder& cache_response() - { - _get_raw_response = &http_response::get_raw_response_cache; - _decorate_response = &http_response::decorate_response_cache; - return *this; - } - - http_response_builder& deferred_response(cycle_callback_ptr cycle_callback) - { - _cycle_callback = cycle_callback; - _get_raw_response = &http_response::get_raw_response_deferred; - _decorate_response = &http_response::decorate_response_deferred; - return *this; - } - - http_response_builder& shoutCAST_response() - { - _response_code |= http::http_utils::shoutcast_response; - return *this; - } - - http_response_builder& with_header(const std::string& key, const std::string& value) - { - _headers[key] = value; return *this; - } - - http_response_builder& with_footer(const std::string& key, const std::string& value) - { - _footers[key] = value; return *this; - } - - http_response_builder& with_cookie(const std::string& key, const std::string& value) - { - _cookies[key] = value; return *this; - } - - private: - std::string _content_hook; - int _response_code; - bool _autodelete; - std::string _realm; - std::string _opaque; - bool _reload_nonce; - std::map _headers; - std::map _footers; - std::map _cookies; - std::vector _topics; - int _keepalive_secs; - std::string _keepalive_msg; - std::string _send_topic; - cycle_callback_ptr _cycle_callback; - details::cache_entry* _ce; - - void (http_response::*_get_raw_response)(MHD_Response**, webserver*); - void (http_response::*_decorate_response)(MHD_Response*); - int (http_response::*_enqueue_response)(MHD_Connection*, MHD_Response*); - - http_response_builder& cache_response(details::cache_entry* ce) - { - _ce = ce; - _get_raw_response = &http_response::get_raw_response_cache; - _decorate_response = &http_response::decorate_response_cache; - return *this; - } - - friend class http_response; -}; - -}; -#endif //_HTTP_RESPONSE_BUILDER_HPP_ diff --git a/src/httpserver/http_utils.hpp b/src/httpserver/http_utils.hpp index 58bc8f6e..8e44c15b 100644 --- a/src/httpserver/http_utils.hpp +++ b/src/httpserver/http_utils.hpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -22,243 +22,288 @@ #error "Only or can be included directly." #endif -#ifndef _HTTPUTILS_H_ -#define _HTTPUTILS_H_ +#ifndef SRC_HTTPSERVER_HTTP_UTILS_HPP_ +#define SRC_HTTPSERVER_HTTP_UTILS_HPP_ -#include -#include -#include -#include -#include -#include -#include -#include #ifdef HAVE_GNUTLS #include #endif +// needed to force Vista as a bare minimum to have inet_ntop (libmicro defines +// this to include XP support as a lower version). +#if defined(__MINGW32__) || defined(__CYGWIN32__) +#define _WINDOWS +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x600 +#endif + +// needed to have the fd_set definition ahead of microhttpd.h import +#if defined(__CYGWIN__) +#include +#endif + +#include +#include + +#if !defined(__MINGW32__) +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "httpserver/http_arg_value.hpp" + #define DEFAULT_MASK_VALUE 0xFFFF +#if MHD_VERSION < 0x00097002 +typedef int MHD_Result; +#endif + namespace httpserver { -namespace http { -class bad_ip_format_exception: public std::exception -{ - virtual const char* what() const throw() - { - return "IP is badly formatted!"; - } +enum file_upload_target_T { + FILE_UPLOAD_MEMORY_ONLY, + FILE_UPLOAD_DISK_ONLY, + FILE_UPLOAD_MEMORY_AND_DISK, }; -class file_access_exception: public std::exception -{ - virtual const char* what() const throw() - { - return "Unable to open file!"; - } -}; +typedef void(*unescaper_ptr)(std::string&); + +namespace http { -class http_utils -{ - public: +struct generateFilenameException : public std::exception { + public: + explicit generateFilenameException(const std::string& message) noexcept : error_message(message) { + } - enum cred_type_T - { - NONE = -1 + const char* what() const noexcept { + return error_message.c_str(); + } + + private: + std::string error_message; +}; + +class http_utils { + public: + enum cred_type_T { + NONE = -1 #ifdef HAVE_GNUTLS - ,CERTIFICATE = GNUTLS_CRD_CERTIFICATE, - ANON = GNUTLS_CRD_ANON, - SRP = GNUTLS_CRD_SRP, - PSK = GNUTLS_CRD_PSK, - IA = GNUTLS_CRD_IA + , CERTIFICATE = GNUTLS_CRD_CERTIFICATE, + ANON = GNUTLS_CRD_ANON, + SRP = GNUTLS_CRD_SRP, + PSK = GNUTLS_CRD_PSK, + IA = GNUTLS_CRD_IA #endif - }; - - enum start_method_T - { - INTERNAL_SELECT = MHD_NO_FLAG, - THREADS = MHD_USE_THREAD_PER_CONNECTION, - POLL = MHD_USE_THREAD_PER_CONNECTION | MHD_USE_POLL - }; - - enum policy_T - { - ACCEPT, - REJECT - }; - - enum IP_version_T - { - IPV4 = 4, IPV6 = 16 - }; - - static const short http_method_connect_code; - static const short http_method_delete_code; - static const short http_method_get_code; - static const short http_method_head_code; - static const short http_method_options_code; - static const short http_method_post_code; - static const short http_method_put_code; - static const short http_method_trace_code; - static const short http_method_unknown_code; - - static const int http_continue; - static const int http_switching_protocol; - static const int http_processing; - - static const int http_ok; - static const int http_created; - static const int http_accepted; - static const int http_non_authoritative_information; - static const int http_no_content; - static const int http_reset_content; - static const int http_partial_content; - static const int http_multi_status; - - static const int http_multiple_choices; - static const int http_moved_permanently; - static const int http_found; - static const int http_see_other; - static const int http_not_modified; - static const int http_use_proxy; - static const int http_switch_proxy; - static const int http_temporary_redirect; - - static const int http_bad_request; - static const int http_unauthorized; - static const int http_payment_required; - static const int http_forbidden; - static const int http_not_found; - static const int http_method_not_allowed; - static const int http_method_not_acceptable; - static const int http_proxy_authentication_required; - static const int http_request_timeout; - static const int http_conflict; - static const int http_gone; - static const int http_length_required; - static const int http_precondition_failed; - static const int http_request_entity_too_large; - static const int http_request_uri_too_long; - static const int http_unsupported_media_type; - static const int http_requested_range_not_satisfiable; - static const int http_expectation_failed; - static const int http_unprocessable_entity; - static const int http_locked; - static const int http_failed_dependency; - static const int http_unordered_collection; - static const int http_upgrade_required; - static const int http_retry_with; - - static const int http_internal_server_error; - static const int http_not_implemented; - static const int http_bad_gateway; - static const int http_service_unavailable; - static const int http_gateway_timeout; - static const int http_version_not_supported; - static const int http_variant_also_negotiated; - static const int http_insufficient_storage; - static const int http_bandwidth_limit_exceeded; - static const int http_not_extended; - - static const int shoutcast_response; - - /* See also: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html */ - static const std::string http_header_accept; - static const std::string http_header_accept_charset; - static const std::string http_header_accept_encoding; - static const std::string http_header_accept_language; - static const std::string http_header_accept_ranges; - static const std::string http_header_age; - static const std::string http_header_allow; - static const std::string http_header_authorization; - static const std::string http_header_cache_control; - static const std::string http_header_connection; - static const std::string http_header_content_encoding; - static const std::string http_header_content_language; - static const std::string http_header_content_length; - static const std::string http_header_content_location; - static const std::string http_header_content_md5; - static const std::string http_header_content_range; - static const std::string http_header_content_type; - static const std::string http_header_date; - static const std::string http_header_etag; - static const std::string http_header_expect; - static const std::string http_header_expires; - static const std::string http_header_from; - static const std::string http_header_host; - static const std::string http_header_if_match; - static const std::string http_header_if_modified_since; - static const std::string http_header_if_none_match; - static const std::string http_header_if_range; - static const std::string http_header_if_unmodified_since; - static const std::string http_header_last_modified; - static const std::string http_header_location; - static const std::string http_header_max_forwards; - static const std::string http_header_pragma; - static const std::string http_header_proxy_authenticate; - static const std::string http_header_proxy_authentication; - static const std::string http_header_range; - static const std::string http_header_referer; - static const std::string http_header_retry_after; - static const std::string http_header_server; - static const std::string http_header_te; - static const std::string http_header_trailer; - static const std::string http_header_transfer_encoding; - static const std::string http_header_upgrade; - static const std::string http_header_user_agent; - static const std::string http_header_vary; - static const std::string http_header_via; - static const std::string http_header_warning; - static const std::string http_header_www_authenticate; - - static const std::string http_version_1_0; - static const std::string http_version_1_1; - - static const std::string http_method_connect; - static const std::string http_method_delete; - static const std::string http_method_head; - static const std::string http_method_get; - static const std::string http_method_options; - static const std::string http_method_post; - static const std::string http_method_put; - static const std::string http_method_trace; - - static const std::string http_post_encoding_form_urlencoded; - static const std::string http_post_encoding_multipart_formdata; - static size_t tokenize_url(const std::string&, - std::vector& result, const char separator = '/' - ); - static void standardize_url(const std::string&, std::string& result); + }; + + enum start_method_T { + INTERNAL_SELECT = MHD_USE_SELECT_INTERNALLY | MHD_USE_AUTO, + THREAD_PER_CONNECTION = MHD_USE_THREAD_PER_CONNECTION | MHD_USE_AUTO + }; + + enum policy_T { + ACCEPT, + REJECT + }; + + enum IP_version_T { + IPV4 = 4, + IPV6 = 16 + }; + +#ifdef HAVE_DAUTH + enum class digest_algorithm { + MD5 = MHD_DIGEST_ALG_MD5, + SHA256 = MHD_DIGEST_ALG_SHA256 + }; + + static constexpr size_t md5_digest_size = 16; + static constexpr size_t sha256_digest_size = 32; +#endif // HAVE_DAUTH + + static const uint16_t http_method_connect_code; + static const uint16_t http_method_delete_code; + static const uint16_t http_method_get_code; + static const uint16_t http_method_head_code; + static const uint16_t http_method_options_code; + static const uint16_t http_method_post_code; + static const uint16_t http_method_put_code; + static const uint16_t http_method_trace_code; + static const uint16_t http_method_patch_code; + static const uint16_t http_method_unknown_code; + + static const int http_continue; + static const int http_switching_protocol; + static const int http_processing; + + static const int http_ok; + static const int http_created; + static const int http_accepted; + static const int http_non_authoritative_information; + static const int http_no_content; + static const int http_reset_content; + static const int http_partial_content; + static const int http_multi_status; + + static const int http_multiple_choices; + static const int http_moved_permanently; + static const int http_found; + static const int http_see_other; + static const int http_not_modified; + static const int http_use_proxy; + static const int http_switch_proxy; + static const int http_temporary_redirect; + + static const int http_bad_request; + static const int http_unauthorized; + static const int http_payment_required; + static const int http_forbidden; + static const int http_not_found; + static const int http_method_not_allowed; + static const int http_method_not_acceptable; + static const int http_proxy_authentication_required; + static const int http_request_timeout; + static const int http_conflict; + static const int http_gone; + static const int http_length_required; + static const int http_precondition_failed; + static const int http_request_entity_too_large; + static const int http_request_uri_too_long; + static const int http_unsupported_media_type; + static const int http_requested_range_not_satisfiable; + static const int http_expectation_failed; + static const int http_unprocessable_entity; + static const int http_locked; + static const int http_failed_dependency; + static const int http_upgrade_required; + static const int http_retry_with; + + static const int http_internal_server_error; + static const int http_not_implemented; + static const int http_bad_gateway; + static const int http_service_unavailable; + static const int http_gateway_timeout; + static const int http_version_not_supported; + static const int http_variant_also_negotiated; + static const int http_insufficient_storage; + static const int http_bandwidth_limit_exceeded; + static const int http_not_extended; + + static const int shoutcast_response; + + // See also: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + static const char* http_header_accept; + static const char* http_header_accept_charset; + static const char* http_header_accept_encoding; + static const char* http_header_accept_language; + static const char* http_header_accept_ranges; + static const char* http_header_age; + static const char* http_header_allow; + static const char* http_header_authorization; + static const char* http_header_cache_control; + static const char* http_header_connection; + static const char* http_header_content_encoding; + static const char* http_header_content_language; + static const char* http_header_content_length; + static const char* http_header_content_location; + static const char* http_header_content_md5; + static const char* http_header_content_range; + static const char* http_header_content_type; + static const char* http_header_date; + static const char* http_header_etag; + static const char* http_header_expect; + static const char* http_header_expires; + static const char* http_header_from; + static const char* http_header_host; + static const char* http_header_if_match; + static const char* http_header_if_modified_since; + static const char* http_header_if_none_match; + static const char* http_header_if_range; + static const char* http_header_if_unmodified_since; + static const char* http_header_last_modified; + static const char* http_header_location; + static const char* http_header_max_forwards; + static const char* http_header_pragma; + static const char* http_header_proxy_authenticate; + static const char* http_header_proxy_authentication; + static const char* http_header_range; + static const char* http_header_referer; + static const char* http_header_retry_after; + static const char* http_header_server; + static const char* http_header_te; + static const char* http_header_trailer; + static const char* http_header_transfer_encoding; + static const char* http_header_upgrade; + static const char* http_header_user_agent; + static const char* http_header_vary; + static const char* http_header_via; + static const char* http_header_warning; + static const char* http_header_www_authenticate; + + static const char* http_version_1_0; + static const char* http_version_1_1; + + static const char* http_method_connect; + static const char* http_method_delete; + static const char* http_method_head; + static const char* http_method_get; + static const char* http_method_options; + static const char* http_method_post; + static const char* http_method_put; + static const char* http_method_trace; + static const char* http_method_patch; + + static const char* http_post_encoding_form_urlencoded; + static const char* http_post_encoding_multipart_formdata; + + static const char* application_octet_stream; + static const char* text_plain; + + static const char* upload_filename_template; + static const char path_separator; + + static std::vector tokenize_url(const std::string&, const char separator = '/'); + static std::string standardize_url(const std::string&); + + static const std::string generate_random_upload_filename(const std::string& directory); + + static std::string sanitize_upload_filename(const std::string& filename); }; -#define COMPARATOR(x, y, op) \ - { \ - size_t l1 = (x).size();\ - size_t l2 = (y).size();\ - if (l1 < l2) return true;\ - if (l1 > l2) return false;\ - \ - for (size_t n = 0; n < l1; n++)\ - {\ - int xc = op((x)[n]);\ - int yc = op((y)[n]);\ - if (xc < yc) return true;\ - if (xc > yc) return false;\ - }\ - return false;\ - } +#define COMPARATOR(x, y, op) { \ + size_t l1 = (x).size(); \ + size_t l2 = (y).size(); \ + if (l1 < l2) return true; \ + if (l1 > l2) return false; \ + \ + for (size_t n = 0; n < l1; n++) { \ + int xc = op((x)[n]); \ + int yc = op((y)[n]); \ + if (xc < yc) return true; \ + if (xc > yc) return false; \ + } \ + return false; \ +} class header_comparator { - public: - /** - * Operator used to compare strings. - * @param first string - * @param second string - **/ - bool operator()(const std::string& x,const std::string& y) const - { - COMPARATOR(x, y, std::toupper); - } + public: + /** + * Operator used to compare strings. + * @param first string + * @param second string + **/ + bool operator()(std::string_view x, std::string_view y) const { + COMPARATOR(x, y, std::toupper); + } + bool operator()(const std::string& x, const std::string& y) const { + COMPARATOR(x, y, std::toupper); + } }; /** @@ -267,43 +312,56 @@ class header_comparator { * compilation phase the flag CASE_INSENSITIVE to the preprocessor. **/ class arg_comparator { - public: - /** - * Operator used to compare strings. - * @param first string - * @param second string - **/ - bool operator()(const std::string& x,const std::string& y) const - { + public: + using is_transparent = std::true_type; + /** + * Operator used to compare strings. + * @param first string + * @param second string + **/ + bool operator()(std::string_view x, std::string_view y) const { #ifdef CASE_INSENSITIVE - COMPARATOR(x, y, std::toupper); + COMPARATOR(x, y, std::toupper); #else - COMPARATOR(x, y, ); + COMPARATOR(x, y,); // NOLINT(whitespace/comma) #endif - } + } + bool operator()(const std::string& x, const std::string& y) const { + return operator()(std::string_view(x), std::string_view(y)); + } + bool operator()(std::string_view x, const std::string& y) const { + return operator()(x, std::string_view(y)); + } + bool operator()(const std::string& x, std::string_view y) const { + return operator()(std::string_view(x), std::string(y)); + } }; -struct ip_representation -{ +using header_map = std::map; +using header_view_map = std::map; +using arg_map = std::map; +using arg_view_map = std::map; + + +struct ip_representation { http_utils::IP_version_T ip_version; - unsigned short pieces[16]; - unsigned int mask:16; - - ip_representation(http_utils::IP_version_T ip_version) : - ip_version(ip_version) - { - mask = DEFAULT_MASK_VALUE; - std::fill(pieces, pieces + 16, 0); + uint16_t pieces[16]; + uint16_t mask; + + explicit ip_representation(http_utils::IP_version_T ip_version) : + ip_version(ip_version) { + mask = DEFAULT_MASK_VALUE; + std::fill(pieces, pieces + 16, 0); } - ip_representation(const std::string& ip); - ip_representation(const struct sockaddr* ip); + explicit ip_representation(const std::string& ip); + explicit ip_representation(const struct sockaddr* ip); - bool operator <(const ip_representation& b) const; - const int weight() const - { - //variable-precision SWAR algorithm - unsigned int x = mask; + bool operator<(const ip_representation& b) const; + + int weight() const { + // variable-precision SWAR algorithm + uint16_t x = mask; x = x - ((x >> 1) & 0x55555555); x = (x & 0x33333333) + ((x >> 2) & 0x33333333); return (((x + (x >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; @@ -316,19 +374,14 @@ struct ip_representation * @param maxlen Maxlen of the address (automatically discovered if not passed) * @return string containing the ip address **/ -void get_ip_str(const struct sockaddr *sa, - std::string& result, socklen_t maxlen = 0 -); +std::string get_ip_str(const struct sockaddr *sa); -std::string get_ip_str_new(const struct sockaddr* sa, - socklen_t maxlen = 0 -); /** * Method used to get a port from a sockaddr * @param sa The sockaddr object to find the port from * @return short representing the port **/ -short get_port(const struct sockaddr* sa); +uint16_t get_port(const struct sockaddr* sa); /** * Method to output the contents of a headers map to a std::ostream @@ -336,8 +389,7 @@ short get_port(const struct sockaddr* sa); * @param prefix Prefix to identify the map * @param map **/ -void dump_header_map(std::ostream &os, const std::string &prefix, - const std::map &map); +void dump_header_map(std::ostream& os, const std::string& prefix, const http::header_view_map& map); /** * Method to output the contents of an arguments map to a std::ostream @@ -345,8 +397,7 @@ void dump_header_map(std::ostream &os, const std::string &prefix, * @param prefix Prefix to identify the map * @param map **/ -void dump_arg_map(std::ostream &os, const std::string &prefix, - const std::map &map); +void dump_arg_map(std::ostream& os, const std::string& prefix, const http::arg_view_map& map); /** * Process escape sequences ('+'=space, %HH) Updates val in place; the @@ -357,66 +408,13 @@ void dump_arg_map(std::ostream &os, const std::string &prefix, * @return length of the resulting val (strlen(val) maybe * shorter afterwards due to elimination of escape sequences) */ -size_t http_unescape (char *val); - -char* load_file (const char *filename); - -size_t load_file (const char* filename, char** content); - -struct httpserver_ska -{ - httpserver_ska(struct sockaddr* addr): - addr(addr), - ip(get_ip_str_new(addr)), - port(get_port(addr)) - { - } - - httpserver_ska(): addr(0x0) { } - - httpserver_ska(const httpserver_ska& o): - addr(o.addr), - ip(o.ip), - port(o.port) - { - } - - bool operator<(const httpserver_ska& o) const - { - if(this->ip < o.ip) - return true; - else if(this->ip > o.ip) - return false; - else if(this->port < o.port) - return true; - else - return false; - } - - httpserver_ska& operator=(const httpserver_ska& o) - { - this->addr = o.addr; - this->ip = o.ip; - this->port = o.port; - return *this; - } - - httpserver_ska& operator=(struct sockaddr* addr) - { - this->addr = addr; - this->ip = get_ip_str_new(addr); - this->port = get_port(addr); - return *this; - } - - struct sockaddr* addr; - std::string ip; - int port; -}; +size_t http_unescape(std::string* val); +const std::string load_file(const std::string& filename); +size_t base_unescaper(std::string*, unescaper_ptr unescaper); -}; -}; -#endif +} // namespace http +} // namespace httpserver +#endif // SRC_HTTPSERVER_HTTP_UTILS_HPP_ diff --git a/src/httpserver/string_response.hpp b/src/httpserver/string_response.hpp new file mode 100644 index 00000000..d2bff4a8 --- /dev/null +++ b/src/httpserver/string_response.hpp @@ -0,0 +1,63 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION) +#error "Only or can be included directly." +#endif + +#ifndef SRC_HTTPSERVER_STRING_RESPONSE_HPP_ +#define SRC_HTTPSERVER_STRING_RESPONSE_HPP_ + +#include +#include +#include "httpserver/http_utils.hpp" +#include "httpserver/http_response.hpp" + +struct MHD_Response; + +namespace httpserver { + +class string_response : public http_response { + public: + string_response() = default; + + explicit string_response( + std::string content, + int response_code = http::http_utils::http_ok, + const std::string& content_type = http::http_utils::text_plain): + http_response(response_code, content_type), + content(std::move(content)) { } + + string_response(const string_response& other) = default; + string_response(string_response&& other) noexcept = default; + + string_response& operator=(const string_response& b) = default; + string_response& operator=(string_response&& b) = default; + + ~string_response() = default; + + MHD_Response* get_raw_response(); + + private: + std::string content = ""; +}; + +} // namespace httpserver +#endif // SRC_HTTPSERVER_STRING_RESPONSE_HPP_ diff --git a/src/httpserver/string_utilities.hpp b/src/httpserver/string_utilities.hpp index 655daaf7..bcb6897f 100644 --- a/src/httpserver/string_utilities.hpp +++ b/src/httpserver/string_utilities.hpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -22,16 +22,15 @@ #error "Only or can be included directly." #endif -#ifndef _STRING_UTILITIES_H_ -#define _STRING_UTILITIES_H_ +#ifndef SRC_HTTPSERVER_STRING_UTILITIES_HPP_ +#define SRC_HTTPSERVER_STRING_UTILITIES_HPP_ #include #include -namespace httpserver -{ -namespace string_utilities -{ +namespace httpserver { + +namespace string_utilities { /** * Function used to convert a string to its uppercase version. @@ -39,17 +38,26 @@ namespace string_utilities * @param str The string to turn uppercase * @return a string that is the uppercase version of the previous **/ -void to_upper_copy(const std::string& str, std::string& result); -void to_lower_copy(const std::string& str, std::string& result); -size_t string_split(const std::string& s, - std::vector& result, - char sep = ' ', bool collapse = true -); -void regex_replace(const std::string& str, const std::string& pattern, - const std::string& replace_str, std::string& result -); -void to_upper(std::string& str); -}; -}; +const std::string to_upper_copy(const std::string& str); +const std::string to_lower_copy(const std::string& str); +const std::vector string_split(const std::string& s, char sep = ' ', bool collapse = true); -#endif +/** + * Validate that a string contains only valid hexadecimal characters (0-9, a-f, A-F) + * @param s The string to validate + * @return true if string contains only valid hex characters, false otherwise + */ +bool is_valid_hex(const std::string& s); + +/** + * Convert a hex character to its numeric value (0-15) + * @param c The hex character to convert + * @return numeric value (0-15), or 0 for invalid characters + */ +unsigned char hex_char_to_val(char c); + +} // namespace string_utilities + +} // namespace httpserver + +#endif // SRC_HTTPSERVER_STRING_UTILITIES_HPP_ diff --git a/src/httpserver/webserver.hpp b/src/httpserver/webserver.hpp index 9a8e172f..66d81ddd 100644 --- a/src/httpserver/webserver.hpp +++ b/src/httpserver/webserver.hpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -22,343 +22,256 @@ #error "Only or can be included directly." #endif -#ifndef _FRAMEWORK_WEBSERVER_HPP_ -#define _FRAMEWORK_WEBSERVER_HPP_ +#ifndef SRC_HTTPSERVER_WEBSERVER_HPP_ +#define SRC_HTTPSERVER_WEBSERVER_HPP_ #define NOT_FOUND_ERROR "Not Found" #define METHOD_ERROR "Method not Allowed" #define NOT_METHOD_ERROR "Method not Acceptable" #define GENERIC_ERROR "Internal Error" -#include +#include +#include +#include +#include +#include + +#if !defined(__MINGW32__) +#include +#endif + +#include #include -#include +#include +#include #include +#include #include -#include -#include -#include -#include +#include +#include -#include +#ifdef HAVE_GNUTLS +#include +#endif // HAVE_GNUTLS +#include "httpserver/http_utils.hpp" #include "httpserver/create_webserver.hpp" +#include "httpserver/details/http_endpoint.hpp" -namespace httpserver { - -template class http_resource; -class http_response; -template class event_supplier; -class create_webserver; +namespace httpserver { class http_resource; } +namespace httpserver { class http_response; } +namespace httpserver { namespace details { struct modded_request; } } -namespace http { -struct ip_representation; -struct httpserver_ska; -}; +struct MHD_Connection; -namespace details { - class http_resource_mirror; - class event_tuple; - class http_endpoint; - struct daemon_item; - struct modded_request; - struct cache_entry; - class comet_manager; -} +namespace httpserver { /** * Class representing the webserver. Main class of the apis. **/ -class webserver -{ - public: - webserver(const create_webserver& params); - /** - * Destructor of the class - **/ - ~webserver(); - /** - * Method used to start the webserver. - * This method can be blocking or not. - * @param blocking param indicating if the method is blocking or not - * @return a boolean indicating if the webserver is running or not. - **/ - bool start(bool blocking = false); - /** - * Method used to stop the webserver. - * @return true if the webserver is stopped. - **/ - bool stop(); - /** - * Method used to evaluate if the server is running or not. - * @return true if the webserver is running - **/ - bool is_running(); - /** - * Method used to register a resource with the webserver. - * @param resource The url pointing to the resource. This url could be also parametrized in the form /path/to/url/{par1}/and/{par2} - * or a regular expression. - * @param http_resource http_resource pointer to register. - * @param family boolean indicating whether the resource is registered for the endpoint and its child or not. - * @return true if the resource was registered - **/ - template - bool register_resource(const std::string& resource, - http_resource* res, bool family = false - ) - { - details::http_resource_mirror hrm(res); - return register_resource(resource, hrm, family); - } - - void unregister_resource(const std::string& resource); - void ban_ip(const std::string& ip); - void allow_ip(const std::string& ip); - void unban_ip(const std::string& ip); - void disallow_ip(const std::string& ip); - - void send_message_to_topic(const std::string& topic, - const std::string& message - ); - void send_message_to_consumer(const http::httpserver_ska& connection_id, - const std::string& message, bool to_lock = true - ); - void register_to_topics(const std::vector& topics, - const http::httpserver_ska& connection_id, int keepalive_secs = -1, - std::string keepalive_msg = "" - ); - size_t read_message(const http::httpserver_ska& connection_id, - std::string& message - ); - size_t get_topic_consumers(const std::string& topic, - std::set& consumers - ); - bool pop_signaled(const http::httpserver_ska& consumer); - - http_response* get_from_cache(const std::string& key, bool* valid, - bool lock = false, bool write = false - ); - http_response* get_from_cache(const std::string& key, bool* valid, - details::cache_entry** ce, bool lock = false, bool write = false - ); - void lock_cache_element(details::cache_entry* ce, bool write = false); - void unlock_cache_element(details::cache_entry* ce); - details::cache_entry* put_in_cache(const std::string& key, http_response* value, - bool* new_elem, bool lock = false, - bool write = false, int validity = -1 - ); - void remove_from_cache(const std::string& key); - bool is_valid(const std::string& key); - void clean_cache(); - - const log_access_ptr get_access_logger() const - { - return this->log_access; - } - - const log_error_ptr get_error_logger() const - { - return this->log_error; - } - - const validator_ptr get_request_validator() const - { - return this->validator; - } - - const unescaper_ptr get_unescaper() const - { - return this->unescaper; - } - - template - void register_event_supplier(const std::string& id, - event_supplier* ev_supplier - ) - { - register_event_supplier(id, details::event_tuple(ev_supplier)); - } - - void remove_event_supplier(const std::string& id); - - /** - * Method used to kill the webserver waiting for it to terminate - **/ - void sweet_kill(); - - protected: - webserver& operator=(const webserver& other); - - private: - const uint16_t port; - http::http_utils::start_method_T start_method; - const int max_threads; - const int max_connections; - const int memory_limit; - const int connection_timeout; - const int per_IP_connection_limit; - log_access_ptr log_access; - log_error_ptr log_error; - validator_ptr validator; - unescaper_ptr unescaper; - const struct sockaddr* bind_address; - /* Changed type to MHD_socket because this type will always reflect the - platform's actual socket type (e.g. SOCKET on windows, int on unixes)*/ - MHD_socket bind_socket; - const int max_thread_stack_size; - const bool use_ssl; - const bool use_ipv6; - const bool debug; - const bool pedantic; - const std::string https_mem_key; - const std::string https_mem_cert; - const std::string https_mem_trust; - const std::string https_priorities; - const http::http_utils::cred_type_T cred_type; - const std::string digest_auth_random; - const int nonce_nc_size; - bool running; - const http::http_utils::policy_T default_policy; - const bool basic_auth_enabled; - const bool digest_auth_enabled; - const bool regex_checking; - const bool ban_system_enabled; - const bool post_process_enabled; - bool single_resource; - pthread_mutex_t mutexwait; - pthread_rwlock_t runguard; - pthread_cond_t mutexcond; - render_ptr not_found_resource; - render_ptr method_not_allowed_resource; - render_ptr method_not_acceptable_resource; - render_ptr internal_error_resource; - std::map registered_resources; - std::map registered_resources_str; - - std::map response_cache; - int next_to_choose; - pthread_rwlock_t cache_guard; - std::set bans; - std::set allowances; - - std::vector daemons; - std::vector threads; - - std::map event_suppliers; - - details::comet_manager* internal_comet_manager; - - static void* select(void* self); - static void* cleaner(void* self); - - bool register_resource(const std::string& resource, - details::http_resource_mirror hrm, bool family = false - ); - - void method_not_allowed_page(http_response** dhrs, - details::modded_request* mr - ); - void internal_error_page(http_response** dhrs, - details::modded_request* mr, bool force_our = false - ); - void not_found_page(http_response** dhrs, details::modded_request* mr); - - static int method_not_acceptable_page - ( - const void *cls, - struct MHD_Connection *connection - ); - static void request_completed(void *cls, - struct MHD_Connection *connection, void **con_cls, - enum MHD_RequestTerminationCode toe - ); - static int build_request_header (void *cls, enum MHD_ValueKind kind, - const char *key, const char *value - ); - static int build_request_footer (void *cls, enum MHD_ValueKind kind, - const char *key, const char *value - ); - static int build_request_cookie (void *cls, enum MHD_ValueKind kind, - const char *key, const char *value - ); - static int build_request_args (void *cls, enum MHD_ValueKind kind, - const char *key, const char *value - ); - static int answer_to_connection - ( - void* cls, MHD_Connection* connection, - const char* url, const char* method, - const char* version, const char* upload_data, - size_t* upload_data_size, void** con_cls - ); - static int post_iterator - ( - void *cls, - enum MHD_ValueKind kind, - const char *key, - const char *filename, - const char *content_type, - const char *transfer_encoding, - const char *data, uint64_t off, size_t size - ); - static void upgrade_handler - ( - void *cls, - struct MHD_Connection* connection, - void **con_cls, int upgrade_socket - ); - - static void unlock_cache_entry(details::cache_entry*); - static void lock_cache_entry(details::cache_entry*); - static void get_response(details::cache_entry*, http_response** res); - - int bodyless_requests_answer(MHD_Connection* connection, - const char* method, const char* version, - struct details::modded_request* mr - ); - - int bodyfull_requests_answer_first_step(MHD_Connection* connection, - struct details::modded_request* mr - ); - - int bodyfull_requests_answer_second_step(MHD_Connection* connection, - const char* method, const char* version, const char* upload_data, - size_t* upload_data_size, struct details::modded_request* mr - ); - - void end_request_construction(MHD_Connection* connection, - struct details::modded_request* mr, const char* version, - const char* method, char* user, char* pass, char* digested_user - ); - - int finalize_answer(MHD_Connection* connection, - struct details::modded_request* mr, const char* method - ); - - int complete_request(MHD_Connection* connection, - struct details::modded_request* mr, - const char* version, const char* method - ); - - void register_event_supplier(const std::string& id, const details::event_tuple& evt); - - bool use_internal_select() - { - return this->start_method == http::http_utils::INTERNAL_SELECT; - } - - friend int policy_callback (void *cls, - const struct sockaddr* addr, socklen_t addrlen - ); - friend void error_log(void* cls, const char* fmt, va_list ap); - friend void access_log(webserver* cls, std::string uri); - friend void* uri_log(void* cls, const char* uri); - friend size_t unescaper_func(void * cls, - struct MHD_Connection *c, char *s - ); - friend size_t internal_unescaper(void * cls, char *s); - friend class http_response; +class webserver { + public: + // Keeping this non explicit on purpose to easy construction through builder class. + webserver(const create_webserver& params); // NOLINT(runtime/explicit) + /** + * Destructor of the class + **/ + ~webserver(); + /** + * Method used to start the webserver. + * This method can be blocking or not. + * @param blocking param indicating if the method is blocking or not + * @return a boolean indicating if the webserver is running or not. + **/ + bool start(bool blocking = false); + /** + * Method used to stop the webserver. + * @return true if the webserver is stopped. + **/ + bool stop(); + /** + * Method used to evaluate if the server is running or not. + * @return true if the webserver is running + **/ + bool is_running(); + /** + * Method used to register a resource with the webserver. + * @param resource The url pointing to the resource. This url could be also parametrized in the form /path/to/url/{par1}/and/{par2} + * or a regular expression. + * @param http_resource http_resource pointer to register. + * @param family boolean indicating whether the resource is registered for the endpoint and its child or not. + * @return true if the resource was registered + **/ + bool register_resource(const std::string& resource, http_resource* res, bool family = false); + + void unregister_resource(const std::string& resource); + void ban_ip(const std::string& ip); + void allow_ip(const std::string& ip); + void unban_ip(const std::string& ip); + void disallow_ip(const std::string& ip); + + log_access_ptr get_access_logger() const { + return log_access; + } + + log_error_ptr get_error_logger() const { + return log_error; + } + + validator_ptr get_request_validator() const { + return validator; + } + + unescaper_ptr get_unescaper() const { + return unescaper; + } + + /** + * Method used to kill the webserver waiting for it to terminate + **/ + void sweet_kill(); + + protected: + webserver& operator=(const webserver& other); + + private: + const uint16_t port; + http::http_utils::start_method_T start_method; + const int max_threads; + const int max_connections; + const int memory_limit; + const size_t content_size_limit; + const int connection_timeout; + const int per_IP_connection_limit; + log_access_ptr log_access; + log_error_ptr log_error; + validator_ptr validator; + unescaper_ptr unescaper; + const struct sockaddr* bind_address; + std::shared_ptr bind_address_storage; + /* Changed type to MHD_socket because this type will always reflect the + platform's actual socket type (e.g. SOCKET on windows, int on unixes)*/ + MHD_socket bind_socket; + const int max_thread_stack_size; + const bool use_ssl; + const bool use_ipv6; + const bool use_dual_stack; + const bool debug; + const bool pedantic; + const std::string https_mem_key; + const std::string https_mem_cert; + const std::string https_mem_trust; + const std::string https_priorities; + const http::http_utils::cred_type_T cred_type; + const psk_cred_handler_callback psk_cred_handler; + const std::string digest_auth_random; + const int nonce_nc_size; + bool running; + const http::http_utils::policy_T default_policy; +#ifdef HAVE_BAUTH + const bool basic_auth_enabled; +#endif // HAVE_BAUTH + const bool digest_auth_enabled; + const bool regex_checking; + const bool ban_system_enabled; + const bool post_process_enabled; + const bool put_processed_data_to_content; + const file_upload_target_T file_upload_target; + const std::string file_upload_dir; + const bool generate_random_filename_on_upload; + const bool deferred_enabled; + const bool single_resource; + const bool tcp_nodelay; + pthread_mutex_t mutexwait; + pthread_cond_t mutexcond; + const render_ptr not_found_resource; + const render_ptr method_not_allowed_resource; + const render_ptr internal_error_resource; + const file_cleanup_callback_ptr file_cleanup_callback; + const auth_handler_ptr auth_handler; + const std::vector auth_skip_paths; + const sni_callback_t sni_callback; + std::shared_mutex registered_resources_mutex; + std::map registered_resources; + std::map registered_resources_str; + std::map registered_resources_regex; + + struct route_cache_entry { + details::http_endpoint matched_endpoint; + http_resource* resource; + }; + static constexpr size_t ROUTE_CACHE_MAX_SIZE = 256; + std::mutex route_cache_mutex; + std::list> route_cache_list; + std::unordered_map>::iterator> route_cache_map; + + std::shared_mutex bans_mutex; + std::set bans; + + std::shared_mutex allowances_mutex; + std::set allowances; + + struct MHD_Daemon* daemon; + + std::shared_ptr method_not_allowed_page(details::modded_request* mr) const; + std::shared_ptr internal_error_page(details::modded_request* mr, bool force_our = false) const; + std::shared_ptr not_found_page(details::modded_request* mr) const; + bool should_skip_auth(const std::string& path) const; + + static void request_completed(void *cls, + struct MHD_Connection *connection, void **con_cls, + enum MHD_RequestTerminationCode toe); + + static MHD_Result answer_to_connection(void* cls, MHD_Connection* connection, const char* url, + const char* method, const char* version, const char* upload_data, + size_t* upload_data_size, void** con_cls); + + static MHD_Result post_iterator(void *cls, enum MHD_ValueKind kind, const char *key, + const char *filename, const char *content_type, const char *transfer_encoding, + const char *data, uint64_t off, size_t size); + + static void upgrade_handler(void *cls, struct MHD_Connection* connection, void **con_cls, int upgrade_socket); + + MHD_Result requests_answer_first_step(MHD_Connection* connection, struct details::modded_request* mr); + + MHD_Result requests_answer_second_step(MHD_Connection* connection, + const char* method, const char* version, const char* upload_data, + size_t* upload_data_size, struct details::modded_request* mr); + + MHD_Result finalize_answer(MHD_Connection* connection, struct details::modded_request* mr, const char* method); + + MHD_Result complete_request(MHD_Connection* connection, struct details::modded_request* mr, const char* version, const char* method); + + void invalidate_route_cache(); + +#ifdef HAVE_GNUTLS + // MHD_PskServerCredentialsCallback signature + static int psk_cred_handler_func(void* cls, + struct MHD_Connection* connection, + const char* username, + void** psk, + size_t* psk_size); + +#ifdef MHD_OPTION_HTTPS_CERT_CALLBACK + // SNI certificate callback function (libmicrohttpd 0.9.71+) + static int sni_cert_callback_func(void* cls, + struct MHD_Connection* connection, + const char* server_name, + gnutls_certificate_credentials_t* creds); + + // Cache for loaded credentials per server name + mutable std::map sni_credentials_cache; + mutable std::shared_mutex sni_credentials_mutex; +#endif // MHD_OPTION_HTTPS_CERT_CALLBACK +#endif // HAVE_GNUTLS + + friend MHD_Result policy_callback(void *cls, const struct sockaddr* addr, socklen_t addrlen); + friend void error_log(void* cls, const char* fmt, va_list ap); + friend void access_log(webserver* cls, std::string uri); + friend void* uri_log(void* cls, const char* uri); + friend size_t unescaper_func(void * cls, struct MHD_Connection *c, char *s); + friend class http_response; }; -}; -#endif //_FRAMEWORK_WEBSERVER_HPP__ +} // namespace httpserver +#endif // SRC_HTTPSERVER_WEBSERVER_HPP_ diff --git a/src/string_response.cpp b/src/string_response.cpp new file mode 100644 index 00000000..df611fda --- /dev/null +++ b/src/string_response.cpp @@ -0,0 +1,36 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include "httpserver/string_response.hpp" +#include +#include +#include + +struct MHD_Response; + +namespace httpserver { + +MHD_Response* string_response::get_raw_response() { + size_t size = &(*content.end()) - &(*content.begin()); + // Need to use a const cast here to satisfy MHD interface that requires a void* + return MHD_create_response_from_buffer(size, reinterpret_cast(const_cast(content.c_str())), MHD_RESPMEM_PERSISTENT); +} + +} // namespace httpserver diff --git a/src/string_utilities.cpp b/src/string_utilities.cpp index eaa321ea..697fbf08 100644 --- a/src/string_utilities.cpp +++ b/src/string_utilities.cpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -18,102 +18,75 @@ USA */ +#include "httpserver/string_utilities.hpp" + #include +#include #include -#include -#include +#include #include -#include -#include -#include -#include -#include "string_utilities.hpp" - -namespace httpserver -{ -namespace string_utilities -{ - -void to_upper_copy(const std::string& str, std::string& result) -{ - result = str; - std::transform(result.begin(), - result.end(), - result.begin(), - (int(*)(int)) std::toupper - ); -} -void to_upper(std::string& str) -{ - std::transform(str.begin(), - str.end(), - str.begin(), - (int(*)(int)) std::toupper - ); +namespace httpserver { +namespace string_utilities { + +const std::string to_upper_copy(const std::string& str) { + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), (int(*)(int)) std::toupper); + + return result; } -void to_lower_copy(const std::string& str, std::string& result) -{ - result = str; - std::transform(result.begin(), - result.end(), - result.begin(), - (int(*)(int)) std::tolower - ); +const std::string to_lower_copy(const std::string& str) { + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), (int(*)(int)) std::tolower); + + return result; } -size_t string_split( - const std::string& s, - std::vector& result, - char sep, - bool collapse -) -{ - std::istringstream buf(s); - for(std::string token; getline(buf, token, sep); ) - { - if((collapse && token != "") || !collapse) - result.push_back(token); +const std::vector string_split(const std::string& s, char sep, bool collapse) { + std::vector result; + if (s.empty()) return result; + + std::string::size_type start = 0; + std::string::size_type end; + + while ((end = s.find(sep, start)) != std::string::npos) { + std::string token = s.substr(start, end - start); + if (!collapse || !token.empty()) { + result.push_back(std::move(token)); + } + start = end + 1; + } + + // Handle the last token (after the final separator) + // Only add if there's content or if not collapsing + // Note: match istringstream behavior which does not emit trailing empty token + if (start < s.size()) { + std::string token = s.substr(start); + if (!collapse || !token.empty()) { + result.push_back(std::move(token)); + } } - return result.size(); + + return result; } -void regex_replace(const std::string& str, - const std::string& pattern, - const std::string& replace_str, - std::string& result -) -{ - regex_t preg; - regmatch_t substmatch[1]; - regcomp(&preg, pattern.c_str(), REG_EXTENDED|REG_ICASE); - if ( regexec(&preg, str.c_str(), 1, substmatch, 0) == 0 ) - { - char ns[substmatch[0].rm_so + 1 + - replace_str.size() + (str.size() - substmatch[0].rm_eo) + 2 - ]; - - memcpy(ns, str.c_str(), substmatch[0].rm_so+1); - - memcpy(&ns[substmatch[0].rm_so], - replace_str.c_str(), - replace_str.size() - ); - - memcpy(&ns[substmatch[0].rm_so+replace_str.size()], - &str[substmatch[0].rm_eo], strlen(&str[substmatch[0].rm_eo]) - ); - - ns[substmatch[0].rm_so + - replace_str.size() + - strlen(&str[substmatch[0].rm_eo]) - ] = 0; - - result = std::string((char*)ns); +bool is_valid_hex(const std::string& s) { + for (char c : s) { + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'))) { + return false; + } } - regfree(&preg); + return true; +} + +unsigned char hex_char_to_val(char c) { + if (c >= '0' && c <= '9') return static_cast(c - '0'); + if (c >= 'a' && c <= 'f') return static_cast(c - 'a' + 10); + if (c >= 'A' && c <= 'F') return static_cast(c - 'A' + 10); + return 0; } -}; -}; +} // namespace string_utilities +} // namespace httpserver diff --git a/src/webserver.cpp b/src/webserver.cpp index d1a3c930..971d3d5a 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -18,146 +18,121 @@ USA */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "httpserver/webserver.hpp" -#if defined(__MINGW32__) || defined(__CYGWIN32__) +#if defined(_WIN32) && !defined(__CYGWIN__) #include +#include #define _WINDOWS #else -#include +#if defined(__CYGWIN__) +#include +#endif +#include +#include #endif +#include +#include #include -#include +#include +#include +#include +#include #include - -#include - -#include "gettext.h" -#include "http_utils.hpp" -#include "http_resource.hpp" -#include "http_response.hpp" -#include "http_request.hpp" -#include "http_response_builder.hpp" -#include "details/http_endpoint.hpp" -#include "string_utilities.hpp" -#include "details/http_resource_mirror.hpp" -#include "details/event_tuple.hpp" -#include "create_webserver.hpp" -#include "details/comet_manager.hpp" -#include "webserver.hpp" -#include "details/modded_request.hpp" -#include "details/cache_entry.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "httpserver/create_webserver.hpp" +#include "httpserver/details/http_endpoint.hpp" +#include "httpserver/details/modded_request.hpp" +#include "httpserver/http_request.hpp" +#include "httpserver/http_resource.hpp" +#include "httpserver/http_response.hpp" +#include "httpserver/http_utils.hpp" +#include "httpserver/string_utilities.hpp" +#include "httpserver/string_response.hpp" + +struct MHD_Connection; #define _REENTRANT 1 +#ifdef HAVE_GNUTLS +#include +#include +#endif // HAVE_GNUTLS + #ifndef SOCK_CLOEXEC #define SOCK_CLOEXEC 02000000 #endif -using namespace std; - -namespace httpserver -{ - -namespace details -{ - -struct daemon_item -{ - webserver* ws; - struct MHD_Daemon* daemon; - daemon_item(webserver* ws, struct MHD_Daemon* daemon): - ws(ws), - daemon(daemon) - { - } - ~daemon_item() - { - MHD_stop_daemon (this->daemon); - } -}; - -void empty_render(const http_request& r, http_response** res) -{ - *res = new http_response(http_response_builder("", 200).string_response()); -} - -void empty_not_acceptable_render(const http_request& r, http_response** res) -{ - *res = new http_response(http_response_builder(NOT_METHOD_ERROR, 200).string_response()); -} +#if MHD_VERSION < 0x00097002 +typedef int MHD_Result; +#endif -bool empty_is_allowed(const std::string& method) -{ - return true; -} +using std::string; +using std::pair; +using std::vector; +using std::map; +using std::set; -} +using httpserver::http::http_utils; +using httpserver::http::ip_representation; +using httpserver::http::base_unescaper; -using namespace http; +namespace httpserver { -int policy_callback (void *, const struct sockaddr*, socklen_t); +MHD_Result policy_callback(void *, const struct sockaddr*, socklen_t); void error_log(void*, const char*, va_list); -void* uri_log(void*, const char*); +void* uri_log(void*, const char*, struct MHD_Connection *con); void access_log(webserver*, string); size_t unescaper_func(void*, struct MHD_Connection*, char*); -size_t internal_unescaper(void*, char*); -struct compare_value -{ - bool operator() (const std::pair& left, - const std::pair& right - ) const - { +struct compare_value { + bool operator() (const std::pair& left, const std::pair& right) const { return left.second < right.second; } }; -#ifndef __MINGW32__ -static void catcher (int sig) -{ -} +#if !defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__) +static void catcher(int) { } #endif -static void ignore_sigpipe () -{ -//Mingw doesn't implement SIGPIPE -#ifndef __MINGW32__ +static void ignore_sigpipe() { +// Mingw doesn't implement SIGPIPE +#if !defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__) struct sigaction oldsig; struct sigaction sig; sig.sa_handler = &catcher; - sigemptyset (&sig.sa_mask); + sigemptyset(&sig.sa_mask); #ifdef SA_INTERRUPT sig.sa_flags = SA_INTERRUPT; /* SunOS */ -#else //SA_INTERRUPT +#else // SA_INTERRUPT sig.sa_flags = SA_RESTART; -#endif //SA_INTERRUPTT - if (0 != sigaction (SIGPIPE, &sig, &oldsig)) - fprintf (stderr, - gettext("Failed to install SIGPIPE handler: %s\n"), - strerror (errno) - ); +#endif // SA_INTERRUPTT + if (0 != sigaction(SIGPIPE, &sig, &oldsig)) { + fprintf(stderr, "Failed to install SIGPIPE handler: %s\n", strerror(errno)); + } #endif } -//WEBSERVER +// WEBSERVER webserver::webserver(const create_webserver& params): port(params._port), start_method(params._start_method), max_threads(params._max_threads), max_connections(params._max_connections), memory_limit(params._memory_limit), + content_size_limit(params._content_size_limit), connection_timeout(params._connection_timeout), per_IP_connection_limit(params._per_IP_connection_limit), log_access(params._log_access), @@ -165,10 +140,12 @@ webserver::webserver(const create_webserver& params): validator(params._validator), unescaper(params._unescaper), bind_address(params._bind_address), + bind_address_storage(params._bind_address_storage), bind_socket(params._bind_socket), max_thread_stack_size(params._max_thread_stack_size), use_ssl(params._use_ssl), use_ipv6(params._use_ipv6), + use_dual_stack(params._use_dual_stack), debug(params._debug), pedantic(params._pedantic), https_mem_key(params._https_mem_key), @@ -176,1385 +153,957 @@ webserver::webserver(const create_webserver& params): https_mem_trust(params._https_mem_trust), https_priorities(params._https_priorities), cred_type(params._cred_type), + psk_cred_handler(params._psk_cred_handler), digest_auth_random(params._digest_auth_random), nonce_nc_size(params._nonce_nc_size), running(false), default_policy(params._default_policy), +#ifdef HAVE_BAUTH basic_auth_enabled(params._basic_auth_enabled), +#endif // HAVE_BAUTH digest_auth_enabled(params._digest_auth_enabled), regex_checking(params._regex_checking), ban_system_enabled(params._ban_system_enabled), post_process_enabled(params._post_process_enabled), + put_processed_data_to_content(params._put_processed_data_to_content), + file_upload_target(params._file_upload_target), + file_upload_dir(params._file_upload_dir), + generate_random_filename_on_upload(params._generate_random_filename_on_upload), + deferred_enabled(params._deferred_enabled), single_resource(params._single_resource), + tcp_nodelay(params._tcp_nodelay), not_found_resource(params._not_found_resource), method_not_allowed_resource(params._method_not_allowed_resource), - method_not_acceptable_resource(params._method_not_acceptable_resource), internal_error_resource(params._internal_error_resource), - next_to_choose(0), - internal_comet_manager(new details::comet_manager()) -{ - if(single_resource != 0x0) - this->single_resource = true; - else - this->single_resource = false; - ignore_sigpipe(); - pthread_mutex_init(&mutexwait, NULL); - pthread_rwlock_init(&runguard, NULL); - pthread_cond_init(&mutexcond, NULL); - pthread_rwlock_init(&cache_guard, NULL); + file_cleanup_callback(params._file_cleanup_callback), + auth_handler(params._auth_handler), + auth_skip_paths(params._auth_skip_paths), + sni_callback(params._sni_callback) { + ignore_sigpipe(); + pthread_mutex_init(&mutexwait, nullptr); + pthread_cond_init(&mutexcond, nullptr); } -webserver::~webserver() -{ - this->stop(); +webserver::~webserver() { + stop(); pthread_mutex_destroy(&mutexwait); - pthread_rwlock_destroy(&runguard); - pthread_rwlock_destroy(&cache_guard); pthread_cond_destroy(&mutexcond); - delete internal_comet_manager; -} -void webserver::sweet_kill() -{ - this->stop(); +#if defined(HAVE_GNUTLS) && defined(MHD_OPTION_HTTPS_CERT_CALLBACK) + // Clean up cached SNI credentials + for (auto& [name, creds] : sni_credentials_cache) { + gnutls_certificate_free_credentials(creds); + } + sni_credentials_cache.clear(); +#endif // HAVE_GNUTLS && MHD_OPTION_HTTPS_CERT_CALLBACK } -void webserver::request_completed ( - void *cls, - struct MHD_Connection *connection, - void **con_cls, - enum MHD_RequestTerminationCode toe -) -{ - details::modded_request* mr = static_cast(*con_cls); - if (mr != 0x0) - { - if(mr->ws != 0x0) - { - mr->ws->internal_comet_manager->complete_request(mr->dhrs->connection_id); - } - if(mr->dhrs.res != 0x0 && mr->dhrs->ca != 0x0) - mr->dhrs->ca(mr->dhrs->closure_data); - delete mr; - mr = 0x0; - } +void webserver::sweet_kill() { + stop(); } -bool webserver::register_resource( - const std::string& resource, - details::http_resource_mirror hrm, - bool family -) -{ - if(method_not_acceptable_resource) - hrm.method_not_acceptable_resource = method_not_acceptable_resource; +void webserver::request_completed(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) { + // These parameters are passed to respect the MHD interface, but are not needed here. + std::ignore = cls; + std::ignore = connection; + std::ignore = toe; - details::http_endpoint idx(resource, family, true, regex_checking); + delete static_cast(*con_cls); +} - pair::iterator, bool> result = registered_resources.insert( - map::value_type(idx, hrm) - ); +bool webserver::register_resource(const std::string& resource, http_resource* hrm, bool family) { + if (hrm == nullptr) { + throw std::invalid_argument("The http_resource pointer cannot be null"); + } - if(result.second) - { - registered_resources_str.insert( - pair(idx.get_url_complete(), &(result.first->second)) - ); + if (single_resource && ((resource != "" && resource != "/") || !family)) { + throw std::invalid_argument("The resource should be '' or '/' and be marked as family when using a single_resource server"); } - return result.second; -} + details::http_endpoint idx(resource, family, true, regex_checking); -void* webserver::select(void* self) -{ - fd_set rs; - fd_set ws; - fd_set es; - struct timeval timeout_value; - details::daemon_item* di = static_cast(self); - MHD_socket max; - while (di->ws->is_running()) - { - max = 0; - FD_ZERO (&rs); - FD_ZERO (&ws); - FD_ZERO (&es); - if (MHD_YES != MHD_get_fdset (di->daemon, &rs, &ws, &es, &max)) - abort(); /* fatal internal error */ - - unsigned long long timeout_microsecs = 0; - unsigned long long timeout_secs = 0; - - if (!(MHD_get_timeout (di->daemon, &timeout_microsecs) == MHD_YES)) - { - timeout_secs = 1; - timeout_microsecs = 0; - } - else - { - if(timeout_microsecs < 1000) - { - timeout_microsecs = timeout_microsecs * 1000; - timeout_secs = 0; - } - } + std::unique_lock registered_resources_lock(registered_resources_mutex); + pair::iterator, bool> result = registered_resources.insert(map::value_type(idx, hrm)); - // SUPPLIERS MANAGEMENT - { - std::map::const_iterator it; - pthread_rwlock_rdlock(&di->ws->runguard); - for(it = di->ws->event_suppliers.begin(); - it != di->ws->event_suppliers.end(); - ++it - ) - { - MHD_socket local_max; - (*it).second.supply_events(&rs, &ws, &es, &local_max); - - if(local_max > max) - max = local_max; - - struct timeval t = (*it).second.get_timeout(); - if((unsigned MHD_LONG_LONG) t.tv_sec < timeout_secs || - ((unsigned MHD_LONG_LONG) t.tv_sec == timeout_secs - && (unsigned MHD_LONG_LONG) t.tv_usec < timeout_microsecs - ) - ) - { - timeout_secs = t.tv_sec; - timeout_microsecs = t.tv_usec; - } - } - pthread_rwlock_unlock(&di->ws->runguard); + if (result.second) { + bool is_exact = !family && idx.get_url_pars().empty(); + if (is_exact) { + registered_resources_str.insert(pair(idx.get_url_complete(), result.first->second)); } - - // COMET CONNECTIONS MANAGEMENT - di->ws->internal_comet_manager->comet_select(&timeout_secs, &timeout_microsecs, di->ws->start_method); - - timeout_value.tv_sec = timeout_secs; - timeout_value.tv_usec = timeout_microsecs; - - /*On unix, MHD_socket will be an int anyway. - On windows, the cast is safe because winsock ignores first argument to select*/ - ::select ((int) max + 1, &rs, &ws, &es, &timeout_value); - MHD_run (di->daemon); - - //EVENT SUPPLIERS DISPATCHING - { - std::map::const_iterator it; - pthread_rwlock_rdlock(&di->ws->runguard); - for(it = di->ws->event_suppliers.begin(); - it != di->ws->event_suppliers.end(); - ++it - ) - (*it).second.dispatch_events(); + if (idx.is_regex_compiled()) { + registered_resources_regex.insert(map::value_type(idx, hrm)); } + registered_resources_lock.unlock(); + invalidate_route_cache(); + return true; } - return 0x0; -} - -MHD_socket create_socket (int domain, int type, int protocol) -{ - int sock_cloexec = SOCK_CLOEXEC; - int ctype = SOCK_STREAM | sock_cloexec; - - /* use SOCK_STREAM rather than ai_socktype: some getaddrinfo - * implementations do not set ai_socktype, e.g. RHL6.2. */ - MHD_socket fd = socket(domain, ctype, protocol); -#ifdef _WINDOWS - if (fd == INVALID_SOCKET) -#else - if ((fd == -1) && - (errno == EINVAL || errno == EPROTONOSUPPORT) && (sock_cloexec != 0) - ) -#endif - { - fd = socket(domain, type, protocol); - } - return fd; + return false; } -bool webserver::start(bool blocking) -{ - +bool webserver::start(bool blocking) { struct { - MHD_OptionItem operator ()( - enum MHD_OPTION opt, - intptr_t val, - void *ptr = 0 - ) - { + MHD_OptionItem operator ()(enum MHD_OPTION opt, intptr_t val, void *ptr = nullptr) { MHD_OptionItem x = {opt, val, ptr}; return x; } } gen; vector iov; - iov.push_back(gen(MHD_OPTION_NOTIFY_COMPLETED, - (intptr_t) &request_completed, - NULL - )); + iov.push_back(gen(MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) &request_completed, nullptr)); iov.push_back(gen(MHD_OPTION_URI_LOG_CALLBACK, (intptr_t) &uri_log, this)); iov.push_back(gen(MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) &error_log, this)); - iov.push_back(gen(MHD_OPTION_UNESCAPE_CALLBACK, - (intptr_t) &unescaper_func, - this) - ); + iov.push_back(gen(MHD_OPTION_UNESCAPE_CALLBACK, (intptr_t) &unescaper_func, this)); iov.push_back(gen(MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout)); - if(bind_address != 0x0) - iov.push_back(gen(MHD_OPTION_SOCK_ADDR, (intptr_t) bind_address)); - if(bind_socket != 0) + if (bind_socket != 0) { iov.push_back(gen(MHD_OPTION_LISTEN_SOCKET, bind_socket)); - if(! (start_method == http_utils::INTERNAL_SELECT)) - { - if(max_threads != 0) - iov.push_back(gen(MHD_OPTION_THREAD_POOL_SIZE, max_threads)); } - if(max_connections != 0) + + if (start_method == http_utils::THREAD_PER_CONNECTION && (max_threads != 0 || max_thread_stack_size != 0)) { + throw std::invalid_argument("Cannot specify maximum number of threads when using a thread per connection"); + } + + if (max_threads != 0) { + iov.push_back(gen(MHD_OPTION_THREAD_POOL_SIZE, max_threads)); + } + + if (max_connections != 0) { iov.push_back(gen(MHD_OPTION_CONNECTION_LIMIT, max_connections)); - if(memory_limit != 0) + } + + if (memory_limit != 0) { iov.push_back(gen(MHD_OPTION_CONNECTION_MEMORY_LIMIT, memory_limit)); - if(per_IP_connection_limit != 0) - iov.push_back(gen(MHD_OPTION_PER_IP_CONNECTION_LIMIT, - per_IP_connection_limit) - ); - if(max_thread_stack_size != 0) + } + + if (per_IP_connection_limit != 0) { + iov.push_back(gen(MHD_OPTION_PER_IP_CONNECTION_LIMIT, per_IP_connection_limit)); + } + + if (max_thread_stack_size != 0) { iov.push_back(gen(MHD_OPTION_THREAD_STACK_SIZE, max_thread_stack_size)); - if(nonce_nc_size != 0) + } + +#ifdef HAVE_DAUTH + if (nonce_nc_size != 0) { iov.push_back(gen(MHD_OPTION_NONCE_NC_SIZE, nonce_nc_size)); - if(use_ssl) - iov.push_back(gen(MHD_OPTION_HTTPS_MEM_KEY, - 0, - (void*)https_mem_key.c_str()) - ); - if(use_ssl) - iov.push_back(gen(MHD_OPTION_HTTPS_MEM_CERT, - 0, - (void*)https_mem_cert.c_str()) - ); - if(https_mem_trust != "" && use_ssl) - iov.push_back(gen(MHD_OPTION_HTTPS_MEM_TRUST, - 0, - (void*)https_mem_trust.c_str()) - ); - if(https_priorities != "" && use_ssl) - iov.push_back(gen(MHD_OPTION_HTTPS_PRIORITIES, - 0, - (void*)https_priorities.c_str()) - ); - if(digest_auth_random != "") - iov.push_back(gen(MHD_OPTION_DIGEST_AUTH_RANDOM, - digest_auth_random.size(), - (char*)digest_auth_random.c_str()) - ); + } +#endif // HAVE_DAUTH + + if (use_ssl) { + // Need for const_cast to respect MHD interface that needs a void* + iov.push_back(gen(MHD_OPTION_HTTPS_MEM_KEY, 0, reinterpret_cast(const_cast(https_mem_key.c_str())))); + } + + if (use_ssl) { + // Need for const_cast to respect MHD interface that needs a void* + iov.push_back(gen(MHD_OPTION_HTTPS_MEM_CERT, 0, reinterpret_cast(const_cast(https_mem_cert.c_str())))); + } + + if (https_mem_trust != "" && use_ssl) { + // Need for const_cast to respect MHD interface that needs a void* + iov.push_back(gen(MHD_OPTION_HTTPS_MEM_TRUST, 0, reinterpret_cast(const_cast(https_mem_trust.c_str())))); + } + + if (https_priorities != "" && use_ssl) { + // Need for const_cast to respect MHD interface that needs a void* + iov.push_back(gen(MHD_OPTION_HTTPS_PRIORITIES, 0, reinterpret_cast(const_cast(https_priorities.c_str())))); + } + +#ifdef HAVE_DAUTH + if (digest_auth_random != "") { + // Need for const_cast to respect MHD interface that needs a char* + iov.push_back(gen(MHD_OPTION_DIGEST_AUTH_RANDOM, digest_auth_random.size(), const_cast(digest_auth_random.c_str()))); + } +#endif // HAVE_DAUTH + #ifdef HAVE_GNUTLS - if(cred_type != http_utils::NONE) + if (cred_type != http_utils::NONE) { iov.push_back(gen(MHD_OPTION_HTTPS_CRED_TYPE, cred_type)); -#endif //HAVE_GNUTLS + } - if(start_method == http_utils::INTERNAL_SELECT) - { - int on = 1; - bool bind_settled = true; - if(!bind_socket) - { - bind_settled = false; - struct sockaddr_in servaddr4; -#if HAVE_INET6 - struct sockaddr_in6 servaddr6; -#endif - const struct sockaddr *servaddr = NULL; - socklen_t addrlen; -#if HAVE_INET6 - if (0 != (options & MHD_USE_IPv6)) - addrlen = sizeof (struct sockaddr_in6); - else -#endif - addrlen = sizeof (struct ::sockaddr_in); - -#if HAVE_INET6 - if (0 != (options & MHD_USE_IPv6)) - { - memset (&servaddr6, 0, sizeof (struct sockaddr_in6)); - servaddr6.sin6_family = AF_INET6; - servaddr6.sin6_port = htons (port); -#if HAVE_SOCKADDR_IN_SIN_LEN - servaddr6.sin6_len = sizeof (struct sockaddr_in6); -#endif - servaddr = (struct sockaddr *) &servaddr6; - } - else -#endif - { - memset (&servaddr4, 0, sizeof (struct ::sockaddr_in)); - servaddr4.sin_family = AF_INET; - servaddr4.sin_port = htons (port); -#if HAVE_SOCKADDR_IN_SIN_LEN - servaddr4.sin_len = sizeof (struct ::sockaddr_in); -#endif - servaddr = (struct sockaddr *) &servaddr4; - } + if (psk_cred_handler != nullptr && use_ssl) { + iov.push_back(gen(MHD_OPTION_GNUTLS_PSK_CRED_HANDLER, + (intptr_t)&psk_cred_handler_func, this)); + } - if (use_ipv6) - bind_socket = create_socket (PF_INET6, SOCK_STREAM, 0); - else - bind_socket = create_socket (PF_INET, SOCK_STREAM, 0); - - setsockopt (bind_socket, - SOL_SOCKET, - SO_REUSEADDR, - (const char*) &on, sizeof (on)); - - if(use_ipv6) - { -#ifdef IPPROTO_IPV6 -#ifdef IPV6_V6ONLY - setsockopt (bind_socket, - IPPROTO_IPV6, IPV6_V6ONLY, - (const char*) &on, sizeof (on) - ); -#endif -#endif - } - bind(bind_socket, servaddr, addrlen); - } -#ifdef _WINDOWS - unsigned long ioarg = 1; - ioctlsocket(bind_socket, FIONBIO, &ioarg); -#else - int flags = fcntl (bind_socket, F_GETFL); - flags |= O_NONBLOCK; - fcntl (bind_socket, F_SETFL, flags); -#endif - if(!bind_settled) - listen(bind_socket, 1); - iov.push_back(gen(MHD_OPTION_LISTEN_SOCKET, bind_socket)); +#ifdef MHD_OPTION_HTTPS_CERT_CALLBACK + if (sni_callback != nullptr && use_ssl) { + iov.push_back(gen(MHD_OPTION_HTTPS_CERT_CALLBACK, + (intptr_t)&sni_cert_callback_func, this)); } +#endif // MHD_OPTION_HTTPS_CERT_CALLBACK +#endif // HAVE_GNUTLS - iov.push_back(gen(MHD_OPTION_END, 0, NULL )); + iov.push_back(gen(MHD_OPTION_END, 0, nullptr)); int start_conf = start_method; - if(use_ssl) + + if (use_ssl) { start_conf |= MHD_USE_SSL; - if(use_ipv6) + } + + if (use_ipv6) { start_conf |= MHD_USE_IPv6; - if(debug) + } + + if (use_dual_stack) { + start_conf |= MHD_USE_DUAL_STACK; + } + + if (debug) { start_conf |= MHD_USE_DEBUG; - if(pedantic) + } + if (pedantic) { start_conf |= MHD_USE_PEDANTIC_CHECKS; + } - int num_threads = 1; - if(max_threads > num_threads) - num_threads = max_threads; - - this->running = true; - if(start_method == http_utils::INTERNAL_SELECT) - { - for(int i = 0; i < num_threads; i++) - { - struct MHD_Daemon* daemon = MHD_start_daemon - ( - start_conf, this->port, &policy_callback, this, - &answer_to_connection, this, MHD_OPTION_ARRAY, - &iov[0], MHD_OPTION_END - ); - if(NULL == daemon) - { - cout << gettext("Unable to connect daemon to port: ") << - this->port << endl; - abort(); - } - details::daemon_item* di = new details::daemon_item(this, daemon); - daemons.push_back(di); - - //RUN SELECT THREADS - pthread_t t; - threads.push_back(t); - - if(pthread_create( - &threads[i], - NULL, - &webserver::select, - static_cast(di) - )) - { - abort(); - } - } + if (deferred_enabled) { + start_conf |= MHD_USE_SUSPEND_RESUME; } - else - { - struct MHD_Daemon* daemon = MHD_start_daemon - ( - start_conf, this->port, &policy_callback, this, + +#ifdef USE_FASTOPEN + start_conf |= MHD_USE_TCP_FASTOPEN; +#endif + + daemon = nullptr; + if (bind_address == nullptr) { + daemon = MHD_start_daemon(start_conf, port, &policy_callback, this, &answer_to_connection, this, MHD_OPTION_ARRAY, - &iov[0], MHD_OPTION_END - ); - if(NULL == daemon) - { - cout << gettext("Unable to connect daemon to port: ") << - this->port << endl; - abort(); - } - details::daemon_item* di = new details::daemon_item(this, daemon); - daemons.push_back(di); + &iov[0], MHD_OPTION_END); + } else { + daemon = MHD_start_daemon(start_conf, 1, &policy_callback, this, + &answer_to_connection, this, MHD_OPTION_ARRAY, + &iov[0], MHD_OPTION_SOCK_ADDR, bind_address, MHD_OPTION_END); + } + + if (daemon == nullptr) { + throw std::invalid_argument("Unable to connect daemon to port: " + std::to_string(port)); } + bool value_onclose = false; - if(blocking) - { + + running = true; + + if (blocking) { pthread_mutex_lock(&mutexwait); - while(blocking && running) + while (blocking && running) { pthread_cond_wait(&mutexcond, &mutexwait); + } pthread_mutex_unlock(&mutexwait); value_onclose = true; } return value_onclose; } -bool webserver::is_running() -{ - return this->running; +bool webserver::is_running() { + return running; } -bool webserver::stop() -{ - if(this->running) - { - pthread_mutex_lock(&mutexwait); - this->running = false; - pthread_cond_signal(&mutexcond); - pthread_mutex_unlock(&mutexwait); - for(unsigned int i = 0; i < threads.size(); ++i) - { - void* t_res; - pthread_join(threads[i], &t_res); - free(t_res); - } - threads.clear(); - typedef vector::const_iterator daemon_item_it; - for(daemon_item_it it = daemons.begin(); it != daemons.end(); ++it) - delete *it; - daemons.clear(); - return true; - } - else - { - return false; - } +bool webserver::stop() { + if (!running) return false; + + pthread_mutex_lock(&mutexwait); + running = false; + pthread_cond_signal(&mutexcond); + pthread_mutex_unlock(&mutexwait); + + MHD_stop_daemon(daemon); + + shutdown(bind_socket, 2); + + return true; } -void webserver::unregister_resource(const string& resource) -{ - details::http_endpoint he(resource); - this->registered_resources.erase(he); - this->registered_resources.erase(he.url_complete); +void webserver::invalidate_route_cache() { + std::lock_guard lock(route_cache_mutex); + route_cache_list.clear(); + route_cache_map.clear(); } -void webserver::ban_ip(const string& ip) -{ - ip_representation t_ip(ip); - set::iterator it = this->bans.find(t_ip); - if(it != this->bans.end() && (t_ip.weight() < (*it).weight())) +void webserver::unregister_resource(const string& resource) { + // family does not matter - it just checks the url_normalized anyhow + details::http_endpoint he(resource, false, true, regex_checking); + std::unique_lock registered_resources_lock(registered_resources_mutex); + + // Invalidate cache while holding registered_resources_mutex to prevent + // any thread from retrieving dangling resource pointers from the cache + // after we erase from the resource maps. { - this->bans.erase(it); - this->bans.insert(t_ip); + std::lock_guard cache_lock(route_cache_mutex); + route_cache_list.clear(); + route_cache_map.clear(); } - else - this->bans.insert(t_ip); + + registered_resources.erase(he); + registered_resources.erase(he.get_url_complete()); + registered_resources_str.erase(he.get_url_complete()); + registered_resources_regex.erase(he); } -void webserver::allow_ip(const string& ip) -{ +void webserver::ban_ip(const string& ip) { + std::unique_lock bans_lock(bans_mutex); ip_representation t_ip(ip); - set::iterator it = this->allowances.find(t_ip); - if(it != this->allowances.end() && (t_ip.weight() < (*it).weight())) - { - this->allowances.erase(it); - this->allowances.insert(t_ip); + set::iterator it = bans.find(t_ip); + if (it != bans.end() && (t_ip.weight() < (*it).weight())) { + bans.erase(it); + bans.insert(t_ip); + } else { + bans.insert(t_ip); } - else - this->allowances.insert(t_ip); } -void webserver::unban_ip(const string& ip) -{ - this->bans.erase(ip); +void webserver::allow_ip(const string& ip) { + std::unique_lock allowances_lock(allowances_mutex); + ip_representation t_ip(ip); + set::iterator it = allowances.find(t_ip); + if (it != allowances.end() && (t_ip.weight() < (*it).weight())) { + allowances.erase(it); + allowances.insert(t_ip); + } else { + allowances.insert(t_ip); + } } -void webserver::disallow_ip(const string& ip) -{ - this->allowances.erase(ip); +void webserver::unban_ip(const string& ip) { + std::unique_lock bans_lock(bans_mutex); + bans.erase(ip_representation(ip)); } -int webserver::build_request_header ( - void *cls, - enum MHD_ValueKind kind, - const char *key, - const char *value -) -{ - http_request* dhr = static_cast(cls); - dhr->set_header(key, value); - return MHD_YES; +void webserver::disallow_ip(const string& ip) { + std::unique_lock allowances_lock(allowances_mutex); + allowances.erase(ip_representation(ip)); } -int webserver::build_request_cookie ( - void *cls, - enum MHD_ValueKind kind, - const char *key, - const char *value -) -{ - http_request* dhr = static_cast(cls); - dhr->set_cookie(key, value); - return MHD_YES; -} +#ifdef HAVE_GNUTLS +// MHD_PskServerCredentialsCallback signature: +// The 'cls' parameter is our webserver pointer (passed via MHD_OPTION) +// Returns 0 on success, -1 on error +// The psk output should be allocated with malloc() - MHD will free it +int webserver::psk_cred_handler_func(void* cls, + struct MHD_Connection* connection, + const char* username, + void** psk, + size_t* psk_size) { + std::ignore = connection; // Not needed - we get context from cls + + webserver* ws = static_cast(cls); + + // Initialize output to safe values + *psk = nullptr; + *psk_size = 0; + + if (ws == nullptr || ws->psk_cred_handler == nullptr) { + return -1; + } -int webserver::build_request_footer ( - void *cls, - enum MHD_ValueKind kind, - const char *key, - const char *value -) -{ - http_request* dhr = static_cast(cls); - dhr->set_footer(key, value); - return MHD_YES; + std::string psk_hex = ws->psk_cred_handler(std::string(username)); + if (psk_hex.empty()) { + return -1; + } + + // Validate hex string before allocating memory + size_t psk_len = psk_hex.size() / 2; + if (psk_len == 0 || (psk_hex.size() % 2 != 0) || + !string_utilities::is_valid_hex(psk_hex)) { + return -1; + } + + // Allocate with malloc - MHD will free this + unsigned char* psk_data = static_cast(malloc(psk_len)); + if (psk_data == nullptr) { + return -1; + } + + // Convert hex string to binary + for (size_t i = 0; i < psk_len; i++) { + psk_data[i] = static_cast( + (string_utilities::hex_char_to_val(psk_hex[i * 2]) << 4) | + string_utilities::hex_char_to_val(psk_hex[i * 2 + 1])); + } + + *psk = psk_data; + *psk_size = psk_len; + return 0; } -int webserver::build_request_args ( - void *cls, - enum MHD_ValueKind kind, - const char *key, - const char *arg_value -) -{ - details::modded_request* mr = static_cast(cls); - char* value = (char*) ((arg_value == NULL) ? "" : arg_value); +#ifdef MHD_OPTION_HTTPS_CERT_CALLBACK +// SNI callback for selecting certificates based on server name +// Returns 0 on success, -1 on failure +int webserver::sni_cert_callback_func(void* cls, + struct MHD_Connection* connection, + const char* server_name, + gnutls_certificate_credentials_t* creds) { + std::ignore = connection; + + webserver* ws = static_cast(cls); + if (ws == nullptr || ws->sni_callback == nullptr || server_name == nullptr) { + return -1; + } + + std::string name(server_name); + + // Check if we have cached credentials for this server name { - char buf[strlen(key) + strlen(value) + 3]; - if(mr->dhr->querystring == "") - { - snprintf(buf, sizeof buf, "?%s=%s", key, value); - mr->dhr->querystring = buf; + std::shared_lock lock(ws->sni_credentials_mutex); + auto it = ws->sni_credentials_cache.find(name); + if (it != ws->sni_credentials_cache.end()) { + *creds = it->second; + return 0; } - else - { - snprintf(buf, sizeof buf, "&%s=%s", key, value); - mr->dhr->querystring += string(buf); + } + + // Call user's callback to get cert/key pair + auto [cert_pem, key_pem] = ws->sni_callback(name); + if (cert_pem.empty() || key_pem.empty()) { + return -1; // Use default certificate + } + + // Create new credentials for this server name + gnutls_certificate_credentials_t new_creds; + if (gnutls_certificate_allocate_credentials(&new_creds) != GNUTLS_E_SUCCESS) { + return -1; + } + + gnutls_datum_t cert_data = { + reinterpret_cast(const_cast(cert_pem.data())), + static_cast(cert_pem.size()) + }; + gnutls_datum_t key_data = { + reinterpret_cast(const_cast(key_pem.data())), + static_cast(key_pem.size()) + }; + + int ret = gnutls_certificate_set_x509_key_mem(new_creds, &cert_data, &key_data, GNUTLS_X509_FMT_PEM); + if (ret != GNUTLS_E_SUCCESS) { + gnutls_certificate_free_credentials(new_creds); + return -1; + } + + // Cache the credentials with double-check to avoid race condition + { + std::unique_lock lock(ws->sni_credentials_mutex); + // Re-check after acquiring exclusive lock - another thread may have inserted + auto it = ws->sni_credentials_cache.find(name); + if (it != ws->sni_credentials_cache.end()) { + // Another thread already cached credentials, use theirs and free ours + gnutls_certificate_free_credentials(new_creds); + *creds = it->second; + return 0; } + ws->sni_credentials_cache[name] = new_creds; } - size_t size = internal_unescaper((void*) mr->ws, value); - mr->dhr->set_arg(key, string(value, size)); - return MHD_YES; + + *creds = new_creds; + return 0; } +#endif // MHD_OPTION_HTTPS_CERT_CALLBACK +#endif // HAVE_GNUTLS -int policy_callback (void *cls, const struct sockaddr* addr, socklen_t addrlen) -{ - if((static_cast(cls))->ban_system_enabled) - { - if((((static_cast(cls))->default_policy == http_utils::ACCEPT) && - ((static_cast(cls))->bans.count(addr)) && - (!(static_cast(cls))->allowances.count(addr)) - ) || - (((static_cast(cls))->default_policy == http_utils::REJECT) - && ((!(static_cast(cls))->allowances.count(addr)) || - ((static_cast(cls))->bans.count(addr))) - )) - return MHD_NO; +MHD_Result policy_callback(void *cls, const struct sockaddr* addr, socklen_t addrlen) { + // Parameter needed to respect MHD interface, but not needed here. + std::ignore = addrlen; + + const auto ws = static_cast(cls); + + if (!ws->ban_system_enabled) return MHD_YES; + + std::shared_lock bans_lock(ws->bans_mutex); + std::shared_lock allowances_lock(ws->allowances_mutex); + const bool is_banned = ws->bans.count(ip_representation(addr)); + const bool is_allowed = ws->allowances.count(ip_representation(addr)); + + if ((ws->default_policy == http_utils::ACCEPT && is_banned && !is_allowed) || + (ws->default_policy == http_utils::REJECT && (!is_allowed || is_banned))) { + return MHD_NO; } + return MHD_YES; } -void* uri_log(void* cls, const char* uri) -{ - struct details::modded_request* mr = new details::modded_request(); - mr->complete_uri = new string(uri); - mr->second = false; - return ((void*)mr); +void* uri_log(void* cls, const char* uri, struct MHD_Connection *con) { + // Parameter needed to respect MHD interface, but not needed here. + std::ignore = cls; + std::ignore = con; + + auto mr = std::make_unique(); + mr->complete_uri = uri; + return reinterpret_cast(mr.release()); } -void error_log(void* cls, const char* fmt, va_list ap) -{ +void error_log(void* cls, const char* fmt, va_list ap) { webserver* dws = static_cast(cls); - if(dws->log_error != 0x0) - dws->log_error(fmt); + + std::string msg; + msg.resize(80); // Asssume one line will be enought most of the time. + + va_list va; + va_copy(va, ap); // Stash a copy in case we need to try again. + + size_t r = vsnprintf(&*msg.begin(), msg.size(), fmt, ap); + va_end(ap); + + if (msg.size() < r) { + msg.resize(r); + r = vsnprintf(&*msg.begin(), msg.size(), fmt, va); + } + va_end(va); + msg.resize(r); + + if (dws->log_error != nullptr) dws->log_error(msg); } -void access_log(webserver* dws, string uri) -{ - if(dws->log_access != 0x0) - dws->log_access(uri); +void access_log(webserver* dws, string uri) { + if (dws->log_access != nullptr) dws->log_access(uri); } -size_t unescaper_func(void * cls, struct MHD_Connection *c, char *s) -{ +size_t unescaper_func(void * cls, struct MHD_Connection *c, char *s) { + // Parameter needed to respect MHD interface, but not needed here. + std::ignore = cls; + std::ignore = c; + // THIS IS USED TO AVOID AN UNESCAPING OF URL BEFORE THE ANSWER. // IT IS DUE TO A BOGUS ON libmicrohttpd (V0.99) THAT PRODUCING A // STRING CONTAINING '\0' AFTER AN UNESCAPING, IS UNABLE TO PARSE // ARGS WITH get_connection_values FUNC OR lookup FUNC. - return strlen(s); + return std::string(s).size(); } -size_t internal_unescaper(void* cls, char* s) -{ - if(s[0] == 0) return 0; +MHD_Result webserver::post_iterator(void *cls, enum MHD_ValueKind kind, + const char *key, const char *filename, const char *content_type, + const char *transfer_encoding, const char *data, uint64_t off, size_t size) { + // Parameter needed to respect MHD interface, but not needed here. + std::ignore = kind; - webserver* dws = static_cast(cls); - if(dws->unescaper != 0x0) - { - dws->unescaper(s); - return strlen(s); - } - else - { - return http_unescape(s); + struct details::modded_request* mr = (struct details::modded_request*) cls; + + if (!filename) { + // There is no actual file, just set the arg key/value and return. + if (off > 0) { + mr->dhr->grow_last_arg(key, std::string(data, size)); + return MHD_YES; + } + + mr->dhr->set_arg(key, std::string(data, size)); + return MHD_YES; } -} -int webserver::post_iterator (void *cls, enum MHD_ValueKind kind, - const char *key, - const char *filename, - const char *content_type, - const char *transfer_encoding, - const char *data, uint64_t off, size_t size - ) -{ - struct details::modded_request* mr = (struct details::modded_request*) cls; - mr->dhr->set_arg(key, data, size); - return MHD_YES; -} + try { + if (mr->ws->file_upload_target != FILE_UPLOAD_DISK_ONLY) { + mr->dhr->set_arg_flat(key, std::string(mr->dhr->get_arg(key)) + std::string(data, size)); + } -void webserver::upgrade_handler (void *cls, struct MHD_Connection* connection, - void **con_cls, int upgrade_socket) -{ -} + if (*filename != '\0' && mr->ws->file_upload_target != FILE_UPLOAD_MEMORY_ONLY) { + // either get the existing file info struct or create a new one in the file map + http::file_info& file = mr->dhr->get_or_create_file_info(key, filename); + // if the file_system_file_name is not filled yet, this is a new entry and the name has to be set + // (either random or copy of the original filename) + if (file.get_file_system_file_name().empty()) { + if (mr->ws->generate_random_filename_on_upload) { + file.set_file_system_file_name(http_utils::generate_random_upload_filename(mr->ws->file_upload_dir)); + } else { + std::string safe_name = http_utils::sanitize_upload_filename(filename); + if (safe_name.empty()) { + return MHD_NO; + } + file.set_file_system_file_name(mr->ws->file_upload_dir + "/" + safe_name); + } + // to not append to an already existing file, delete an already existing file + unlink(file.get_file_system_file_name().c_str()); + if (content_type != nullptr) { + file.set_content_type(content_type); + } + if (transfer_encoding != nullptr) { + file.set_transfer_encoding(transfer_encoding); + } + } -void webserver::not_found_page( - http_response** dhrs, - details::modded_request* mr -) -{ - if(not_found_resource != 0x0) - not_found_resource(*mr->dhr, dhrs); - else - *dhrs = new http_response(http_response_builder(NOT_FOUND_ERROR, http_utils::http_not_found).string_response()); -} + // if multiple files are uploaded, a different filename or a different key indicates + // the start of a new file, so close the previous one + if (mr->upload_filename.empty() || + mr->upload_key.empty() || + 0 != strcmp(filename, mr->upload_filename.c_str()) || + 0 != strcmp(key, mr->upload_key.c_str())) { + if (mr->upload_ostrm != nullptr) { + mr->upload_ostrm->close(); + } + } -int webserver::method_not_acceptable_page (const void *cls, - struct MHD_Connection *connection) -{ - int ret; - struct MHD_Response *response; - - /* unsupported HTTP method */ - response = MHD_create_response_from_buffer (strlen (NOT_METHOD_ERROR), - (void *) NOT_METHOD_ERROR, - MHD_RESPMEM_PERSISTENT); - ret = MHD_queue_response (connection, - MHD_HTTP_METHOD_NOT_ACCEPTABLE, - response); - MHD_add_response_header (response, - MHD_HTTP_HEADER_CONTENT_ENCODING, - "text/plain"); - MHD_destroy_response (response); - - return ret; + if (mr->upload_ostrm == nullptr || !mr->upload_ostrm->is_open()) { + mr->upload_key = key; + mr->upload_filename = filename; + mr->upload_ostrm = std::make_unique(); + mr->upload_ostrm->open(file.get_file_system_file_name(), std::ios::binary | std::ios::app); + } + + if (size > 0) { + mr->upload_ostrm->write(data, size); + if (!mr->upload_ostrm->good()) { + return MHD_NO; + } + } + + // update the file size in the map + file.grow_file_size(size); + } + return MHD_YES; + } catch(const http::generateFilenameException& e) { + return MHD_NO; + } } -void webserver::method_not_allowed_page( - http_response** dhrs, - details::modded_request* mr -) -{ - if(method_not_acceptable_resource != 0x0) - method_not_allowed_resource(*mr->dhr, dhrs); - else - *dhrs = new http_response(http_response_builder(METHOD_ERROR, http_utils::http_method_not_allowed).string_response()); +void webserver::upgrade_handler(void *cls, struct MHD_Connection* connection, void **con_cls, int upgrade_socket) { + std::ignore = cls; + std::ignore = connection; + std::ignore = con_cls; + std::ignore = upgrade_socket; } -void webserver::internal_error_page( - http_response** dhrs, - details::modded_request* mr, - bool force_our -) -{ - if(internal_error_resource != 0x0 && !force_our) - internal_error_resource(*mr->dhr, dhrs); - else - *dhrs = new http_response(http_response_builder(GENERIC_ERROR, http_utils::http_internal_server_error).string_response()); +std::shared_ptr webserver::not_found_page(details::modded_request* mr) const { + if (not_found_resource != nullptr) { + return not_found_resource(*mr->dhr); + } else { + return std::make_shared(NOT_FOUND_ERROR, http_utils::http_not_found); + } } -int webserver::bodyless_requests_answer( - MHD_Connection* connection, const char* method, - const char* version, struct details::modded_request* mr - ) -{ - http_request req; - mr->dhr = &(req); - return complete_request(connection, mr, version, method); +std::shared_ptr webserver::method_not_allowed_page(details::modded_request* mr) const { + if (method_not_allowed_resource != nullptr) { + return method_not_allowed_resource(*mr->dhr); + } else { + return std::make_shared(METHOD_ERROR, http_utils::http_method_not_allowed); + } } -int webserver::bodyfull_requests_answer_first_step( - MHD_Connection* connection, - struct details::modded_request* mr -) -{ - mr->second = true; - mr->dhr = new http_request(); - const char *encoding = MHD_lookup_connection_value ( - connection, - MHD_HEADER_KIND, - http_utils::http_header_content_type.c_str() - ); - if(encoding != 0x0) - mr->dhr->set_header(http_utils::http_header_content_type, encoding); - if ( post_process_enabled && - ( - 0x0 != encoding && - ((0 == strncasecmp ( - MHD_HTTP_POST_ENCODING_FORM_URLENCODED, - encoding, - strlen (MHD_HTTP_POST_ENCODING_FORM_URLENCODED) - ) - )) - ) - ) - { - mr->pp = MHD_create_post_processor ( - connection, - 1024, - &post_iterator, - mr - ); - } - else - { - mr->pp = NULL; +std::shared_ptr webserver::internal_error_page(details::modded_request* mr, bool force_our) const { + if (internal_error_resource != nullptr && !force_our) { + return internal_error_resource(*mr->dhr); + } else { + return std::make_shared(GENERIC_ERROR, http_utils::http_internal_server_error); } - return MHD_YES; } -int webserver::bodyfull_requests_answer_second_step( - MHD_Connection* connection, const char* method, - const char* version, const char* upload_data, - size_t* upload_data_size, struct details::modded_request* mr -) -{ - if ( 0 != *upload_data_size) +bool webserver::should_skip_auth(const std::string& path) const { + // Normalize path: resolve ".." and "." segments to prevent bypass + std::string normalized; { -#ifdef DEBUG - cout << "Writing content: " << upload_data << endl; -#endif //DEBUG - mr->dhr->grow_content(upload_data, *upload_data_size); - if (mr->pp != NULL) - { - MHD_post_process(mr->pp, upload_data, *upload_data_size); + std::vector segments; + std::string::size_type start = 0; + // Skip leading slash + if (!path.empty() && path[0] == '/') { + start = 1; + } + while (start < path.size()) { + auto end = path.find('/', start); + if (end == std::string::npos) end = path.size(); + std::string seg = path.substr(start, end - start); + if (seg == "..") { + if (!segments.empty()) segments.pop_back(); + } else if (!seg.empty() && seg != ".") { + segments.push_back(seg); + } + start = end + 1; + } + normalized = "/"; + for (size_t i = 0; i < segments.size(); i++) { + if (i > 0) normalized += "/"; + normalized += segments[i]; } - *upload_data_size = 0; - return MHD_YES; } - return complete_request(connection, mr, version, method); + for (const auto& skip_path : auth_skip_paths) { + if (skip_path == normalized) return true; + // Support wildcard suffix (e.g., "/public/*") + if (skip_path.size() > 2 && skip_path.back() == '*' && + skip_path[skip_path.size() - 2] == '/') { + std::string prefix = skip_path.substr(0, skip_path.size() - 1); + if (normalized.compare(0, prefix.size(), prefix) == 0) return true; + } + } + return false; } -void webserver::end_request_construction( - MHD_Connection* connection, - struct details::modded_request* mr, - const char* version, - const char* method, - char* user, - char* pass, - char* digested_user -) -{ - mr->ws = this; - MHD_get_connection_values ( - connection, - MHD_GET_ARGUMENT_KIND, - &build_request_args, - (void*) mr - ); - MHD_get_connection_values ( - connection, - MHD_HEADER_KIND, - &build_request_header, - (void*) mr->dhr - ); - MHD_get_connection_values ( - connection, - MHD_FOOTER_KIND, - &build_request_footer, - (void*) mr->dhr - ); - MHD_get_connection_values ( - connection, - MHD_COOKIE_KIND, - &build_request_cookie, - (void*) mr->dhr - ); - - mr->dhr->set_path(mr->standardized_url->c_str()); - mr->dhr->set_method(method); +MHD_Result webserver::requests_answer_first_step(MHD_Connection* connection, struct details::modded_request* mr) { + mr->dhr.reset(new http_request(connection, unescaper)); + mr->dhr->set_file_cleanup_callback(file_cleanup_callback); - if(basic_auth_enabled) - { - user = MHD_basic_auth_get_username_password(connection, &pass); + if (!mr->has_body) { + return MHD_YES; } - if(digest_auth_enabled) - digested_user = MHD_digest_auth_get_username(connection); - mr->dhr->set_version(version); - const MHD_ConnectionInfo * conninfo = MHD_get_connection_info( - connection, - MHD_CONNECTION_INFO_CLIENT_ADDRESS - ); - std::string ip_str; - get_ip_str(conninfo->client_addr, ip_str); - mr->dhr->set_requestor(ip_str); - mr->dhr->set_requestor_port(get_port(conninfo->client_addr)); - if(pass != 0x0) - { - mr->dhr->set_pass(pass); - mr->dhr->set_user(user); + + mr->dhr->set_content_size_limit(content_size_limit); + const char *encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, http_utils::http_header_content_type); + + if (post_process_enabled && + (nullptr != encoding && + ((0 == strncasecmp(http_utils::http_post_encoding_form_urlencoded, encoding, strlen(http_utils::http_post_encoding_form_urlencoded))) || + (0 == strncasecmp(http_utils::http_post_encoding_multipart_formdata, encoding, strlen(http_utils::http_post_encoding_multipart_formdata)))))) { + const size_t post_memory_limit(32 * 1024); // Same as #MHD_POOL_SIZE_DEFAULT + mr->pp = MHD_create_post_processor(connection, post_memory_limit, &post_iterator, mr); + } else { + mr->pp = nullptr; } - if(digested_user != 0x0) - { - mr->dhr->set_digested_user(digested_user); + return MHD_YES; +} + +MHD_Result webserver::requests_answer_second_step(MHD_Connection* connection, const char* method, + const char* version, const char* upload_data, + size_t* upload_data_size, struct details::modded_request* mr) { + if (0 == *upload_data_size) return complete_request(connection, mr, version, method); + + if (mr->has_body) { +#ifdef DEBUG + std::cout << "Writing content: " << std::string(upload_data, *upload_data_size) << std::endl; +#endif // DEBUG + // The post iterator is only created from the libmicrohttpd for content of type + // multipart/form-data and application/x-www-form-urlencoded + // all other content (which is indicated by mr-pp == nullptr) + // has to be put to the content even if put_processed_data_to_content is set to false + if (mr->pp == nullptr || put_processed_data_to_content) { + mr->dhr->grow_content(upload_data, *upload_data_size); + } + + if (mr->pp != nullptr) { + mr->ws = this; + MHD_post_process(mr->pp, upload_data, *upload_data_size); + if (mr->upload_ostrm != nullptr && mr->upload_ostrm->is_open()) { + mr->upload_ostrm->close(); + } + } } + + *upload_data_size = 0; + return MHD_YES; } -int webserver::finalize_answer( - MHD_Connection* connection, - struct details::modded_request* mr, - const char* method -) -{ +MHD_Result webserver::finalize_answer(MHD_Connection* connection, struct details::modded_request* mr, const char* method) { int to_ret = MHD_NO; - http_response* dhrs = 0x0; - map::iterator fe; + map::iterator fe; - details::http_resource_mirror* hrm; + http_resource* hrm; bool found = false; struct MHD_Response* raw_response; - if(!single_resource) { - const char* st_url = mr->standardized_url->c_str(); - fe = registered_resources_str.find(st_url); - if(fe == registered_resources_str.end()) - { - if(regex_checking) - { - - map< - details::http_endpoint, details::http_resource_mirror - >::iterator found_endpoint; - - details::http_endpoint endpoint( - st_url, false, false, regex_checking - ); - - map< - details::http_endpoint, - details::http_resource_mirror - >::iterator it; - - size_t len = 0; - size_t tot_len = 0; - for( - it=registered_resources.begin(); - it!=registered_resources.end(); - ++it - ) - { - size_t endpoint_pieces_len = (*it).first.get_url_pieces_num(); - size_t endpoint_tot_len = (*it).first.get_url_complete_size(); - if(!found || - endpoint_pieces_len > len || - ( - endpoint_pieces_len == len && - endpoint_tot_len > tot_len - ) - ) + std::shared_lock registered_resources_lock(registered_resources_mutex); + if (!single_resource) { + const char* st_url = mr->standardized_url.c_str(); + fe = registered_resources_str.find(st_url); + if (fe == registered_resources_str.end()) { + if (regex_checking) { + details::http_endpoint endpoint(st_url, false, false, false); + + // Data needed for parameter extraction after match. + // On cache hit, we copy these while holding the cache lock + // to avoid use-after-free if another thread invalidates cache. + vector matched_url_pars; + vector matched_chunks; + + // Check the LRU route cache first { - if((*it).first.match(endpoint)) - { + std::lock_guard cache_lock(route_cache_mutex); + auto cache_it = route_cache_map.find(mr->standardized_url); + if (cache_it != route_cache_map.end()) { + // Cache hit — move to front of LRU list + route_cache_list.splice(route_cache_list.begin(), route_cache_list, cache_it->second); + const route_cache_entry& cached = cache_it->second->second; + matched_url_pars = cached.matched_endpoint.get_url_pars(); + matched_chunks = cached.matched_endpoint.get_chunk_positions(); + hrm = cached.resource; found = true; - len = endpoint_pieces_len; - tot_len = endpoint_tot_len; - found_endpoint = it; } } - } - if(found) - { - vector url_pars; - - size_t pars_size = - found_endpoint->first.get_url_pars(url_pars); - - vector url_pieces; - endpoint.get_url_pieces(url_pieces); - vector chunkes; - found_endpoint->first.get_chunk_positions(chunkes); - for(unsigned int i = 0; i < pars_size; i++) - { - mr->dhr->set_arg(url_pars[i], url_pieces[chunkes[i]]); + + if (!found) { + // Cache miss — perform regex scan + map::iterator found_endpoint; + + size_t len = 0; + size_t tot_len = 0; + for (auto it = registered_resources_regex.begin(); it != registered_resources_regex.end(); ++it) { + size_t endpoint_pieces_len = it->first.get_url_pieces().size(); + size_t endpoint_tot_len = it->first.get_url_complete().size(); + if (!found || endpoint_pieces_len > len || (endpoint_pieces_len == len && endpoint_tot_len > tot_len)) { + if (it->first.match(endpoint)) { + found = true; + len = endpoint_pieces_len; + tot_len = endpoint_tot_len; + found_endpoint = it; + } + } + } + + if (found) { + // Safe to reference: registered_resources_mutex (shared) is still held + matched_url_pars = found_endpoint->first.get_url_pars(); + matched_chunks = found_endpoint->first.get_chunk_positions(); + hrm = found_endpoint->second; + + // Store in LRU cache + { + std::lock_guard cache_lock(route_cache_mutex); + route_cache_list.emplace_front(mr->standardized_url, route_cache_entry{found_endpoint->first, hrm}); + route_cache_map[mr->standardized_url] = route_cache_list.begin(); + + if (route_cache_map.size() > ROUTE_CACHE_MAX_SIZE) { + route_cache_map.erase(route_cache_list.back().first); + route_cache_list.pop_back(); + } + } + } } - hrm = &found_endpoint->second; + // Extract URL parameters from matched endpoint + if (found) { + const auto& url_pieces = endpoint.get_url_pieces(); + for (unsigned int i = 0; i < matched_url_pars.size(); i++) { + if (matched_chunks[i] >= 0 && static_cast(matched_chunks[i]) < url_pieces.size()) { + mr->dhr->set_arg(matched_url_pars[i], url_pieces[matched_chunks[i]]); + } + } + } } + } else { + hrm = fe->second; + found = true; } - } - else - { - hrm = fe->second; + } else { + hrm = registered_resources.begin()->second; found = true; } } - else - { - hrm = ®istered_resources.begin()->second; - found = true; - } - mr->dhr->set_underlying_connection(connection); - if(found) - { - try - { - if(hrm->is_allowed(method)) - { - ((hrm)->*(mr->callback))(*mr->dhr, &dhrs); - if (dhrs == 0x0) internal_error_page(&dhrs, mr); - } - else - { - method_not_allowed_page(&dhrs, mr); + // Check centralized authentication if handler is configured + if (found && auth_handler != nullptr) { + std::string path(mr->dhr->get_path()); + if (!should_skip_auth(path)) { + std::shared_ptr auth_response = auth_handler(*mr->dhr); + if (auth_response != nullptr) { + mr->dhrs = auth_response; + found = false; // Skip resource rendering, go directly to response } } - catch(const std::exception& e) - { - internal_error_page(&dhrs, mr); - } - catch(...) - { - internal_error_page(&dhrs, mr); - } - } - else - { - not_found_page(&dhrs, mr); } - mr->dhrs = dhrs; - mr->dhrs->underlying_connection = connection; - try - { - try - { - dhrs->get_raw_response(&raw_response, this); - } - catch(const file_access_exception& fae) - { - not_found_page(&dhrs, mr); - dhrs->get_raw_response(&raw_response, this); - } - catch(const std::exception& e) - { - internal_error_page(&dhrs, mr); - dhrs->get_raw_response(&raw_response, this); - } - catch(...) - { - internal_error_page(&dhrs, mr); - dhrs->get_raw_response(&raw_response, this); + + if (found) { + try { + if (mr->pp != nullptr) { + MHD_destroy_post_processor(mr->pp); + mr->pp = nullptr; + } + if (hrm->is_allowed(method)) { + mr->dhrs = ((hrm)->*(mr->callback))(*mr->dhr); // copy in memory (move in case) + if (mr->dhrs.get() == nullptr || mr->dhrs->get_response_code() == -1) { + mr->dhrs = internal_error_page(mr); + } + } else { + mr->dhrs = method_not_allowed_page(mr); + + vector allowed_methods = hrm->get_allowed_methods(); + if (allowed_methods.size() > 0) { + string header_value = allowed_methods[0]; + for (auto it = allowed_methods.cbegin() + 1; it != allowed_methods.cend(); ++it) { + header_value += ", " + (*it); + } + mr->dhrs->with_header(http_utils::http_header_allow, header_value); + } + } + } catch(const std::exception& e) { + mr->dhrs = internal_error_page(mr); + } catch(...) { + mr->dhrs = internal_error_page(mr); } + } else if (mr->dhrs == nullptr) { + mr->dhrs = not_found_page(mr); } - catch(...) - { - internal_error_page(&dhrs, mr, true); - dhrs->get_raw_response(&raw_response, this); - } - dhrs->decorate_response(raw_response); - to_ret = dhrs->enqueue_response(connection, raw_response); - MHD_destroy_response (raw_response); - return to_ret; -} - -int webserver::complete_request( - MHD_Connection* connection, - struct details::modded_request* mr, - const char* version, - const char* method -) -{ - char* pass = 0x0; - char* user = 0x0; - char* digested_user = 0x0; - - end_request_construction( - connection, - mr, - version, - method, - pass, - user, - digested_user - ); - - int to_ret = finalize_answer(connection, mr, method); - - if (user != 0x0) - free (user); - if (pass != 0x0) - free (pass); - if (digested_user != 0x0) - free (digested_user); - - return to_ret; -} -int webserver::answer_to_connection(void* cls, MHD_Connection* connection, - const char* url, const char* method, - const char* version, const char* upload_data, - size_t* upload_data_size, void** con_cls - ) -{ - struct details::modded_request* mr = - static_cast(*con_cls); - - if(mr->second == false) - { - mr->standardized_url = new string(); - internal_unescaper((void*) static_cast(cls), (char*) url); - http_utils::standardize_url(url, *mr->standardized_url); - - bool body = false; - - access_log( - static_cast(cls), - *(mr->complete_uri) + " METHOD: " + method - ); - - if( 0 == strcasecmp(method, http_utils::http_method_get.c_str())) - { - mr->callback = &details::http_resource_mirror::render_GET; - } - else if (0 == strcmp(method, http_utils::http_method_post.c_str())) - { - mr->callback = &details::http_resource_mirror::render_POST; - body = true; - } - else if (0 == strcasecmp(method, http_utils::http_method_put.c_str())) - { - mr->callback = &details::http_resource_mirror::render_PUT; - body = true; - } - else if (0 == strcasecmp(method,http_utils::http_method_delete.c_str())) - { - mr->callback = &details::http_resource_mirror::render_DELETE; - } - else if (0 == strcasecmp(method, http_utils::http_method_head.c_str())) - { - mr->callback = &details::http_resource_mirror::render_HEAD; - } - else if (0 ==strcasecmp(method,http_utils::http_method_connect.c_str())) - { - mr->callback = &details::http_resource_mirror::render_CONNECT; - } - else if (0 == strcasecmp(method, http_utils::http_method_trace.c_str())) - { - mr->callback = &details::http_resource_mirror::render_TRACE; - } - else if (0 ==strcasecmp(method,http_utils::http_method_options.c_str())) - { - mr->callback = &details::http_resource_mirror::render_OPTIONS; - } - else - { - using namespace details; - if(static_cast(cls)->method_not_acceptable_resource) - mr->callback = - &http_resource_mirror::method_not_acceptable_resource; - else - return static_cast(cls)->method_not_acceptable_page( - cls, - connection - ); + try { + try { + raw_response = mr->dhrs->get_raw_response(); + if (raw_response == nullptr) { + mr->dhrs = internal_error_page(mr); + raw_response = mr->dhrs->get_raw_response(); + } + } catch(const std::invalid_argument& iae) { + mr->dhrs = not_found_page(mr); + raw_response = mr->dhrs->get_raw_response(); + } catch(const std::exception& e) { + mr->dhrs = internal_error_page(mr); + raw_response = mr->dhrs->get_raw_response(); + } catch(...) { + mr->dhrs = internal_error_page(mr); + raw_response = mr->dhrs->get_raw_response(); } - - if(body) - return static_cast(cls)-> - bodyfull_requests_answer_first_step(connection, mr); - else - return static_cast(cls)-> - bodyless_requests_answer(connection, method, version, mr); - } - else - { - return static_cast(cls)-> - bodyfull_requests_answer_second_step( - connection, - method, - version, - upload_data, - upload_data_size, - mr - ); + } catch(...) { // catches errors in internal error page + mr->dhrs = internal_error_page(mr, true); + raw_response = mr->dhrs->get_raw_response(); } + mr->dhrs->decorate_response(raw_response); + to_ret = mr->dhrs->enqueue_response(connection, raw_response); + MHD_destroy_response(raw_response); + return (MHD_Result) to_ret; } -void webserver::send_message_to_consumer( - const httpserver_ska& connection_id, - const std::string& message, - bool to_lock -) -{ - internal_comet_manager->send_message_to_consumer(connection_id, message, to_lock, start_method); -} - -void webserver::send_message_to_topic( - const std::string& topic, - const std::string& message -) -{ - internal_comet_manager->send_message_to_topic(topic, message, start_method); -} - -void webserver::register_to_topics( - const std::vector& topics, - const httpserver_ska& connection_id, - int keepalive_secs, - string keepalive_msg -) -{ - internal_comet_manager->register_to_topics(topics, connection_id, keepalive_secs, keepalive_msg, start_method); -} - -size_t webserver::read_message(const httpserver_ska& connection_id, - std::string& message -) -{ - return internal_comet_manager->read_message(connection_id, message); -} - -size_t webserver::get_topic_consumers( - const std::string& topic, - std::set& consumers -) -{ - return internal_comet_manager->get_topic_consumers(topic, consumers); -} +MHD_Result webserver::complete_request(MHD_Connection* connection, struct details::modded_request* mr, const char* version, const char* method) { + mr->ws = this; -bool webserver::pop_signaled(const httpserver_ska& consumer) -{ - return internal_comet_manager->pop_signaled(consumer, start_method); -} + mr->dhr->set_path(mr->standardized_url); + mr->dhr->set_method(method); + mr->dhr->set_version(version); -http_response* webserver::get_from_cache( - const std::string& key, - bool* valid, - bool lock, - bool write -) -{ - details::cache_entry* ce = 0x0; - return get_from_cache(key, valid, &ce, lock, write); + return finalize_answer(connection, mr, method); } -http_response* webserver::get_from_cache( - const std::string& key, - bool* valid, - details::cache_entry** ce, - bool lock, - bool write -) -{ - pthread_rwlock_rdlock(&cache_guard); - *valid = true; - map::iterator it(response_cache.find(key)); - if(it != response_cache.end()) - { - if(lock) - (*it).second->lock(write); - if((*it).second->validity != -1) - { - timeval now; - gettimeofday(&now, NULL); - if( now.tv_sec - (*it).second->ts > (*it).second->validity) - *valid = false; - } - *ce = (*it).second; - pthread_rwlock_unlock(&cache_guard); - return (*it).second->response.ptr(); - } - else - { - pthread_rwlock_unlock(&cache_guard); - *valid = false; - return 0x0; - } -} +MHD_Result webserver::answer_to_connection(void* cls, MHD_Connection* connection, const char* url, const char* method, + const char* version, const char* upload_data, size_t* upload_data_size, void** con_cls) { + struct details::modded_request* mr = static_cast(*con_cls); -bool webserver::is_valid(const std::string& key) -{ - pthread_rwlock_rdlock(&cache_guard); - map::iterator it(response_cache.find(key)); - if(it != response_cache.end()) - { - if((*it).second->validity != -1) - { - timeval now; - gettimeofday(&now, NULL); - if( now.tv_sec - (*it).second->ts > (*it).second->validity) - { - pthread_rwlock_unlock(&cache_guard); - return false; - } - else - { - pthread_rwlock_unlock(&cache_guard); - return true; - } - } - else - { - pthread_rwlock_unlock(&cache_guard); - return true; - } + if (mr->dhr) { + return static_cast(cls)->requests_answer_second_step(connection, method, version, upload_data, upload_data_size, mr); } - pthread_rwlock_unlock(&cache_guard); - return false; -} -void webserver::lock_cache_element(details::cache_entry* ce, bool write) -{ - if(ce) - ce->lock(write); -} + const MHD_ConnectionInfo * conninfo = MHD_get_connection_info(connection, MHD_CONNECTION_INFO_CONNECTION_FD); -void webserver::unlock_cache_element(details::cache_entry* ce) -{ - if(ce) - ce->unlock(); -} - -details::cache_entry* webserver::put_in_cache( - const std::string& key, - http_response* value, - bool* new_elem, - bool lock, - bool write, - int validity -) -{ - pthread_rwlock_wrlock(&cache_guard); - map::iterator it(response_cache.find(key)); - details::cache_entry* to_ret; - bool already_in = false; - if(it != response_cache.end()) - { - (*it).second->lock(true); - already_in = true; + if (conninfo != nullptr && static_cast(cls)->tcp_nodelay) { + int yes = 1; + setsockopt(conninfo->connect_fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), sizeof(int)); } - if(validity == -1) - { - if(already_in) - { - (*it).second->response = value; - to_ret = (*it).second; - *new_elem = false; - } - else - { - pair::iterator, bool> res = - response_cache.insert(pair( - key, new details::cache_entry(value)) - ); - - to_ret = (*res.first).second; - *new_elem = res.second; - } - } - else - { - timeval now; - gettimeofday(&now, NULL); - if(already_in) - { - (*it).second->response = value; - (*it).second->ts = now.tv_sec; - (*it).second->validity = validity; - to_ret = (*it).second; - *new_elem = false; - } - else - { - pair::iterator, bool> res = - response_cache.insert(pair( - key, new details::cache_entry(value, now.tv_sec, validity)) - ); - to_ret = (*res.first).second; - *new_elem = res.second; - } - } - if(already_in) - (*it).second->unlock(); - if(lock) - to_ret->lock(write); - pthread_rwlock_unlock(&cache_guard); - return to_ret; -} -void webserver::remove_from_cache(const std::string& key) -{ - pthread_rwlock_wrlock(&cache_guard); - map::iterator it(response_cache.find(key)); - if(it != response_cache.end()) - { - details::cache_entry* ce = (*it).second; - response_cache.erase(it); - delete ce; + std::string t_url = url; + + base_unescaper(&t_url, static_cast(cls)->unescaper); + mr->standardized_url = http_utils::standardize_url(t_url); + + mr->has_body = false; + + access_log(static_cast(cls), mr->complete_uri + " METHOD: " + method); + + // Case-sensitive per RFC 7230 §3.1.1: HTTP method is case-sensitive. + if (0 == strcmp(method, http_utils::http_method_get)) { + mr->callback = &http_resource::render_GET; + } else if (0 == strcmp(method, http_utils::http_method_post)) { + mr->callback = &http_resource::render_POST; + mr->has_body = true; + } else if (0 == strcmp(method, http_utils::http_method_put)) { + mr->callback = &http_resource::render_PUT; + mr->has_body = true; + } else if (0 == strcmp(method, http_utils::http_method_delete)) { + mr->callback = &http_resource::render_DELETE; + mr->has_body = true; + } else if (0 == strcmp(method, http_utils::http_method_patch)) { + mr->callback = &http_resource::render_PATCH; + mr->has_body = true; + } else if (0 == strcmp(method, http_utils::http_method_head)) { + mr->callback = &http_resource::render_HEAD; + } else if (0 == strcmp(method, http_utils::http_method_connect)) { + mr->callback = &http_resource::render_CONNECT; + } else if (0 == strcmp(method, http_utils::http_method_trace)) { + mr->callback = &http_resource::render_TRACE; + } else if (0 == strcmp(method, http_utils::http_method_options)) { + mr->callback = &http_resource::render_OPTIONS; } - pthread_rwlock_unlock(&cache_guard); -} -void webserver::clean_cache() -{ - pthread_rwlock_wrlock(&cache_guard); - response_cache.clear(); //manage this because obviously causes leaks - pthread_rwlock_unlock(&cache_guard); + return static_cast(cls)->requests_answer_first_step(connection, mr); } -void webserver::unlock_cache_entry(details::cache_entry* ce) -{ - ce->unlock(); -} - -void webserver::lock_cache_entry(details::cache_entry* ce) -{ - ce->lock(); -} - -void webserver::get_response(details::cache_entry* ce, http_response** res) -{ - *res = ce->response.ptr(); -} - -void webserver::remove_event_supplier(const std::string& id) -{ - pthread_rwlock_wrlock(&runguard); - event_suppliers.erase(id); - pthread_rwlock_unlock(&runguard); -} - -void webserver::register_event_supplier(const std::string& id, const details::event_tuple& evt) -{ - pthread_rwlock_wrlock(&runguard); - event_suppliers.insert(std::pair(id, evt)); - pthread_rwlock_unlock(&runguard); -} - -}; +} // namespace httpserver diff --git a/test/CPPLINT.cfg b/test/CPPLINT.cfg new file mode 100644 index 00000000..175a3836 --- /dev/null +++ b/test/CPPLINT.cfg @@ -0,0 +1 @@ +exclude_files=littletest.hpp diff --git a/test/Makefile.am b/test/Makefile.am index 9e706ef6..cdbacf26 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,6 +1,6 @@ # # This file is part of libhttpserver -# Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino +# Copyright (C) 2011-2019 Sebastiano Merlino # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -17,17 +17,36 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA LDADD = $(top_builddir)/src/libhttpserver.la + +if HAVE_GNUTLS +LDADD += -lgnutls +endif + +LDADD += -lcurl + AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/ METASOURCES = AUTO -check_PROGRAMS = basic http_utils +check_PROGRAMS = basic file_upload http_utils threaded nodelay string_utilities http_endpoint ban_system ws_start_stop authentication deferred http_resource http_response create_webserver MOSTLYCLEANFILES = *.gcda *.gcno *.gcov -basic_SOURCES = integ/basic.cpp +basic_SOURCES = integ/basic.cpp +file_upload_SOURCES = integ/file_upload.cpp +threaded_SOURCES = integ/threaded.cpp +ban_system_SOURCES = integ/ban_system.cpp +ws_start_stop_SOURCES = integ/ws_start_stop.cpp +authentication_SOURCES = integ/authentication.cpp +deferred_SOURCES = integ/deferred.cpp http_utils_SOURCES = unit/http_utils_test.cpp +string_utilities_SOURCES = unit/string_utilities_test.cpp +http_endpoint_SOURCES = unit/http_endpoint_test.cpp +nodelay_SOURCES = integ/nodelay.cpp +http_resource_SOURCES = unit/http_resource_test.cpp +http_response_SOURCES = unit/http_response_test.cpp +create_webserver_SOURCES = unit/create_webserver_test.cpp noinst_HEADERS = littletest.hpp -AM_CXXFLAGS += -lcurl -Wall -fPIC +AM_CXXFLAGS += -Wall -fPIC -Wno-overloaded-virtual if COND_GCOV AM_CFLAGS += -O0 --coverage --no-inline @@ -36,3 +55,7 @@ AM_LDFLAGS += -O0 --coverage -lgcov --no-inline endif TESTS = $(check_PROGRAMS) + +@VALGRIND_CHECK_RULES@ +VALGRIND_SUPPRESSIONS_FILES = libhttpserver.supp +EXTRA_DIST = libhttpserver.supp diff --git a/test/cert.pem b/test/cert.pem new file mode 100644 index 00000000..b95e01f0 --- /dev/null +++ b/test/cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUFSkcZr3SpJgnSFZ7usAd7EHeL6YwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDIwNDE5MzQyMFoXDTM2MDIw +MjE5MzQyMFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAotKw+oEWvVB3Gr5cQDeREfkrYz3wQr/iBXQcxieHgm2O ++zddEKgGIzZGLWAFt4dERt9EIPuhyIs5cX70d7SDPZEkq9ne1qg8wxo9BoLj6pGq +iLzbmfhjOsApSBIMEo9j461YPgJvmoPcR9WtJQwxtPCaBaDe/GuuQlE4c9Ocfn5c +Y/cQ7r0LpIXpz+2I3IXeMJNPClNTEcOn3jM/mdCkechsyGgwTSxup019HPQNCefY +27SRyjgKn476WTWP3HSzuz+vdJeeOsr3imCWAbLU0Y3g7bW9HddCKBpu+9Er7A8T +7Tizuqid4ZxWCBjoUKW3PGZXb5GN27hamdOuYXuu+wIDAQABo1MwUTAdBgNVHQ4E +FgQURWjt3upGzg4lwPOvAC5T6IIFjxAwHwYDVR0jBBgwFoAURWjt3upGzg4lwPOv +AC5T6IIFjxAwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEADn3r +keOKT0INpytFjwedeC9TNN0W0PFGoPCl6/aPQpRD5adY0xaOMaFMrfuew0I7dI0m +Ro9HICBQ4DLHB/ZwjvuioJSQlwYJ6SnatKlZib0qAHMvnSLr/rWUKu5KzLIhvXHs +6zG7/ZqRt6XlME4olJ/QzyhyPtXK2AumHdB/GJk9d//n4Qj+4cXSTA1KHxZPU67x +0Ow0zI0CRgDN4sYlgOcLwMI0I59MwXlzIeMR6E2YSxow7P+89kFMRmaO5N1aCSXl +PYOlkXbh4iZ2cBMj4dfQBA+cgkm+KjVr/jwpBlAZJswtkyDD+zJf+ua+z1eOczBv +HsZIDEqIkkqH/ZuV7w== +-----END CERTIFICATE----- diff --git a/test/client_cert.pem b/test/client_cert.pem new file mode 100644 index 00000000..b8e3d817 --- /dev/null +++ b/test/client_cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDMzCCAhugAwIBAgIUdGMRlO8Hu2eYWdVXgkHQ57NXeJ8wDQYJKoZIhvcNAQEL +BQAwKTEUMBIGA1UEAwwLVGVzdCBDbGllbnQxETAPBgNVBAoMCFRlc3QgT3JnMB4X +DTI2MDIwNDE5MjEzOVoXDTM2MDIwMjE5MjEzOVowKTEUMBIGA1UEAwwLVGVzdCBD +bGllbnQxETAPBgNVBAoMCFRlc3QgT3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAuZ5JmKPRbM9UfeQ9cJMne6Lt084gQsw+yI2hHwvkeYm+8c/HdQ3E +YKsCojON6X6gMInvblvvsJaRNtBOAUaHoOshDH9ZeZD3hsd3fmyxIqQKCOr1DoxZ ++72FmHNHcGfcti1KVwrxMHhL5TUhDJfoVPcH0OO0Yo7JI0PzdZTkoUVZN1mqQ3M2 +zS5KqyyQ/+M02VmUdI7CQezextCzQj2BLgyy2/WJOuEUUtDn35VXTt0bvs95ICnm +tWhKDHzIceJGCLUFbtVPmsj/zutYv6RUkg02nx3a+l3leD5kboLD41O8G1SwyAw2 +g7WfifFO0B0QDoAQ5sqgMrnHClAmc46FAwIDAQABo1MwUTAdBgNVHQ4EFgQUY6/3 +etxsZGycCVNS3/yVUP2mgOQwHwYDVR0jBBgwFoAUY6/3etxsZGycCVNS3/yVUP2m +gOQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAQxOYgHw3T+Ko +BXmegtQGWSBUuu2ootg26zIDt4x2dwT3fPuitFJZnywG/1EHs9qVPPlPbtqMqpmT +kxxcpanZPpTLwj+QbrrkqKfIq1qlXivrjsP+idALO4zouSJBTqpC+wksL2AxdegX +FF3zCMtw+LVxgrwU4Ml/ydNu1Z1Zq1KDZOXEOug9C/CABEgngQfr3IO9M7wQIYvf +pgieUOQxPM6O5kS0yBp/WGDwYjz0Ijbfp/yvel9eaQgvMT3rmKI8/fOCM5ax+IEk +0eJaz4dS9GkSQT+mAkAT/PKurkDpSmPz/If/CyLScSr6f/s/+fC0YLfZ95/FjNqx +O/ONgBdcbw== +-----END CERTIFICATE----- diff --git a/test/client_cert_no_cn.pem b/test/client_cert_no_cn.pem new file mode 100644 index 00000000..77f708ab --- /dev/null +++ b/test/client_cert_no_cn.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDHTCCAgWgAwIBAgIUZxrdiFzIPZz71PW6KGhQgzUJYY0wDQYJKoZIhvcNAQEL +BQAwHjEcMBoGA1UECgwTVGVzdCBPcmcgV2l0aG91dCBDTjAeFw0yNjAyMDUwMDAy +NTFaFw0zNjAyMDMwMDAyNTFaMB4xHDAaBgNVBAoME1Rlc3QgT3JnIFdpdGhvdXQg +Q04wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQClSmDXcECe5ID3F/yb +PB7XrT2jGZq51MLnfv+WSRrj4ORuae03VhrCUw1oqodxZgwU89xtjkfLX8iItH3E +20RfhVj/GBZuHw+7iGyNP5dHiSiYq5bfNFbpNI/yO3/NEKflALQ0DZDjeGaZhv08 +wWDRkea//oFJfGeJM6IRcmXv0MG7woZohkQfobfvnj8plMl0PAkHGEcnZFhauvjB +d4d/TcmZhuDfRychP2HRy4UhqKuisa0wvLvE7KN4OZsegRYIIVKMDWl6odquzyD7 +KE6POT+BTv7WoCP3UWlYJtX27kx8iJMFNWGUv2DGllBY4Q9o1rhJr5pFBtd873Xh +wE29AgMBAAGjUzBRMB0GA1UdDgQWBBQdtRld75yAChAw/rcfqtTB0Prq/TAfBgNV +HSMEGDAWgBQdtRld75yAChAw/rcfqtTB0Prq/TAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4IBAQB9ynRcMBZp2jkekBsvtyyydp4OKWBwXhiLX5jJWMKL +GqEm9quqM7iH+W7trxRz1GHrqkHRz37TUp8jU9mnDZ7aaXIbhBu4RMnao36O3R6d +lA43mN+4ZTUecsJAY9hR4X3+oLLndrLlmte8NkpwKNIuo32XEfu97wXUuEP5W17s +GJh3EGh7lrz8TS4GO4Oek/qK+6dgDhHLQcmqoRUnBj1mb+0ffcsWTyWFRD9W2oyW +L1S1t/Q6L4sJEIzvU1qUtO5kiWBsd3uq5oZibKYdU1nYs9nucFE8Fers+qafqbKR +wHHH4vsiZVIU1n7yqG4kLi5uL4KO5XcYJmjANB0lEM8r +-----END CERTIFICATE----- diff --git a/test/client_cert_untrusted.pem b/test/client_cert_untrusted.pem new file mode 100644 index 00000000..39c29402 --- /dev/null +++ b/test/client_cert_untrusted.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDRzCCAi+gAwIBAgIUGKzbXdmC0G8pSeYuFwMoWQCHYuEwDQYJKoZIhvcNAQEL +BQAwMzEZMBcGA1UEAwwQVW50cnVzdGVkIENsaWVudDEWMBQGA1UECgwNVW50cnVz +dGVkIE9yZzAeFw0yNjAyMDUwMDAyNTdaFw0zNjAyMDMwMDAyNTdaMDMxGTAXBgNV +BAMMEFVudHJ1c3RlZCBDbGllbnQxFjAUBgNVBAoMDVVudHJ1c3RlZCBPcmcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCT9JwyKk5xPhz5aHlYkN1u4jCm +SeIavIicQCbYgcmCwFgdH0i+fP1s2MrcZgeEoiqN4VK8zmxtpj1QME0KGAImwn9k +ffROftIQ0pdebIvImD/QWmR+bNCXQYWmyX6c52ESKPrCSVPstHj1r2pOCHA0j/s0 +4V1gMNI/snv4CxZ+H+JGBikE+ycvZYTgZa3HiAjm9rtQu1zU5blwuZ2NhUumkdfB +cc/oC+6yxUiPaD84poLefmF9vdqmGKEIWxWQB+Ijvll1iieEf47lOqxokLWWWsxH +bfWOGagzdQJKHzeDj76KjfTbSTsMsIyCxAbJU2K53ccCLlQYYHzn1h1zaQntAgMB +AAGjUzBRMB0GA1UdDgQWBBS3oS6d4JFK/ZrF4uDyAxqP8BDJCDAfBgNVHSMEGDAW +gBS3oS6d4JFK/ZrF4uDyAxqP8BDJCDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBrltJfDg26jzDEsWoqJM7/xuYM5EVebbIiFUQgM7AWPtFGewbM +cb9TPPHRMx3izv2E95JWaQ0YXSuxkGISkJQEisTPzssIDQfrpcAyZdMgR5XSWKVC +t8ychNwE7rKdJfRGMoXrqAD4R1h0NQpl0V86rwieA23voBOK/5xE6ja0JIsso8YG +mQpqxPROxtpJ4J59BnwQnhhZ66GQ+HpqTN1cc8Pl0kqVzBvwnkeiz6/6h4qX6dVQ +eI0OA9LARB1uYqK9sTdZ2KA45rJLXDsOeWB/WAs0ZOSMQSO5qkFYA1FkPpwc+mcR +ckU7IZOyPFf4Xr98Jbaf+LFcq8WsCA10xzJ2 +-----END CERTIFICATE----- diff --git a/test/client_key.pem b/test/client_key.pem new file mode 100644 index 00000000..49a96fa0 --- /dev/null +++ b/test/client_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5nkmYo9Fsz1R9 +5D1wkyd7ou3TziBCzD7IjaEfC+R5ib7xz8d1DcRgqwKiM43pfqAwie9uW++wlpE2 +0E4BRoeg6yEMf1l5kPeGx3d+bLEipAoI6vUOjFn7vYWYc0dwZ9y2LUpXCvEweEvl +NSEMl+hU9wfQ47RijskjQ/N1lOShRVk3WapDczbNLkqrLJD/4zTZWZR0jsJB7N7G +0LNCPYEuDLLb9Yk64RRS0OfflVdO3Ru+z3kgKea1aEoMfMhx4kYItQVu1U+ayP/O +61i/pFSSDTafHdr6XeV4PmRugsPjU7wbVLDIDDaDtZ+J8U7QHRAOgBDmyqAyuccK +UCZzjoUDAgMBAAECggEABpAV/7txuPxUUJvZykRXBd7ltB1FHxIpTsj8fzmvQIGC +H/rzN+xz2uEhAjV6w9yHcxU6wyGlW2PaIDXZlDT3WwAFQUkIE61wUF0hbfW6MO8w +2vjdD5XJzAX668AjsKc+HVjY7d6ZVtfiAxQk+3+Be5jnt1czC7W1tIqqAyhvtFNN +Ga7JLHbybxSi5batdsZXdlchCOtD0ZQYd0fS/WpB+RSyVj01j0trkg+uL/ok0XFj +Y9SYIWKIdPn2gPs9oUXhxjoTUQYi6iqZD8lp/4qDbCw4nidz/rVCu1UuCtEV44it +00AqSO3NadRLpTmxi7TKDj+IekzXKJUEKzlNtscXIQKBgQDn4C5q8k2r2wwV+uJ9 +hU0d0YDCAzyUlOPjXBv6/dj62hRKvmo0fbisTXKyrSMLoXNOQdkYAASQoRYUqoL9 +feKpDU+luCfGnr9hg/odM4ESP16UZbJirajHll/RQp4yYAi3vhvEvzgQKiUInGwI +G4BC6/Ah+9YTSyNMDnN2Uf45IwKBgQDM7hQUswlrH+a7utmuHnMBPvwUMPOsbpF5 +lHjYwjOmWSAwQa846n5bJvHMuJZohX0ntR/skl0lYAuh72sFsKQvnQtiIhZ6rkbf +YMh9RPgVfAXFJlFAV53iw+u3pghSnkeIugbCoYn2Lz8To4RYD9mlj4pr0K5hxVaT +tGvp2QlyoQKBgGfW4FKqghgVN3tcaDN4D8nruXKpCmcrqkZ2SF2FcrccFHxIe71Y +E+ytnlDf8lLSEZYZLQRvdZvjV8UXeyPUTT4RpPp81us+ykv8U3TiTMoEMPHZ/SHt +zSjccbp/z+KVWTIX482fKJcsmHsbudGDp1PQ3zAI3Jy1SHBWBGUXYPbrAoGBALtD +R0hO/mlMonyj1uzcWD0oQBN3VAQamYbfHLr+Y1I8GUTfkO3SohpLcSOg/ZiPevmA +8qYsbT+ND7QvYr21V6NGv7Mx8Ra0EIFpIGwQTR7c0S0BwbepGNayL8EG0I4mormX +PDw4fyheriYVAwexnDJFA7lX3THssRuSABaVxKNhAoGAY0ejNhVt4pXTeXWH3l/Z +TFPg+EKFCvn9dM8YeN+X1hMurRUb7cg9blx71mQmcYRZtnqQ1EmLSe1iMT1dXTi9 +xLdK5M7LR+rMF0FGHmj8po3tLzkQwYqDjVoEa8cMJun0sZdjP4npP7XA/9T6LMdj +7kCN3QfCVwW6uHLA27zbDKc= +-----END PRIVATE KEY----- diff --git a/test/client_key_no_cn.pem b/test/client_key_no_cn.pem new file mode 100644 index 00000000..59c028ba --- /dev/null +++ b/test/client_key_no_cn.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQClSmDXcECe5ID3 +F/ybPB7XrT2jGZq51MLnfv+WSRrj4ORuae03VhrCUw1oqodxZgwU89xtjkfLX8iI +tH3E20RfhVj/GBZuHw+7iGyNP5dHiSiYq5bfNFbpNI/yO3/NEKflALQ0DZDjeGaZ +hv08wWDRkea//oFJfGeJM6IRcmXv0MG7woZohkQfobfvnj8plMl0PAkHGEcnZFha +uvjBd4d/TcmZhuDfRychP2HRy4UhqKuisa0wvLvE7KN4OZsegRYIIVKMDWl6odqu +zyD7KE6POT+BTv7WoCP3UWlYJtX27kx8iJMFNWGUv2DGllBY4Q9o1rhJr5pFBtd8 +73XhwE29AgMBAAECggEAGBo28eNkAOd4KM/eHXLQWongDYr7xXJRc3lQ4sTJP4Z5 +OOKIXUPYhhKfR25qbq4/P8TplS4kqPLQJqMPHegNWdJzjksgZjFwVVvI3HXz5NIK +0exfhS+4Jqxr+xoTAj+WA+4s2NRLluflKikFf1kBeb3JRKDjkGgsHtUhImMomyX9 +Q9GaElwM6AQTKRxChlmglxnErgYip59l/ECaGiU/sSMK5vmqiOLeBk7r++xjOFcH +9JbBDVKJT+0urwYn8Istwe7UnoYStPUwIVjBlcvI1d+5k3OuLsJ0QJ8ThXBIN9Pn +wMBHsR/vkC06I1eS4htaJEdrCS/R+MQUXHaP9f6o3wKBgQDPE6kh4fBj44L7SAi+ +ooKwJrNOE20IVR7LSPQ1ZfWoQVSkqv/hpZyN+zlFOpLepLNVpzR3/XXAUg1LRJSU +lvN+fRSjGQO2uNgt/APs5+wsZvwtuQHfPgh4zcNtr5GqxLVhH7V5YgEZ02SWSlmx +c9x+ETQ6zhV05sRfYh44DZKK1wKBgQDMV2rKhbK7oI/3HVU/TyWFkvWJK/t79Uuu +wFoPpKf0oqnImzOGb8EMi/ecjEgktRKUnBB/hY3tQIZCB3LaOujIp/OVY3Saa1Gb +s1G1QlTvhgWbLHEHeZvA4qolmEYnNxgupmWounrICk3bd5Hr0zX8K2fzKPAlDs4O +65r/bK4NiwKBgQC0vNNFWH/Jn3zmN8QyJ4NrnguoHLpwqGK9SYqkxL46QfNP2lSG +LVdMcTZWXz5rh1NjchIQnK/W0Yb65/vLCUmzYBbQF/gu1n0Q/cKrVu3C/4whmDWz +FOCuF+H37WJ1q0UoZVWugUS2ttQ3fON2R8ruWbO9k7wUkYpaOjhn8iiydwKBgGSw +9tiRBT/boNVeSPGHaK/neMJ9P9EXUJHuCvMGahTsSsmlYMBwNSqflgY4QhyEdYFx +XdfY0dUFJKNI1FmhCbBGworslTq6g148AJlW9E+LNRv/zDqovA1SJBGedYNBbNMf +/5wjN/l2ymLJCsiwLTvzj6eMlrlMEFHd22TeAu59AoGAVWK/s5loFUk0XUrkRC6m +Ys28G2jTHsgwPvfIBRIsoiN1j4w2rrMw/79tpBscCQK6v5Z7jCiahOz1Xe8fNkId +Z8oiErDKKOiUY86umg421mhuwzBU+gXsiHuBYrdH3e3aw+fGYyMNpbFdRvJVy2lv +pm+6ev0dkUX/fjUl3UNydW8= +-----END PRIVATE KEY----- diff --git a/test/client_key_untrusted.pem b/test/client_key_untrusted.pem new file mode 100644 index 00000000..3850dfa9 --- /dev/null +++ b/test/client_key_untrusted.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCT9JwyKk5xPhz5 +aHlYkN1u4jCmSeIavIicQCbYgcmCwFgdH0i+fP1s2MrcZgeEoiqN4VK8zmxtpj1Q +ME0KGAImwn9kffROftIQ0pdebIvImD/QWmR+bNCXQYWmyX6c52ESKPrCSVPstHj1 +r2pOCHA0j/s04V1gMNI/snv4CxZ+H+JGBikE+ycvZYTgZa3HiAjm9rtQu1zU5blw +uZ2NhUumkdfBcc/oC+6yxUiPaD84poLefmF9vdqmGKEIWxWQB+Ijvll1iieEf47l +OqxokLWWWsxHbfWOGagzdQJKHzeDj76KjfTbSTsMsIyCxAbJU2K53ccCLlQYYHzn +1h1zaQntAgMBAAECggEACwdw8qG0tzxUwe1yc5JY71XCdU2MopxAkrqK1W4srKfU +lFcrVQfRhwuiE6EyGPD9uxXQ1ReOKEj8HmjQqq/0zm7b5Yx+FFvfzOE7PLlasi6n +Pct/MkK/nzGDL6uq2dy69QpDpw1QSZTVGiYOsUJvxXtLfpBOJZ1+DsF/UZOCBGS+ +9/xC7gU3bGDhjAgzgPBhyO+WVPLORvV6/mRVQalB7MiH/kgZDKZi0dZ50qgCyc95 +CUnrf7Mck0c782SN7rN3W4ZAKWb8YaWCtGUGYpwk7E/eIw23eCNQZjeLB21w4uge +7rCSRzEFy1DeLgWWSPnNfHO59fJ3ii31aHQWRu1YAQKBgQDQ7JJ/S+FWMEkso7A1 +qNraAzyyFYXMeV79/VSbFWO1dT0+mxfG9LekAGzF7BLOwW7mKM+iJqWaSg1Up9GU +FDINV8R4s1+0syKdBLNrd02qGYkqaSs1A75q2n2ExvJsBwNXcUtHjcUIL3ui56aH +RpMX3GomYCC6ormw4nFENWTlJQKBgQC1SytEA5LS3okrZzzCilnBr2SOBjBdQrxK +D2CgVqwE0/8blmqy5w/ZE/Rn44RirQmhAG1g7qS+WQy8cvTBUCnqXov9kAtnL7oP +0iZFA5Mjo2o80wT+1haZolUMqHXchC5nBXGUBcwulZdZdFmjo3H64vpHQLQE9D7a +f06OmpzLKQKBgCpv41H4F81qAXMPzLsZkVq3TZzewk7GWIU+7/CQZ7B0H/yXhDzl +eGfXrkCFs0xL/jrCD2rgbsLoR8zqSafKcmBDc6UQyl/qAx3h1o/9q8jhZvs2YZBj +MkqCFvzhbFyFECiy2peuNFd1TafJZgoUS8yM+QLSg9NlOlKzrE4uilABAoGAOTv7 +8sL2DWB4CZ3UDs7Cu2T15+iISEkTTIZCSRxTvkp3VWxNTyGnXS7xkALB/q0GRy/t +WBa/J+DRJoVcQ9NdCELFC034a6Ejqm776fnQ8AVdOsqb3yATjnkzRIXCf9WzGI8d +Zk/WQDa1y2XyDrlA+KXDwc7phk7dsPlUAa1KJtECgYBW8MlkBt76OEg+J0ZMgBVU +ze4fuCAEtFbetd7DjsId4rSqsawWaJZX0ZRo8gZsLUrNM3hcZfZTELF2uL5eNhMu +t/ig52WfLVHwmtVAdIIbDdyKFO/4mY84IbWjAmYI08SvBb+Mk5KTJR80B8idVM3D +Yge1bloM2atDhO63yM1LwA== +-----END PRIVATE KEY----- diff --git a/test/integ/authentication.cpp b/test/integ/authentication.cpp new file mode 100644 index 00000000..b043f566 --- /dev/null +++ b/test/integ/authentication.cpp @@ -0,0 +1,1136 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#if defined(_WIN32) && !defined(__CYGWIN__) +#define _WINDOWS +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x600 +#include +#include +#else +#include +#include +#include +#endif + +#include +#include +#include + +#include "./httpserver.hpp" +#include "./littletest.hpp" + +#define MY_OPAQUE "11733b200778ce33060f31c9af70a870ba96ddd4" + +using std::shared_ptr; +using httpserver::webserver; +using httpserver::create_webserver; +using httpserver::http_response; +#ifdef HAVE_BAUTH +using httpserver::basic_auth_fail_response; +#endif // HAVE_BAUTH +#ifdef HAVE_DAUTH +using httpserver::digest_auth_fail_response; +#endif // HAVE_DAUTH +using httpserver::string_response; +using httpserver::http_resource; +using httpserver::http_request; + +#ifdef HTTPSERVER_PORT +#define PORT HTTPSERVER_PORT +#else +#define PORT 8080 +#endif // PORT + +#define STR2(p) #p +#define STR(p) STR2(p) +#define PORT_STRING STR(PORT) + +size_t writefunc(void *ptr, size_t size, size_t nmemb, std::string *s) { + s->append(reinterpret_cast(ptr), size*nmemb); + return size*nmemb; +} + +#ifdef HAVE_BAUTH +class user_pass_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + if (req.get_user() != "myuser" || req.get_pass() != "mypass") { + return std::make_shared("FAIL", "examplerealm"); + } + return std::make_shared(std::string(req.get_user()) + " " + std::string(req.get_pass()), 200, "text/plain"); + } +}; +#endif // HAVE_BAUTH + +#ifdef HAVE_DAUTH +class digest_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + if (req.get_digested_user() == "") { + return std::make_shared("FAIL", "examplerealm", MY_OPAQUE, true); + } else { + bool reload_nonce = false; + if (!req.check_digest_auth("examplerealm", "mypass", 300, &reload_nonce)) { + return std::make_shared("FAIL", "examplerealm", MY_OPAQUE, reload_nonce); + } + } + return std::make_shared("SUCCESS", 200, "text/plain"); + } +}; +#endif // HAVE_DAUTH + +LT_BEGIN_SUITE(authentication_suite) + void set_up() { + } + + void tear_down() { + } +LT_END_SUITE(authentication_suite) + +#ifdef HAVE_BAUTH +LT_BEGIN_AUTO_TEST(authentication_suite, base_auth) + webserver ws = create_webserver(PORT); + + user_pass_resource user_pass; + LT_ASSERT_EQ(true, ws.register_resource("base", &user_pass)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_USERNAME, "myuser"); + curl_easy_setopt(curl, CURLOPT_PASSWORD, "mypass"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "myuser mypass"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(base_auth) + +LT_BEGIN_AUTO_TEST(authentication_suite, base_auth_fail) + webserver ws = create_webserver(PORT); + + user_pass_resource user_pass; + LT_ASSERT_EQ(true, ws.register_resource("base", &user_pass)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_USERNAME, "myuser"); + curl_easy_setopt(curl, CURLOPT_PASSWORD, "wrongpass"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "FAIL"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(base_auth_fail) +#endif // HAVE_BAUTH + +// do not run the digest auth tests on windows as curl +// appears to have problems with it. +// Will fix this separately +// Also skip if libmicrohttpd was built without digest auth support +#if !defined(_WINDOWS) && defined(HAVE_DAUTH) + +// Pre-computed MD5 hash of "myuser:examplerealm:mypass" +// printf "myuser:examplerealm:mypass" | md5sum +// 6ceef750e0130d6528b938c3abd94110 +static const unsigned char PRECOMPUTED_HA1_MD5[16] = { + 0x6c, 0xee, 0xf7, 0x50, 0xe0, 0x13, 0x0d, 0x65, + 0x28, 0xb9, 0x38, 0xc3, 0xab, 0xd9, 0x41, 0x10 +}; + +// Pre-computed SHA-256 hash of "myuser:examplerealm:mypass" +// printf "myuser:examplerealm:mypass" | sha256sum +// d4ff5b1795b23b4c625975959f3276526f3f4f4ef7d22083207e02d7c4bd8a05 +static const unsigned char PRECOMPUTED_HA1_SHA256[32] = { + 0xd4, 0xff, 0x5b, 0x17, 0x95, 0xb2, 0x3b, 0x4c, + 0x62, 0x59, 0x75, 0x95, 0x9f, 0x32, 0x76, 0x52, + 0x6f, 0x3f, 0x4f, 0x4e, 0xf7, 0xd2, 0x20, 0x83, + 0x20, 0x7e, 0x02, 0xd7, 0xc4, 0xbd, 0x8a, 0x05 +}; + +class digest_ha1_md5_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + if (req.get_digested_user() == "") { + return std::make_shared( + "FAIL", "examplerealm", MY_OPAQUE, true, + httpserver::http::http_utils::http_ok, + httpserver::http::http_utils::text_plain, + httpserver::http::http_utils::digest_algorithm::MD5); + } + bool reload_nonce = false; + if (!req.check_digest_auth_ha1("examplerealm", PRECOMPUTED_HA1_MD5, + httpserver::http::http_utils::md5_digest_size, 300, &reload_nonce, + httpserver::http::http_utils::digest_algorithm::MD5)) { + return std::make_shared( + "FAIL", "examplerealm", MY_OPAQUE, reload_nonce, + httpserver::http::http_utils::http_ok, + httpserver::http::http_utils::text_plain, + httpserver::http::http_utils::digest_algorithm::MD5); + } + return std::make_shared("SUCCESS", 200, "text/plain"); + } +}; + +class digest_ha1_sha256_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + if (req.get_digested_user() == "") { + return std::make_shared( + "FAIL", "examplerealm", MY_OPAQUE, true, + httpserver::http::http_utils::http_ok, + httpserver::http::http_utils::text_plain, + httpserver::http::http_utils::digest_algorithm::SHA256); + } + bool reload_nonce = false; + if (!req.check_digest_auth_ha1("examplerealm", PRECOMPUTED_HA1_SHA256, + httpserver::http::http_utils::sha256_digest_size, 300, &reload_nonce, + httpserver::http::http_utils::digest_algorithm::SHA256)) { + return std::make_shared( + "FAIL", "examplerealm", MY_OPAQUE, reload_nonce, + httpserver::http::http_utils::http_ok, + httpserver::http::http_utils::text_plain, + httpserver::http::http_utils::digest_algorithm::SHA256); + } + return std::make_shared("SUCCESS", 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth) + webserver ws = create_webserver(PORT) + .digest_auth_random("myrandom") + .nonce_nc_size(300); + + digest_resource digest; + LT_ASSERT_EQ(true, ws.register_resource("base", &digest)); + ws.start(false); + +#if defined(_WINDOWS) + curl_global_init(CURL_GLOBAL_WIN32); +#else + curl_global_init(CURL_GLOBAL_ALL); +#endif + + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); +#if defined(_WINDOWS) + curl_easy_setopt(curl, CURLOPT_USERPWD, "examplerealm/myuser:mypass"); +#else + curl_easy_setopt(curl, CURLOPT_USERPWD, "myuser:mypass"); +#endif + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "SUCCESS"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(digest_auth) + +LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_wrong_pass) + webserver ws = create_webserver(PORT) + .digest_auth_random("myrandom") + .nonce_nc_size(300); + + digest_resource digest; + LT_ASSERT_EQ(true, ws.register_resource("base", &digest)); + ws.start(false); + +#if defined(_WINDOWS) + curl_global_init(CURL_GLOBAL_WIN32); +#else + curl_global_init(CURL_GLOBAL_ALL); +#endif + + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); +#if defined(_WINDOWS) + curl_easy_setopt(curl, CURLOPT_USERPWD, "examplerealm/myuser:wrongpass"); +#else + curl_easy_setopt(curl, CURLOPT_USERPWD, "myuser:wrongpass"); +#endif + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "FAIL"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(digest_auth_wrong_pass) + +LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_with_ha1_md5) + webserver ws = create_webserver(PORT) + .digest_auth_random("myrandom") + .nonce_nc_size(300); + + digest_ha1_md5_resource digest_ha1; + LT_ASSERT_EQ(true, ws.register_resource("base", &digest_ha1)); + ws.start(false); + +#if defined(_WINDOWS) + curl_global_init(CURL_GLOBAL_WIN32); +#else + curl_global_init(CURL_GLOBAL_ALL); +#endif + + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); +#if defined(_WINDOWS) + curl_easy_setopt(curl, CURLOPT_USERPWD, "examplerealm/myuser:mypass"); +#else + curl_easy_setopt(curl, CURLOPT_USERPWD, "myuser:mypass"); +#endif + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "SUCCESS"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(digest_auth_with_ha1_md5) + +LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_with_ha1_md5_wrong_pass) + webserver ws = create_webserver(PORT) + .digest_auth_random("myrandom") + .nonce_nc_size(300); + + digest_ha1_md5_resource digest_ha1; + LT_ASSERT_EQ(true, ws.register_resource("base", &digest_ha1)); + ws.start(false); + +#if defined(_WINDOWS) + curl_global_init(CURL_GLOBAL_WIN32); +#else + curl_global_init(CURL_GLOBAL_ALL); +#endif + + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); +#if defined(_WINDOWS) + curl_easy_setopt(curl, CURLOPT_USERPWD, "examplerealm/myuser:wrongpass"); +#else + curl_easy_setopt(curl, CURLOPT_USERPWD, "myuser:wrongpass"); +#endif + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "FAIL"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(digest_auth_with_ha1_md5_wrong_pass) + +LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_with_ha1_sha256) + webserver ws = create_webserver(PORT) + .digest_auth_random("myrandom") + .nonce_nc_size(300); + + digest_ha1_sha256_resource digest_ha1; + LT_ASSERT_EQ(true, ws.register_resource("base", &digest_ha1)); + ws.start(false); + +#if defined(_WINDOWS) + curl_global_init(CURL_GLOBAL_WIN32); +#else + curl_global_init(CURL_GLOBAL_ALL); +#endif + + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); +#if defined(_WINDOWS) + curl_easy_setopt(curl, CURLOPT_USERPWD, "examplerealm/myuser:mypass"); +#else + curl_easy_setopt(curl, CURLOPT_USERPWD, "myuser:mypass"); +#endif + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "SUCCESS"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(digest_auth_with_ha1_sha256) + +LT_BEGIN_AUTO_TEST(authentication_suite, digest_auth_with_ha1_sha256_wrong_pass) + webserver ws = create_webserver(PORT) + .digest_auth_random("myrandom") + .nonce_nc_size(300); + + digest_ha1_sha256_resource digest_ha1; + LT_ASSERT_EQ(true, ws.register_resource("base", &digest_ha1)); + ws.start(false); + +#if defined(_WINDOWS) + curl_global_init(CURL_GLOBAL_WIN32); +#else + curl_global_init(CURL_GLOBAL_ALL); +#endif + + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); +#if defined(_WINDOWS) + curl_easy_setopt(curl, CURLOPT_USERPWD, "examplerealm/myuser:wrongpass"); +#else + curl_easy_setopt(curl, CURLOPT_USERPWD, "myuser:wrongpass"); +#endif + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "FAIL"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(digest_auth_with_ha1_sha256_wrong_pass) + +// Resource that tests get_digested_user() caching +// Covers http_request.cpp lines 293-295 (cache hit) and 300 (nullptr branch) +class digest_user_cache_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + // First call - will populate cache (line 300 nullptr or non-null branch) + std::string user1 = std::string(req.get_digested_user()); + + // Second call - should hit cache (lines 293-295) + std::string user2 = std::string(req.get_digested_user()); + + // Verify caching works correctly (both calls return same value) + if (user1 != user2) { + return std::make_shared("CACHE_MISMATCH", 500, "text/plain"); + } + + if (user1.empty()) { + // No digest auth provided - tests the nullptr branch (line 299-300) + return std::make_shared("NO_DIGEST_USER", 200, "text/plain"); + } + + // Return the digested user (tests cache hit with valid user) + return std::make_shared("USER:" + user1, 200, "text/plain"); + } +}; + +// Test digested user caching when no digest auth is provided (nullptr branch) +LT_BEGIN_AUTO_TEST(authentication_suite, digest_user_cache_no_auth) + webserver ws = create_webserver(PORT); + + digest_user_cache_resource resource; + LT_ASSERT_EQ(true, ws.register_resource("cache_test", &resource)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + // No authentication - should trigger nullptr branch in get_digested_user + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/cache_test"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "NO_DIGEST_USER"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(digest_user_cache_no_auth) + +// Test digested user caching with digest auth (cache hit with valid user) +LT_BEGIN_AUTO_TEST(authentication_suite, digest_user_cache_with_auth) + webserver ws = create_webserver(PORT) + .digest_auth_random("myrandom") + .nonce_nc_size(300); + + digest_user_cache_resource resource; + LT_ASSERT_EQ(true, ws.register_resource("cache_test", &resource)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + curl_easy_setopt(curl, CURLOPT_USERPWD, "testuser:testpass"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/cache_test"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 150L); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // After digest auth handshake, the server should return USER:testuser + // or NO_DIGEST_USER if no auth was provided. With CURLAUTH_DIGEST, + // curl will respond to the 401 challenge and include auth headers. + // The resource calls get_digested_user twice to test caching. + // Check that response is not empty and not a cache mismatch + LT_CHECK_EQ(s.find("CACHE_MISMATCH") == std::string::npos, true); + // Should contain either "USER:" (auth worked) or "NO_DIGEST_USER" (fallback) + LT_CHECK_EQ(s.find("USER:") != std::string::npos || s == "NO_DIGEST_USER", true); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(digest_user_cache_with_auth) + +#endif + +#ifdef HAVE_BAUTH +// Simple resource for centralized auth tests +class simple_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("SUCCESS", 200, "text/plain"); + } +}; + +// Centralized authentication handler +std::shared_ptr centralized_auth_handler(const http_request& req) { + if (req.get_user() != "admin" || req.get_pass() != "secret") { + return std::make_shared("Unauthorized", "testrealm"); + } + return nullptr; // Allow request +} + +LT_BEGIN_AUTO_TEST(authentication_suite, centralized_auth_fail) + webserver ws = create_webserver(PORT) + .auth_handler(centralized_auth_handler); + + simple_resource sr; + LT_ASSERT_EQ(true, ws.register_resource("protected", &sr)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/protected"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 401); + LT_CHECK_EQ(s, "Unauthorized"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(centralized_auth_fail) + +LT_BEGIN_AUTO_TEST(authentication_suite, centralized_auth_success) + webserver ws = create_webserver(PORT) + .auth_handler(centralized_auth_handler); + + simple_resource sr; + LT_ASSERT_EQ(true, ws.register_resource("protected", &sr)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + curl_easy_setopt(curl, CURLOPT_USERNAME, "admin"); + curl_easy_setopt(curl, CURLOPT_PASSWORD, "secret"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/protected"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 200); + LT_CHECK_EQ(s, "SUCCESS"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(centralized_auth_success) + +LT_BEGIN_AUTO_TEST(authentication_suite, auth_skip_paths) + webserver ws = create_webserver(PORT) + .auth_handler(centralized_auth_handler) + .auth_skip_paths({"/health", "/public/*"}); + + simple_resource sr; + LT_ASSERT_EQ(true, ws.register_resource("health", &sr)); + LT_ASSERT_EQ(true, ws.register_resource("public/info", &sr)); + LT_ASSERT_EQ(true, ws.register_resource("protected", &sr)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + CURL *curl; + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + std::string s; + + // Test /health (exact match skip path) - should succeed without auth + curl = curl_easy_init(); + s = ""; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/health"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 200); + LT_CHECK_EQ(s, "SUCCESS"); + curl_easy_cleanup(curl); + + // Test /public/info (wildcard skip path) - should succeed without auth + curl = curl_easy_init(); + s = ""; + http_code = 0; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/public/info"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 200); + LT_CHECK_EQ(s, "SUCCESS"); + curl_easy_cleanup(curl); + + // Test /protected (not in skip paths) - should fail without auth + curl = curl_easy_init(); + s = ""; + http_code = 0; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/protected"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 401); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(auth_skip_paths) + +// Test that wildcard doesn't match partial prefix +// /publicinfo should NOT match /public/* (wildcard requires the slash) +LT_BEGIN_AUTO_TEST(authentication_suite, auth_skip_paths_no_partial_match) + webserver ws = create_webserver(PORT) + .auth_handler(centralized_auth_handler) + .auth_skip_paths({"/public/*"}); + + simple_resource sr; + LT_ASSERT_EQ(true, ws.register_resource("publicinfo", &sr)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + + // /publicinfo should NOT be skipped (doesn't match /public/*) + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/publicinfo"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 401); // Should require auth + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(auth_skip_paths_no_partial_match) + +// Test deeply nested wildcard paths +LT_BEGIN_AUTO_TEST(authentication_suite, auth_skip_paths_deep_nested) + webserver ws = create_webserver(PORT) + .auth_handler(centralized_auth_handler) + .auth_skip_paths({"/api/v1/public/*"}); + + simple_resource sr; + LT_ASSERT_EQ(true, ws.register_resource("api/v1/public/users/list", &sr)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + + // Deep nested path should be skipped + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/api/v1/public/users/list"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 200); + LT_CHECK_EQ(s, "SUCCESS"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(auth_skip_paths_deep_nested) + +// Test POST method with centralized auth +class post_resource : public http_resource { + public: + shared_ptr render_POST(const http_request&) { + return std::make_shared("POST_SUCCESS", 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(authentication_suite, centralized_auth_post_method) + webserver ws = create_webserver(PORT) + .auth_handler(centralized_auth_handler); + + post_resource pr; + LT_ASSERT_EQ(true, ws.register_resource("data", &pr)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + + // POST without auth should fail + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/data"); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "test=data"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 401); + curl_easy_cleanup(curl); + + // POST with auth should succeed + curl = curl_easy_init(); + s = ""; + http_code = 0; + curl_easy_setopt(curl, CURLOPT_USERNAME, "admin"); + curl_easy_setopt(curl, CURLOPT_PASSWORD, "secret"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/data"); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "test=data"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 200); + LT_CHECK_EQ(s, "POST_SUCCESS"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(centralized_auth_post_method) + +// Test wrong credentials (different from no credentials) +LT_BEGIN_AUTO_TEST(authentication_suite, centralized_auth_wrong_credentials) + webserver ws = create_webserver(PORT) + .auth_handler(centralized_auth_handler); + + simple_resource sr; + LT_ASSERT_EQ(true, ws.register_resource("protected", &sr)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + + // Wrong username + curl_easy_setopt(curl, CURLOPT_USERNAME, "wronguser"); + curl_easy_setopt(curl, CURLOPT_PASSWORD, "secret"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/protected"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 401); + curl_easy_cleanup(curl); + + // Wrong password + curl = curl_easy_init(); + s = ""; + http_code = 0; + curl_easy_setopt(curl, CURLOPT_USERNAME, "admin"); + curl_easy_setopt(curl, CURLOPT_PASSWORD, "wrongpass"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/protected"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 401); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(centralized_auth_wrong_credentials) + +// Test that 404 is returned for non-existent resources (auth doesn't interfere) +LT_BEGIN_AUTO_TEST(authentication_suite, centralized_auth_not_found) + webserver ws = create_webserver(PORT) + .auth_handler(centralized_auth_handler); + + simple_resource sr; + LT_ASSERT_EQ(true, ws.register_resource("exists", &sr)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + + // Non-existent resource without auth - should get 401 (auth checked first) + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/nonexistent"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + // Note: Auth is only checked when resource is found, so 404 should be returned + LT_CHECK_EQ(http_code, 404); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(centralized_auth_not_found) + +// Test no auth handler (default behavior - no auth required) +LT_BEGIN_AUTO_TEST(authentication_suite, no_auth_handler_default) + webserver ws = create_webserver(PORT); // No auth_handler + + simple_resource sr; + LT_ASSERT_EQ(true, ws.register_resource("open", &sr)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + + // Should succeed without any auth + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/open"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 200); + LT_CHECK_EQ(s, "SUCCESS"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(no_auth_handler_default) + +// Test multiple skip paths +LT_BEGIN_AUTO_TEST(authentication_suite, auth_multiple_skip_paths) + webserver ws = create_webserver(PORT) + .auth_handler(centralized_auth_handler) + .auth_skip_paths({"/health", "/metrics", "/status", "/public/*"}); + + simple_resource sr; + LT_ASSERT_EQ(true, ws.register_resource("health", &sr)); + LT_ASSERT_EQ(true, ws.register_resource("metrics", &sr)); + LT_ASSERT_EQ(true, ws.register_resource("status", &sr)); + LT_ASSERT_EQ(true, ws.register_resource("protected", &sr)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + CURL *curl; + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + std::string s; + + // All skip paths should work without auth + const char* skip_urls[] = {"/health", "/metrics", "/status"}; + for (const char* url : skip_urls) { + curl = curl_easy_init(); + s = ""; + http_code = 0; + std::string full_url = std::string("localhost:" PORT_STRING) + url; + curl_easy_setopt(curl, CURLOPT_URL, full_url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 200); + curl_easy_cleanup(curl); + } + + // Protected should still require auth + curl = curl_easy_init(); + s = ""; + http_code = 0; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/protected"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 401); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(auth_multiple_skip_paths) + +// Test skip path for root "/" +LT_BEGIN_AUTO_TEST(authentication_suite, auth_skip_path_root) + webserver ws = create_webserver(PORT) + .auth_handler(centralized_auth_handler) + .auth_skip_paths({"/"}); + + simple_resource sr; + LT_ASSERT_EQ(true, ws.register_resource("/", &sr, true)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + + // Root path should be skipped + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 200); + LT_CHECK_EQ(s, "SUCCESS"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(auth_skip_path_root) + +// Test wildcard path matching "/pub/*" +LT_BEGIN_AUTO_TEST(authentication_suite, auth_skip_path_wildcard) + webserver ws = create_webserver(PORT) + .auth_handler(centralized_auth_handler) + .auth_skip_paths({"/pub/*"}); + + simple_resource sr; + LT_ASSERT_EQ(true, ws.register_resource("pub/anything", &sr)); + LT_ASSERT_EQ(true, ws.register_resource("pub/nested/path", &sr)); + LT_ASSERT_EQ(true, ws.register_resource("private/secret", &sr)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + CURL *curl; + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + std::string s; + + // /pub/anything should be skipped (matches /pub/*) + curl = curl_easy_init(); + s = ""; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/pub/anything"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 200); + curl_easy_cleanup(curl); + + // /pub/nested/path should also be skipped (matches /pub/*) + curl = curl_easy_init(); + s = ""; + http_code = 0; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/pub/nested/path"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 200); + curl_easy_cleanup(curl); + + // /private/secret should NOT be skipped (doesn't match /pub/*) + curl = curl_easy_init(); + s = ""; + http_code = 0; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/private/secret"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 401); // Should require auth + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(auth_skip_path_wildcard) + +// Test empty skip paths (should require auth for everything) +LT_BEGIN_AUTO_TEST(authentication_suite, auth_empty_skip_paths) + webserver ws = create_webserver(PORT) + .auth_handler(centralized_auth_handler) + .auth_skip_paths({}); // Empty skip paths + + simple_resource sr; + LT_ASSERT_EQ(true, ws.register_resource("test", &sr)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + + // Should require auth + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/test"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 401); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(auth_empty_skip_paths) + +// Test that path traversal cannot bypass auth skip paths +// Requesting /public/../protected should NOT skip auth +LT_BEGIN_AUTO_TEST(authentication_suite, auth_skip_path_traversal_bypass) + webserver ws = create_webserver(PORT) + .auth_handler(centralized_auth_handler) + .auth_skip_paths({"/public/*"}); + + simple_resource sr; + LT_ASSERT_EQ(true, ws.register_resource("protected", &sr)); + LT_ASSERT_EQ(true, ws.register_resource("public/info", &sr)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl; + CURLcode res; + long http_code = 0; // NOLINT(runtime/int) + + // /public/../protected should normalize to /protected, which requires auth + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/public/../protected"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 401); // Should require auth, not be skipped + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(auth_skip_path_traversal_bypass) +#endif // HAVE_BAUTH + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV() diff --git a/test/integ/ban_system.cpp b/test/integ/ban_system.cpp new file mode 100644 index 00000000..48d808e4 --- /dev/null +++ b/test/integ/ban_system.cpp @@ -0,0 +1,462 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include +#include +#include + +#include "./httpserver.hpp" +#include "httpserver/http_utils.hpp" +#include "./littletest.hpp" + +using std::shared_ptr; + +using httpserver::webserver; +using httpserver::create_webserver; +using httpserver::http_resource; +using httpserver::http_response; +using httpserver::string_response; +using httpserver::http_request; +using httpserver::http::http_utils; + +#ifdef HTTPSERVER_PORT +#define PORT HTTPSERVER_PORT +#else +#define PORT 8080 +#endif // PORT + +#define STR2(p) #p +#define STR(p) STR2(p) +#define PORT_STRING STR(PORT) + +size_t writefunc(void *ptr, size_t size, size_t nmemb, std::string *s) { + s->append(reinterpret_cast(ptr), size*nmemb); + return size*nmemb; +} + +class ok_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } +}; + +LT_BEGIN_SUITE(ban_system_suite) + void set_up() { + } + + void tear_down() { + } +LT_END_SUITE(ban_system_suite) + +LT_BEGIN_AUTO_TEST(ban_system_suite, accept_default_ban_blocks) + webserver ws = create_webserver(PORT).default_policy(http_utils::ACCEPT); + ws.start(false); + + ok_resource resource; + LT_ASSERT_EQ(true, ws.register_resource("base", &resource)); + + curl_global_init(CURL_GLOBAL_ALL); + + { + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + { + ws.ban_ip("127.0.0.1"); + + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + res = curl_easy_perform(curl); + LT_ASSERT_NEQ(res, 0); + curl_easy_cleanup(curl); + } + + { + ws.unban_ip("127.0.0.1"); + + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + ws.stop(); +LT_END_AUTO_TEST(accept_default_ban_blocks) + +LT_BEGIN_AUTO_TEST(ban_system_suite, reject_default_allow_passes) + webserver ws = create_webserver(PORT).default_policy(http_utils::REJECT); + ws.start(false); + + ok_resource resource; + LT_ASSERT_EQ(true, ws.register_resource("base", &resource)); + + curl_global_init(CURL_GLOBAL_ALL); + + { + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + res = curl_easy_perform(curl); + LT_ASSERT_NEQ(res, 0); + curl_easy_cleanup(curl); + } + + { + ws.allow_ip("127.0.0.1"); + + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + { + ws.disallow_ip("127.0.0.1"); + + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + res = curl_easy_perform(curl); + LT_ASSERT_NEQ(res, 0); + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + ws.stop(); +LT_END_AUTO_TEST(reject_default_allow_passes) + +// Test ACCEPT policy with IP on allow list - allow overrides ban +// In ACCEPT mode: condition is (is_banned && !is_allowed) +// If IP is on allow list, !is_allowed is false, so connection is always allowed +LT_BEGIN_AUTO_TEST(ban_system_suite, accept_policy_allow_overrides_ban) + webserver ws = create_webserver(PORT).default_policy(http_utils::ACCEPT); + ws.start(false); + + ok_resource resource; + LT_ASSERT_EQ(true, ws.register_resource("base", &resource)); + + curl_global_init(CURL_GLOBAL_ALL); + + // Add IP to allow list + ws.allow_ip("127.0.0.1"); + + // Request should work (ACCEPT policy + on allow list) + { + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + // Ban the IP - but in ACCEPT mode, allow list overrides ban + ws.ban_ip("127.0.0.1"); + + { + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); // Still allowed - allow list overrides ban in ACCEPT mode + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + // Remove from allow list - now ban should take effect + ws.disallow_ip("127.0.0.1"); + + { + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + res = curl_easy_perform(curl); + LT_ASSERT_NEQ(res, 0); // Now blocked - ban takes effect without allow list + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + ws.stop(); +LT_END_AUTO_TEST(accept_policy_allow_overrides_ban) + +// Test REJECT policy with IP that is allowed but then banned +// Tests: (!is_allowed || is_banned) - banned overrides allowed +LT_BEGIN_AUTO_TEST(ban_system_suite, reject_policy_allowed_then_banned) + webserver ws = create_webserver(PORT).default_policy(http_utils::REJECT); + ws.start(false); + + ok_resource resource; + LT_ASSERT_EQ(true, ws.register_resource("base", &resource)); + + curl_global_init(CURL_GLOBAL_ALL); + + // First, IP is not allowed - should be blocked + { + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + res = curl_easy_perform(curl); + LT_ASSERT_NEQ(res, 0); + curl_easy_cleanup(curl); + } + + // Allow the IP - should work + ws.allow_ip("127.0.0.1"); + + { + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + // Now ban the IP - ban should override allow + ws.ban_ip("127.0.0.1"); + + { + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + res = curl_easy_perform(curl); + LT_ASSERT_NEQ(res, 0); // Should be blocked + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + ws.stop(); +LT_END_AUTO_TEST(reject_policy_allowed_then_banned) + +// Test REJECT policy with IP that is neither allowed nor banned +// Tests default REJECT behavior +LT_BEGIN_AUTO_TEST(ban_system_suite, reject_policy_neither_allowed_nor_banned) + webserver ws = create_webserver(PORT).default_policy(http_utils::REJECT); + ws.start(false); + + ok_resource resource; + LT_ASSERT_EQ(true, ws.register_resource("base", &resource)); + + curl_global_init(CURL_GLOBAL_ALL); + + // IP is not in any list - REJECT policy should block + { + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + res = curl_easy_perform(curl); + LT_ASSERT_NEQ(res, 0); + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + ws.stop(); +LT_END_AUTO_TEST(reject_policy_neither_allowed_nor_banned) + +// Test ban/allow with wildcard then more specific IP +// This tests the weight comparison branch in ban_ip/allow_ip +LT_BEGIN_AUTO_TEST(ban_system_suite, ban_with_weight_comparison) + webserver ws = create_webserver(PORT).default_policy(http_utils::ACCEPT); + ws.start(false); + + ok_resource resource; + LT_ASSERT_EQ(true, ws.register_resource("base", &resource)); + + curl_global_init(CURL_GLOBAL_ALL); + + // First ban with wildcard (lower weight) + ws.ban_ip("127.0.0.*"); + + // Then ban with more specific IP (higher weight) + // This should hit the weight comparison branch + ws.ban_ip("127.0.0.1"); + + // Request should still be blocked + { + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + res = curl_easy_perform(curl); + LT_ASSERT_NEQ(res, 0); + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + ws.stop(); +LT_END_AUTO_TEST(ban_with_weight_comparison) + +// Test allow with wildcard then more specific IP +LT_BEGIN_AUTO_TEST(ban_system_suite, allow_with_weight_comparison) + webserver ws = create_webserver(PORT).default_policy(http_utils::REJECT); + ws.start(false); + + ok_resource resource; + LT_ASSERT_EQ(true, ws.register_resource("base", &resource)); + + curl_global_init(CURL_GLOBAL_ALL); + + // First allow with wildcard (lower weight) + ws.allow_ip("127.0.0.*"); + + // Then allow with more specific IP (higher weight) + // This should hit the weight comparison branch + ws.allow_ip("127.0.0.1"); + + // Request should be allowed + { + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + ws.stop(); +LT_END_AUTO_TEST(allow_with_weight_comparison) + +// Test ban with specific IP first, then wildcard (lower weight replaces higher) +// This tests the t_ip.weight() < (*it).weight() branch +LT_BEGIN_AUTO_TEST(ban_system_suite, ban_specific_then_wildcard) + webserver ws = create_webserver(PORT).default_policy(http_utils::ACCEPT); + ws.start(false); + + ok_resource resource; + LT_ASSERT_EQ(true, ws.register_resource("base", &resource)); + + curl_global_init(CURL_GLOBAL_ALL); + + // First ban specific IP (higher weight = 4) + ws.ban_ip("127.0.0.1"); + + // Then ban with wildcard (lower weight = 3) + // This should trigger the erase-and-insert branch + ws.ban_ip("127.0.0.*"); + + // Request should still be blocked + { + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + res = curl_easy_perform(curl); + LT_ASSERT_NEQ(res, 0); + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + ws.stop(); +LT_END_AUTO_TEST(ban_specific_then_wildcard) + +// Test allow with specific IP first, then wildcard (lower weight replaces higher) +LT_BEGIN_AUTO_TEST(ban_system_suite, allow_specific_then_wildcard) + webserver ws = create_webserver(PORT).default_policy(http_utils::REJECT); + ws.start(false); + + ok_resource resource; + LT_ASSERT_EQ(true, ws.register_resource("base", &resource)); + + curl_global_init(CURL_GLOBAL_ALL); + + // First allow specific IP (higher weight = 4) + ws.allow_ip("127.0.0.1"); + + // Then allow with wildcard (lower weight = 3) + // This should trigger the erase-and-insert branch + ws.allow_ip("127.0.0.*"); + + // Request should be allowed + { + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + ws.stop(); +LT_END_AUTO_TEST(allow_specific_then_wildcard) + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV() diff --git a/test/integ/basic.cpp b/test/integ/basic.cpp index 597067f3..14d4eea0 100644 --- a/test/integ/basic.cpp +++ b/test/integ/basic.cpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -18,158 +18,412 @@ USA */ -#include "littletest.hpp" #include -#include +#include +#include +#include +#include #include -#include "httpserver.hpp" +#include +#include +#include +#include +#include +#include +#include + +#include "./httpserver.hpp" +#include "httpserver/string_utilities.hpp" +#include "./littletest.hpp" + +using std::string; +using std::map; +using std::shared_ptr; +using std::vector; +using std::stringstream; -using namespace httpserver; -using namespace std; +using httpserver::http_resource; +using httpserver::http_request; +using httpserver::http_response; +using httpserver::string_response; +using httpserver::file_response; +using httpserver::webserver; +using httpserver::create_webserver; -std::string lorem_ipsum(" , unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci v'elit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur? [33] At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. , unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci v'elit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur? [33] At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. , unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci v'elit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur? [33] At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. , unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci v'elit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur? [33] At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. , unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci v'elit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur? [33] At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat."); +string lorem_ipsum(" , unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci v'elit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur? [33] At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. , unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci v'elit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur? [33] At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. , unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci v'elit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur? [33] At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. , unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci v'elit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur? [33] At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. , unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci v'elit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur? [33] At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat."); // NOLINT -size_t writefunc(void *ptr, size_t size, size_t nmemb, std::string *s) -{ - s->append((char*) ptr, size*nmemb); +size_t writefunc(void *ptr, size_t size, size_t nmemb, string *s) { + s->append(reinterpret_cast(ptr), size*nmemb); return size*nmemb; } -size_t headerfunc(void *ptr, size_t size, size_t nmemb, map* ss) -{ - string s_ptr((char*)ptr, size*nmemb); +size_t headerfunc(void *ptr, size_t size, size_t nmemb, map* ss) { + string s_ptr(reinterpret_cast(ptr), size * nmemb); size_t pos = s_ptr.find(":"); - if(pos != string::npos) - (*ss)[s_ptr.substr(0, pos)] = - s_ptr.substr(pos + 2, s_ptr.size() - pos - 4); + if (pos != string::npos) { + (*ss)[s_ptr.substr(0, pos)] = s_ptr.substr(pos + 2, s_ptr.size() - pos - 4); + } return size*nmemb; } -class simple_resource : public http_resource -{ - public: - void render_GET(const http_request& req, http_response** res) - { - *res = new http_response(http_response_builder("OK", 200, "text/plain").string_response()); - } - void render_POST(const http_request& req, http_response** res) - { - *res = new http_response( - http_response_builder(req.get_arg("arg1")+req.get_arg("arg2"), 200, "text/plain").string_response() - ); - } +class simple_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } + shared_ptr render_POST(const http_request& req) { + return std::make_shared(std::string(req.get_arg("arg1")) + std::string(req.get_arg("arg2")), 200, "text/plain"); + } }; -class long_content_resource : public http_resource -{ - public: - void render_GET(const http_request& req, http_response** res) - { - *res = new http_response(http_response_builder(lorem_ipsum, 200, "text/plain").string_response()); - } +class large_post_resource_last_value : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } + shared_ptr render_POST(const http_request& req) { + return std::make_shared(std::string(req.get_arg("arg1").get_all_values().back()), 200, "text/plain"); + } }; -class header_test_resource : public http_resource -{ - public: - void render_GET(const http_request& req, http_response** res) - { - http_response_builder hrb("OK", 200, "text/plain"); - hrb.with_header("KEY", "VALUE"); - *res = new http_response(hrb.string_response()); - } +class large_post_resource_first_value : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } + shared_ptr render_POST(const http_request& req) { + return std::make_shared(std::string(req.get_arg("arg1").get_all_values().front()), 200, "text/plain"); + } }; -class complete_test_resource : public http_resource -{ - public: - void render_GET(const http_request& req, http_response** res) - { - *res = new http_response(http_response_builder("OK", 200, "text/plain").string_response()); - } - void render_POST(const http_request& req, http_response** res) - { - *res = new http_response(http_response_builder("OK", 200, "text/plain").string_response()); - } - void render_PUT(const http_request& req, http_response** res) - { - *res = new http_response(http_response_builder("OK", 200, "text/plain").string_response()); - } - void render_DELETE(const http_request& req, http_response** res) - { - *res = new http_response(http_response_builder("OK", 200, "text/plain").string_response()); - } - void render_CONNECT(const http_request& req, http_response** res) - { - *res = new http_response(http_response_builder("OK", 200, "text/plain").string_response()); - } +class arg_value_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } + shared_ptr render_POST(const http_request& req) { + auto const arg_value = req.get_arg("arg").get_all_values(); + for (auto const & a : arg_value) { + std::cerr << a << std::endl; + } + std::string all_values = std::accumulate(std::next(arg_value.begin()), arg_value.end(), std::string(arg_value[0]), [](std::string a, std::string_view in) { + return std::move(a) + std::string(in); + }); + return std::make_shared(all_values, 200, "text/plain"); + } }; -class only_render_resource : public http_resource -{ - public: - void render(const http_request& req, http_response** res) - { - *res = new http_response(http_response_builder("OK", 200, "text/plain").string_response()); - } +class args_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + return std::make_shared(std::string(req.get_arg("arg")) + std::string(req.get_arg("arg2")), 200, "text/plain"); + } }; -class ok_resource : public http_resource -{ - public: - void render_GET(const http_request& req, http_response** res) - { - *res = new http_response(http_response_builder("OK", 200, "text/plain").string_response()); - } +class args_flat_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + auto args = req.get_args_flat(); + stringstream ss; + for (const auto& [key, value] : args) { + ss << key << "=" << value << ";"; + } + return std::make_shared(ss.str(), 200, "text/plain"); + } }; -class nok_resource : public http_resource -{ - public: - void render_GET(const http_request& req, http_response** res) - { - *res = new http_response(http_response_builder("NOK", 200, "text/plain").string_response()); - } +class long_content_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared(lorem_ipsum, 200, "text/plain"); + } }; -class no_response_resource : public http_resource -{ - public: - void render_GET(const http_request& req, http_response** res) - { - } +class header_set_test_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + auto hrb = std::make_shared("OK", 200, "text/plain"); + hrb->with_header("KEY", "VALUE"); + return hrb; + } }; -LT_BEGIN_SUITE(basic_suite) +class cookie_set_test_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + auto hrb = std::make_shared("OK", 200, "text/plain"); + hrb->with_cookie("MyCookie", "CookieValue"); + return hrb; + } +}; - webserver* ws; +class cookie_reading_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + return std::make_shared(std::string(req.get_cookie("name")), 200, "text/plain"); + } +}; - void set_up() - { - ws = new webserver(create_webserver(8080)); +class header_reading_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + return std::make_shared(std::string(req.get_header("MyHeader")), 200, "text/plain"); + } +}; + +class full_args_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + return std::make_shared(std::string(req.get_args().at("arg")), 200, "text/plain"); + } +}; + +class querystring_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + return std::make_shared(std::string(req.get_querystring()), 200, "text/plain"); + } +}; + +class path_pieces_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + stringstream ss; + for (unsigned int i = 0; i < req.get_path_pieces().size(); i++) { + ss << req.get_path_piece(i) << ","; + } + return std::make_shared(ss.str(), 200, "text/plain"); + } +}; + +class complete_test_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } + + shared_ptr render_POST(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } + + shared_ptr render_PUT(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } + + shared_ptr render_DELETE(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } + + shared_ptr render_PATCH(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } + + shared_ptr render_HEAD(const http_request&) { + return std::make_shared("", 200, "text/plain"); + } + + shared_ptr render_OPTIONS(const http_request&) { + auto resp = std::make_shared("", 200, "text/plain"); + resp->with_header("Allow", "GET, POST, PUT, DELETE, HEAD, OPTIONS"); + return resp; + } + + shared_ptr render_TRACE(const http_request&) { + return std::make_shared("TRACE OK", 200, "message/http"); + } +}; + +class only_render_resource : public http_resource { + public: + shared_ptr render(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } +}; + +class ok_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } +}; + +class nok_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("NOK", 200, "text/plain"); + } +}; + +class static_resource : public http_resource { + public: + explicit static_resource(std::string r) : resp(std::move(r)) {} + + shared_ptr render_GET(const http_request&) { + return std::make_shared(resp, 200, "text/plain"); + } + + std::string resp; +}; + +class no_response_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared(); + } +}; + +class empty_response_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return shared_ptr(nullptr); + } +}; + +#ifndef HTTPSERVER_NO_LOCAL_FS +class file_response_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("test_content", 200, "text/plain"); + } +}; + +class file_response_resource_empty : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("test_content_empty", 200, "text/plain"); + } +}; + +class file_response_resource_default_content_type : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("test_content", 200); + } +}; +#endif // HTTPSERVER_NO_LOCAL_FS + +class file_response_resource_missing : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("missing", 200); + } +}; + +#ifndef HTTPSERVER_NO_LOCAL_FS +class file_response_resource_dir : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("integ", 200); + } +}; +#endif // HTTPSERVER_NO_LOCAL_FS + +class exception_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + throw std::domain_error("invalid"); + } +}; + +class error_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + throw "invalid"; + } +}; + +class print_request_resource : public http_resource { + public: + explicit print_request_resource(stringstream* ss) : ss(ss) {} + + shared_ptr render_GET(const http_request& req) { + (*ss) << req; + return std::make_shared("OK", 200, "text/plain"); + } + + private: + stringstream* ss; +}; + +class print_response_resource : public http_resource { + public: + explicit print_response_resource(stringstream* ss) : ss(ss) {} + + shared_ptr render_GET(const http_request&) { + auto hresp = std::make_shared("OK", 200, "text/plain"); + + hresp->with_header("MyResponseHeader", "MyResponseHeaderValue"); + hresp->with_footer("MyResponseFooter", "MyResponseFooterValue"); + hresp->with_cookie("MyResponseCookie", "MyResponseCookieValue"); + + (*ss) << *hresp; + + return hresp; + } + + private: + stringstream* ss; +}; + +class request_info_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + stringstream ss; + ss << "requestor=" << req.get_requestor() + << "&port=" << req.get_requestor_port() + << "&version=" << req.get_version(); + return std::make_shared(ss.str(), 200, "text/plain"); + } +}; + +class content_limit_resource : public http_resource { + public: + shared_ptr render_POST(const http_request& req) { + return std::make_shared( + req.content_too_large() ? "TOO_LARGE" : "OK", 200, "text/plain"); + } +}; + +#ifdef HTTPSERVER_PORT +#define PORT HTTPSERVER_PORT +#else +#define PORT 8080 +#endif // PORT + +#define STR2(p) #p +#define STR(p) STR2(p) +#define PORT_STRING STR(PORT) + + +LT_BEGIN_SUITE(basic_suite) + std::unique_ptr ws; + + void set_up() { + ws = std::make_unique(create_webserver(PORT)); ws->start(false); } - void tear_down() - { + void tear_down() { ws->stop(); - delete ws; } LT_END_SUITE(basic_suite) -LT_BEGIN_AUTO_TEST(basic_suite, two_endpoints) +LT_BEGIN_AUTO_TEST(basic_suite, server_runs) + LT_CHECK_EQ(ws->is_running(), true); +LT_END_AUTO_TEST(server_runs) - ok_resource* ok = new ok_resource(); - ws->register_resource("OK", ok); - nok_resource* nok = new nok_resource(); - ws->register_resource("NOK", nok); +LT_BEGIN_AUTO_TEST(basic_suite, two_endpoints) + ok_resource ok; + LT_ASSERT_EQ(true, ws->register_resource("OK", &ok)); + nok_resource nok; + LT_ASSERT_EQ(true, ws->register_resource("NOK", &nok)); curl_global_init(CURL_GLOBAL_ALL); - std::string s; + string s; { CURL *curl = curl_easy_init(); CURLcode res; - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/OK"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/OK"); curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); @@ -179,11 +433,11 @@ LT_BEGIN_AUTO_TEST(basic_suite, two_endpoints) curl_easy_cleanup(curl); } - std::string t; + string t; { CURL *curl = curl_easy_init(); CURLcode res; - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/NOK"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/NOK"); curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &t); @@ -192,213 +446,2869 @@ LT_BEGIN_AUTO_TEST(basic_suite, two_endpoints) LT_CHECK_EQ(t, "NOK"); curl_easy_cleanup(curl); } - LT_END_AUTO_TEST(two_endpoints) -LT_BEGIN_AUTO_TEST(basic_suite, read_body) - simple_resource* resource = new simple_resource(); - ws->register_resource("base", resource); +LT_BEGIN_AUTO_TEST(basic_suite, duplicate_endpoints) + ok_resource ok1, ok2; + LT_CHECK_EQ(true, ws->register_resource("OK", &ok1)); + + // All of these collide and the registration fails + LT_CHECK_EQ(false, ws->register_resource("OK", &ok2)); + LT_CHECK_EQ(false, ws->register_resource("/OK", &ok2)); + LT_CHECK_EQ(false, ws->register_resource("/OK/", &ok2)); + LT_CHECK_EQ(false, ws->register_resource("OK/", &ok2)); + + // Check how family interacts. + LT_CHECK_EQ(true, ws->register_resource("OK", &ok2, true)); + + // Check that switched case does the right thing, whatever that is here. +#ifdef CASE_INSENSITIVE + LT_CHECK_EQ(false, ws->register_resource("ok", &ok2)); +#else + // TODO(etr): this should be true. + // However, http_endpoint::operator< is always case-insensitive + LT_CHECK_EQ(false, ws->register_resource("ok", &ok2)); +#endif +LT_END_AUTO_TEST(duplicate_endpoints) + +LT_BEGIN_AUTO_TEST(basic_suite, family_endpoints) + static_resource ok1("1"), ok2("2"); + LT_CHECK_EQ(true, ws->register_resource("OK", &ok1)); + LT_CHECK_EQ(true, ws->register_resource("OK", &ok2, true)); + curl_global_init(CURL_GLOBAL_ALL); - std::string s; + + { + string s; CURL *curl = curl_easy_init(); CURLcode res; - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/OK"); curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); - LT_CHECK_EQ(s, "OK"); + LT_CHECK_EQ(s, "1"); curl_easy_cleanup(curl); -LT_END_AUTO_TEST(read_body) + } -LT_BEGIN_AUTO_TEST(basic_suite, read_long_body) - long_content_resource* resource = new long_content_resource(); - ws->register_resource("base", resource); - curl_global_init(CURL_GLOBAL_ALL); - std::string s; + { + string s; CURL *curl = curl_easy_init(); CURLcode res; - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/OK/"); curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); - LT_CHECK_EQ(s.size(), lorem_ipsum.size()); + LT_CHECK_EQ(s, "1"); curl_easy_cleanup(curl); -LT_END_AUTO_TEST(read_long_body) + } -LT_BEGIN_AUTO_TEST(basic_suite, read_header) - header_test_resource* resource = new header_test_resource(); - ws->register_resource("base", resource); - curl_global_init(CURL_GLOBAL_ALL); - std::string s; - map ss; + { + string s; CURL *curl = curl_easy_init(); CURLcode res; - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/OK/go"); curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerfunc); - curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &ss); res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); - LT_CHECK_EQ(s, "OK"); - LT_CHECK_EQ(ss["KEY"], "VALUE"); + LT_CHECK_EQ(s, "2"); curl_easy_cleanup(curl); -LT_END_AUTO_TEST(read_header) + } -LT_BEGIN_AUTO_TEST(basic_suite, complete) - complete_test_resource* resource = new complete_test_resource(); - ws->register_resource("base", resource); - curl_global_init(CURL_GLOBAL_ALL); - std::string s; - CURL* curl; +#ifdef CASE_INSENSITIVE + { + string s; + CURL *curl = curl_easy_init(); CURLcode res; - - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/OK"); curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "1"); curl_easy_cleanup(curl); + } - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/OK/"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "1"); curl_easy_cleanup(curl); + } - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/OK/go"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "2"); curl_easy_cleanup(curl); + } +#endif +LT_END_AUTO_TEST(family_endpoints) - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "CONNECT"); +LT_BEGIN_AUTO_TEST(basic_suite, overlapping_endpoints) + // Setup two different resources that can both match the same URL. + static_resource ok1("1"), ok2("2"); + LT_CHECK_EQ(true, ws->register_resource("/foo/{var|([a-z]+)}/", &ok1)); + LT_CHECK_EQ(true, ws->register_resource("/{var|([a-z]+)}/bar/", &ok2)); + + curl_global_init(CURL_GLOBAL_ALL); + + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/foo/bar/"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "2"); // Not sure why regex wins, but it does... curl_easy_cleanup(curl); + } - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); - curl_easy_setopt(curl, CURLOPT_POST, 1L); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0); + static_resource ok3("3"); + LT_CHECK_EQ(true, ws->register_resource("/foo/bar/", &ok3)); + + { + // Check that an exact, non-RE match overrides both patterns. + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/foo/bar/"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "3"); curl_easy_cleanup(curl); -LT_END_AUTO_TEST(complete) + } +LT_END_AUTO_TEST(overlapping_endpoints) -LT_BEGIN_AUTO_TEST(basic_suite, only_render) - only_render_resource* resource = new only_render_resource(); - ws->register_resource("base", resource); +LT_BEGIN_AUTO_TEST(basic_suite, read_body) + simple_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); curl_global_init(CURL_GLOBAL_ALL); - std::string s; - CURL* curl; + string s; + CURL *curl = curl_easy_init(); CURLcode res; - - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); curl_easy_cleanup(curl); +LT_END_AUTO_TEST(read_body) - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); +LT_BEGIN_AUTO_TEST(basic_suite, read_long_body) + long_content_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s.size(), lorem_ipsum.size()); curl_easy_cleanup(curl); +LT_END_AUTO_TEST(read_long_body) - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); +LT_BEGIN_AUTO_TEST(basic_suite, resource_setting_header) + header_set_test_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + map ss; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerfunc); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &ss); res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + LT_CHECK_EQ(ss["KEY"], "VALUE"); curl_easy_cleanup(curl); +LT_END_AUTO_TEST(resource_setting_header) - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "CONNECT"); +LT_BEGIN_AUTO_TEST(basic_suite, resource_setting_cookie) + cookie_set_test_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, ""); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); res = curl_easy_perform(curl); - LT_ASSERT_EQ(res, 0); - curl_easy_cleanup(curl); - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "NOT_EXISTENT"); - res = curl_easy_perform(curl); - LT_ASSERT_EQ(res, 0); - curl_easy_cleanup(curl); + struct curl_slist *cookies; + curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &cookies); - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); - curl_easy_setopt(curl, CURLOPT_POST, 1L); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0); - res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); - curl_easy_cleanup(curl); + LT_CHECK_EQ(s, "OK"); + string read_cookie = ""; -LT_END_AUTO_TEST(only_render) + read_cookie = cookies->data; + curl_slist_free_all(cookies); -LT_BEGIN_AUTO_TEST(basic_suite, postprocessor) - simple_resource* resource = new simple_resource(); - ws->register_resource("base", resource); + vector cookie_parts = httpserver::string_utilities::string_split(read_cookie, '\t', false); + LT_CHECK_EQ(cookie_parts[5], "MyCookie"); + LT_CHECK_EQ(cookie_parts[6], "CookieValue"); + + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(resource_setting_cookie) + +LT_BEGIN_AUTO_TEST(basic_suite, request_with_header) + header_reading_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); curl_global_init(CURL_GLOBAL_ALL); - std::string s; + string s; CURL *curl = curl_easy_init(); CURLcode res; - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "arg1=lib&arg2=httpserver"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + + struct curl_slist *list = nullptr; + list = curl_slist_append(list, "MyHeader: MyValue"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); - LT_CHECK_EQ(s, "libhttpserver"); + LT_CHECK_EQ(s, "MyValue"); + curl_slist_free_all(list); curl_easy_cleanup(curl); -LT_END_AUTO_TEST(postprocessor) +LT_END_AUTO_TEST(request_with_header) -LT_BEGIN_AUTO_TEST(basic_suite, empty_arg) - simple_resource* resource = new simple_resource(); - ws->register_resource("base", resource); +LT_BEGIN_AUTO_TEST(basic_suite, request_with_cookie) + cookie_reading_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); curl_global_init(CURL_GLOBAL_ALL); + string s; CURL *curl = curl_easy_init(); CURLcode res; - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "arg1"); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_COOKIE, "name=myname; present=yes;"); res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "myname"); curl_easy_cleanup(curl); -LT_END_AUTO_TEST(empty_arg) +LT_END_AUTO_TEST(request_with_cookie) -LT_BEGIN_AUTO_TEST(basic_suite, no_response) - no_response_resource* resource = new no_response_resource(); - ws->register_resource("base", resource); +LT_BEGIN_AUTO_TEST(basic_suite, complete) + complete_test_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); curl_global_init(CURL_GLOBAL_ALL); - std::string s; - CURL* curl; - CURLcode res; - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/base"); + { + CURL* curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); - res = curl_easy_perform(curl); + CURLcode res = curl_easy_perform(curl); LT_ASSERT_EQ(res, 0); - long http_code = 0; - curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code); - LT_ASSERT_EQ(http_code, 500); curl_easy_cleanup(curl); -LT_END_AUTO_TEST(no_response) + } + + { + CURL* curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + CURLcode res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + curl_easy_cleanup(curl); + } + + { + CURL* curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + CURLcode res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + curl_easy_cleanup(curl); + } + + { + CURL* curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); + CURLcode res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + curl_easy_cleanup(curl); + } +/* + { + CURL* curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "CONNECT"); + CURLcode res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + curl_easy_cleanup(curl); + } +*/ + + { + CURL* curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, nullptr); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0); + CURLcode res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + curl_easy_cleanup(curl); + } +LT_END_AUTO_TEST(complete) + +LT_BEGIN_AUTO_TEST(basic_suite, only_render) + only_render_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL* curl; + CURLcode res; + + s = ""; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + s = ""; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + s = ""; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + +/* + s = ""; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "CONNECT"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); +*/ + + s = ""; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "NOT_EXISTENT"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "Method not Allowed"); + curl_easy_cleanup(curl); + + s = ""; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, nullptr); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(only_render) + +LT_BEGIN_AUTO_TEST(basic_suite, postprocessor) + simple_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "arg1=lib&arg2=httpserver"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "libhttpserver"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(postprocessor) + +LT_BEGIN_AUTO_TEST(basic_suite, postprocessor_large_field_last_field) + large_post_resource_last_value resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + + const int size = 20000; + const char value = 'A'; + const char* prefix = "arg1=BB&arg1="; + + // Calculate the total length of the string + int totalLength = std::strlen(prefix) + size; + + // Allocate memory for the char array + char* cString = new char[totalLength + 1]; // +1 for the null terminator + + // Copy the prefix + int offset = std::snprintf(cString, totalLength + 1, "%s", prefix); + + // Append 20,000 times the character 'A' to the string + for (int i = 0; i < size; ++i) { + cString[offset++] = value; + } + + // Append the suffix + cString[offset] = '\0'; + + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, cString); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, std::string(20000, 'A')); + + curl_easy_cleanup(curl); + delete[] cString; +LT_END_AUTO_TEST(postprocessor_large_field_last_field) + +LT_BEGIN_AUTO_TEST(basic_suite, postprocessor_large_field_first_field) + large_post_resource_first_value resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + + const int size = 20000; + const char value = 'A'; + const char* prefix = "arg1="; + const char* middle = "&arg1="; + const char* suffix = "BB"; + + // Calculate the total length of the string + int totalLength = std::strlen(prefix) + size + std::strlen(middle) + std::strlen(suffix); + + // Allocate memory for the char array + char* cString = new char[totalLength + 1]; // +1 for the null terminator + + // Copy the prefix + int offset = std::snprintf(cString, totalLength + 1, "%s", prefix); + + // Append 20,000 times the character 'A' to the string + for (int i = 0; i < size; ++i) { + cString[offset++] = value; + } + + // Append the middle part + offset += std::snprintf(cString + offset, totalLength + 1 - offset, "%s", middle); + + // Append the suffix + std::snprintf(cString + offset, totalLength + 1 - offset, "%s", suffix); + + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, cString); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, std::string(20000, 'A')); + + curl_easy_cleanup(curl); + delete[] cString; +LT_END_AUTO_TEST(postprocessor_large_field_first_field) + +LT_BEGIN_AUTO_TEST(basic_suite, same_key_different_value) + arg_value_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + // The curl default content type triggers the file processing + // logic in the webserver. However, since there is no actual + // file, the arg handling should be the same. + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "arg=inertia&arg=isaproperty&arg=ofmatter"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "inertiaisapropertyofmatter"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(same_key_different_value) + +LT_BEGIN_AUTO_TEST(basic_suite, same_key_different_value_plain_content) + arg_value_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base?arg=beep&arg=boop&arg=hello&arg=what"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "arg=beep&arg=boop&arg=hello&arg=what"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + struct curl_slist *list = NULL; + list = curl_slist_append(list, "content-type: text/plain"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + res = curl_easy_perform(curl); + curl_slist_free_all(list); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "beepboophellowhat"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(same_key_different_value_plain_content) + +LT_BEGIN_AUTO_TEST(basic_suite, empty_arg) + simple_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "arg1"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(empty_arg) + +LT_BEGIN_AUTO_TEST(basic_suite, empty_arg_value_at_end) + // Test for issue #268: POST body keys without values at the end + // are not processed when using application/x-www-form-urlencoded + simple_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + // Test case 1: arg2 has empty value at end (the bug case) + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "arg1=val1&arg2="); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // arg1="val1", arg2="" -> response should be "val1" + LT_CHECK_EQ(s, "val1"); + curl_easy_cleanup(curl); + } + + // Test case 2: only arg1 with empty value + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "arg1="); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // arg1="" -> response should be "" + LT_CHECK_EQ(s, ""); + curl_easy_cleanup(curl); + } + + // Test case 3: both args with empty values + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "arg1=&arg2="); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // arg1="", arg2="" -> response should be "" + LT_CHECK_EQ(s, ""); + curl_easy_cleanup(curl); + } +LT_END_AUTO_TEST(empty_arg_value_at_end) + +LT_BEGIN_AUTO_TEST(basic_suite, no_response) + no_response_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + CURL* curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + CURLcode res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 500); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(no_response) + +LT_BEGIN_AUTO_TEST(basic_suite, empty_response) + empty_response_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + CURL* curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + CURLcode res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 500); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(empty_response) + +LT_BEGIN_AUTO_TEST(basic_suite, regex_matching) + simple_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("regex/matching/number/[0-9]+", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/regex/matching/number/10"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(regex_matching) + +LT_BEGIN_AUTO_TEST(basic_suite, regex_matching_arg) + args_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("this/captures/{arg}/passed/in/input", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/this/captures/whatever/passed/in/input"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "whatever"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(regex_matching_arg) + +LT_BEGIN_AUTO_TEST(basic_suite, regex_matching_arg_with_url_pars) + args_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("this/captures/{arg}/passed/in/input", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/this/captures/whatever/passed/in/input?arg2=second_argument"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "whateversecond_argument"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(regex_matching_arg_with_url_pars) + +LT_BEGIN_AUTO_TEST(basic_suite, regex_matching_arg_custom) + args_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("this/captures/numeric/{arg|([0-9]+)}/passed/in/input", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/this/captures/numeric/11/passed/in/input"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "11"); + curl_easy_cleanup(curl); + } + + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/this/captures/numeric/text/passed/in/input"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "Not Found"); + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 404); + curl_easy_cleanup(curl); + } +LT_END_AUTO_TEST(regex_matching_arg_custom) + +LT_BEGIN_AUTO_TEST(basic_suite, querystring_processing) + args_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("this/captures/args/passed/in/the/querystring", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/this/captures/args/passed/in/the/querystring?arg=first&arg2=second"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "firstsecond"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(querystring_processing) + +LT_BEGIN_AUTO_TEST(basic_suite, full_arguments_processing) + full_args_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("this/captures/args/passed/in/the/querystring", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/this/captures/args/passed/in/the/querystring?arg=argument"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "argument"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(full_arguments_processing) + +LT_BEGIN_AUTO_TEST(basic_suite, querystring_query_processing) + querystring_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("this/captures/args/passed/in/the/querystring", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/this/captures/args/passed/in/the/querystring?arg1=value1&arg2=value2&arg3=value3"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "?arg1=value1&arg2=value2&arg3=value3"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(querystring_query_processing) + +LT_BEGIN_AUTO_TEST(basic_suite, register_unregister) + simple_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + ws->unregister_resource("base"); + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 404); + + LT_CHECK_EQ(s, "Not Found"); + + curl_easy_cleanup(curl); + } + + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } +LT_END_AUTO_TEST(register_unregister) + +#ifndef HTTPSERVER_NO_LOCAL_FS +LT_BEGIN_AUTO_TEST(basic_suite, file_serving_resource) + file_response_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "test content of file\n"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(file_serving_resource) + +LT_BEGIN_AUTO_TEST(basic_suite, file_serving_resource_empty) + file_response_resource_empty resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, ""); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(file_serving_resource_empty) + +LT_BEGIN_AUTO_TEST(basic_suite, file_serving_resource_default_content_type) + file_response_resource_default_content_type resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + map ss; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerfunc); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &ss); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(ss["Content-Type"], "application/octet-stream"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(file_serving_resource_default_content_type) +#endif // HTTPSERVER_NO_LOCAL_FS + +LT_BEGIN_AUTO_TEST(basic_suite, file_serving_resource_missing) + file_response_resource_missing resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "Internal Error"); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 500); + + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(file_serving_resource_missing) + +#ifndef HTTPSERVER_NO_LOCAL_FS +LT_BEGIN_AUTO_TEST(basic_suite, file_serving_resource_dir) + file_response_resource_dir resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "Internal Error"); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 500); + + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(file_serving_resource_dir) +#endif // HTTPSERVER_NO_LOCAL_FS + +LT_BEGIN_AUTO_TEST(basic_suite, exception_forces_500) + exception_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "Internal Error"); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 500); + + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(exception_forces_500) + +LT_BEGIN_AUTO_TEST(basic_suite, untyped_error_forces_500) + error_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "Internal Error"); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 500); + + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(untyped_error_forces_500) + +LT_BEGIN_AUTO_TEST(basic_suite, request_is_printable) + stringstream ss; + print_request_resource resource(&ss); + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + + struct curl_slist *list = nullptr; + list = curl_slist_append(nullptr, "MyHeader: MyValue"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + + res = curl_easy_perform(curl); + curl_slist_free_all(list); + + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + + string actual = ss.str(); + LT_CHECK_EQ(actual.find("GET Request") != string::npos, true); + LT_CHECK_EQ(actual.find("Headers [") != string::npos, true); + LT_CHECK_EQ(actual.find("Host") != string::npos, true); + LT_CHECK_EQ(actual.find("Accept:\"*/*\"") != string::npos, true); + LT_CHECK_EQ(actual.find("MyHeader:\"MyValue\"") != string::npos, true); + LT_CHECK_EQ(actual.find("Version [ HTTP/1.1 ]") != string::npos, true); + + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(request_is_printable) + +LT_BEGIN_AUTO_TEST(basic_suite, response_is_printable) + stringstream ss; + print_response_resource resource(&ss); + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + + struct curl_slist *list = nullptr; + list = curl_slist_append(nullptr, "MyHeader: MyValue"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + + res = curl_easy_perform(curl); + curl_slist_free_all(list); + + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + + string actual = ss.str(); + LT_CHECK_EQ(actual.find("Response [response_code:200]") != string::npos, true); + LT_CHECK_EQ(actual.find("Headers [Content-Type:\"text/plain\" MyResponseHeader:\"MyResponseHeaderValue\" ]") != string::npos, true); + LT_CHECK_EQ(actual.find("Footers [MyResponseFooter:\"MyResponseFooterValue\" ]") != string::npos, true); + LT_CHECK_EQ(actual.find("Cookies [MyResponseCookie:\"MyResponseCookieValue\" ]") != string::npos, true); + + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(response_is_printable) + +LT_BEGIN_AUTO_TEST(basic_suite, long_path_pieces) + path_pieces_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("/settings", &resource, true)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/settings/somestringthatisreallylong/with_really_a_lot_of_content/and_underscores_and_looooooooooooooooooong_stuff"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "settings,somestringthatisreallylong,with_really_a_lot_of_content,and_underscores_and_looooooooooooooooooong_stuff,"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(long_path_pieces) + +LT_BEGIN_AUTO_TEST(basic_suite, url_with_regex_like_pieces) + path_pieces_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("/settings", &resource, true)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/settings/{}"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "settings,{},"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(url_with_regex_like_pieces) + +LT_BEGIN_AUTO_TEST(basic_suite, non_family_url_with_regex_like_pieces) + ok_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("/settings", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/settings/{}"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE , &http_code); + LT_ASSERT_EQ(http_code, 404); + + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(non_family_url_with_regex_like_pieces) + +LT_BEGIN_AUTO_TEST(basic_suite, regex_url_exact_match) + ok_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("/foo/{v|[a-z]}/bar", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/foo/a/bar/"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE , &http_code); + LT_ASSERT_EQ(http_code, 200); + + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/foo/{v|[a-z]}/bar/"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE , &http_code); + LT_ASSERT_EQ(http_code, 404); + curl_easy_cleanup(curl); + } +LT_END_AUTO_TEST(regex_url_exact_match) + +LT_BEGIN_AUTO_TEST(basic_suite, method_not_allowed_header) + simple_resource resource; + resource.disallow_all(); + resource.set_allowing("POST", true); + resource.set_allowing("HEAD", true); + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + map ss; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerfunc); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &ss); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 405); + // elements in http_resource::method_state are sorted (std::map) + LT_CHECK_EQ(ss["Allow"], "HEAD, POST"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(method_not_allowed_header) + +LT_BEGIN_AUTO_TEST(basic_suite, request_info_getters) + request_info_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("request_info", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/request_info"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_NEQ(s.find("127.0.0.1"), string::npos); + LT_CHECK_NEQ(s.find("HTTP/1.1"), string::npos); + LT_CHECK_NEQ(s.find("port="), string::npos); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(request_info_getters) + +LT_BEGIN_AUTO_TEST(basic_suite, unregister_then_404) + simple_resource res; + LT_ASSERT_EQ(true, ws->register_resource("temp", &res)); + curl_global_init(CURL_GLOBAL_ALL); + + { + string s; + CURL *curl = curl_easy_init(); + CURLcode result; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/temp"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + result = curl_easy_perform(curl); + LT_ASSERT_EQ(result, 0); + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_CHECK_EQ(http_code, 200); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + ws->unregister_resource("temp"); + + { + string s; + CURL *curl = curl_easy_init(); + CURLcode result; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/temp"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + result = curl_easy_perform(curl); + LT_ASSERT_EQ(result, 0); + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_CHECK_EQ(http_code, 404); + curl_easy_cleanup(curl); + } +LT_END_AUTO_TEST(unregister_then_404) + +LT_BEGIN_AUTO_TEST(basic_suite, thread_safety) + simple_resource resource; + + std::atomic_bool done = false; + auto register_thread = std::thread([&]() { + int i = 0; + while (!done) { + ws->register_resource( + std::string("/route") + std::to_string(++i), &resource); + } + }); + + auto get_thread = std::thread([&](){ + while (!done) { + CURL *curl = curl_easy_init(); + std::string s; + std::string url = "localhost:" PORT_STRING "/route" + std::to_string(rand() % 10000000); // NOLINT(runtime/threadsafe_fn) + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_perform(curl); + curl_easy_cleanup(curl); + } + }); + + using std::chrono_literals::operator""s; + std::this_thread::sleep_for(10s); + done = true; + if (register_thread.joinable()) { + register_thread.join(); + } + if (get_thread.joinable()) { + get_thread.join(); + } + LT_CHECK_EQ(1, 1); +LT_END_AUTO_TEST(thread_safety) + +LT_BEGIN_AUTO_TEST(basic_suite, head_request) + complete_test_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_CHECK_EQ(http_code, 200); + LT_CHECK_EQ(s, ""); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(head_request) + +LT_BEGIN_AUTO_TEST(basic_suite, options_request) + complete_test_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + map ss; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "OPTIONS"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerfunc); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &ss); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_CHECK_EQ(http_code, 200); + LT_CHECK_EQ(ss["Allow"], "GET, POST, PUT, DELETE, HEAD, OPTIONS"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(options_request) + +LT_BEGIN_AUTO_TEST(basic_suite, trace_request) + complete_test_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "TRACE"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_CHECK_EQ(http_code, 200); + LT_CHECK_EQ(s, "TRACE OK"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(trace_request) + +LT_BEGIN_SUITE(content_limit_suite) + std::unique_ptr ws; + int content_limit_port; + string content_limit_url; + + void set_up() { + content_limit_port = PORT + 10; + content_limit_url = "localhost:" + std::to_string(content_limit_port) + "/limit"; + ws = std::make_unique(create_webserver(content_limit_port).content_size_limit(100)); + ws->start(false); + } + + void tear_down() { + ws->stop(); + } +LT_END_SUITE(content_limit_suite) + +LT_BEGIN_AUTO_TEST(content_limit_suite, content_exceeds_limit) + content_limit_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("limit", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + + std::string large_data(200, 'X'); + + curl_easy_setopt(curl, CURLOPT_URL, content_limit_url.c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, large_data.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, large_data.size()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "TOO_LARGE"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(content_exceeds_limit) + +LT_BEGIN_AUTO_TEST(content_limit_suite, content_within_limit) + content_limit_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("limit", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + + std::string small_data(50, 'X'); + + curl_easy_setopt(curl, CURLOPT_URL, content_limit_url.c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, small_data.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, small_data.size()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(content_within_limit) + +LT_BEGIN_AUTO_TEST(basic_suite, get_args_flat) + args_flat_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("args_flat", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/args_flat?foo=bar&baz=qux"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_NEQ(s.find("foo=bar"), string::npos); + LT_CHECK_NEQ(s.find("baz=qux"), string::npos); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(get_args_flat) + +LT_BEGIN_AUTO_TEST(basic_suite, only_render_head) + only_render_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("only_render_head", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/only_render_head"); + curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_CHECK_EQ(http_code, 200); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(only_render_head) + +LT_BEGIN_AUTO_TEST(basic_suite, only_render_options) + only_render_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("only_render_options", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/only_render_options"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "OPTIONS"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(only_render_options) + +LT_BEGIN_AUTO_TEST(basic_suite, only_render_trace) + only_render_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("only_render_trace", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/only_render_trace"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "TRACE"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(only_render_trace) + +// Test for long error log message (triggers resize branch) +class long_error_message_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + // Generate an error with a message longer than 80 characters + throw std::runtime_error( + "This is a very long error message that exceeds the default buffer " + "size of 80 characters to trigger the resize branch in error_log"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, long_error_message) + long_error_message_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("longerror", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/longerror"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 500); + + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(long_error_message) + +// Test PATCH request on a resource that only implements render() +LT_BEGIN_AUTO_TEST(basic_suite, only_render_patch) + only_render_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("only_render_patch", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/only_render_patch"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(only_render_patch) + +// Custom response class that throws std::invalid_argument in get_raw_response +class invalid_argument_response : public http_response { + public: + invalid_argument_response() : http_response(200, "text/plain") {} + MHD_Response* get_raw_response() override { + throw std::invalid_argument("Resource not found"); + } +}; + +// Resource that returns invalid_argument_response +class invalid_arg_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared(); + } +}; + +// Custom response class that throws std::runtime_error in get_raw_response +class runtime_error_response : public http_response { + public: + runtime_error_response() : http_response(200, "text/plain") {} + MHD_Response* get_raw_response() override { + throw std::runtime_error("Internal error in response"); + } +}; + +// Resource that returns runtime_error_response +class runtime_error_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared(); + } +}; + +// Custom response class that throws non-std exception in get_raw_response +class non_std_exception_response : public http_response { + public: + non_std_exception_response() : http_response(200, "text/plain") {} + MHD_Response* get_raw_response() override { + throw 42; // Throws an int, not a std::exception + } +}; + +// Resource that returns non_std_exception_response +class non_std_exception_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared(); + } +}; + +// Test response throwing std::invalid_argument -> should get 404 +LT_BEGIN_AUTO_TEST(basic_suite, response_throws_invalid_argument) + invalid_arg_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("invalid_arg", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/invalid_arg"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 404); // invalid_argument -> not found + + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(response_throws_invalid_argument) + +// Test response throwing std::runtime_error -> should get 500 +LT_BEGIN_AUTO_TEST(basic_suite, response_throws_runtime_error) + runtime_error_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("runtime_err", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/runtime_err"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 500); // runtime_error -> internal server error + + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(response_throws_runtime_error) + +// Test response throwing non-std exception -> should get 500 +LT_BEGIN_AUTO_TEST(basic_suite, response_throws_non_std_exception) + non_std_exception_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("non_std_exc", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/non_std_exc"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 500); // non-std exception -> internal server error + + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(response_throws_non_std_exception) + +// Custom internal error handler that also throws an exception +// This tests the outer catch block (lines 826-829 in webserver.cpp) +shared_ptr throwing_internal_error_handler(const http_request&) { + throw std::runtime_error("Internal error handler also throws"); +} + +// Test case: resource throws exception AND internal error handler throws +// This triggers the outer catch block which uses force_our=true +LT_BEGIN_AUTO_TEST(basic_suite, internal_error_handler_also_throws) + // Create a separate webserver with throwing internal error handler + webserver ws2 = create_webserver(PORT + 50) + .internal_error_resource(throwing_internal_error_handler); + runtime_error_resource resource; // Resource that throws in get_raw_response + LT_ASSERT_EQ(true, ws2.register_resource("error_cascade", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 50) + "/error_cascade"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + // When internal error handler throws, we fall back to the built-in error page + LT_ASSERT_EQ(http_code, 500); + + curl_easy_cleanup(curl); + ws2.stop(); +LT_END_AUTO_TEST(internal_error_handler_also_throws) + +// Test tcp_nodelay option +LT_BEGIN_AUTO_TEST(basic_suite, tcp_nodelay_option) + webserver ws2 = create_webserver(PORT + 51).tcp_nodelay(); + ok_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("nodelay_test", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 51) + "/nodelay_test"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + + curl_easy_cleanup(curl); + ws2.stop(); +LT_END_AUTO_TEST(tcp_nodelay_option) + +// Custom unescaper function to test the unescaper branch +void my_custom_unescaper(std::string& s) { + // Simple unescaper that just converts '+' to space + for (size_t i = 0; i < s.size(); ++i) { + if (s[i] == '+') s[i] = ' '; + } +} + +// Resource that returns the query string argument +class arg_echo_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + std::string arg = std::string(req.get_arg_flat("key")); + return std::make_shared(arg, 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, custom_unescaper) + webserver ws2 = create_webserver(PORT + 52).unescaper(my_custom_unescaper); + arg_echo_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("echo", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 52) + "/echo?key=hello+world"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "hello world"); + + curl_easy_cleanup(curl); + ws2.stop(); +LT_END_AUTO_TEST(custom_unescaper) + +// Custom not_found handler +shared_ptr my_custom_not_found(const http_request&) { + return std::make_shared("CUSTOM_404", 404, "text/plain"); +} + +LT_BEGIN_AUTO_TEST(basic_suite, custom_not_found_handler) + webserver ws2 = create_webserver(PORT + 53).not_found_resource(my_custom_not_found); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 53) + "/nonexistent"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "CUSTOM_404"); + + curl_easy_cleanup(curl); + ws2.stop(); +LT_END_AUTO_TEST(custom_not_found_handler) + +// Custom method_not_allowed handler +shared_ptr my_custom_method_not_allowed(const http_request&) { + return std::make_shared("CUSTOM_405", 405, "text/plain"); +} + +// Resource that only allows POST +class post_only_resource : public http_resource { + public: + post_only_resource() { + disallow_all(); + set_allowing("POST", true); + } + shared_ptr render_POST(const http_request&) { + return std::make_shared("POST_OK", 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, custom_method_not_allowed_handler) + webserver ws2 = create_webserver(PORT + 54).method_not_allowed_resource(my_custom_method_not_allowed); + post_only_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("postonly", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 54) + "/postonly"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); // GET on a POST-only resource + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "CUSTOM_405"); + + curl_easy_cleanup(curl); + ws2.stop(); +LT_END_AUTO_TEST(custom_method_not_allowed_handler) + +// Resource that tests requestor info caching +class requestor_cache_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + // Test requestor IP and port + std::string ip = std::string(req.get_requestor()); + uint16_t port = req.get_requestor_port(); + + // Call them again to test caching (should hit cache on second call) + std::string ip2 = std::string(req.get_requestor()); + + std::string response = "IP:" + ip + ",PORT:" + std::to_string(port); + return std::make_shared(response, 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, requestor_info) + webserver ws2 = create_webserver(PORT + 55); + requestor_cache_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("reqinfo", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 55) + "/reqinfo"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // Response should contain IP and PORT + LT_CHECK_EQ(s.find("IP:127.0.0.1") != string::npos, true); + LT_CHECK_EQ(s.find("PORT:") != string::npos, true); + + curl_easy_cleanup(curl); + ws2.stop(); +LT_END_AUTO_TEST(requestor_info) + +// Resource that tests querystring caching +class querystring_cache_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + // Call get_querystring twice to test caching + std::string qs1 = std::string(req.get_querystring()); + std::string qs2 = std::string(req.get_querystring()); // Should hit cache + + return std::make_shared(qs1, 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, querystring_caching) + webserver ws2 = create_webserver(PORT + 56); + querystring_cache_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("qscache", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 56) + "/qscache?foo=bar&baz=qux"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // Check querystring contains the parameters + LT_CHECK_EQ(s.find("foo") != string::npos, true); + LT_CHECK_EQ(s.find("bar") != string::npos, true); + + curl_easy_cleanup(curl); + ws2.stop(); +LT_END_AUTO_TEST(querystring_caching) + +// Resource that tests args caching +class args_cache_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + // Call get_args twice to test caching + auto args1 = req.get_args(); + auto args2 = req.get_args(); // Should hit cache + + // Also test get_args_flat + auto flat = req.get_args_flat(); + + std::string response; + for (const auto& [key, val] : flat) { + response += std::string(key) + "=" + std::string(val) + ";"; + } + return std::make_shared(response, 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, args_caching) + webserver ws2 = create_webserver(PORT + 57); + args_cache_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("argscache", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 57) + "/argscache?key1=val1&key2=val2"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s.find("key1=val1") != string::npos, true); + LT_CHECK_EQ(s.find("key2=val2") != string::npos, true); + + curl_easy_cleanup(curl); + ws2.stop(); +LT_END_AUTO_TEST(args_caching) + +// Resource that tests footer/trailer access +class footer_test_resource : public http_resource { + public: + shared_ptr render_POST(const http_request& req) { + // Test get_footers() - returns empty map for non-chunked requests + auto footers = req.get_footers(); + + // Test get_footer() with a key that doesn't exist + auto footer_val = req.get_footer("X-Test-Trailer"); + + // Build response showing footer count and specific footer value + std::string response = "footers=" + std::to_string(footers.size()); + if (!footer_val.empty()) { + response += ",X-Test-Trailer=" + std::string(footer_val); + } + + return std::make_shared(response, 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, footer_access_no_trailers) + webserver ws2 = create_webserver(PORT + 58); + footer_test_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("footers", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 58) + "/footers"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "test=data"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // Without trailers, footers should be empty + LT_CHECK_EQ(s, "footers=0"); + + curl_easy_cleanup(curl); + ws2.stop(); +LT_END_AUTO_TEST(footer_access_no_trailers) + +// Resource that returns a response with footers (trailers) +class response_footer_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + auto response = std::make_shared("body content", 200, "text/plain"); + // Add a footer to the response + response->with_footer("X-Checksum", "abc123"); + response->with_footer("X-Processing-Time", "42ms"); + + // Test get_footer and get_footers on response + auto checksum = response->get_footer("X-Checksum"); + auto all_footers = response->get_footers(); + + return response; + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, response_with_footers) + webserver ws2 = create_webserver(PORT + 59); + response_footer_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("resp_footers", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 59) + "/resp_footers"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "body content"); + + curl_easy_cleanup(curl); + ws2.stop(); +LT_END_AUTO_TEST(response_with_footers) + +// Resource that tests get_arg with non-existent key +class arg_not_found_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + // Get an arg that doesn't exist - should return empty http_arg_value + auto missing_arg = req.get_arg("nonexistent_key"); + // http_arg_value.get_all_values() should return empty vector + std::string result = missing_arg.get_all_values().empty() ? "EMPTY" : "HAS_VALUES"; + return std::make_shared(result, 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, arg_not_found) + arg_not_found_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("arg_not_found", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/arg_not_found?existing=value"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "EMPTY"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(arg_not_found) + +// Resource that tests get_arg_flat fallback to connection value +class arg_flat_fallback_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + // Test get_arg_flat with a key that exists in GET args but not in unescaped_args + // This tests the fallback branch in get_arg_flat + std::string val = std::string(req.get_arg_flat("qparam")); + return std::make_shared(val, 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, arg_flat_fallback) + arg_flat_fallback_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("arg_flat_fb", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/arg_flat_fb?qparam=myvalue"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "myvalue"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(arg_flat_fallback) + +// Resource that tests get_path_piece with out of bounds index +class path_piece_oob_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + // Get path piece at an index that's out of bounds + std::string piece = req.get_path_piece(100); // Way beyond the path pieces + // Should return empty string + std::string result = piece.empty() ? "OOB_EMPTY" : piece; + return std::make_shared(result, 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, path_piece_out_of_bounds) + path_piece_oob_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("path/piece/test", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/path/piece/test"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OOB_EMPTY"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(path_piece_out_of_bounds) + +// Resource that tests empty querystring +class empty_querystring_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + std::string qs = std::string(req.get_querystring()); + std::string result = qs.empty() ? "NO_QS" : qs; + return std::make_shared(result, 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, empty_querystring) + empty_querystring_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("empty_qs", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + // URL without any query string + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/empty_qs"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "NO_QS"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(empty_querystring) + +// Resource that tests query parameters with null/empty values +// Covers http_request.cpp lines 234 and 248 (arg_value == nullptr branches) +class null_value_query_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + // Test getting an argument that was passed without a value (e.g., ?keyonly) + auto keyonly_arg = req.get_arg("keyonly"); + auto normal_arg = req.get_arg("normal"); + + // Also test querystring which exercises build_request_querystring + std::string qs = std::string(req.get_querystring()); + + stringstream ss; + ss << "keyonly=" << (keyonly_arg.get_all_values().empty() ? "MISSING" : + (keyonly_arg.get_all_values()[0].empty() ? "EMPTY" : "VALUE")); + ss << ",normal=" << (normal_arg.get_all_values().empty() ? "MISSING" : + std::string(normal_arg.get_all_values()[0])); + ss << ",qs=" << (qs.find("keyonly") != string::npos ? "HAS_KEYONLY" : "NO_KEYONLY"); + + return std::make_shared(ss.str(), 200, "text/plain"); + } +}; + +#ifdef HAVE_BAUTH +// Resource that tests auth caching (get_user/get_pass called multiple times) +class auth_cache_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + // Call get_user and get_pass multiple times to test caching + std::string user1 = std::string(req.get_user()); + std::string pass1 = std::string(req.get_pass()); + std::string user2 = std::string(req.get_user()); // Should hit cache + std::string pass2 = std::string(req.get_pass()); // Should hit cache + + std::string result = user1.empty() ? "NO_AUTH" : ("USER:" + user1); + return std::make_shared(result, 200, "text/plain"); + } +}; +#endif // HAVE_BAUTH + +#ifdef HAVE_BAUTH +LT_BEGIN_AUTO_TEST(basic_suite, auth_caching) + auth_cache_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("auth_cache", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/auth_cache"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + // No authentication provided + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "NO_AUTH"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(auth_caching) +#endif // HAVE_BAUTH + +// Test query parameters with null/empty values (e.g., ?keyonly&normal=value) +// This covers http_request.cpp lines 234 and 248 (arg_value == nullptr branches) +LT_BEGIN_AUTO_TEST(basic_suite, null_value_query_param) + null_value_query_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("null_val_query", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + // Query string with a key that has no value (keyonly) and one with value (normal=test) + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/null_val_query?keyonly&normal=test"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // keyonly should have an empty value (not missing) + LT_CHECK_EQ(s.find("keyonly=EMPTY") != string::npos, true); + LT_CHECK_EQ(s.find("normal=test") != string::npos, true); + LT_CHECK_EQ(s.find("qs=HAS_KEYONLY") != string::npos, true); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(null_value_query_param) + +// Test PUT method on a resource that only implements render() +LT_BEGIN_AUTO_TEST(basic_suite, only_render_put) + only_render_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("only_render_put", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/only_render_put"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(only_render_put) + +// Test DELETE method on a resource that only implements render() +LT_BEGIN_AUTO_TEST(basic_suite, only_render_delete) + only_render_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("only_render_delete", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/only_render_delete"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(only_render_delete) + +// Test POST method on a resource that only implements render() +LT_BEGIN_AUTO_TEST(basic_suite, only_render_post) + only_render_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("only_render_post", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/only_render_post"); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "test=data"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(only_render_post) + +// Test unregister_resource functionality +LT_BEGIN_AUTO_TEST(basic_suite, unregister_resource) + webserver ws2 = create_webserver(PORT + 67); + ok_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("test_unreg", &resource)); + ws2.start(false); + + // First verify resource works + { + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 67) + "/test_unreg"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + // Now unregister + ws2.unregister_resource("test_unreg"); + + // Resource should no longer be accessible (404) + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 67) + "/test_unreg"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_CHECK_EQ(http_code, 404); + curl_easy_cleanup(curl); + } + + ws2.stop(); +LT_END_AUTO_TEST(unregister_resource) + +// Resource that tests get_arg_flat() returning first value for multi-value arg +class arg_flat_multi_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + // get_arg_flat should return the first value even for multi-value args + std::string flat_val = std::string(req.get_arg_flat("key")); + return std::make_shared("flat=" + flat_val, 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, get_arg_flat_first_value) + arg_flat_multi_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("arg_flat_first", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/arg_flat_first?key=value1&key=value2"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "flat=value1"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(get_arg_flat_first_value) + +// Test access and error log callbacks +struct LogCapture { + static std::string& access_log_msg() { + static std::string msg; + return msg; + } + static std::string& error_log_msg() { + static std::string msg; + return msg; + } +}; + +void test_access_logger(const std::string& msg) { + LogCapture::access_log_msg() = msg; +} + +void test_error_logger(const std::string& msg) { + LogCapture::error_log_msg() = msg; +} + +LT_BEGIN_AUTO_TEST(basic_suite, log_access_callback) + LogCapture::access_log_msg().clear(); + + webserver ws2 = create_webserver(PORT + 70) + .log_access(test_access_logger); + ok_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("logtest", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 70) + "/logtest"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + + // The access log should have been called with the request info + LT_CHECK_EQ(LogCapture::access_log_msg().find("/logtest") != std::string::npos, true); + LT_CHECK_EQ(LogCapture::access_log_msg().find("METHOD") != std::string::npos, true); + + curl_easy_cleanup(curl); + ws2.stop(); +LT_END_AUTO_TEST(log_access_callback) + +// Test single_resource mode +LT_BEGIN_AUTO_TEST(basic_suite, single_resource_mode) + webserver ws2 = create_webserver(PORT + 71) + .single_resource(); + ok_resource resource; + // In single_resource mode, must register at "/" with family=true + LT_ASSERT_EQ(true, ws2.register_resource("/", &resource, true)); + ws2.start(false); + + // All paths should route to the single resource + { + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 71) + "/any/path/here"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + // Even root should work + { + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 71) + "/"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + ws2.stop(); +LT_END_AUTO_TEST(single_resource_mode) + +// Test validator builder method (validator is stored but not currently called in webserver) +bool test_validator_func(const std::string& url) { + return url.find("valid") != std::string::npos; +} + +LT_BEGIN_AUTO_TEST(basic_suite, validator_builder) + // Test that the validator builder method works (for coverage of create_webserver.hpp) + webserver ws2 = create_webserver(PORT + 72) + .validator(test_validator_func); + ok_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("test", &resource)); + ws2.start(false); + + // Just verify the server works with a validator set + { + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 72) + "/test"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + ws2.stop(); +LT_END_AUTO_TEST(validator_builder) + +// Test resource with no render methods overridden (exercises empty_render path) +// Note: empty_render returns string_response with code -1, which triggers internal error +class empty_render_resource : public http_resource { + public: + // No render methods overridden - uses default empty_render() path +}; + +LT_BEGIN_AUTO_TEST(basic_suite, default_render_method) + // Test that a resource with no render overrides triggers internal error + // (because empty_render returns response code -1) + webserver ws2 = create_webserver(PORT + 73); + empty_render_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("empty", &resource)); + ws2.start(false); + + { + curl_global_init(CURL_GLOBAL_ALL); + string s; + int64_t http_code = 0; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 73) + "/empty"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + // Default empty_render returns code -1, which causes internal error (500) + LT_CHECK_EQ(http_code, 500); + curl_easy_cleanup(curl); + } + + ws2.stop(); +LT_END_AUTO_TEST(default_render_method) + +// Test resource that overrides only render() (not render_GET) +class render_override_resource : public http_resource { + public: + shared_ptr render(const http_request&) { + return std::make_shared("base_render", 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, render_fallthrough_to_base) + // Test that render_GET calls render() when not overridden + webserver ws2 = create_webserver(PORT + 74); + render_override_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("base", &resource)); + ws2.start(false); + + { + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 74) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "base_render"); + curl_easy_cleanup(curl); + } + + ws2.stop(); +LT_END_AUTO_TEST(render_fallthrough_to_base) + +// Note: CONNECT method is a special HTTP method for tunneling that +// behaves differently than standard HTTP methods, so we don't test +// it the same way as other methods. + +// Test all HTTP methods falling through to base render() +LT_BEGIN_AUTO_TEST(basic_suite, all_methods_fallthrough_to_render) + // render_override_resource only defines render(), not render_GET/POST/etc. + // So all method-specific calls should fall through to render() + webserver ws2 = create_webserver(PORT + 75); + render_override_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("fallthrough", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + CURL* curl; + CURLcode res; + string s; + + // Test POST fallthrough + s = ""; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, ("localhost:" + std::to_string(PORT + 75) + "/fallthrough").c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ""); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "base_render"); + curl_easy_cleanup(curl); + + // Test PUT fallthrough + s = ""; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, ("localhost:" + std::to_string(PORT + 75) + "/fallthrough").c_str()); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "base_render"); + curl_easy_cleanup(curl); + + // Test DELETE fallthrough + s = ""; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, ("localhost:" + std::to_string(PORT + 75) + "/fallthrough").c_str()); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "base_render"); + curl_easy_cleanup(curl); + + // Test PATCH fallthrough + s = ""; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, ("localhost:" + std::to_string(PORT + 75) + "/fallthrough").c_str()); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "base_render"); + curl_easy_cleanup(curl); + + // Test HEAD fallthrough (body is empty for HEAD) + s = ""; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, ("localhost:" + std::to_string(PORT + 75) + "/fallthrough").c_str()); + curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // HEAD response has no body + curl_easy_cleanup(curl); + + // Test OPTIONS fallthrough + s = ""; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, ("localhost:" + std::to_string(PORT + 75) + "/fallthrough").c_str()); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "OPTIONS"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "base_render"); + curl_easy_cleanup(curl); + + // Test TRACE fallthrough + s = ""; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, ("localhost:" + std::to_string(PORT + 75) + "/fallthrough").c_str()); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "TRACE"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "base_render"); + curl_easy_cleanup(curl); + + ws2.stop(); +LT_END_AUTO_TEST(all_methods_fallthrough_to_render) + +// Test internal_error_resource custom handler +shared_ptr custom_internal_error_handler(const http_request&) { + return std::make_shared("Custom Internal Error", 500, "text/plain"); +} + +class throwing_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + throw std::runtime_error("Intentional test exception"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, custom_internal_error_resource) + webserver ws2 = create_webserver(PORT + 76) + .internal_error_resource(custom_internal_error_handler); + throwing_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("throw", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + int64_t http_code = 0; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 76) + "/throw"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(http_code, 500); + LT_CHECK_EQ(s, "Custom Internal Error"); + curl_easy_cleanup(curl); + + ws2.stop(); +LT_END_AUTO_TEST(custom_internal_error_resource) + +// Test get_arg_flat fallback to MHD connection value +class arg_flat_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + // get_arg_flat should fall back to MHD connection value for query params + std::string result = std::string(req.get_arg_flat("q")); + return std::make_shared(result, 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, get_arg_flat_fallback) + webserver ws2 = create_webserver(PORT + 77); + arg_flat_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("argflat", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 77) + "/argflat?q=test_value"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "test_value"); + curl_easy_cleanup(curl); + + ws2.stop(); +LT_END_AUTO_TEST(get_arg_flat_fallback) + +// Test large multipart form field that triggers grow_last_arg path +class large_multipart_resource : public http_resource { + public: + shared_ptr render_POST(const http_request& req) { + std::string result = std::string(req.get_arg("large_field")); + return std::make_shared(std::to_string(result.size()), 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(basic_suite, large_multipart_form_field) + // This test sends a large text field via multipart form-data + // to trigger the grow_last_arg path in http_request.cpp (line 544) + webserver ws2 = create_webserver(PORT + 78); + large_multipart_resource resource; + LT_ASSERT_EQ(true, ws2.register_resource("largemp", &resource)); + ws2.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + string s; + CURL *curl = curl_easy_init(); + CURLcode res; + + // Create a large string (100KB) to ensure MHD chunks it + const size_t large_size = 100 * 1024; + std::string large_data(large_size, 'X'); + + // Use curl_mime for multipart/form-data + curl_mime *form = curl_mime_init(curl); + curl_mimepart *field = curl_mime_addpart(form); + curl_mime_name(field, "large_field"); + curl_mime_data(field, large_data.c_str(), CURL_ZERO_TERMINATED); + + std::string url = "http://localhost:" + std::to_string(PORT + 78) + "/largemp"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_MIMEPOST, form); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // The response should be the size of the large field + LT_CHECK_EQ(s, std::to_string(large_size)); + + curl_mime_free(form); + curl_easy_cleanup(curl); + + ws2.stop(); +LT_END_AUTO_TEST(large_multipart_form_field) + +#ifdef HAVE_GNUTLS +// Resource that tests client certificate methods on non-TLS requests +class client_cert_non_tls_resource : public http_resource { + public: + shared_ptr render_GET(const http_request& req) { + std::string result; + // All these should return false/empty since this is not a TLS connection + result += "has_tls_session:" + std::string(req.has_tls_session() ? "yes" : "no") + ";"; + result += "has_client_cert:" + std::string(req.has_client_certificate() ? "yes" : "no") + ";"; + result += "dn:" + req.get_client_cert_dn() + ";"; + result += "issuer:" + req.get_client_cert_issuer_dn() + ";"; + result += "cn:" + req.get_client_cert_cn() + ";"; + result += "verified:" + std::string(req.is_client_cert_verified() ? "yes" : "no") + ";"; + result += "fingerprint:" + req.get_client_cert_fingerprint_sha256() + ";"; + result += "not_before:" + std::to_string(req.get_client_cert_not_before()) + ";"; + result += "not_after:" + std::to_string(req.get_client_cert_not_after()); + return std::make_shared(result, 200, "text/plain"); + } +}; + +// Test that client certificate methods return appropriate values for non-TLS requests +LT_BEGIN_AUTO_TEST(basic_suite, client_cert_methods_non_tls) + webserver ws = create_webserver(PORT + 79); + client_cert_non_tls_resource ccnr; + ws.register_resource("/cert_test", &ccnr); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(PORT + 79) + "/cert_test"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + + // Verify all methods return false/empty for non-TLS + LT_CHECK_NEQ(s.find("has_tls_session:no"), std::string::npos); + LT_CHECK_NEQ(s.find("has_client_cert:no"), std::string::npos); + LT_CHECK_NEQ(s.find("dn:;"), std::string::npos); + LT_CHECK_NEQ(s.find("issuer:;"), std::string::npos); + LT_CHECK_NEQ(s.find("cn:;"), std::string::npos); + LT_CHECK_NEQ(s.find("verified:no"), std::string::npos); + LT_CHECK_NEQ(s.find("fingerprint:;"), std::string::npos); + LT_CHECK_NEQ(s.find("not_before:-1"), std::string::npos); + LT_CHECK_NEQ(s.find("not_after:-1"), std::string::npos); + + curl_easy_cleanup(curl); + ws.stop(); +LT_END_AUTO_TEST(client_cert_methods_non_tls) +#endif // HAVE_GNUTLS LT_BEGIN_AUTO_TEST_ENV() AUTORUN_TESTS() diff --git a/test/integ/deferred.cpp b/test/integ/deferred.cpp new file mode 100644 index 00000000..64ccfb12 --- /dev/null +++ b/test/integ/deferred.cpp @@ -0,0 +1,197 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#if defined(_WIN32) && !defined(__CYGWIN__) +#define _WINDOWS +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x600 +#include +#include +#else +#include +#include +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include + +#include "./httpserver.hpp" +#include "./littletest.hpp" + +using std::shared_ptr; +using std::string; + +using httpserver::webserver; +using httpserver::create_webserver; +using httpserver::http_response; +using httpserver::http_request; +using httpserver::http_resource; +using httpserver::deferred_response; + +size_t writefunc(void *ptr, size_t size, size_t nmemb, string *s) { + s->append(reinterpret_cast(ptr), size*nmemb); + return size*nmemb; +} + +static int counter = 0; + +struct test_data { + int value; +}; + +ssize_t test_callback(shared_ptr closure_data, char* buf, size_t max) { + std::ignore = closure_data; + + if (counter == 2) { + return -1; + } else { + memset(buf, 0, max); + snprintf(buf, max, "%s", "test"); + counter++; + return string(buf).size(); + } +} + +ssize_t test_callback_with_data(shared_ptr closure_data, char* buf, size_t max) { + if (counter == 2) { + return -1; + } else { + memset(buf, 0, max); + snprintf(buf, max, "%s%s", "test", std::to_string(closure_data->value).c_str()); + + closure_data->value = 84; + + counter++; + return std::string(buf).size(); + } +} + +class deferred_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared>(test_callback, nullptr, "cycle callback response"); + } +}; + +class deferred_resource_with_data : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + auto internal_info = std::make_shared(); + internal_info->value = 42; + return std::make_shared>(test_callback_with_data, internal_info, "cycle callback response"); + } +}; + +class deferred_resource_empty_content : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared>(test_callback, nullptr); + } +}; + +#ifdef HTTPSERVER_PORT +#define PORT HTTPSERVER_PORT +#else +#define PORT 8080 +#endif // PORT + +#define STR2(p) #p +#define STR(p) STR2(p) +#define PORT_STRING STR(PORT) + +LT_BEGIN_SUITE(deferred_suite) + std::unique_ptr ws; + + void set_up() { + ws = std::make_unique(create_webserver(PORT)); + ws->start(false); + } + + void tear_down() { + counter = 0; + + ws->stop(); + } +LT_END_SUITE(deferred_suite) + +LT_BEGIN_AUTO_TEST(deferred_suite, deferred_response_suite) + deferred_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "cycle callback responsetesttest"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(deferred_response_suite) + +LT_BEGIN_AUTO_TEST(deferred_suite, deferred_response_with_data) + deferred_resource_with_data resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "cycle callback responsetest42test84"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(deferred_response_with_data) + +LT_BEGIN_AUTO_TEST(deferred_suite, deferred_response_empty_content) + deferred_resource_empty_content resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "testtest"); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(deferred_response_empty_content) + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV() diff --git a/test/integ/file_upload.cpp b/test/integ/file_upload.cpp new file mode 100644 index 00000000..5361c1d0 --- /dev/null +++ b/test/integ/file_upload.cpp @@ -0,0 +1,1059 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include +#ifdef _WIN32 +#include +#define MKDIR(path) _mkdir(path) +#else +#define MKDIR(path) mkdir(path, 0755) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./httpserver.hpp" +#include "httpserver/string_utilities.hpp" +#include "./littletest.hpp" + +using std::string; +using std::string_view; +using std::map; +using std::shared_ptr; +using std::vector; +using std::stringstream; + +using httpserver::http_resource; +using httpserver::http_request; +using httpserver::http_response; +using httpserver::string_response; +using httpserver::file_response; +using httpserver::webserver; +using httpserver::create_webserver; +using httpserver::http::arg_map; + +#ifdef HTTPSERVER_PORT +#define PORT HTTPSERVER_PORT +#else +#define PORT 8080 +#endif // PORT + +#define STR2(p) #p +#define STR(p) STR2(p) +#define PORT_STRING STR(PORT) + +#ifdef HTTPSERVER_DATA_ROOT +#define ROOT STR(HTTPSERVER_DATA_ROOT) +#else +#define ROOT "." +#endif // HTTPSERVER_DATA_ROOT + +static const char* TEST_CONTENT_FILENAME = "test_content"; +static const char* TEST_CONTENT_FILEPATH = ROOT "/test_content"; +static const char* FILENAME_IN_GET_CONTENT = "filename=\"test_content\""; +static const char* TEST_CONTENT = "test content of file\n"; +static const char* TEST_KEY = "file"; +static size_t TEST_CONTENT_SIZE = 21; + +static const char* TEST_CONTENT_FILENAME_2 = "test_content_2"; +static const char* TEST_CONTENT_FILEPATH_2 = ROOT "/test_content_2"; +static const char* FILENAME_IN_GET_CONTENT_2 = "filename=\"test_content_2\""; +static const char* TEST_CONTENT_2 = "test content of second file\n"; +static const char* TEST_KEY_2 = "file2"; +static size_t TEST_CONTENT_SIZE_2 = 28; + +static const char* TEST_PARAM_KEY = "param_key"; +static const char* TEST_PARAM_VALUE = "Value of test param"; + +// The large file test_content_large is large enough to ensure +// that MHD splits the underlying request into several chunks. +static const char* LARGE_FILENAME_IN_GET_CONTENT = "filename=\"test_content_large\""; +static const char* LARGE_CONTENT_FILEPATH = ROOT "/test_content_large"; +static const char* LARGE_KEY = "large_file"; + +static bool file_exists(const string &path) { + struct stat sb; + + return (stat(path.c_str(), &sb) == 0); +} + +static std::pair send_file_to_webserver(bool add_second_file, bool append_parameters) { + curl_global_init(CURL_GLOBAL_ALL); + + CURL *curl = curl_easy_init(); + + curl_mime *form = curl_mime_init(curl); + curl_mimepart *field = curl_mime_addpart(form); + curl_mime_name(field, TEST_KEY); + curl_mime_filedata(field, TEST_CONTENT_FILEPATH); + if (add_second_file) { + field = curl_mime_addpart(form); + curl_mime_name(field, TEST_KEY_2); + curl_mime_filedata(field, TEST_CONTENT_FILEPATH_2); + } + + if (append_parameters) { + field = curl_mime_addpart(form); + curl_mime_name(field, TEST_PARAM_KEY); + curl_mime_data(field, TEST_PARAM_VALUE, CURL_ZERO_TERMINATED); + } + + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/upload"); + curl_easy_setopt(curl, CURLOPT_MIMEPOST, form); + + res = curl_easy_perform(curl); + long http_code = 0; // NOLINT [runtime/int] + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + curl_easy_cleanup(curl); + curl_mime_free(form); + return {res, http_code}; +} + +// Send file with explicit content-type and transfer-encoding headers +static std::pair send_file_with_content_type(int port, const char* content_type) { + curl_global_init(CURL_GLOBAL_ALL); + + CURL *curl = curl_easy_init(); + + curl_mime *form = curl_mime_init(curl); + curl_mimepart *field = curl_mime_addpart(form); + curl_mime_name(field, TEST_KEY); + curl_mime_filedata(field, TEST_CONTENT_FILEPATH); + // Set explicit content-type for the file part + curl_mime_type(field, content_type); + // Add transfer-encoding header + struct curl_slist *headers = nullptr; + headers = curl_slist_append(headers, "Content-Transfer-Encoding: binary"); + curl_mime_headers(field, headers, 1); // 1 means take ownership + + CURLcode res; + std::string url = "localhost:" + std::to_string(port) + "/upload"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_MIMEPOST, form); + + res = curl_easy_perform(curl); + long http_code = 0; // NOLINT [runtime/int] + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + curl_easy_cleanup(curl); + curl_mime_free(form); + return {res, http_code}; +} + +static std::pair send_large_file(string* content, std::string args = "") { + // Generate a large (100K) file of random bytes. Upload the file with + // a curl request, then delete the file. The default chunk size of MHD + // appears to be around 16K, so 100K should be enough to trigger the + // behavior. Return the content via the pointer parameter so that test + // cases can make required checks for the content. + curl_global_init(CURL_GLOBAL_ALL); + + CURL *curl = curl_easy_init(); + + curl_mime *form = curl_mime_init(curl); + curl_mimepart *field = curl_mime_addpart(form); + + std::ifstream infile(LARGE_CONTENT_FILEPATH); + std::stringstream buffer; + buffer << infile.rdbuf(); + infile.close(); + *content = buffer.str(); + + curl_mime_name(field, LARGE_KEY); + curl_mime_filedata(field, LARGE_CONTENT_FILEPATH); + + std::string url = "localhost:" PORT_STRING "/upload"; + if (!args.empty()) { + url.append(args); + } + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_MIMEPOST, form); + + res = curl_easy_perform(curl); + long http_code = 0; // NOLINT [runtime/int] + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + curl_easy_cleanup(curl); + curl_mime_free(form); + + return {res, http_code}; +} + +static std::tuple send_file_via_put() { + curl_global_init(CURL_GLOBAL_ALL); + + CURL *curl; + CURLcode res; + struct stat file_info; + FILE *fd; + + fd = fopen(TEST_CONTENT_FILEPATH, "rb"); + if (!fd) { + return {false, CURLcode{}, 0L}; + } + + if (fstat(fileno(fd), &file_info) != 0) { + return {false, CURLcode{}, 0L}; + } + + curl = curl_easy_init(); + if (!curl) { + fclose(fd); + return {false, CURLcode{}, 0L}; + } + + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/upload"); + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + curl_easy_setopt(curl, CURLOPT_READDATA, fd); + curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) file_info.st_size); + + res = curl_easy_perform(curl); + long http_code = 0; // NOLINT [runtime/int] + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + curl_easy_cleanup(curl); + + fclose(fd); + + return {true, res, http_code}; +} + +class print_file_upload_resource : public http_resource { + public: + shared_ptr render_POST(const http_request& req) { + content = req.get_content(); + auto args_view = req.get_args(); + // req may go out of scope, so we need to copy the values. + for (auto const& item : args_view) { + for (auto const & value : item.second.get_all_values()) { + args[string(item.first)].push_back(string(value)); + } + } + files = req.get_files(); + return std::make_shared("OK", 201, "text/plain"); + } + + shared_ptr render_PUT(const http_request& req) { + content = req.get_content(); + auto args_view = req.get_args(); + // req may go out of scope, so we need to copy the values. + for (auto const& item : args_view) { + for (auto const & value : item.second.get_all_values()) { + args[string(item.first)].push_back(string(value)); + } + } + files = req.get_files(); + return std::make_shared("OK", 200, "text/plain"); + } + + const std::map, httpserver::http::arg_comparator> get_args() const { + return args; + } + + const map> get_files() const { + return files; + } + + const string get_content() const { + return content; + } + + private: + std::map, httpserver::http::arg_comparator> args; + map> files; + string content; +}; + +LT_BEGIN_SUITE(file_upload_suite) + void set_up() { + } + + void tear_down() { + } +LT_END_SUITE(file_upload_suite) + +LT_BEGIN_AUTO_TEST(file_upload_suite, check_files) + std::ifstream it; + it.open(TEST_CONTENT_FILEPATH); + LT_CHECK_EQ(it.is_open(), true); + + it.open(TEST_CONTENT_FILEPATH_2); + LT_CHECK_EQ(it.is_open(), true); + + it.open(LARGE_CONTENT_FILEPATH); + LT_CHECK_EQ(it.is_open(), true); +LT_END_AUTO_TEST(check_files) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_and_disk) + string upload_directory = "."; + + auto ws = std::make_unique(create_webserver(PORT) + .put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_AND_DISK) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload()); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + auto res = send_file_to_webserver(false, false); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + ws->stop(); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.find(FILENAME_IN_GET_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_CONTENT) != string::npos, true); + + auto args = resource.get_args(); + LT_CHECK_EQ(args.size(), 1); + auto arg = args.begin(); + LT_CHECK_EQ(arg->first, TEST_KEY); + LT_CHECK_EQ(arg->second[0], TEST_CONTENT); + + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 1); + map>::iterator file_key = files.begin(); + LT_CHECK_EQ(file_key->first, TEST_KEY); + LT_CHECK_EQ(file_key->second.size(), 1); + map::iterator file = file_key->second.begin(); + LT_CHECK_EQ(file->first, TEST_CONTENT_FILENAME); + LT_CHECK_EQ(file->second.get_file_size(), TEST_CONTENT_SIZE); + LT_CHECK_EQ(file->second.get_content_type(), httpserver::http::http_utils::application_octet_stream); + + string expected_filename = upload_directory + + httpserver::http::http_utils::path_separator + + httpserver::http::http_utils::upload_filename_template; + LT_CHECK_EQ(file->second.get_file_system_file_name().substr(0, file->second.get_file_system_file_name().size() - 6), + expected_filename.substr(0, expected_filename.size() - 6)); + LT_CHECK_EQ(file_exists(file->second.get_file_system_file_name()), false); +LT_END_AUTO_TEST(file_upload_memory_and_disk) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_and_disk_via_put) + string upload_directory = "."; + + auto ws = std::make_unique(create_webserver(PORT) + .put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_AND_DISK) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload()); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + auto ret = send_file_via_put(); + LT_CHECK_EQ(std::get<1>(ret), 0); + LT_CHECK_EQ(std::get<2>(ret), 200); + LT_ASSERT_EQ(std::get<0>(ret), true); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content, TEST_CONTENT); + + auto args = resource.get_args(); + LT_CHECK_EQ(args.size(), 0); + + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 0); + + ws->stop(); +LT_END_AUTO_TEST(file_upload_memory_and_disk_via_put) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_and_disk_additional_params) + string upload_directory = "."; + + auto ws = std::make_unique(create_webserver(PORT) + .put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_AND_DISK) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload()); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + auto res = send_file_to_webserver(false, true); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + ws->stop(); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.find(FILENAME_IN_GET_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_PARAM_KEY) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_PARAM_VALUE) != string::npos, true); + + auto args = resource.get_args(); + LT_CHECK_EQ(args.size(), 2); + auto arg = args.begin(); + LT_CHECK_EQ(arg->first, TEST_KEY); + LT_CHECK_EQ(arg->second[0], TEST_CONTENT); + arg++; + LT_CHECK_EQ(arg->first, TEST_PARAM_KEY); + LT_CHECK_EQ(arg->second[0], TEST_PARAM_VALUE); + + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 1); + map>::iterator file_key = files.begin(); + LT_CHECK_EQ(file_key->first, TEST_KEY); + LT_CHECK_EQ(file_key->second.size(), 1); + map::iterator file = file_key->second.begin(); + LT_CHECK_EQ(file->first, TEST_CONTENT_FILENAME); + LT_CHECK_EQ(file->second.get_file_size(), TEST_CONTENT_SIZE); + LT_CHECK_EQ(file->second.get_content_type(), httpserver::http::http_utils::application_octet_stream); + + string expected_filename = upload_directory + + httpserver::http::http_utils::path_separator + + httpserver::http::http_utils::upload_filename_template; + LT_CHECK_EQ(file->second.get_file_system_file_name().substr(0, file->second.get_file_system_file_name().size() - 6), + expected_filename.substr(0, expected_filename.size() - 6)); + LT_CHECK_EQ(file_exists(file->second.get_file_system_file_name()), false); +LT_END_AUTO_TEST(file_upload_memory_and_disk_additional_params) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_and_disk_two_files) + string upload_directory = "."; + + auto ws = std::make_unique(create_webserver(PORT) + .put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_AND_DISK) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload()); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + auto res = send_file_to_webserver(true, false); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + ws->stop(); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.find(FILENAME_IN_GET_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(FILENAME_IN_GET_CONTENT_2) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_CONTENT_2) != string::npos, true); + + auto args = resource.get_args(); + LT_CHECK_EQ(args.size(), 2); + auto arg = args.begin(); + LT_CHECK_EQ(arg->first, TEST_KEY); + LT_CHECK_EQ(arg->second[0], TEST_CONTENT); + arg++; + LT_CHECK_EQ(arg->first, TEST_KEY_2); + LT_CHECK_EQ(arg->second[0], TEST_CONTENT_2); + + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 2); + map>::iterator file_key = files.begin(); + LT_CHECK_EQ(file_key->first, TEST_KEY); + LT_CHECK_EQ(file_key->second.size(), 1); + map::iterator file = file_key->second.begin(); + LT_CHECK_EQ(file->first, TEST_CONTENT_FILENAME); + LT_CHECK_EQ(file->second.get_file_size(), TEST_CONTENT_SIZE); + LT_CHECK_EQ(file->second.get_content_type(), httpserver::http::http_utils::application_octet_stream); + + string expected_filename = upload_directory + + httpserver::http::http_utils::path_separator + + httpserver::http::http_utils::upload_filename_template; + LT_CHECK_EQ(file->second.get_file_system_file_name().substr(0, file->second.get_file_system_file_name().size() - 6), + expected_filename.substr(0, expected_filename.size() - 6)); + LT_CHECK_EQ(file_exists(file->second.get_file_system_file_name()), false); + + file_key++; + LT_CHECK_EQ(file_key->first, TEST_KEY_2); + LT_CHECK_EQ(file_key->second.size(), 1); + file = file_key->second.begin(); + LT_CHECK_EQ(file->first, TEST_CONTENT_FILENAME_2); + LT_CHECK_EQ(file->second.get_file_size(), TEST_CONTENT_SIZE_2); + LT_CHECK_EQ(file->second.get_content_type(), httpserver::http::http_utils::application_octet_stream); + + expected_filename = upload_directory + + httpserver::http::http_utils::path_separator + + httpserver::http::http_utils::upload_filename_template; + LT_CHECK_EQ(file->second.get_file_system_file_name().substr(0, file->second.get_file_system_file_name().size() - 6), + expected_filename.substr(0, expected_filename.size() - 6)); + LT_CHECK_EQ(file_exists(file->second.get_file_system_file_name()), false); +LT_END_AUTO_TEST(file_upload_memory_and_disk_two_files) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_disk_only) + string upload_directory = "."; + + auto ws = std::make_unique(create_webserver(PORT) + .no_put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload()); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + auto res = send_file_to_webserver(false, false); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + ws->stop(); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.size(), 0); + + auto args = resource.get_args(); + LT_CHECK_EQ(args.size(), 0); + + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 1); + map>::iterator file_key = files.begin(); + LT_CHECK_EQ(file_key->first, TEST_KEY); + LT_CHECK_EQ(file_key->second.size(), 1); + map::iterator file = file_key->second.begin(); + LT_CHECK_EQ(file->first, TEST_CONTENT_FILENAME); + LT_CHECK_EQ(file->second.get_file_size(), TEST_CONTENT_SIZE); + LT_CHECK_EQ(file->second.get_content_type(), httpserver::http::http_utils::application_octet_stream); + + string expected_filename = upload_directory + + httpserver::http::http_utils::path_separator + + httpserver::http::http_utils::upload_filename_template; + LT_CHECK_EQ(file->second.get_file_system_file_name().substr(0, file->second.get_file_system_file_name().size() - 6), + expected_filename.substr(0, expected_filename.size() - 6)); + LT_CHECK_EQ(file_exists(file->second.get_file_system_file_name()), false); +LT_END_AUTO_TEST(file_upload_disk_only) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_only_incl_content) + auto ws = std::make_unique(create_webserver(PORT) + .put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_ONLY)); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + auto res = send_file_to_webserver(false, false); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.find(FILENAME_IN_GET_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(TEST_CONTENT) != string::npos, true); + + auto args = resource.get_args(); + LT_CHECK_EQ(args.size(), 1); + auto arg = args.begin(); + LT_CHECK_EQ(arg->first, TEST_KEY); + LT_CHECK_EQ(arg->second[0], TEST_CONTENT); + + map> files = resource.get_files(); + LT_CHECK_EQ(resource.get_files().size(), 0); + + ws->stop(); +LT_END_AUTO_TEST(file_upload_memory_only_incl_content) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_large_content) + auto ws = std::make_unique(create_webserver(PORT) + .put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_ONLY)); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + // Upload a large file to trigger the chunking behavior of MHD. + std::string file_content; + auto res = send_large_file(&file_content); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.find(LARGE_FILENAME_IN_GET_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(file_content) != string::npos, true); + + // The chunks of the file should be concatenated into the first + // arg value of the key. + auto const args = resource.get_args(); + LT_CHECK_EQ(args.size(), 1); + auto const file_arg_iter = args.find(std::string_view(LARGE_KEY)); + if (file_arg_iter == args.end()) { + LT_FAIL("file arg not found"); + } + LT_CHECK_EQ(file_arg_iter->second.size(), 1); + LT_CHECK_EQ(file_arg_iter->second[0], file_content); + + map> files = resource.get_files(); + LT_CHECK_EQ(resource.get_files().size(), 0); + + ws->stop(); +LT_END_AUTO_TEST(file_upload_large_content) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_large_content_with_args) + auto ws = std::make_unique(create_webserver(PORT) + .put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_ONLY)); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + // Upload a large file to trigger the chunking behavior of MHD. + // Include some additional args to make sure those are processed as well. + std::string file_content; + auto res = send_large_file(&file_content, "?arg1=hello&arg1=world"); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.find(LARGE_FILENAME_IN_GET_CONTENT) != string::npos, true); + LT_CHECK_EQ(actual_content.find(file_content) != string::npos, true); + + auto const args = resource.get_args(); + LT_CHECK_EQ(args.size(), 2); + auto const file_arg_iter = args.find(std::string_view(LARGE_KEY)); + if (file_arg_iter == args.end()) { + LT_FAIL("file arg not found"); + } + LT_CHECK_EQ(file_arg_iter->second.size(), 1); + LT_CHECK_EQ(file_arg_iter->second[0], file_content); + auto const other_arg_iter = args.find(std::string_view("arg1")); + if (other_arg_iter == args.end()) { + LT_FAIL("other arg(s) not found"); + } + LT_CHECK_EQ(other_arg_iter->second.size(), 2); + LT_CHECK_EQ(other_arg_iter->second[0], "hello"); + LT_CHECK_EQ(other_arg_iter->second[1], "world"); + + map> files = resource.get_files(); + LT_CHECK_EQ(resource.get_files().size(), 0); + + ws->stop(); +LT_END_AUTO_TEST(file_upload_large_content_with_args) + +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_memory_only_excl_content) + auto ws = std::make_unique(create_webserver(PORT) + .no_put_processed_data_to_content() + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_ONLY)); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + auto res = send_file_to_webserver(false, false); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + string actual_content = resource.get_content(); + LT_CHECK_EQ(actual_content.size(), 0); + + auto args = resource.get_args(); + LT_CHECK_EQ(args.size(), 1); + auto arg = args.begin(); + LT_CHECK_EQ(arg->first, TEST_KEY); + LT_CHECK_EQ(arg->second[0], TEST_CONTENT); + + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 0); + + ws->stop(); +LT_END_AUTO_TEST(file_upload_memory_only_excl_content) + +// Test that file cleanup callback returning true causes file deletion (default behavior) +LT_BEGIN_AUTO_TEST(file_upload_suite, file_cleanup_callback_returns_true) + string upload_directory = "."; + + // Track callback invocations + std::vector> callback_invocations; + + auto ws = std::make_unique(create_webserver(PORT) + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload() + .file_cleanup_callback([&callback_invocations]( + const std::string& key, + const std::string& filename, + const httpserver::http::file_info& info) { + callback_invocations.push_back({key, filename, info.get_file_size()}); + return true; // Delete the file + })); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + auto res = send_file_to_webserver(false, false); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + ws->stop(); + + // Verify callback was called with correct parameters + LT_CHECK_EQ(callback_invocations.size(), 1); + LT_CHECK_EQ(std::get<0>(callback_invocations[0]), TEST_KEY); + LT_CHECK_EQ(std::get<1>(callback_invocations[0]), TEST_CONTENT_FILENAME); + LT_CHECK_EQ(std::get<2>(callback_invocations[0]), TEST_CONTENT_SIZE); + + // Verify file was deleted (callback returned true) + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 1); + map::iterator file = files.begin()->second.begin(); + LT_CHECK_EQ(file_exists(file->second.get_file_system_file_name()), false); +LT_END_AUTO_TEST(file_cleanup_callback_returns_true) + +// Test that file cleanup callback returning false keeps the file +LT_BEGIN_AUTO_TEST(file_upload_suite, file_cleanup_callback_returns_false) + string upload_directory = "."; + string kept_file_path; + + auto ws = std::make_unique(create_webserver(PORT) + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload() + .file_cleanup_callback([&kept_file_path]( + const std::string& key, + const std::string& filename, + const httpserver::http::file_info& info) { + (void)key; + (void)filename; + kept_file_path = info.get_file_system_file_name(); + return false; // Keep the file + })); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + auto res = send_file_to_webserver(false, false); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + ws->stop(); + + // Verify file still exists (callback returned false) + LT_CHECK_EQ(file_exists(kept_file_path), true); + + // Cleanup: manually delete the file + remove(kept_file_path.c_str()); + LT_CHECK_EQ(file_exists(kept_file_path), false); +LT_END_AUTO_TEST(file_cleanup_callback_returns_false) + +// Test selective cleanup: callback can keep some files and delete others +LT_BEGIN_AUTO_TEST(file_upload_suite, file_cleanup_callback_selective) + string upload_directory = "."; + string kept_file_path; + + auto ws = std::make_unique(create_webserver(PORT) + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload() + .file_cleanup_callback([&kept_file_path]( + const std::string& key, + const std::string& filename, + const httpserver::http::file_info& info) { + (void)filename; + // Keep first file, delete second + if (key == TEST_KEY) { + kept_file_path = info.get_file_system_file_name(); + return false; // Keep + } + return true; // Delete + })); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + // Upload two files + auto res = send_file_to_webserver(true, false); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + ws->stop(); + + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 2); + + // First file should exist (callback returned false) + LT_CHECK_EQ(file_exists(kept_file_path), true); + + // Second file should be deleted (callback returned true) + auto file_key_2 = files.find(TEST_KEY_2); + LT_ASSERT_EQ(file_key_2 != files.end(), true); + string deleted_file_path = file_key_2->second.begin()->second.get_file_system_file_name(); + LT_CHECK_EQ(file_exists(deleted_file_path), false); + + // Cleanup: manually delete the kept file + remove(kept_file_path.c_str()); +LT_END_AUTO_TEST(file_cleanup_callback_selective) + +// Test that exception in callback defaults to deleting the file +LT_BEGIN_AUTO_TEST(file_upload_suite, file_cleanup_callback_throws) + string upload_directory = "."; + + auto ws = std::make_unique(create_webserver(PORT) + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload() + .file_cleanup_callback([]( + const std::string& key, + const std::string& filename, + const httpserver::http::file_info& info) -> bool { + (void)key; + (void)filename; + (void)info; + throw std::runtime_error("Test exception"); + })); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + auto res = send_file_to_webserver(false, false); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + ws->stop(); + + // Verify file was deleted (exception causes default delete behavior) + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 1); + map::iterator file = files.begin()->second.begin(); + LT_CHECK_EQ(file_exists(file->second.get_file_system_file_name()), false); +LT_END_AUTO_TEST(file_cleanup_callback_throws) + +// Test that no callback defaults to deleting files (backward compatibility) +LT_BEGIN_AUTO_TEST(file_upload_suite, file_cleanup_no_callback_deletes) + string upload_directory = "."; + + auto ws = std::make_unique(create_webserver(PORT) + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload()); + // No file_cleanup_callback set + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + auto res = send_file_to_webserver(false, false); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + ws->stop(); + + // Verify file was deleted (default behavior) + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 1); + map::iterator file = files.begin()->second.begin(); + LT_CHECK_EQ(file_exists(file->second.get_file_system_file_name()), false); +LT_END_AUTO_TEST(file_cleanup_no_callback_deletes) + +// Test file upload keeping original filename (without random generation) +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_original_filename) + // Use a subdirectory to avoid overwriting the test input file + string upload_directory = "upload_test_dir"; + MKDIR(upload_directory.c_str()); + + auto ws = std::make_unique(create_webserver(PORT) + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY) + .file_upload_dir(upload_directory)); + // Note: NOT using generate_random_filename_on_upload() + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + auto res = send_file_to_webserver(false, false); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + // Verify file was created with original name + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 1); + map::iterator file = files.begin()->second.begin(); + // The filename should be upload_directory/test_content (the original name) + string expected_path = upload_directory + "/" + TEST_CONTENT_FILENAME; + LT_CHECK_EQ(file->second.get_file_system_file_name(), expected_path); + + ws->stop(); + + // Clean up the file and directory + unlink(expected_path.c_str()); + rmdir(upload_directory.c_str()); +LT_END_AUTO_TEST(file_upload_original_filename) + +// Test file upload with explicit content-type header +// This exercises the content_type != nullptr branch in webserver.cpp post_iterator +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_with_content_type) + int port = PORT + 1; + string upload_directory = "."; + + auto ws = std::make_unique(create_webserver(port) + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY) + .file_upload_dir(upload_directory) + .generate_random_filename_on_upload()); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + // Send file with explicit content-type "text/plain" + auto res = send_file_with_content_type(port, "text/plain"); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + // Verify file_info has the correct content-type + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 1); + auto file_key = files.find(TEST_KEY); + LT_CHECK_EQ(file_key != files.end(), true); + auto file = file_key->second.begin(); + // The content-type should be what we set + LT_CHECK_EQ(file->second.get_content_type(), "text/plain"); + + // Clean up the uploaded file + unlink(file->second.get_file_system_file_name().c_str()); + + ws->stop(); +LT_END_AUTO_TEST(file_upload_with_content_type) + +// Send file with a crafted filename for path traversal testing +static std::pair send_file_with_traversal_name(int port, const char* crafted_filename) { + curl_global_init(CURL_GLOBAL_ALL); + + CURL *curl = curl_easy_init(); + + curl_mime *form = curl_mime_init(curl); + curl_mimepart *field = curl_mime_addpart(form); + curl_mime_name(field, TEST_KEY); + // Use the real file for data, but override the filename + curl_mime_filedata(field, TEST_CONTENT_FILEPATH); + curl_mime_filename(field, crafted_filename); + + CURLcode res; + std::string url = "localhost:" + std::to_string(port) + "/upload"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_MIMEPOST, form); + + res = curl_easy_perform(curl); + long http_code = 0; // NOLINT [runtime/int] + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + curl_easy_cleanup(curl); + curl_mime_free(form); + return {res, http_code}; +} + +// Test that path traversal filenames are rejected +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_path_traversal_rejected) + string upload_directory = "upload_test_dir"; + MKDIR(upload_directory.c_str()); + + int port = PORT + 2; + auto ws = std::make_unique(create_webserver(port) + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY) + .file_upload_dir(upload_directory)); + // NOT using generate_random_filename_on_upload - this is the vulnerable path + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + // Attempt path traversal with "../escape" + send_file_with_traversal_name(port, "../escape"); + // The server should reject the upload (MHD_NO causes connection close) + // The key check is that no file was created outside the upload dir + LT_CHECK_EQ(file_exists("escape"), false); + LT_CHECK_EQ(file_exists("./escape"), false); + + ws->stop(); + + // Clean up + rmdir(upload_directory.c_str()); +LT_END_AUTO_TEST(file_upload_path_traversal_rejected) + +// Test that sanitize keeps the basename for normal filenames +LT_BEGIN_AUTO_TEST(file_upload_suite, file_upload_sanitize_keeps_basename) + string upload_directory = "upload_test_dir"; + MKDIR(upload_directory.c_str()); + + int port = PORT + 3; + auto ws = std::make_unique(create_webserver(port) + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY) + .file_upload_dir(upload_directory)); + ws->start(false); + LT_CHECK_EQ(ws->is_running(), true); + + print_file_upload_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("upload", &resource)); + + // Upload with a path-like filename — should strip to just "myfile.txt" + auto res = send_file_with_traversal_name(port, "some/path/myfile.txt"); + LT_ASSERT_EQ(res.first, 0); + LT_ASSERT_EQ(res.second, 201); + + // The file should be created with only the basename + map> files = resource.get_files(); + LT_CHECK_EQ(files.size(), 1); + auto file = files.begin()->second.begin(); + string expected_path = upload_directory + "/myfile.txt"; + LT_CHECK_EQ(file->second.get_file_system_file_name(), expected_path); + + ws->stop(); + + // Clean up + unlink(expected_path.c_str()); + rmdir(upload_directory.c_str()); +LT_END_AUTO_TEST(file_upload_sanitize_keeps_basename) + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV() diff --git a/test/integ/nodelay.cpp b/test/integ/nodelay.cpp new file mode 100644 index 00000000..068ef18a --- /dev/null +++ b/test/integ/nodelay.cpp @@ -0,0 +1,87 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include +#include +#include + +#include "./httpserver.hpp" +#include "./littletest.hpp" + +using std::shared_ptr; + +using httpserver::http_resource; +using httpserver::http_response; +using httpserver::string_response; +using httpserver::http_request; +using httpserver::http_resource; +using httpserver::webserver; +using httpserver::create_webserver; + +#ifdef HTTPSERVER_PORT +#define PORT HTTPSERVER_PORT +#else +#define PORT 8080 +#endif // PORT + +#define STR2(p) #p +#define STR(p) STR2(p) +#define PORT_STRING STR(PORT) + +class ok_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } +}; + +LT_BEGIN_SUITE(threaded_suite) + std::unique_ptr ws; + + void set_up() { + ws = std::make_unique(create_webserver(PORT)); + ws->start(false); + } + + void tear_down() { + ws->stop(); + } +LT_END_SUITE(threaded_suite) + +LT_BEGIN_AUTO_TEST(threaded_suite, base) + ok_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL* curl; + CURLcode res; + + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + curl_easy_cleanup(curl); +LT_END_AUTO_TEST(base) + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV() diff --git a/test/integ/threaded.cpp b/test/integ/threaded.cpp new file mode 100644 index 00000000..849285c5 --- /dev/null +++ b/test/integ/threaded.cpp @@ -0,0 +1,99 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#if defined(_WIN32) && !defined(__CYGWIN__) +#define _WINDOWS +#endif + +#include +#include +#include +#include + +#include "./httpserver.hpp" +#include "./littletest.hpp" + +using std::shared_ptr; + +using httpserver::http_resource; +using httpserver::http_request; +using httpserver::http_response; +using httpserver::string_response; +using httpserver::webserver; +using httpserver::create_webserver; + +#ifdef HTTPSERVER_PORT +#define PORT HTTPSERVER_PORT +#else +#define PORT 8080 +#endif // PORT + +#define STR2(p) #p +#define STR(p) STR2(p) +#define PORT_STRING STR(PORT) + +class ok_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } +}; + +LT_BEGIN_SUITE(threaded_suite) + +#ifndef _WINDOWS + std::unique_ptr ws; +#endif + + void set_up() { +#ifndef _WINDOWS + ws = std::make_unique(create_webserver(PORT).start_method(httpserver::http::http_utils::INTERNAL_SELECT).max_threads(5)); + ws->start(false); +#endif + } + + void tear_down() { +#ifndef _WINDOWS + ws->stop(); +#endif + } +LT_END_SUITE(threaded_suite) + +LT_BEGIN_AUTO_TEST(threaded_suite, base) +#ifndef _WINDOWS + ok_resource resource; + LT_ASSERT_EQ(true, ws->register_resource("base", &resource)); + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL* curl; + CURLcode res; + + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + curl_easy_cleanup(curl); +#endif +LT_END_AUTO_TEST(base) + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV() diff --git a/test/integ/ws_start_stop.cpp b/test/integ/ws_start_stop.cpp new file mode 100644 index 00000000..a754cad3 --- /dev/null +++ b/test/integ/ws_start_stop.cpp @@ -0,0 +1,1837 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#if defined(_WIN32) && !defined(__CYGWIN__) +#define _WINDOWS +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x600 +#include +#include +#else +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_GNUTLS +#include +#endif + +#include "./httpserver.hpp" +#include "./littletest.hpp" + +using std::shared_ptr; + +#ifdef HTTPSERVER_PORT +#define PORT HTTPSERVER_PORT +#else +#define PORT 8080 +#endif // PORT + +#define STR2(p) #p +#define STR(p) STR2(p) +#define PORT_STRING STR(PORT) + +#ifdef HTTPSERVER_DATA_ROOT +#define ROOT STR(HTTPSERVER_DATA_ROOT) +#else +#define ROOT "." +#endif // HTTPSERVER_DATA_ROOT + +size_t writefunc(void *ptr, size_t size, size_t nmemb, std::string *s) { + s->append(reinterpret_cast(ptr), size*nmemb); + return size*nmemb; +} + +class ok_resource : public httpserver::http_resource { + public: + shared_ptr render_GET(const httpserver::http_request&) { + return std::make_shared("OK", 200, "text/plain"); + } +}; + +#ifdef HAVE_GNUTLS +class tls_info_resource : public httpserver::http_resource { + public: + shared_ptr render_GET(const httpserver::http_request& req) { + std::string response; + if (req.has_tls_session()) { + gnutls_session_t session = req.get_tls_session(); + if (session != nullptr) { + response = "TLS_SESSION_PRESENT"; + } else { + response = "TLS_SESSION_NULL"; + } + } else { + response = "NO_TLS_SESSION"; + } + return std::make_shared(response, 200, "text/plain"); + } +}; +#endif // HAVE_GNUTLS + +shared_ptr not_found_custom(const httpserver::http_request&) { + return std::make_shared("Not found custom", 404, "text/plain"); +} + +shared_ptr not_allowed_custom(const httpserver::http_request&) { + return std::make_shared("Not allowed custom", 405, "text/plain"); +} + +LT_BEGIN_SUITE(ws_start_stop_suite) + void set_up() { + } + + void tear_down() { + } +LT_END_SUITE(ws_start_stop_suite) + +#ifndef _WINDOWS + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, start_stop) + { // NOLINT (internal scope opening - not method start) + httpserver::webserver ws = httpserver::create_webserver(PORT); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); + } + + { + httpserver::webserver ws = httpserver::create_webserver(PORT).start_method(httpserver::http::http_utils::INTERNAL_SELECT); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); + } + + { + httpserver::webserver ws = httpserver::create_webserver(PORT).start_method(httpserver::http::http_utils::THREAD_PER_CONNECTION); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); + } +LT_END_AUTO_TEST(start_stop) + +#if defined(IPV6_TESTS_ENABLED) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, ipv6) + { // NOLINT (internal scope opening - not method start) + httpserver::webserver ws = httpserver::create_webserver(PORT).use_ipv6(); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); + } +LT_END_AUTO_TEST(ipv6) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, dual_stack) + { // NOLINT (internal scope opening - not method start) + httpserver::webserver ws = httpserver::create_webserver(PORT).use_dual_stack(); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); + } +LT_END_AUTO_TEST(dual_stack) + +#endif + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, sweet_kill) + httpserver::webserver ws = httpserver::create_webserver(PORT); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + { + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + ws.sweet_kill(); + + { + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 7); + curl_easy_cleanup(curl); + } +LT_END_AUTO_TEST(sweet_kill) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, disable_options) + httpserver::webserver ws = httpserver::create_webserver(PORT) + .no_ssl() + .no_ipv6() + .no_debug() + .no_pedantic() +#ifdef HAVE_BAUTH + .no_basic_auth() +#endif // HAVE_BAUTH + .no_digest_auth() + .no_deferred() + .no_regex_checking() + .no_ban_system() + .no_post_process(); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(disable_options) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, enable_options) + httpserver::webserver ws = httpserver::create_webserver(PORT) + .debug() + .pedantic() + .deferred() + .regex_checking() + .ban_system() + .post_process(); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(enable_options) + +#ifndef DARWIN +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, custom_socket) + int fd = socket(AF_INET, SOCK_STREAM, 0); + + int one = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + + struct sockaddr_in address; + address.sin_family = AF_INET; + address.sin_addr.s_addr = inet_addr("127.0.0.1"); + address.sin_port = htons(PORT); + bind(fd, (struct sockaddr*) &address, sizeof(address)); + listen(fd, 10000); + + httpserver::webserver ws = httpserver::create_webserver(-1).bind_socket(fd); // whatever port here doesn't matter + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "127.0.0.1:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(custom_socket) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, bind_address_string) + httpserver::webserver ws = httpserver::create_webserver(PORT).bind_address("127.0.0.1"); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "127.0.0.1:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(bind_address_string) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, bind_address_string_invalid) + LT_CHECK_THROW(httpserver::create_webserver(PORT).bind_address("not_an_ip")); +LT_END_AUTO_TEST(bind_address_string_invalid) +#endif + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, single_resource) + httpserver::webserver ws = httpserver::create_webserver(PORT).single_resource(); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("/", &ok, true)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/any/url/works"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(single_resource) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, single_resource_not_default_resource) + httpserver::webserver ws = httpserver::create_webserver(PORT).single_resource(); + ok_resource ok; + LT_CHECK_THROW(ws.register_resource("/other", &ok, true)); + LT_CHECK_THROW(ws.register_resource("/", &ok, false)); + ws.start(false); + + ws.stop(); +LT_END_AUTO_TEST(single_resource_not_default_resource) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, register_resource_nullptr_throws) + httpserver::webserver ws = httpserver::create_webserver(PORT); + LT_CHECK_THROW(ws.register_resource("/test", nullptr)); +LT_END_AUTO_TEST(register_resource_nullptr_throws) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, register_empty_resource_non_family) + httpserver::webserver ws = httpserver::create_webserver(PORT); + ok_resource ok; + // Register empty resource with family=false + LT_CHECK_EQ(true, ws.register_resource("", &ok, false)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(register_empty_resource_non_family) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, register_resource_with_url_params_non_family) + httpserver::webserver ws = httpserver::create_webserver(PORT).regex_checking(); + ok_resource ok; + // Register resource with URL parameters, non-family + LT_CHECK_EQ(true, ws.register_resource("/user/{id}", &ok, false)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/user/123"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(register_resource_with_url_params_non_family) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, register_duplicate_resource_returns_false) + httpserver::webserver ws = httpserver::create_webserver(PORT); + ok_resource ok1, ok2; + // First registration should succeed + LT_CHECK_EQ(true, ws.register_resource("/duplicate", &ok1, false)); + // Second registration of same path should fail (return false) + LT_CHECK_EQ(false, ws.register_resource("/duplicate", &ok2, false)); + // But with family=true should succeed (different type of registration) + LT_CHECK_EQ(true, ws.register_resource("/duplicate", &ok2, true)); +LT_END_AUTO_TEST(register_duplicate_resource_returns_false) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, thread_per_connection_fails_with_max_threads) + { // NOLINT (internal scope opening - not method start) + httpserver::webserver ws = httpserver::create_webserver(PORT) + .start_method(httpserver::http::http_utils::THREAD_PER_CONNECTION) + .max_threads(5); + LT_CHECK_THROW(ws.start(false)); + } +LT_END_AUTO_TEST(thread_per_connection_fails_with_max_threads) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, thread_per_connection_fails_with_max_threads_stack_size) + { // NOLINT (internal scope opening - not method start) + httpserver::webserver ws = httpserver::create_webserver(PORT) + .start_method(httpserver::http::http_utils::THREAD_PER_CONNECTION) + .max_thread_stack_size(4*1024*1024); + LT_CHECK_THROW(ws.start(false)); + } +LT_END_AUTO_TEST(thread_per_connection_fails_with_max_threads_stack_size) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, tuning_options) + httpserver::webserver ws = httpserver::create_webserver(PORT) + .max_connections(10) + .max_threads(10) + .memory_limit(10000) + .per_IP_connection_limit(10) + .max_thread_stack_size(4*1024*1024) + .nonce_nc_size(10); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + LT_CHECK_NOTHROW(ws.start(false)); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(tuning_options) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, ssl_base) + httpserver::webserver ws = httpserver::create_webserver(PORT) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem"); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // avoid verifying ssl + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); // avoid verifying ssl + curl_easy_setopt(curl, CURLOPT_URL, "https://localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + curl_global_cleanup(); + + ws.stop(); +LT_END_AUTO_TEST(ssl_base) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, ssl_with_protocol_priorities) + httpserver::webserver ws = httpserver::create_webserver(PORT) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .https_priorities("NORMAL:-MD5"); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // avoid verifying ssl + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); // avoid verifying ssl + curl_easy_setopt(curl, CURLOPT_URL, "https://localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(ssl_with_protocol_priorities) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, ssl_with_trust) + httpserver::webserver ws = httpserver::create_webserver(PORT) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .https_mem_trust(ROOT "/test_root_ca.pem"); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // avoid verifying ssl + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); // avoid verifying ssl + curl_easy_setopt(curl, CURLOPT_URL, "https://localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(ssl_with_trust) + +void* start_ws_blocking(void* par) { + httpserver::webserver* ws = (httpserver::webserver*) par; + ok_resource ok; + if (!ws->register_resource("base", &ok)) return PTHREAD_CANCELED; + try { + ws->start(true); + } catch (...) { + return PTHREAD_CANCELED; + } + + return nullptr; +} + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, blocking_server) + httpserver::webserver ws = httpserver::create_webserver(PORT); + + pthread_t tid; + pthread_create(&tid, nullptr, start_ws_blocking, reinterpret_cast(&ws)); + + sleep(1); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); + + char* b; + pthread_join(tid, reinterpret_cast(&b)); + LT_CHECK_EQ(b, nullptr); + free(b); +LT_END_AUTO_TEST(blocking_server) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, custom_error_resources) + httpserver::webserver ws = httpserver::create_webserver(PORT) + .not_found_resource(not_found_custom) + .method_not_allowed_resource(not_allowed_custom); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + { + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + } + + { + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/not_registered"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "Not found custom"); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 404); + + curl_easy_cleanup(curl); + } + + { + ok.set_allowing("PUT", false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" PORT_STRING "/base"); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "Not allowed custom"); + + int64_t http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + LT_ASSERT_EQ(http_code, 405); + + curl_easy_cleanup(curl); + } + + ws.stop(); +LT_END_AUTO_TEST(custom_error_resources) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, ipv6_webserver) + httpserver::webserver ws = httpserver::create_webserver(PORT + 20).use_ipv6(); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + try { + ws.start(false); + } catch (const std::exception& e) { + // IPv6 may not be available, skip the test + LT_CHECK_EQ(1, 1); + return; + } + if (ws.is_running()) { + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "http://[::1]:" STR(PORT + 20) "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + if (res == 0) { + LT_CHECK_EQ(s, "OK"); + } + curl_easy_cleanup(curl); + ws.stop(); + } + LT_CHECK_EQ(1, 1); // Test passes even if IPv6 not available +LT_END_AUTO_TEST(ipv6_webserver) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, dual_stack_webserver) + httpserver::webserver ws = httpserver::create_webserver(PORT + 21).use_dual_stack(); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + try { + ws.start(false); + } catch (const std::exception& e) { + // Dual stack may not be available, skip the test + LT_CHECK_EQ(1, 1); + return; + } + if (ws.is_running()) { + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "localhost:" STR(PORT + 21) "/base"); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + if (res == 0) { + LT_CHECK_EQ(s, "OK"); + } + curl_easy_cleanup(curl); + ws.stop(); + } + LT_CHECK_EQ(1, 1); // Test passes even if dual stack not available +LT_END_AUTO_TEST(dual_stack_webserver) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, bind_address_ipv4) + int port = PORT + 22; + httpserver::webserver ws = httpserver::create_webserver(port).bind_address("127.0.0.1"); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://127.0.0.1:" + std::to_string(port) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(bind_address_ipv4) + +// Test bind_address with IPv6 address string (covers IPv6 branch in create_webserver.cpp) +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, bind_address_ipv6_string) + int port = PORT + 31; + // This tests the IPv6 branch in bind_address + // Note: This may fail if IPv6 is not available on the system + try { + httpserver::webserver ws = httpserver::create_webserver(port).bind_address("::1"); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + if (ws.is_running()) { + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://[::1]:" + std::to_string(port) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + if (res == 0) { + LT_CHECK_EQ(s, "OK"); + } + curl_easy_cleanup(curl); + ws.stop(); + } + } catch (...) { + // IPv6 may not be available, that's OK for coverage purposes + } + LT_CHECK_EQ(1, 1); // Test passes even if IPv6 not available +LT_END_AUTO_TEST(bind_address_ipv6_string) + +#ifdef HAVE_GNUTLS +// Test TLS session getters on non-TLS connection (should return false/nullptr) +class tls_check_non_tls_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const httpserver::http_request& req) { + // On non-TLS connection, has_tls_session should return false + std::string response = req.has_tls_session() ? "HAS_TLS" : "NO_TLS"; + return std::make_shared(response, 200, "text/plain"); + } +}; + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, tls_session_on_non_tls_connection) + int port = PORT + 25; + httpserver::webserver ws = httpserver::create_webserver(port); // No SSL + tls_check_non_tls_resource tls_check; + LT_ASSERT_EQ(true, ws.register_resource("tls_check", &tls_check)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(port) + "/tls_check"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "NO_TLS"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(tls_session_on_non_tls_connection) +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, https_webserver) + int port = PORT + 23; + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem"); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + try { + ws.start(false); + } catch (const std::exception& e) { + // SSL setup may fail in some environments, skip the test + LT_CHECK_EQ(1, 1); + return; + } + { + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "https://localhost:" + std::to_string(port) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + ws.stop(); + } +LT_END_AUTO_TEST(https_webserver) + +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, tls_session_getters) + int port = PORT + 24; + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem"); + tls_info_resource tls_info; + LT_ASSERT_EQ(true, ws.register_resource("tls_info", &tls_info)); + try { + ws.start(false); + } catch (const std::exception& e) { + // SSL setup may fail in some environments, skip the test + LT_CHECK_EQ(1, 1); + return; + } + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "https://localhost:" + std::to_string(port) + "/tls_info"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "TLS_SESSION_PRESENT"); + curl_easy_cleanup(curl); + ws.stop(); +LT_END_AUTO_TEST(tls_session_getters) + +// Resource that extracts client certificate info +class client_cert_info_resource : public httpserver::http_resource { + public: + std::shared_ptr render_GET(const httpserver::http_request& req) { + std::string response; + if (req.has_client_certificate()) { + response = "HAS_CLIENT_CERT"; + std::string dn = req.get_client_cert_dn(); + std::string issuer = req.get_client_cert_issuer_dn(); + std::string cn = req.get_client_cert_cn(); + std::string fingerprint = req.get_client_cert_fingerprint_sha256(); + bool verified = req.is_client_cert_verified(); + time_t not_before = req.get_client_cert_not_before(); + time_t not_after = req.get_client_cert_not_after(); + + response += "|DN:" + dn; + response += "|ISSUER:" + issuer; + response += "|CN:" + cn; + response += "|FP:" + fingerprint; + response += "|VERIFIED:" + std::string(verified ? "yes" : "no"); + response += "|NOT_BEFORE:" + std::to_string(not_before); + response += "|NOT_AFTER:" + std::to_string(not_after); + } else { + response = "NO_CLIENT_CERT"; + } + return std::make_shared(response, 200, "text/plain"); + } +}; + +// Test client certificate methods without a client certificate (no mTLS) +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, client_cert_no_certificate) + int port = PORT + 46; + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem"); + client_cert_info_resource cert_info; + LT_ASSERT_EQ(true, ws.register_resource("cert_info", &cert_info)); + try { + ws.start(false); + } catch (const std::exception& e) { + // SSL setup may fail in some environments, skip the test + LT_CHECK_EQ(1, 1); + return; + } + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "https://localhost:" + std::to_string(port) + "/cert_info"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "NO_CLIENT_CERT"); + curl_easy_cleanup(curl); + ws.stop(); +LT_END_AUTO_TEST(client_cert_no_certificate) + +// Test client certificate methods with mTLS (client sends certificate) +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, client_cert_with_certificate) + int port = PORT + 47; + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .https_mem_trust(ROOT "/client_cert.pem"); // Trust the client cert as CA + client_cert_info_resource cert_info; + LT_ASSERT_EQ(true, ws.register_resource("cert_info", &cert_info)); + try { + ws.start(false); + } catch (const std::exception& e) { + // SSL setup may fail in some environments, skip the test + LT_CHECK_EQ(1, 1); + return; + } + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "https://localhost:" + std::to_string(port) + "/cert_info"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_SSLCERT, ROOT "/client_cert.pem"); + curl_easy_setopt(curl, CURLOPT_SSLKEY, ROOT "/client_key.pem"); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // Check that we got client cert info + LT_CHECK_NEQ(s.find("HAS_CLIENT_CERT"), std::string::npos); + LT_CHECK_NEQ(s.find("CN:Test Client"), std::string::npos); + LT_CHECK_NEQ(s.find("FP:"), std::string::npos); + curl_easy_cleanup(curl); + ws.stop(); +LT_END_AUTO_TEST(client_cert_with_certificate) + +// Test client certificate DN extraction +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, client_cert_dn_extraction) + int port = PORT + 48; + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .https_mem_trust(ROOT "/client_cert.pem"); + client_cert_info_resource cert_info; + LT_ASSERT_EQ(true, ws.register_resource("cert_info", &cert_info)); + try { + ws.start(false); + } catch (const std::exception& e) { + // SSL setup may fail in some environments, skip the test + LT_CHECK_EQ(1, 1); + return; + } + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "https://localhost:" + std::to_string(port) + "/cert_info"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_SSLCERT, ROOT "/client_cert.pem"); + curl_easy_setopt(curl, CURLOPT_SSLKEY, ROOT "/client_key.pem"); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // Check DN contains expected organization + LT_CHECK_NEQ(s.find("O=Test Org"), std::string::npos); + curl_easy_cleanup(curl); + ws.stop(); +LT_END_AUTO_TEST(client_cert_dn_extraction) + +// Test client certificate fingerprint generation +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, client_cert_fingerprint) + int port = PORT + 49; + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .https_mem_trust(ROOT "/client_cert.pem"); + client_cert_info_resource cert_info; + LT_ASSERT_EQ(true, ws.register_resource("cert_info", &cert_info)); + try { + ws.start(false); + } catch (const std::exception& e) { + // SSL setup may fail in some environments, skip the test + LT_CHECK_EQ(1, 1); + return; + } + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "https://localhost:" + std::to_string(port) + "/cert_info"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_SSLCERT, ROOT "/client_cert.pem"); + curl_easy_setopt(curl, CURLOPT_SSLKEY, ROOT "/client_key.pem"); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // Fingerprint should be 64 hex characters (32 bytes SHA-256) + size_t fp_pos = s.find("FP:"); + LT_ASSERT_NEQ(fp_pos, std::string::npos); + size_t fp_end = s.find("|", fp_pos); + LT_ASSERT_NEQ(fp_end, std::string::npos); + std::string fp = s.substr(fp_pos + 3, fp_end - fp_pos - 3); + LT_CHECK_EQ(fp.length(), 64u); + curl_easy_cleanup(curl); + ws.stop(); +LT_END_AUTO_TEST(client_cert_fingerprint) + +// Test client certificate without CN field (covers cn_size == 0 branch) +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, client_cert_no_cn) + int port = PORT + 51; + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .https_mem_trust(ROOT "/client_cert_no_cn.pem"); + client_cert_info_resource cert_info; + LT_ASSERT_EQ(true, ws.register_resource("cert_info", &cert_info)); + try { + ws.start(false); + } catch (const std::exception& e) { + LT_CHECK_EQ(1, 1); + return; + } + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "https://localhost:" + std::to_string(port) + "/cert_info"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_SSLCERT, ROOT "/client_cert_no_cn.pem"); + curl_easy_setopt(curl, CURLOPT_SSLKEY, ROOT "/client_key_no_cn.pem"); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // Certificate has no CN, so CN should be empty but other fields should work + LT_CHECK_NEQ(s.find("HAS_CLIENT_CERT"), std::string::npos); + LT_CHECK_NEQ(s.find("CN:"), std::string::npos); // CN field present but empty + // DN should contain "O=Test Org Without CN" + LT_CHECK_NEQ(s.find("Test Org Without CN"), std::string::npos); + curl_easy_cleanup(curl); + ws.stop(); +LT_END_AUTO_TEST(client_cert_no_cn) + +// Test client certificate that fails verification (covers status != 0 branch) +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, client_cert_untrusted) + int port = PORT + 52; + // Don't add untrusted cert to trust store - verification should fail + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .https_mem_trust(ROOT "/client_cert.pem"); // Only trust the original client cert + client_cert_info_resource cert_info; + LT_ASSERT_EQ(true, ws.register_resource("cert_info", &cert_info)); + try { + ws.start(false); + } catch (const std::exception& e) { + LT_CHECK_EQ(1, 1); + return; + } + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "https://localhost:" + std::to_string(port) + "/cert_info"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + // Use the untrusted certificate + curl_easy_setopt(curl, CURLOPT_SSLCERT, ROOT "/client_cert_untrusted.pem"); + curl_easy_setopt(curl, CURLOPT_SSLKEY, ROOT "/client_key_untrusted.pem"); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + // Certificate is present but should NOT be verified (untrusted) + LT_CHECK_NEQ(s.find("HAS_CLIENT_CERT"), std::string::npos); + LT_CHECK_NEQ(s.find("VERIFIED:no"), std::string::npos); + curl_easy_cleanup(curl); + ws.stop(); +LT_END_AUTO_TEST(client_cert_untrusted) + +// Test SNI callback configuration +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, sni_callback_setup) + int port = PORT + 50; + + // Simple SNI callback that returns empty (uses default cert) + auto sni_cb = [](const std::string& server_name) -> std::pair { + std::ignore = server_name; + return {"", ""}; // Use default cert + }; + + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .sni_callback(sni_cb); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + try { + ws.start(false); + } catch (const std::exception& e) { + // SSL setup may fail in some environments, skip the test + LT_CHECK_EQ(1, 1); + return; + } + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "https://localhost:" + std::to_string(port) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + ws.stop(); +LT_END_AUTO_TEST(sni_callback_setup) +#endif // HAVE_GNUTLS + +#endif // _WINDOWS + +// Test pedantic mode configuration +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, pedantic_mode) + int port = PORT + 26; + httpserver::webserver ws = httpserver::create_webserver(port).pedantic(); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(port) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(pedantic_mode) + +#ifdef HAVE_DAUTH +// Test digest_auth_random configuration +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, digest_auth_random) + int port = PORT + 27; + httpserver::webserver ws = httpserver::create_webserver(port) + .digest_auth_random("random_string_for_digest"); + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(port) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(digest_auth_random) +#endif // HAVE_DAUTH + +#ifdef HAVE_GNUTLS +// PSK handler that returns a hex-encoded key for the test user +std::string test_psk_handler(const std::string& username) { + if (username == "testuser") { + // Return hex-encoded PSK key (16 bytes = 32 hex chars) + return "0123456789abcdef0123456789abcdef"; + } + return ""; // Unknown user - return empty to trigger error path +} + +// PSK handler that always returns empty (for error path testing) +std::string empty_psk_handler(const std::string&) { + return ""; +} + +// PSK handler that returns invalid hex (for hex conversion error path) +std::string invalid_hex_psk_handler(const std::string&) { + return "ZZZZ"; // Invalid hex characters +} + +// Helper to check if gnutls-cli is available +bool has_gnutls_cli() { + return system("which gnutls-cli > /dev/null 2>&1") == 0; +} + +// Test PSK credential handler setup +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, psk_handler_setup) + int port = PORT + 28; + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .cred_type(httpserver::http::http_utils::PSK) + .psk_cred_handler(test_psk_handler); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + try { + ws.start(false); + } catch (const std::exception& e) { + // PSK setup may fail if libmicrohttpd/gnutls doesn't support it + LT_CHECK_EQ(1, 1); + return; + } + + // Just verify the server can be configured with PSK options + if (ws.is_running()) { + ws.stop(); + } + LT_CHECK_EQ(1, 1); // Test passes if we get here without crashing +LT_END_AUTO_TEST(psk_handler_setup) + +// Test PSK with empty handler (error path) +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, psk_handler_empty) + int port = PORT + 29; + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .cred_type(httpserver::http::http_utils::PSK) + .psk_cred_handler(empty_psk_handler); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + try { + ws.start(false); + } catch (const std::exception& e) { + LT_CHECK_EQ(1, 1); + return; + } + + if (ws.is_running()) { + ws.stop(); + } + LT_CHECK_EQ(1, 1); +LT_END_AUTO_TEST(psk_handler_empty) + +// Test PSK without handler (nullptr check) +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, psk_no_handler) + int port = PORT + 30; + // Configure PSK mode but don't set a handler + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .cred_type(httpserver::http::http_utils::PSK); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + try { + ws.start(false); + } catch (const std::exception& e) { + LT_CHECK_EQ(1, 1); + return; + } + + if (ws.is_running()) { + ws.stop(); + } + LT_CHECK_EQ(1, 1); +LT_END_AUTO_TEST(psk_no_handler) + +// Test PSK connection attempt using gnutls-cli +// This triggers the psk_cred_handler_func callback to execute, providing coverage +// The callback now uses the static registry to get the webserver pointer +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, psk_connection_success) + if (!has_gnutls_cli()) { + LT_CHECK_EQ(1, 1); // Skip if gnutls-cli not available + return; + } + + int port = PORT + 41; + try { + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .cred_type(httpserver::http::http_utils::PSK) + .psk_cred_handler(test_psk_handler) + .https_priorities("NORMAL:+PSK:+DHE-PSK"); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + + ws.start(false); + + // Make PSK connection attempt with gnutls-cli + // This triggers the PSK credential handler callback, providing coverage + // Note: Full PSK success depends on libmicrohttpd/gnutls configuration + char cmd[512]; + snprintf(cmd, sizeof(cmd), + "echo -e 'GET /base HTTP/1.0\\r\\n\\r\\n' | " + "gnutls-cli --pskusername=testuser " + "--pskkey=0123456789abcdef0123456789abcdef " + "--priority='NORMAL:+PSK:+DHE-PSK' " + "--insecure localhost -p %d 2>&1 || true", + port); + + // Execute the command to trigger the PSK handler callback + system(cmd); + ws.stop(); + + // Test passes - we exercised the PSK callback code path + LT_CHECK_EQ(1, 1); + } catch (...) { + // PSK server may not be supported, skip test + LT_CHECK_EQ(1, 1); + } +LT_END_AUTO_TEST(psk_connection_success) + +// Test PSK connection with unknown user (empty PSK response) +// This covers lines 438-440 in psk_cred_handler_func +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, psk_connection_unknown_user) + if (!has_gnutls_cli()) { + LT_CHECK_EQ(1, 1); // Skip if gnutls-cli not available + return; + } + + int port = PORT + 42; + try { + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .cred_type(httpserver::http::http_utils::PSK) + .psk_cred_handler(test_psk_handler) + .https_priorities("NORMAL:+PSK:+DHE-PSK"); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + + ws.start(false); + + // Try to connect with unknown username - should fail + char cmd[512]; + snprintf(cmd, sizeof(cmd), + "echo -e 'GET /base HTTP/1.0\\r\\n\\r\\n' | " + "gnutls-cli --pskusername=unknownuser " + "--pskkey=0123456789abcdef0123456789abcdef " + "--priority='NORMAL:+PSK:+DHE-PSK' " + "--insecure localhost -p %d 2>/dev/null | grep -q 'OK'", + port); + + int result = system(cmd); + ws.stop(); + + LT_CHECK_NEQ(result, 0); // Connection should fail + } catch (...) { + // PSK server may not be supported, skip test + LT_CHECK_EQ(1, 1); + } +LT_END_AUTO_TEST(psk_connection_unknown_user) + +// Test PSK connection with handler returning empty string +// This covers lines 438-440 in psk_cred_handler_func +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, psk_connection_empty_handler) + if (!has_gnutls_cli()) { + LT_CHECK_EQ(1, 1); // Skip if gnutls-cli not available + return; + } + + int port = PORT + 43; + try { + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .cred_type(httpserver::http::http_utils::PSK) + .psk_cred_handler(empty_psk_handler) + .https_priorities("NORMAL:+PSK:+DHE-PSK"); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + + ws.start(false); + + // Try to connect - should fail because handler returns empty + char cmd[512]; + snprintf(cmd, sizeof(cmd), + "echo -e 'GET /base HTTP/1.0\\r\\n\\r\\n' | " + "gnutls-cli --pskusername=testuser " + "--pskkey=0123456789abcdef0123456789abcdef " + "--priority='NORMAL:+PSK:+DHE-PSK' " + "--insecure localhost -p %d 2>/dev/null | grep -q 'OK'", + port); + + int result = system(cmd); + ws.stop(); + + LT_CHECK_NEQ(result, 0); // Connection should fail + } catch (...) { + // PSK server may not be supported, skip test + LT_CHECK_EQ(1, 1); + } +LT_END_AUTO_TEST(psk_connection_empty_handler) + +// Test PSK connection with invalid hex key +// This covers lines 451-456 in psk_cred_handler_func +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, psk_connection_invalid_hex) + if (!has_gnutls_cli()) { + LT_CHECK_EQ(1, 1); // Skip if gnutls-cli not available + return; + } + + int port = PORT + 44; + try { + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .cred_type(httpserver::http::http_utils::PSK) + .psk_cred_handler(invalid_hex_psk_handler) + .https_priorities("NORMAL:+PSK:+DHE-PSK"); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + + ws.start(false); + + // Try to connect - should fail because handler returns invalid hex + char cmd[512]; + snprintf(cmd, sizeof(cmd), + "echo -e 'GET /base HTTP/1.0\\r\\n\\r\\n' | " + "gnutls-cli --pskusername=testuser " + "--pskkey=0123456789abcdef0123456789abcdef " + "--priority='NORMAL:+PSK:+DHE-PSK' " + "--insecure localhost -p %d 2>/dev/null | grep -q 'OK'", + port); + + int result = system(cmd); + ws.stop(); + + LT_CHECK_NEQ(result, 0); // Connection should fail + } catch (...) { + // PSK server may not be supported, skip test + LT_CHECK_EQ(1, 1); + } +LT_END_AUTO_TEST(psk_connection_invalid_hex) + +// Test PSK connection with no handler set (nullptr check) +// This covers lines 432-435 in psk_cred_handler_func +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, psk_connection_no_handler) + if (!has_gnutls_cli()) { + LT_CHECK_EQ(1, 1); // Skip if gnutls-cli not available + return; + } + + int port = PORT + 45; + try { + httpserver::webserver ws = httpserver::create_webserver(port) + .use_ssl() + .https_mem_key(ROOT "/key.pem") + .https_mem_cert(ROOT "/cert.pem") + .cred_type(httpserver::http::http_utils::PSK) + // Note: NOT setting psk_cred_handler - handler is nullptr + .https_priorities("NORMAL:+PSK:+DHE-PSK"); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + + ws.start(false); + + // Try to connect - should fail because no handler is set + char cmd[512]; + snprintf(cmd, sizeof(cmd), + "echo -e 'GET /base HTTP/1.0\\r\\n\\r\\n' | " + "gnutls-cli --pskusername=testuser " + "--pskkey=0123456789abcdef0123456789abcdef " + "--priority='NORMAL:+PSK:+DHE-PSK' " + "--insecure localhost -p %d 2>/dev/null | grep -q 'OK'", + port); + + int result = system(cmd); + ws.stop(); + + LT_CHECK_NEQ(result, 0); // Connection should fail + } catch (...) { + // PSK server may not be supported, skip test + LT_CHECK_EQ(1, 1); + } +LT_END_AUTO_TEST(psk_connection_no_handler) + +#endif + +// Test max_threads configuration with a running server +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, max_threads_running) + int port = PORT + 34; + httpserver::webserver ws = httpserver::create_webserver(port) + .max_threads(4); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(port) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(max_threads_running) + +// Test max_connections configuration +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, max_connections_running) + int port = PORT + 35; + httpserver::webserver ws = httpserver::create_webserver(port) + .max_connections(100); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(port) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(max_connections_running) + +// Test memory_limit configuration +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, memory_limit_running) + int port = PORT + 36; + httpserver::webserver ws = httpserver::create_webserver(port) + .memory_limit(32 * 1024); // 32KB memory limit + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(port) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(memory_limit_running) + +// Test per_IP_connection_limit configuration +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, per_ip_limit_running) + int port = PORT + 37; + httpserver::webserver ws = httpserver::create_webserver(port) + .per_IP_connection_limit(5); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(port) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(per_ip_limit_running) + +// Test max_thread_stack_size configuration (covers line 257 branch) +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, thread_stack_size_running) + int port = PORT + 38; + httpserver::webserver ws = httpserver::create_webserver(port) + .max_thread_stack_size(4 * 1024 * 1024); // 4MB stack size + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(port) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(thread_stack_size_running) + +// Test deferred mode +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, deferred_mode_running) + int port = PORT + 39; + httpserver::webserver ws = httpserver::create_webserver(port) + .deferred(); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(port) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(deferred_mode_running) + +// Test debug mode with actual request +LT_BEGIN_AUTO_TEST(ws_start_stop_suite, debug_mode_running) + int port = PORT + 40; + httpserver::webserver ws = httpserver::create_webserver(port) + .debug(); + + ok_resource ok; + LT_ASSERT_EQ(true, ws.register_resource("base", &ok)); + ws.start(false); + + curl_global_init(CURL_GLOBAL_ALL); + std::string s; + CURL *curl = curl_easy_init(); + CURLcode res; + std::string url = "http://localhost:" + std::to_string(port) + "/base"; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + LT_ASSERT_EQ(res, 0); + LT_CHECK_EQ(s, "OK"); + curl_easy_cleanup(curl); + + ws.stop(); +LT_END_AUTO_TEST(debug_mode_running) + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV() diff --git a/test/key.pem b/test/key.pem new file mode 100644 index 00000000..3b04751d --- /dev/null +++ b/test/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAotKw+oEWvVB3Gr5cQDeREfkrYz3wQr/iBXQcxieHgm2O+zdd +EKgGIzZGLWAFt4dERt9EIPuhyIs5cX70d7SDPZEkq9ne1qg8wxo9BoLj6pGqiLzb +mfhjOsApSBIMEo9j461YPgJvmoPcR9WtJQwxtPCaBaDe/GuuQlE4c9Ocfn5cY/cQ +7r0LpIXpz+2I3IXeMJNPClNTEcOn3jM/mdCkechsyGgwTSxup019HPQNCefY27SR +yjgKn476WTWP3HSzuz+vdJeeOsr3imCWAbLU0Y3g7bW9HddCKBpu+9Er7A8T7Tiz +uqid4ZxWCBjoUKW3PGZXb5GN27hamdOuYXuu+wIDAQABAoIBADD0F7G5ThTtNGIe +Ca5lBoDY4WqdHLd06YeqOVx6Vguo1OxC4QA5BF9h2geabx2W1bhZOCqSfTnGYib1 +fJrg8vR3xwbEInN3cY1XPjHO+Kd11Ef4QC4yt+LaE49PncGWyvmRDI7YPKXAL2KJ +o90XpXo5PJWkoGZUGbhmowpv/QUqjcLCt4djbELl+ZUOoYpkl4S8RnSy8M9Q3W5l +IVE7aLvZ8K5NuWXAXC4V3UruWgfO7HtGea1ce9UIaKOPu3sO1dUnP2go4yp5Q6H+ +QssAyLXBfjfPNxaosS44WzL5FyjDyG99ziZyhDFAt+bZ169UUUCV1AyPrqGbekfX +hdLgiQECgYEA1mDeSpntQDBdSIhLZ1GLBpUhSR7l3/KLzfleaTUrqNargZXokgks +XzI9TBdXJ0EX9M/16hsQwMkGX6JhxgaPy0JSbLdYbjIep8kahvLbJ8FWY/JkA3p2 +8m3yY/bYnWFfSKUUgy8yWRhU5C1b9oS//bxA8VyMVNc4mx+S5duC7msCgYEAwm9k +7ocu9G1fqLlWy0LEWo1dTEXwjFmBe1HUUk8RXXPj5tQkrRVpvZL4jbl7kqhe9UVk +X0sVtRUnPpLBgfpYrwvu9+lQFhwNT4E5G7jWy9kZ0G1fdZYTMPm/Jp+t5sLhC5O6 +NAX/HwH3MHuco4QGJVMnGv/zgwE/4RxE+J2WRbECgYEAuwqxaC18zrBj81DXWUHQ +JuIetIl8zzPzvraAJRL7EMibwuhkjmXqjPRsfuMua1Vj7Xk0ehk7OLksEmy/GePH +ufQXrjsZsKuSC5puxqdFhx4sne9yS4aiGUrMXWOWA1pdpChECWE4cHvGNX9N6XxR +drS1hODWn39YKCAYLuyjBBkCgYA+RGZSbUCATraf1hsRpSQ0y6jhUFSk3dU1pRMV ++PRatU57Ed1dAMqIR5UJ7ijA4uLmMX7fdbBR+aBDzcPi2EWmaW/yPOnE6t7oYz3i +vuMrDS/TK/OyOImU2aZ5vBF5IVfo2Tp8hp8ZUwvSnwOe6hz9vw96+hUGE1Rdxyvf +YrhJQQKBgQCrSMgbOEL3p7h+7iZXfTtWjEu+IW0qn00jPXoW2VoBylYDYJjz5QL+ +mUaE+7Tl9Fyvq/uuU2K+2blAiGa/fJemaPCUeIDQBLcDc0nYm09llFw/qQkMgEqa +c9yBQm53lQsP208WQJEr6fexVz6p4qe3FdBpZAu0XYszSCzzGvxLOA== +-----END RSA PRIVATE KEY----- diff --git a/test/libhttpserver.supp b/test/libhttpserver.supp new file mode 100644 index 00000000..6da3d3c8 --- /dev/null +++ b/test/libhttpserver.supp @@ -0,0 +1,5 @@ +{ + gnutls_session_get_data + Memcheck:Cond + fun:gnutls_session_get_data +} diff --git a/test/littletest.hpp b/test/littletest.hpp index 681d47f4..93b0e256 100644 --- a/test/littletest.hpp +++ b/test/littletest.hpp @@ -23,11 +23,11 @@ #ifndef _LITTLETEST_HPP_ #define _LITTLETEST_HPP_ -#include +#include +#include #include #include -#include -#include +#include #include #define LT_VERSION 1.0 @@ -70,6 +70,7 @@ #define LT_BEGIN_TEST(__lt_suite_name__, __lt_test_name__) \ struct __lt_test_name__ ## _class: public __lt_suite_name__, littletest::test<__lt_test_name__ ## _class> \ { \ + using littletest::test_base::operator(); \ __lt_test_name__ ## _class() \ { \ __lt_name__ = #__lt_test_name__; \ @@ -125,6 +126,13 @@ { \ (__lt_operation__) ;\ } \ + catch(std::exception& e) \ + { \ + std::stringstream __lt_ss__; \ + __lt_ss__ << "(" << __lt_file__ << ":" << __lt_line__ << ") - error in " << "\"" << __lt_name__ << "\": exceptions thown by " << #__lt_operation__; \ + __lt_ss__ << " [ " << e.what() << " ]"; \ + LT_SWITCH_MODE(__lt_mode__) \ + } \ catch(...) \ { \ std::stringstream __lt_ss__; \ @@ -152,7 +160,8 @@ if((__lt_a__) __lt_op__ (__lt_b__)) \ { \ std::stringstream __lt_ss__; \ - __lt_ss__ << "(" << __lt_file__ << ":" << __lt_line__ << ") - error in " << "\"" << __lt_name__ << "\": " << (__lt_a__) << #__lt_op__ << (__lt_b__); \ + __lt_ss__ << "(" << __lt_file__ << ":" << __lt_line__ << ") - error in " << "\"" << __lt_name__ << "\": " \ + << ::littletest::make_streamable(__lt_a__) << #__lt_op__ << ::littletest::make_streamable(__lt_b__); \ LT_SWITCH_MODE(__lt_mode__) \ } @@ -262,12 +271,16 @@ #define LT_ASSERT_COLLECTIONS_NEQ(__lt_first_begin__, __lt_first_end__, __lt_second_begin__) LT_COLLNEQ_EV((__lt_first_begin__), (__lt_first_end__), (__lt_second_begin__), ASSERT) #define LT_FAIL(__lt_message__) \ - std::cout << "[ASSERT FAILURE] (" << __FILE__ << ":" << __LINE__ << ") - error in " << "\"" << (__lt_name__) << "\": " << (__lt_message__) << std::endl; \ + std::cout << "[ASSERT FAILURE] (" << __FILE__ << ":" << __LINE__ << ") - error in " << "\"" << (__lt_name__) << "\": " \ + << ::littletest::make_streamable(__lt_message__) << std::endl; \ __lt_tr__->add_failure(); \ throw littletest::assert_unattended(""); namespace littletest { +// Special case logging of types that don't print well. +template const T& make_streamable(const T& v) { return v; } +const char* make_streamable(std::nullptr_t) { return "nullptr"; } struct check_unattended : public std::exception { @@ -333,7 +346,7 @@ class suite } suite() { } - suite(const suite& s) { } + suite(const suite&) { } }; double calculate_duration(timeval* before, timeval* after) @@ -499,7 +512,7 @@ class test_base { public: const char* __lt_name__; - virtual void run_test(test_runner* tr) { } + virtual void run_test(test_runner*) { } virtual void operator()() { } }; @@ -522,12 +535,14 @@ class test : public test_base } catch(std::exception& e) { - std::cout << "Exception during " << static_cast(this)->__lt_name__ << " set up" << std::endl; + std::cout << "[FAILURE] Exception during " << static_cast(this)->__lt_name__ << " set up" << std::endl; std::cout << e.what() << std::endl; + tr->add_failure(); } catch(...) { - std::cout << "Exception during " << static_cast(this)->__lt_name__ << " set up" << std::endl; + std::cout << "[FAILURE] Exception during " << static_cast(this)->__lt_name__ << " set up" << std::endl; + tr->add_failure(); } try { @@ -540,16 +555,18 @@ class test : public test_base } catch(std::exception& e) { - std::cout << "Exception during " << static_cast(this)->__lt_name__ << " run" << std::endl; + std::cout << "[FAILURE] Exception during " << static_cast(this)->__lt_name__ << " run" << std::endl; std::cout << e.what() << std::endl; if(tr->last_checkpoint_line != -1) std::cout << "Last checkpoint in " << tr->last_checkpoint_file << ":" << tr->last_checkpoint_line << std::endl; + tr->add_failure(); } catch(...) { - std::cout << "Exception during " << static_cast(this)->__lt_name__ << " run" << std::endl; + std::cout << "[FAILURE] Exception during " << static_cast(this)->__lt_name__ << " run" << std::endl; if(tr->last_checkpoint_line != -1) std::cout << "Last checkpoint in " << tr->last_checkpoint_file << ":" << tr->last_checkpoint_line << std::endl; + tr->add_failure(); } gettimeofday(&after, NULL); @@ -569,23 +586,25 @@ class test : public test_base } catch(std::exception& e) { - std::cout << "Exception during " << static_cast(this)->__lt_name__ << " tear down" << std::endl; + std::cout << "[FAILURE] Exception during " << static_cast(this)->__lt_name__ << " tear down" << std::endl; std::cout << e.what() << std::endl; + tr->add_failure(); } catch(...) { - std::cout << "Exception during " << static_cast(this)->__lt_name__ << " tear down" << std::endl; + std::cout << "[FAILURE] Exception during " << static_cast(this)->__lt_name__ << " tear down" << std::endl; + tr->add_failure(); } double total = set_up_duration + test_duration + tear_down_duration; tr->add_total_time(total); } protected: test() { } - test(const test& t) { } + test(const test&) { } friend class test_runner; }; -}; +} #endif //_LITTLETEST_HPP_ diff --git a/test/test_content b/test/test_content new file mode 100755 index 00000000..5f643138 --- /dev/null +++ b/test/test_content @@ -0,0 +1 @@ +test content of file diff --git a/test/test_content_2 b/test/test_content_2 new file mode 100644 index 00000000..18c3afa0 --- /dev/null +++ b/test/test_content_2 @@ -0,0 +1 @@ +test content of second file diff --git a/test/test_content_empty b/test/test_content_empty new file mode 100755 index 00000000..e69de29b diff --git a/test/test_content_large b/test/test_content_large new file mode 100644 index 00000000..0c8026f1 --- /dev/null +++ b/test/test_content_large @@ -0,0 +1,260 @@ +QrG7suKv8NqD9/T8JceikXvcZmr93F1Vbfjty2scvWQYg0DwvfCXPrnBv7MNXAcyKsV+Szf7GXMO +xzXAB6ZRMSJyxEBFBDaHP0GBlbEs6WyYOq3sKqgXRh3onW0jydMnT+Ij3EtCc9YgWQcoNAFK91Ii +TvTud56SyZusDDwfHsxvU4aMxwWfRy3dOPipwYNAWyca7HH06h+DaBrPWaAuQGLEbvn2HbgzB8lw +WnBllCJCsdPCnqmksG75T4WQxYYdZyLn8ejZlVQ3aibaNFrZPvco/HtiiPt1iZjFy2AQuTg1UHq1 +wdbwXeQfrOWETazkOATMGVmy1Oo0Vh9LiNaIfGoEAa7hze/A48bqapBs4faOOErMtfRkN6IVEsXT +5HecE7pup/iyM9j3t0zQZ32nSewvEFbTQLoaTzlilxara3Mrw5P6xKvyVnPMIkM32b/nFaOptEcV +hDqpGVl7e6NArOPkGMEtr8QWUBtjoAMS7chp6eIurX0++8900aj4cZexDNRFflufx4THRoTl1E8g +O1AZs3vI072UaaJqemX4pQSVmU7RxPAyBnZ0ORJOl0auP15zuM+6fpRRVKAd8e+je80NbAS5mMpv +zyqLh+ISQKDgAPEeDDZPi8VDbh0ZdgoIqUtGLCOJ/04ZZyEZOo6mQctweDcCyhtbR9xV2+8dhqsm +cR2d4aeXtIKp31QA6MyS146Ii/BVnDD6x2Htl6nHQEfSzHHWBf+izpeogbN+0nHnGKl3Y2rK3xWb +1+rafZHCnOdsa9Gz1m9uvZkIIWjAEgn3xYpVqvVWjvaf8o3IssUKxy+ppUrMmXKxx+hGx4ZBNdlv +0QtSNzgInCv/2SFPPYptiExKpnX1f+GTSAHNvg62KAGJecnWA2XkjAXH3VMXZAoSL/bS4utMt3zV +ZEa19wW6SVQUo4Yi1sBl+Zg2iOYai5/v9Dx1gVGYKM+mRoqDret81EtodWAZgg9/rq+NOFknI3Tq +yiZzIFgl/m3k//MQzxcK26VOAn/L9PyORsXeIeev9M+o/61V4SCFfs3pYQPu4jr0/b4EO5orjyys +xukSRf3L/XiBcqFeLMn1mTodMrqcDcLFvnenOJ5tOLvSQ5t0sYrM2EzKZ7EfOzFzkDlOS/Xz/6Jp +B9MnlBBFMhZAg2Qj59LlzINrSXyUwZ4Oyx7do1boYhbbBmGa99TUEGSYHzzlXosWdYprwlqLbIze +Z+xea9AWAGswNLBtemK034L0lpGEXEx/ztajmZNxDg+Iz1DLGoN4xJQJRjeza7IaeEKhHYO3CkcY +hTAtaB6MS6BL6hE4EWz1NO9WZNnbCRQVR0QKgzDqYFGoSfLS5YA6wtBWWYQYZBt00sxKopI9qVZv +RJYKsoZIWyKw7z6qdGnSec7SnzrnCvIWGvEL7hCYKoGCLws+s/z51WXNXSspuT/NncAsxwYFAnRE +uoLJS5rJTjgZ+nVzRkWnkMGOJOLEQZhe20qO8Z/25rS9virUOhXWSN7zlmkWCEsPlPTyLidQg+sy +6u6vhBEa2sg6OEjyCt/iUbXA3n0MmOa2qZ0EhsvCbVLQueONnIwcJ1n7B1iFwOqFyhxdu33TIKnR +Wf/8WpiC7N+i4qcLEBE+HK9lBWDP2j430emm8p+BVj/e2chFzBekenXnXX6oN49Fct49YoNlITy5 +Khb73d3jVnIJgrL3/l+YaCg1UCU1iMeJaJ0bSUUL/kpG6UVT+0wgG6BK4c3x2p0wIDTVJIhPoWSy +E+tKZsr0RiyKG7RvntLrQ7T3DrwEeNUaS+Qw7OQUT9oXSONQuQcXtRBzjOeVbbPcoZ+CaAHjr7RS +erKmqRDn1h44NkIDkalsm1BuIA91L8QlYrrqTKb9XZGpof70sIpnsZ8BCZNXnQlrqoZTumonOClU +IpFLJSq6t0wnqA5NLcx5cVywoIfLEc5DNOAZGtQ5BKGok795TSXmQ6H5tracbHXVhUlTfsxFkE89 +2jptwV2tZW6lVA2wfq9MH/IerdQx+zE7OeolNWlEamcsDRaswaWUjsvTR7AOW7M3LJkehykVJSUk +Dri29epZKdDQ+AH15KXy8zRXY2IFR5EKnUmY1e6d551zn8BBdwgyaWTnWyXs4OlQYKtH+8x0nvfS +PY2MGb/Q5zGW8GWYYI5ORNQfq5qUhb5+LbG7/CNV8f7FIdYIr5cFJeoAksKDWTj7ZjyxVgSE7Frg +NpjtxG0hm1WH52NyA1rjeNE0TifusiqI6TVwFQN2Y1QLaS15UiD6zujK4QEJIW3W6Ok5iCNVnfk/ +7T5f5Edlx8kFQ5akaQqBKzN3hfRciSdV5r4IBBRo2KKILtBw86v0leuo7fyyI92O4N/GC9eusu3G +m0szo7G9zq/XbytmGeDpv1rt8I1xERYG3M7kSO6cjya+jxNYP3euX/rzhk00L6R7hRITd4iAjEwF +Yj20+CntZaH47cM32x8tjz0gk0XofzabPnt6WvqLjwIeoailn9tfE2pWJ5EWQmnV4SGCwDJAdQri +bWw5ddJbsQlpdif+O0kMA/LMQ0dWi6Dsf7wjynICftB+bmy7cd5NWuCvJ5417/KZkMRRnhgcg8a9 +2vDuouS03bEC5V6PXzEErhwc/zBmJOYqRFOsOPJMuu04UB6IK/bqM9AEaeMpeerS9LHWFNzPNilG +7JYvEO1GIAUqh8Ir+61jAKRTg9Do24w8VDEbNvKVY3uIJCDWbz+Ng61a3Ydz3VbCEC+ccCa6F5eI +oICPRs4VW6TxhzJkV1JTl8vg0qlN9b1EYTtkSLKUeea8xIB3zk06Mb1x6nT5gQcYymgdztCL1T24 +EMUxnwuuNAPWxNjKvXLgze8fWkWkphK3EraN6MCfTrihZPSALnigp1ItlQcdgpoFcdnsyWeXNQVT +fRwKhxWMraiNI1c53OSDWIXqjtQ6GpTgbtxO6FQFcb5pu2Ra5cUmMhORc8jy4EEs1Y19eb/epcyl +9km97Is7C68yRHqlQXkJAzMnnoxiWK3uxYzfQt0JNdKDD8qQrD7iVXRkJIarHGsyH9Xe6t3OfdXW +k3iPu7kLq5ZpLi8oRjUg0tEAxcw33oq29W/Uu2U3Y18EH1TsTF77BRI9HKGhpLovB2sxd3LgDMnJ +QXgs+QNC2VDo14cBdigA393Jyzs/P8RdITgoQcoDTQ5et5hgpcVQP0lrCvyXRuccyO51sCNgw7ra +9uQfIsYtzplrguABskZKQVdXorD+Oyh/vVMWO0cvLoq3UIpnvyEZFGEH/xN9EKKcMXU+7dLuaK32 +u6rPBVxyrzf45GOKDUZRlFAWPfCj99GLTQ0NoMr3HrTxfcFwrxW8n0MPuSr6OMZMkXDMl9OiCu2f +LPqFc0ZndAD/FMrT2eAVZxW6cZV7dkKNK6I1e4e7UjoAK+QdtjE7ex6WaeMkTwDsuekKZ7iyV6LH +RaJKgcjFVgrX4CZ0MzxbuzLMDWrxaOiljA3+fwQfujAe2JFdazvB+d1Z/4moUBvrIMvbrOdC3IzR +Wqd58acS0bXguioNEVAxQ9iGz0L2GUaUmLn1T7NPyYTDlwp9rySHl152DEn9UmH4EyBB6Rsm6CgD +b6G3ZMVhe6K3YJXOhXhK97stP5KuL7cw2jq8NW7S95+Ap0i9zTVVJ0D+ymc3mx8LE8ZhpI13Ia6T +IoSQoPGAsevnzKpwulXn3O/zTpiIHASc0cpScZdMxVhHgZEYWhZvy5BdGV38O4gmE1LVwp6K84Ct +bwsxYhiR5BuN1BMF1WC9bWo9v50F425RlPAZaoC0ut851rU6fQhI+V7C7JfBnm3WMeCpXsnHE6p3 +ctJgjUOwYfr31p0rWQMolpxL0oWOG/M5uAdiE1uDaJUQA0rue5lCvOKrn0osvd7R07mtz4o0nFTl +C0DF/kED/IeWWosa3ooDxETprH7Gt9ICVitNbc1zvMLLI1kvqD5/iGZ+69o88R4JCom9xHf/nkdj +HCkTsN34f+33j8H7yKDcw+7KV5NI/OjnlQi2ZUuZrgmI+KhtzoKdoDgwZtXfCuvMozzIgmDqScjp +LSf9usLVVmfo5uoZyIksQFtxfwPB24EMWbZqrJivKmIbMSPJ4ad2iX79r806C2PgDTHRcibN8mqK +EjjXv6Db553U5+prQIHWcq19lP072NBtrpHmj4rhZbmCIteyVLd69JNiRogfLwTMzkfu5nP3ej55 +7CdLwCdowsSK7k4IaICDN/mUrEiYULkeTVI4RRG1rNm2hN0y3GUj/tEMvwXkC57SJs2kjwEh5ih4 +yFLILiuRRmJCqANWdbgoMg08vA3tDOKVgQcgyNoV/MPA1Iwh39TKuYN1SCvG97KnTLUpd2OOvyun +e1ZfDWPvQqkN0gmYOPcodGP3qHKuaZk5XYrTzGs78R8n9+FgQczHsp2EE9vTG+VuP2qgTnMXd11Z +ATDVHfW74n/E1IyWSSLvbp6PLZUxx9+KEl82xw6lkJxrF0MlAkFGjxCLWK4KamZh2uE84vqdcCiE +CFebHNqLTgQV9NjpbNGX9khcA566r/X1Oq8c3AsaFAxJ16dvo3S/c1aUd9KcduVNlZltvhaQfz1d +gVgdFHCMgPf+80D1QoIQynabZeQuUCg+Ij5OtRjEb9fP34/WVyQxmBYdFHPGsiO2KgwqjNwEmHZ4 +9rLXJnv/E6dQuoHZlB06fl+jSqB1QLpl7Su5Vx62u3nbxkhmILGVmaHWGk9zEMX36/zL4U/Gu4vW +1CUdKzk7Lk/v2Jp9ZNvlvJTKvaGvLHTq2hK0KktMWYdfnNpCz6BhiBv9yuQt4AhH6ZCcYdHh2AhC +y48FP4bXPrwlcNrgX2DNhHejJyC3G2No+EKLXDxzc/VQQIlX+Z2lo7qfUprtUHHMlDMlD48vVtJ/ +j7fzcm28qlMVDPeu4OZiNLFdky1qWxrrdgkK+Ey6qpzqxo1LrJAeC2r+WJJhUR5+3WqI+SAfVoW7 +gV1Q1vpGBWLKRwr45pj+NrW9O5dv7i0Gu0KpcqIpV1rCDjC7HiNj8biT9sEHZSNR9TauFlRWpZkA +EvE+9aGNpmtC3r2kprPuK9HkHBYbjeIMdEGNGSQc5pC439ORtntBFcxj5VvBiLocJtwB0hmOQzuu +Hu5ATF6Og+CddfGUJk1T0GN41elfcCq8eSx3b5XzdCa23A36IZSvxN+RfhboNea9lQHPLYUHog/H +t++YxBv5hpAUjLYDEqWBqnLLfp/rq1qnm/QmMVfCa5W8YjWnsW3z1QWbH5ViLSvfXyhDP5kVHWHn +Ij2RlK6JaCbNNmDYWnEQ0NaYp20QzVZ2ydY6bmnP6pcPlA/ZEOBBWmDWJO2w4tsK1tIejr86HDfa +ecLKTJjNy+Tzw3kwYczwGC3P5uDar7pz6cjJCH09UESGF9TzzIWTR8ovrj/a1ZTu/MaCsmT2AACs +8eAnpluYFHVKEi+V8xN64nEGK+ZkG0wroZzH0Ruv4zPbk9LAqeAkzR32JXD0aIF3d+o66Qh6LeaP +0T3Igi84F1sgNSXXVQZTphEJExAna0pHPqyVcDd+FUmjZlckyYsBL/i3v47UWyr0/zW9tlIJH15P +lv6Nvn4Gz6U+jPow817YOx7R9dv1x1tyMoHceNtcVZKtQ0dJ3ghaJGtbvg2ay6/7XHzNF9f8zLzr +JKTG9qcfZnNRoYBfj5Ikx+wE+SUusJVFM6fUO6+/xycSnV+xz4qtrylfM9U8zE+Nj3zohOegYKJl +2CSM/4oM2+eEn0Rb10cPFquPkkIFxik0TszejKiv0WddFaB+7JCK41whniwf6EK1E2jCnk7o+O4r +8Zf8FtHCa/lTa/5uUOnQ6AYmtpaGXquAE/jiogPDEQke1My+2zBz1lql5tlqk6l1U1UYOuNuMNcg +umxOLdipN73Tnvo9mH3X16KUg8C+ot8rOQSw5V4gbOVITbJzRLL1JMsckzkwImE8A8J4rNBCVjIc +xbeAAiglHrpyG3QQco+rZTCBCd7BQ2Oc6fUzpsGLJNoS+SXQqqg+RVkooBUzicJ3Rs3itrkZg2Ns +YaqpBtkHce+iko18x2ORDy6T60r0Y5N5tt3MKvAse9qRLX18m2+9PtHIjSJED54C7b2V3mwGqraq +rj8BvTfw/fH/OjGIVDkLZqqF2JcEffWQF1/jDHSbLs6ZZar0gyY/BF3m3xNz7uGhBHcbbE1ZmIdg +32unqTabWX8G/XoGSiFC1R8HuoJ86bMMVfY8Vki6a8uYaxMIqWzwNLDa08yuHEmP9Mcgk+UuY3qJ +5aEV1FIKPOpLgvuh8Z77Ix0j7eX1etLMpQcf33yeKrFG3rlN1IBkm3IS1ehPESnp4YfvVXFrH0GY +C5Gf+PzY8E5MMVq/Z+kSSkoETJQL4b7E2ON2NTpnE8nbqWi1SwuK7wUql5Bqh42E6QN2lXtrErpT +JnWgSzX3B5uTbGh2YT32AvqNokxCP9vqxJ5g9+UQXEJPlnVC7Chk4PEHXT9D6aUzOx9qMOshWG5i +RPX8gUmpV7RfwMYx2AycrA9GIlsb5rx2ZC1QX9hgvuBh8Mb107s36fpGwrcmU1+FLocFmVFHbEl7 +3wNAdMB8AshtadBqUTwR3qDYGPahlmK+gfxTYenb2sypFYeTEtihoeHH0Wq29o3SVNTwIpk8BDK/ +C28qlUhRgTPCWzjOWta10Rs9YLO2yPJq5DppHELFhHhoiq8NipQ5TxahmpXKC+yPHzNdgoxZbk53 +sol4ZMRkpWueDs1svw1qFaBHPqwM0TsVkUnLiqhpRzBXdmax/uwt7ybBnpoOkPgOIQkKB92qT6s1 ++7r2A+IJxcPQA5JD4XcaGN2UOPTMcVEECHqxPiEN2hhslDaJoMOEPgONAZPuxmyGs0Hey69Xwxt+ +zIMug2JROJ5UoZHdxOonkgZNN14PHZr2ZB1tdnTfgl44aM0G9i4YG3JIHXRKeyucMn7JxoCAzxWo +a9dQsbMdzOeNeyXh0j9ahoDSMj8DWI5KZiI7XsskcM6m8Bwwq9UV0MPoyB0Dsdztrn2VV6FyW2+N +Zr1DkDDZrJSYTWwn2DayHVVGhaio1Tr0HmNDreo5gtdbXw/iz/Q0HseJlMQtiWCBZJ8pNlRsTZ72 +RywCds/eXQQ00nB9quw78uPjhSZLpLB7VmMahpbE+7tCoJ8oTZoS2cU0fwo6EBLahtaboqwg+nNO +FZtcN51dIuGnaDUEp8//0vks510B4SZWc7libV3cBo2BWn66+7Srn68TSVSgqgauQU3uiJQH+GMl +brw9vZd8LMumHHyFbj1SCJTBTlunIkjdEyyw5Bbi5te39IDV72LEIl86t7kT97gWQNP+XbT/pDDJ +oXWwsLnEyd1J8P5U98B/ww8lK5ShugBxlMBN2vPBNQX+cMBGzaSG7OD8XVlD/1syiluBdkd46bjU +Hy+RerAfAztHPtQSug5K/1xGefDT87eGz0LVHpo54OAccb3OLfVmyqjphaW3WEKZ/2LMGo5jElbJ +rg3mkO0wrU48E2RsoO/rK8q9jFvC0Ny63sNDE9bleuWm5I67JoYKZehRFmQ+LyvXFCkgtxfnX2M5 +7gUKkKparJ2czwRKo7l3XJFjvn+Hv26JTNaeI/cIJq7CnsE4tzpA/9knFK/Rv9fphzTvq/QGsyri +GoQV3+e1RR91FB7QjzRFLjQQO6TqGWXNPe78bsHdO/OyfECtWOBDIIKdRPV5oOluKb4NwZSLaeyP +mJPhlb+L07G7SBuhpQ/1Z7uPBAcwG2UwK4aNLrd9HoF373s/MrZYc7H1kCZPaVvimS7XfYP8ISE7 +skrvET0WwqlbBEiH2yTYH++TGZ0AlN188vo/iO7dKZlgtrTeC3h2LifiWZ3/yB3FHWsn6zYhdEo2 +McxayPA3WhhWcjjM0L5QMkTvgXMNXLZ0C7qgRLte63ga2fDI+fOJba3ZKi04lKTaibCU8N4xHTQg +W4AxRtEZ1ArRtlEQ/0rYWp4XMabl4OW4E7BhZIUXi/IvpfGOJNad/ZeloUvnXHLW2QLwpSR04DhK +iBPEJ3DA/0chIinGhoaDQ/tqRD8I41vqiCXZ4tNP2Ba5sW9bhrs49uf+0Nnwkf7li5tLTC9VNtsM +g1P0m6E4U97C81tEdvclWfertse1LppycNmZbDYm3QvQQ2neNrjaY4TotXGlvm0jJBib8tNe0/XS +C47S31DALcVJVRlTIvDRsvQWM9boM29eOABv0a2vSWzCftqFSh2CtPAi7rof3x10B+KjWqB9ppoL +BtBH9fi7jPHfUrLVwDfAHYPiE0l4d/qkHHKhy6mabq7tq/eiaC6sQDW99bZUDVYtYYX8k0oIe7C7 +lKdP5uWgXYb5KT5kvPRoiSkEUyKbJ7j/g+1JrgblRN+xAobkERxwIKpfGpOk9WTNRPTVo/5nHII3 +G3bgUyNi5jt4eZSGiymIaOzSrZTScO9FrE3/hol5AAyzFqvzBQrhDZQphkCSrl3cAvamJkbWL5VT +tZ+wn4+tgLuLzk0wOVmrII22rEI+vD5PCit/j9aUD5zN1NXbVazZYPewHDsVAR3zLI2nzSztWhpg +CR0qeXgIA/vkHpNpx148WLHBjkp494p+UOyTb7gbcWFZhJdivDzInsTt2770MAfoW46BNLx6B1L0 +cA3y5m98SPOAo5U9Abb/YkeyZ//Cv/ksLH+ssOb+rhNgvzVHmBUgUWNW+TvmkG2oZJmIxvpiXzlR +6i3Fc6ZKOxoqu9KRcHz6TpgjVrcKfi6lUinwziFPesZ1O/fV8mFh3rzz9rdSFlIyVICBHeZSRl1B +2W2xnf5XufJamGK4l6grYPFnaBXS9DXAB69RFrH5sL+qHfKmKTPqca+zeEURcOGNrweJFaiv0lax +xNJOctrt8MxfnoRgkTZZrrffKbOUljszGgKPIsudOhnRT/PtaYEEvyGU2zb7R26nuURmUibqvG5p +j/ganBRAjtSKxru2/lRGVbPkUevON8qOcRrHeqkKMvmsOVLbH7K1smGst96c3yEXmuUoebOd8ecA +EW8Z4BE5IAT3Nc6yDTSht3H6Wp5mFx3Dqzy1Cke0sUOZGpbMxL6Q0ENSt5elBzGH122ygBVPkRno +Xy19pBaHAylB/NMJCG/fJiWTWV25EW5JoTqnTovxm/f0LbWvC0Nca/TpnPR9SD1oWEIWvk7zWt3K +RxliLE4BRiX8c8llDEsT/ZKmsL9217VPYrqUcKR3+I8Hve10/ZOoTAsfxDrtc/BTUnGTHxlOQ57c +Zg+4BUIrkvSbLu8JreiyV83UhEOD1JRxm3fq5WC+EpGphbUBgp33jmJPxwmKe2oJrjvIfe9WQVWH +4H7Fh4N1ayQMx7kXneBAoMIauD96L6Sb5sCWaSHTd3iBEgmmlM2kn9fXtWpnen4OAqQOeW9xiAu3 +BjzFBhERtJ10pfdjyWeSlw4LnHf3aDGrTEGUZ0sx79EMRT0CH8+28jD3Io+yN65eBYgMQ18E/IoN +uqPWQI4MsGrlpM6efPwe4P2vXyh1qTMNVOX8ib+OIm8s+eN4yzGELJbHS0Sx0MKVU/zXnaRr7JIg +A8LIgPf/6Rss0APvl+JPxhY9aYXBGZQdThNXvgDwP3S50as503XdXs0mzbrUCzAOFMtiK3bKpLqL +e5nntVABXwzVX+qNISOkP/a+0Gg/plzM0PM35/QSHNrXPIpmaYqF3jb61x8ztVFjKDJdxdkziO9H +Gku7bzNkS6guYNfHKMoC4DC07WG6z2tymGuSUV+CYUPyrkvzNFZSL+1OY1eSwRuzcJ/zLt0eqN/w +R/Zko0j1sMZGY4fLUFbC5vURtM5A9t4p5xPMlZq/toPw3UyGE+AYJ0llySuLL9eBimBwyrdYqoVi +0SyYSJOM7M1mN6WqpwU9Vp7Xfts9UtglrN7mJSTfNrLxGr1ybS0LT0OdoOmrhwhKou87QdKH9MNX +Q6HBqgdQfG6oA1FEn8OP2c3GYrV0Vv49WSsKP3W0+zuUxQS4ygleWEkmCRpqYg1PBv71zt8Rc7gJ +cngrusAhuhzJl7rVyTBe2+ru4wSn/wDUP3fsqEZWQzMGk4tFXTq5e0WCbHhqLKMia31zLHN/nP8b ++yhQnG0jXYmynMclqZDTlAk6x9Zc5G+xF05y5jBUfogt+q+eT6VEh5GfhgyPnVrvi/3QdjrWcGlA +uL4f2Cp9ri9O7zkW1R76LqCVvprreK3p8Lf4POHE36YNlHsyVJmAejGg2M0p3bofwUiYkTlzlhDZ +wISCch/l3UlcN3cdcmsSdNpMheCXCXQ9ofkkA1ur/9G+hnGTgPi1tjOUTqXLiarDA3IjMzewxIAP +bQBiPXDMtJXkcLCySZ/3JvB0TmOds5pvwCn2+wmdJxDtBm+zxiuxJF1O75nQ+ldjYU4kLAabHYkb +nP9pHPSwE64YjzN81wjRpNyTrZkDOHBEUbx4rdNKF4+ESXPeGDVIPfmRwINLmx5+HKiOaCfW+Ljk +0bc7X9j/1Ad7Fi9N6uXuP4+n7WNk06oOaf8I/i99C2QS+Jehab1fsuRfI/tMj6kS5ha9KlJHHK0w +bDfsV0PUQj4+UADWKlBSjzTHRCFXTcnGUP3y4pxpr3ouzWrwOF8v/iuM6mLZztTxI8apKF+ns2Tg +JcsEQk+vLs28A4iI/YYGvfXQeE/VX6+fRhSGXJm2gx8vPx6z4+B4mERIVmlpHD3O6fonKJhwCDcN +u8SDjs4EjbhRPc8D6pysnkDs6GexOFfxqr6kDuWCy0VEunr6EK6ccr8gY1a1/cIj++OVchw6ruKW +sRQbqUfz3ggAsy5/ULvbfm4WWbuhe27TXn8Fkh0otgGeMuH51nfKMZrNjFj0K6/T8UhU4g+gRZS4 +SuWlzNJ0NArE2rsqRxkI3lcODvPRo7/oftMunBVBseJKaQbt6zbGF8iFw//xyOOODxdiToEjopXa +QL3K3pmaFsxkscrswoEGm54vwF02zmndmTk1xmwfqo3t4gNhxTrj2RG7R1wtnGNm92UF8vi7pcNv +th03hj9hnYm9RqZimxwz9teQpUO5orSwWa8f6TkjXRMWfNWxcULZUGAROn7R541r0FjH+svs3bYT +jbMoasrnVTHMS4vHe2+49B6/SbXYcU0ly29D9Ob1ES/OTZR71PxzIcgok4vTxczGNKqTN/JSbRqT +NN4/Yi6NKi5MPFsq9zwsLRFeIlPcLcAuLYx8c3RNCyZktSNKiLDby2CD5It0+5xqA1ZT0jgY/yk7 +Z9DSe0Aw/lQfajRhA19jDV/Il4wvX61WVdfNcAl1hWN0I1IFetOuGEBK+aAk46Nwz0P2L1PF1faq +SrnyBQ7juVyl/1SOgILQh5sOX+1FIw8tPpPRtVmXZT2b+JzuxiYm746XHwo+Tc/ld16+WQlrRwBq +0rnr2acaegcBmDuDnjSCNdaxU79+eV5t51Xt3CtyW82avxk1nA74ADT281rO50nVvHja/gaIcBzN +UMV/3Iw/hNa7mcMEh5PglOcMCZitiEwF8C+f76WwtvP3XB4d7pSBPLTdogRtSQEvQq7E7W2I1rpQ +OjEbFCBcWMoMTNrRWQnE2RHnrQwIZIGGmBp+hQ7WDlbUWB4X5UeUhBELp5SLz9DOOJoo7ixlNi98 +IZJWaELu3Dl2+vP0GXNNjhqPd9aoQlouprnSyQTivDQjWn+x6nDwM9xrLv20ernkbfJe5IkZlbem +uNTdTyg3bPvBAvqKV8I0yI7P48Er+B1/nVqXihgABGNMWUjpsRvdQpE5Hb5zKSanxzk1To+UNajs ++Mfzj80LETiUHCBNj23XTuDdVbK0P76D17VBV0QHKXc8IyblHH4ilqGoPJez6fjeMsh3YUhDl3nY +Ez7DIibl6jRQaqx2DwPCqBaLciKAT8GvhJwsdZuhqr73I+sqGobfMuCzEu60kgKB1UFLtpcZ6hIc +TtycNi6JpfDFVAYAIjS4aOFyNo3aA399z36Y4aPu6Rs5oThiVy/zvAAk7KWBY5nwphwfYEXILUZy +Sqhij+sZ5GHinkqJ3EO5Qd6YN5UV2/Ia+Rh3RCFMYMzJhpil2j/Y2HONWWvQkeFwZ7afIQF5J1OQ +DT+QldmIv+Ip4YR2C2maMj6JAlVSKEPbEY4xnrg3PyYE4gNwqlgvcZIwKdj6cXG9sZLlOjb/DdED +I7hkADfKFDsebMNIh6YmnhIGlCyqvazl9C5PC6W6FEZHcCc1JPTjUT2MgvMB0dnT0/BJXP+YxVmP +UvS0DTVAkR02BOV0fokT7TSumYZOFDoLzwsGBEiVpeTRGdBdj9BAfNKj+/ziKlg4JmHUEONVh1vO +pSiX6Q5YtAXo1fRea+gmeGArpy5p3EsiNbJkS/zthHY0C9liH4DyfYer+rth+t6Mo30QjzzAw6bx +Abx0hC0FKu6EI4sS2mMzOxXOaSAc5IqvGocJwsSkhFWaFa0MUYwxOwZyCHj21YkqDrfLZKpwOaY5 +RtmFPObFN9FqVzDPzwjQbFFVRybG6N9p22+RDYXsHc/I+3i/JIcPv+EYo7dVBGH+W8MlxKVa8qDU +OYZefo6tbMy4AZr5woEODVBvqCH7DNFPSzS8qWt50DvR38+TxmRhEcT1GGmfK49lvpqMWMzA62Tz +nmhMg/rKwn2a61RknS6DOU/pEhplJzljWh6NTg6ClziN7yyhrNFqCj0NSdQoA27el/Js2iI7Q1jt +9gG8hL4Qt6ZrkB3vs9vjbipl9Qh9kJ9FSJCajVGAHz+qlv4AewhYT4dUMOY8Q/9LQ9Ulj1oGeKgE ++caNdcqpmxb0DnLEbNIags2zqGEAm4AhXzFxHJsgroWBYI0zD+oE0qE3ZdSpWOfy09+aQP8NUuBR +JwlnjP+f6X6bHyIZCClsz9AhjSFb7oaYnR+pGTAzhpuASS+FSixueUGqcAy1oqY2IBj6IVU5SjLl +fAd8Ce+MRbr5AvgqbQa7q3E1CLmrKxayTbVmf8q8u8Ul6WNwykn4NSHkTFCvfcax4CqKh5FmVWYf +2x29h3jgbVgM54l7rTP4PMdB3GvmsFxTSPoJcB5k4tqxDNRh4rOKB8uPy3sxpZGm0JNzdOmRI7T4 +MDwGVA359I10+LrecDLRfW3mW2CKdtfnWVrNyk6lnvMaCYHkDJE8zlv1SRUeXy+LJ1YuYgeEA3no +7mvb/54F8Y9HdvWJRV+5gAqm5u/MC2MUcT0if/essx0/KV2XPh5ynZ7BDSoXbkdsRhopSdUHezJz +A1yj+qnS+Kc7OWW5FrB0UnYeKe8e52Wv0hmluWcuQQc2KHmGB5YiiCJv457B7qpk5aD2h/p8gyb6 +QJr3C8Xj8JN0CaHQ40ftO25yConAj/iW+r4CG3+hZB3E+RgxLxVlVnnWeAC3R80ScN95YdJAF+cU +65B9KXeAYkKO8trI/gX0TOymopJAlOpW3ouqliavz4J4yAj07t8bFdEV0x4m5VBtDO/Qx4sw/n1x +bX7ls/oTHD5SyW8d5cAOgEEAjPCEH53YJMITqqCB9SOKIXAkIqr4R9L0TyQqG4iEi97dQ2CMlFZV +mdjlmCDdVvHND52G7mcQeemv4c89m00Oyr3UExYahHJSbAaBobzsfd5lTiBlEervNw5VMewn/KHd +1xWNh5EF6fUPcXEVrD2q+Brwa5J3NGU2Sx0CO4eZIUTdY/7q6sYwK0h80VXCM64FxDRl8Mho0IlL +YTK/STHN8/taFyHJ6cyLSQHGsP2ftjc1Q73DjRNvvQjF0zsXVJ1tROlp9y4O1eHNyvr5eW+NbCpX +x4XCDgBA4NYw5Ze5ZycrEjUiC65vK39lL8n4GW3eEDDycCnULcnvZPX9Wh4qEaejheo3xMc+SfJc +mo/hpn+UH2czSpDLpCqBjRhwp2GfciXvqOd0IFc5dYxc0E0zY4Ncr4eazlfEpKYDkZu68TberB/8 +c+lgWPoIQ+f/3Eut9c6DiDwi/orMAUlh/OnCdiQ8sW6n6i0IeiC+2PU+GivmezXnrBlSujRdl+F6 +36rEe/4/JXU/mmFqU85m0MCd98gWv/b6RHFettdQjuZrkkdZt5qwUoVI6RNCw1TyURGH59pXwsid +Hw81X6lID1MAThfEv2kYXV2cIAbUehTeBFNJgEu/OZZ8lqx2rcwJrhcM997YfogxBEEj9pG729m1 +M2iPt0DFzH/yaUoTHrR42kVKz6NWgHd7iL/0nzS/Y4bld1uU6rqhjRuV9+QyNXRktYxxjsKsKRaN +QFaWkjuykR1dtjn5XVJrb5Dc2TmHzrYg8CZ17G07vToMcBURaJcIMwZyDSTeOjk+uYTxTETkwy5t +h+bhAp8BsMdUgFVHPSvwr047ngMFpR8qzpjA87S1KHf9w5AhBKMw5r1ehDNPEaUOE0GQyIfhb9DR +ZAOdo8bDMavnaYEKdQuHweZ23oXygmilfojbB3jQMAG8fiTqXbI6VqXiaIFTANcSQgExKEGUxUnl +QAmnAXpvZ5x4REuehvvrWhR5L/cnfSzkcyu6reBxAeqEXEcqn1rL6dQhbwEG62360DGBNjo02BpS +8W5WfyG3pvXK0WZUtIN4qwRHpanZ6djt4iwB600La6ZLvHtrbnl0XhhdbjF6LtF6Kor/yXZsMba9 +vdGb2YDH+5Md+Tx1AJ5IGTSJJEZSuk2/AJYG1GydtzOLP7NUyAv1XMVrFvEG4foWbTt27ctVC+YP +LNq0pcvxPZc043J0O5bCRlSFPe+fYC42DjN9qZ09mLQtY4v4fvGCA8cJqTbUWA0XXrdPDfW7h0qw +ltqwgH/8QAUygf/enK2KKR2QBQnxlH8aw9armPupcMtmp+V/inIbocjb6TCmlAsu/XjVmz8qn5PL +Xh8102R189DROl0FW9xTh78Ht9Ze3PwIvrZK0n8ixySe+czgdf7U9dWQE+3WeNOiooC+sMp+wo+t +w/0nP8+Ul9se5JbPh15tBv6+K6Ov+aE4WszNLf3V3tZDX+hNgF9UJpmjfND3qzyVaIUYAaLT6+zk +crOtKawzEBWCthkIJdADkxk0qY2I5gtyrjF30wQuBH1/+Tlbj2ggxZow8Iar2B+KSffQxqgkIgph +otA7yg0kVzKuXnl1nvn69JTb+lnzK+/xtt057eWmt1oWmp7ds8AnTTdzvonU+XAaYm2erGZehub4 +fbx+y+ckYjohvLUNkITBp8+FGsSshRFHh95Bex5n0rtqnjaorUWe/jKKi7NcH2oznTDaEa4HmohN +K5yiDeRisWtZrppMYYK3+AQYCGzbZJnWvN5+vZqaIhsfPJMiMySNcy8YwsDiIwQZsVj3bJM2Z/wq +4/mXc8bU/nT/3txo6nq6oPn5SsSXX7/bP0f09i2wG7WwP4Mm/RoH/XMY2QqQibPUkYiUrMMCAaa8 +l8vwwG3f1C73hHXI659uUvtA5zx/osUEg1NEwPRtSyL9masqKHwPgEmctDUpk3uyk8nHgySF9UNa +rfiyhSWH4jCgFduIbLDFUpq4sy9Uw4Fc3M8FBoau8VgSAwtGTHFGztiMBAdpnj8Z9ZRwJ+2gMrS0 +w6BSV5B+6VdxlcPYvTGYuWm3dtATAsS3YtX4GQrbfZjLPVxfL+jmuO2DZ3JfaSLtaysTPVk+upS1 +dnn+jrN7DljkWM8dHmHQRBCAVXx0xZLb/Y4AvGn5mGylIxkFKsck68tRByRBbyvFrv7AqnxqflyQ +UWA++bWRm7gU2KSglCh4f1DPqe6oRI2Qjj3J22mKRLhWfwLO78KEc+wYbzbSOqlEuDADvi5Kuu2q +SdOMKTNIo32DvyuvdnMFHaXAsnXxtS+g+hK2T2ciQG1Hw4+U6l0Au5tOij/RzkpvJptEe8CUjH87 +AByEcwP0rGAtRIZD1xudwIopGAb7VVVYl0yzhYmCR//l9/6syyUT5Lk8rQ1cEWFu8HSozfib9Ler +3Mc4TrcV38WdeWRvOueAEMk2cmZIQrVaRNvQYM7PWazbQZS/MVVrif4k34SL9EVEzVzdQMYBJbRO +IcZRmskVODPTsUFq1tzzZ6Xg3gI2NX9z+PaA/jYjiNUHaMm+b3QKBCaHCNloHU8bXaXy8lsvscqU +x2B8iMWAw6zJFKhX/ey01hTVKvVGgSvSdhO6+k9kYO3XVN0URmjtQOGNjvY1mlhlbpN50/oopa1c +d3IOYuvL16ZCZJZxOtCCWA6BL33RPf8xVMmhtDFj4nHHj2/gHal0kKaZqEoOIyYKkOFr3O2fa7an +TBQL8ObnS+kvmrZsyS9PJIeZ8AsPITFMYStu++5Bld7QCCghbl/1oLcHCTwX9rddeuyN0d0/FNgc +3cc3P9WAQXh/Slmv6WuiiHe168TE/NElPQipXe6N5AUWNUBONcQ/RWSaYSqigkgDvxVgrvd5qElu +lKNp4zeAup3faYqO3CLOq7d/zS5zDur0w8ohSrQGGdRy7Kw+3+J1RUy/Ybeh62b1LqiNczVsbYrF +8Zlx5nH3KrwXz2f3uSPDCHeb8QLCIREfF7AFh4bWgTHuJWGfxCYwT8lTr5pLzHHeJgbto6Pex5Yt +78EcbUkHSCRx48MSwPCDkvXCrx+sUFhESGPdnm2t0VumTl9KiyShAJJ3MSZYeJ3VJL0UrWbMxcKw +W/lfOyMcBOxuLDY6qDSH9xIoeiUJpU7/swW9Az6EGnDhog4wM2elSYwFl24eSZzy5CDzaXEJ+gUy +mwOTo3X6Uxqwq6Q0/3W9gLj1cpGYOB4CuPNMkxYiPS0fd/3tXNU/D4C+QbId+j76nn+Ak3ErEYz5 +6Emzgmtvpqnqz8iiKheeqRoq4/w/t5i3HCRFr3SI/vL86RnPLMIyNKp0rmRV6lUsWbAhj2g3xJW/ +ZKofrlFE+Vj9tNc2Iqjog8ZhqbQyn63BQuvnTAhXYiwxVVkywog2tZH8RoVDalo5Ohxq0KIQSz5e +fw3zg3C4Ba0elEZt6dqqVzaNAL4SbDkZcnEtCQ1g3IuQtiY9yk4mYAzebqJrbfmrbUi0uD+3ZQek +WAmTmjj++NuuZ3bm60ezA2uxvs7/o/WFgQ7Ntpx9e0pdm2GjP7jY1QxkzktD3HAhN6tvEFmyUKbM +6+5p/JWwGLenT0334uNE+5AD/wX0262xOSnnGI15jVb9B8djHjDbHdb+7UjX64LL5IHclBwAaX4/ +0xqM4dof2y88/Y4FOSE/tgTpAil72iatLbTVES6EsJwEY5ye2y8Dlxzr+qi955H3zUpfIOnXeoKR +g57Q6S52gRz7lSU9e6mJsl5s43KZFQTB/4cotF6235sLq5jKiBxbcYGWQwRR0AK/n/vxyJK4j9F4 +Ez/JJeNuqIzRfxASPHHjPRtHj6zjg54PH0hanrQZiKcH2Ye03DO4W4iPqzeGSfOcgOMtC48U0u1Y +PmuWCldyzeWia6WReMqZ/rT+0Xp1klMWxeSSoPIFFU9iMOJIE3TheRW4pZua0mlrBqdK9bLF49Fi +iy+OV8WxSr19TfBPLassjJ638ViVROne8rO1nUVbRCnXFu0LG4EbB00QS6wVufRKT5m7pcm8zeCd +dkQn4ZdlEQ44htt155CQ3E8Mb2+Paxu/tL1D5qfPlZ9+cNhGTFDehL7U4PXvYTbyp1RBBzK20oOb +bEi8Q600E5EEoKOzN89dOpJln//xvc1alQle1Z9G+5P7AWTFsd/eGY2MSgWSgDAIxabd1nQ59zgf +PjdB1VOfsMjjFmA8PlKqnZmfLsZnKdXRK44OpAuWDjcAqA/jHUy9p74zLeXlUnc5SYur7wA4zTk6 +PyIZZSq4bndYv55aTedEXI3iKeHZNy2oYmlmAJ/udUgpt8fafJE2h2AmfUkKZnLidIIAso6NP9wB +tbBJOWV+QEolAmqdpSi+5o1GX4rUGSX1UOMFi7UPYPg/8dXHQPwWfSR/FWJW/LCDBMvEgetp45zI +DjBlpOkAUYPJAJtIb6DwYCu8PUNDu8h9rizojjUz00ESnoF8IHKHjgTwMwU98sjAQr21bfM2yCN7 +ZLQwmib6imhdplaO/+zZhjWX5G5yB6mCuwouL9eD46IIRZjGfEW2y9wEo8NPwKEo5rTdGoK3CRoC +CPOosa+u1X7tPaVacB+OSKQDe9RgXkmDiAtI2pas1bFoK3TfoS4eET0nOccF7X+OY9LJGwrBpeT4 +Ws5xQRfsCNeIiGkrP4RUnm+a++5bjYeZ3aRMH4BWD/B+Jo6+SeLazF0I7xTlsYVEOxu5FzDgJNNZ +xhXIB1KtlYYOiV/DVsyOcx1BO23XjwgFj1VvnP6HxpB1Jqi6pV/ifDFu3okf7l5t3VtS7iVGy+Ws +wgK+fF7tabkVlSqUeyHKBYL8pReUGCYmUSNmzICA+gEXXGqf3rQG2nfpiLOi7BYFnFj5ZOi0qSHZ +D+VmdnwWMvjymo54TcC0z9DgeEdIdgUL+8Wm77OZbVfWs2mDRKujD7ggE4S+mZ89pptaFWsluguR +PoXvtvqL87bdBDeuR9VD3W3N5Ae04+J1iVGCoqYGllEWC8PjFHFisSj6dACwmf6FWozi67hERtF+ +Qw6jwGY1Jw1KBCFGO9g1bhXwaH68psZS4PCUa0bTHWBGebdFJTbuE39URnS9NgfC4bpRyvKtErUz +Yfs07N6RjRwxoTvM8W4s7L4bP8+mLAYKY3q9jzbkNwZvCAcUkbm81KVDwR4AYEjUata7HaIX3jCs +W5UJX+JQaaX0cK4DL+QEel6A2TKVcZzagCDUUhBNbF0a7PLIFIQNTj1Cb/KKrcX/HO/VMF5GVkWU +ZAm5O5sjBN9filnOUAEzZq8RxSn4HpPbUNxELmHxVe+NitYAGrqR9qoANnJrISO2HMYmBcNmdtrj +/NjURQC1RtLY16Xlrr+/mmB9zEd9j1GU6vcFBjNz3KRi4CkQ/U1EXoSwacTvS+Wlaa6HT+IXRisC +Wrjr9aNaSTxzwRJPeZIPxFr9cJNU/o3tx5+h3V3XOo3m81h6NvE2LIAmqx7O9FJQ+JxJ11RSpGbV +ImTXeZVsPAEymsC7jus6shdd5wUq26/E2SyUJFBZHuZbAh44c6AHipVxIQuaWjCUOY5Za3FecrL1 +wBe33OgnwutRy0CyiHk6Ss1soXQ7I6gMP0urIHMG9sqUAmPX/tdm6KFBDmg+7vWkKyKD4ZdKwGpP +zsLYqlNBLgDhiXvPKNBMtkb7hqzp1c19Pjd8iU4mHryqV0eDtWKoas7dg8oUa0OBnnN7oegBOgRq +HbvHHBPGpucN0aW0s3ToEEeTaCOuU0OdbgUVkH6d3xZ2rDHH0RRnKJEOha1TdBfXoqeNvAJIgdMs +yl3s9SfoJJOSiISbVLzRkAo2yjf+4hV8rIXhXl+9VAPNI/dn8D6lgYPK2ziXIfBtTrYkdc87Bad6 +Pfx6B6hpQit+9Tvxz9TUJ+fzgoJhpgOXsDMuZmtkYDwR2+YcZOYzxgnJdD0iJR/CQ6zWP6u0R9TB +/cgRayH9lkLpAXzHDOFLT9CmdZkRsaNNTk7oXiFtr0ZN8SCpV5mwDfA1F6tNhJxnhq+xiFOrGJyW +yRMhVi/khKhC8ZJAvBJkdizbuSRoCbQhTR5LK1LVVOVS9A+T/6xHjTOLGI12R9XXkvlxyAkC1SpZ +4oE+jTAfyF5/hnAXm7KbHlE4IoEfQ5Cn2n7fO4yIYHN3JqLCHFcmdjtPZGjI/q1ul/HfdDwPfuHj ++Ps20MiIgdCk70ArMImKlZesdoyEt340rSxufwEtSbH0Re5mSjg3bxS13FRX2Ggn8G3tSalw50Tj +VigysmbUdzYCrqsKa3pAcT8LraiX6+em3zByEnnVbFpfY6lwQwD+bFv9zAxDlvQZ17KdEIuhKz5x +iaelxFK0ZlgBraCI6hlKdc6xzVVKK2hNagTYzkuci9XF9u+fHMgnVnAIR \ No newline at end of file diff --git a/test/test_root_ca.pem b/test/test_root_ca.pem new file mode 100644 index 00000000..998460f1 --- /dev/null +++ b/test/test_root_ca.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB +qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV +BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw +NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j +LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG +A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs +W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta +3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk +6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 +Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J +NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP +r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU +DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz +YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 +/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ +LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 +jVaMaA== +-----END CERTIFICATE----- diff --git a/test/unit/create_test_request_test.cpp b/test/unit/create_test_request_test.cpp new file mode 100644 index 00000000..db94d176 --- /dev/null +++ b/test/unit/create_test_request_test.cpp @@ -0,0 +1,285 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include + +#include "./httpserver.hpp" +#include "./littletest.hpp" + +using httpserver::create_test_request; +using httpserver::http_request; +using httpserver::http_resource; +using httpserver::http_response; +using httpserver::string_response; +using httpserver::file_response; + +LT_BEGIN_SUITE(create_test_request_suite) + void set_up() { + } + + void tear_down() { + } +LT_END_SUITE(create_test_request_suite) + +// Test default values +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_default) + auto req = create_test_request().build(); + LT_CHECK_EQ(std::string(req.get_method()), std::string("GET")); + LT_CHECK_EQ(std::string(req.get_path()), std::string("/")); + LT_CHECK_EQ(std::string(req.get_version()), std::string("HTTP/1.1")); + LT_CHECK_EQ(std::string(req.get_content()), std::string("")); +LT_END_AUTO_TEST(build_default) + +// Test custom method and path +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_method_path) + auto req = create_test_request() + .method("POST") + .path("/api/users") + .build(); + LT_CHECK_EQ(std::string(req.get_method()), std::string("POST")); + LT_CHECK_EQ(std::string(req.get_path()), std::string("/api/users")); +LT_END_AUTO_TEST(build_method_path) + +// Test headers +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_headers) + auto req = create_test_request() + .header("Content-Type", "application/json") + .header("Accept", "text/plain") + .build(); + LT_CHECK_EQ(std::string(req.get_header("Content-Type")), std::string("application/json")); + LT_CHECK_EQ(std::string(req.get_header("Accept")), std::string("text/plain")); + LT_CHECK_EQ(std::string(req.get_header("NonExistent")), std::string("")); + + auto headers = req.get_headers(); + LT_CHECK_EQ(headers.size(), static_cast(2)); +LT_END_AUTO_TEST(build_headers) + +// Test footers and cookies +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_footers_cookies) + auto req = create_test_request() + .footer("X-Checksum", "abc123") + .cookie("session_id", "xyz789") + .build(); + LT_CHECK_EQ(std::string(req.get_footer("X-Checksum")), std::string("abc123")); + LT_CHECK_EQ(std::string(req.get_cookie("session_id")), std::string("xyz789")); + + auto footers = req.get_footers(); + LT_CHECK_EQ(footers.size(), static_cast(1)); + + auto cookies = req.get_cookies(); + LT_CHECK_EQ(cookies.size(), static_cast(1)); +LT_END_AUTO_TEST(build_footers_cookies) + +// Test args +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_args) + auto req = create_test_request() + .arg("name", "World") + .arg("lang", "en") + .build(); + LT_CHECK_EQ(std::string(req.get_arg_flat("name")), std::string("World")); + LT_CHECK_EQ(std::string(req.get_arg_flat("lang")), std::string("en")); +LT_END_AUTO_TEST(build_args) + +// Test multiple values per arg key +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_multi_args) + auto req = create_test_request() + .arg("color", "red") + .arg("color", "blue") + .build(); + auto arg = req.get_arg("color"); + LT_CHECK_EQ(arg.values.size(), static_cast(2)); + LT_CHECK_EQ(std::string(arg.values[0]), std::string("red")); + LT_CHECK_EQ(std::string(arg.values[1]), std::string("blue")); +LT_END_AUTO_TEST(build_multi_args) + +// Test querystring +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_querystring) + auto req = create_test_request() + .querystring("?foo=bar&baz=qux") + .build(); + LT_CHECK_EQ(std::string(req.get_querystring()), std::string("?foo=bar&baz=qux")); +LT_END_AUTO_TEST(build_querystring) + +// Test content +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_content) + auto req = create_test_request() + .content("{\"key\":\"value\"}") + .build(); + LT_CHECK_EQ(std::string(req.get_content()), std::string("{\"key\":\"value\"}")); +LT_END_AUTO_TEST(build_content) + +#ifdef HAVE_BAUTH +// Test basic auth +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_basic_auth) + auto req = create_test_request() + .user("admin") + .pass("secret") + .build(); + LT_CHECK_EQ(std::string(req.get_user()), std::string("admin")); + LT_CHECK_EQ(std::string(req.get_pass()), std::string("secret")); +LT_END_AUTO_TEST(build_basic_auth) +#endif // HAVE_BAUTH + +// Test requestor +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_requestor) + auto req = create_test_request() + .requestor("192.168.1.1") + .requestor_port(8080) + .build(); + LT_CHECK_EQ(std::string(req.get_requestor()), std::string("192.168.1.1")); + LT_CHECK_EQ(req.get_requestor_port(), static_cast(8080)); +LT_END_AUTO_TEST(build_requestor) + +// Test default requestor +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_default_requestor) + auto req = create_test_request().build(); + LT_CHECK_EQ(std::string(req.get_requestor()), std::string("127.0.0.1")); + LT_CHECK_EQ(req.get_requestor_port(), static_cast(0)); +LT_END_AUTO_TEST(build_default_requestor) + +#ifdef HAVE_GNUTLS +// Test TLS enabled flag +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_tls_enabled) + auto req = create_test_request() + .tls_enabled() + .build(); + LT_CHECK_EQ(req.has_tls_session(), true); +LT_END_AUTO_TEST(build_tls_enabled) + +// Test TLS not enabled by default +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_no_tls) + auto req = create_test_request().build(); + LT_CHECK_EQ(req.has_tls_session(), false); +LT_END_AUTO_TEST(build_no_tls) +#endif // HAVE_GNUTLS + +// Test that all getters on a minimal request return empty without crashing +LT_BEGIN_AUTO_TEST(create_test_request_suite, empty_getters_no_crash) + auto req = create_test_request().build(); + // These should all return empty/default without crashing + LT_CHECK_EQ(std::string(req.get_header("Anything")), std::string("")); + LT_CHECK_EQ(std::string(req.get_footer("Anything")), std::string("")); + LT_CHECK_EQ(std::string(req.get_cookie("Anything")), std::string("")); + LT_CHECK_EQ(std::string(req.get_arg_flat("Anything")), std::string("")); + LT_CHECK_EQ(std::string(req.get_querystring()), std::string("")); + LT_CHECK_EQ(std::string(req.get_content()), std::string("")); + LT_CHECK_EQ(req.get_headers().size(), static_cast(0)); + LT_CHECK_EQ(req.get_footers().size(), static_cast(0)); + LT_CHECK_EQ(req.get_cookies().size(), static_cast(0)); + LT_CHECK_EQ(req.get_args().size(), static_cast(0)); + LT_CHECK_EQ(req.get_args_flat().size(), static_cast(0)); + LT_CHECK_EQ(req.get_path_pieces().size(), static_cast(0)); +LT_END_AUTO_TEST(empty_getters_no_crash) + +// End-to-end: build request, call render, inspect response +class greeting_resource : public http_resource { + public: + std::shared_ptr render_GET(const http_request& req) override { + std::string name(req.get_arg_flat("name")); + if (name.empty()) name = "World"; + return std::make_shared("Hello, " + name); + } +}; + +LT_BEGIN_AUTO_TEST(create_test_request_suite, render_with_test_request) + greeting_resource resource; + auto req = create_test_request() + .path("/greet") + .arg("name", "Alice") + .build(); + auto resp = resource.render_GET(req); + auto* sr = dynamic_cast(resp.get()); + LT_ASSERT(sr != nullptr); + LT_CHECK_EQ(std::string(sr->get_content()), std::string("Hello, Alice")); +LT_END_AUTO_TEST(render_with_test_request) + +// Test string_response get_content +LT_BEGIN_AUTO_TEST(create_test_request_suite, string_response_get_content) + string_response resp("test body", 200); + LT_CHECK_EQ(std::string(resp.get_content()), std::string("test body")); +LT_END_AUTO_TEST(string_response_get_content) + +// Test file_response get_filename +LT_BEGIN_AUTO_TEST(create_test_request_suite, file_response_get_filename) + file_response resp("/tmp/test.txt", 200); + LT_CHECK_EQ(std::string(resp.get_filename()), std::string("/tmp/test.txt")); +LT_END_AUTO_TEST(file_response_get_filename) + +// Test full chain of all builder methods +LT_BEGIN_AUTO_TEST(create_test_request_suite, full_chain) + auto req = create_test_request() + .method("PUT") + .path("/api/resource/42") + .version("HTTP/1.0") + .content("request body") + .header("Content-Type", "text/plain") + .header("Authorization", "Bearer token123") + .footer("Trailer", "checksum") + .cookie("session", "abc") + .arg("key1", "val1") + .arg("key2", "val2") + .querystring("?key1=val1&key2=val2") + .user("testuser") + .pass("testpass") + .requestor("10.0.0.1") + .requestor_port(9090) + .build(); + + LT_CHECK_EQ(std::string(req.get_method()), std::string("PUT")); + LT_CHECK_EQ(std::string(req.get_path()), std::string("/api/resource/42")); + LT_CHECK_EQ(std::string(req.get_version()), std::string("HTTP/1.0")); + LT_CHECK_EQ(std::string(req.get_content()), std::string("request body")); + LT_CHECK_EQ(std::string(req.get_header("Content-Type")), std::string("text/plain")); + LT_CHECK_EQ(std::string(req.get_header("Authorization")), std::string("Bearer token123")); + LT_CHECK_EQ(std::string(req.get_footer("Trailer")), std::string("checksum")); + LT_CHECK_EQ(std::string(req.get_cookie("session")), std::string("abc")); + LT_CHECK_EQ(std::string(req.get_arg_flat("key1")), std::string("val1")); + LT_CHECK_EQ(std::string(req.get_arg_flat("key2")), std::string("val2")); + LT_CHECK_EQ(std::string(req.get_querystring()), std::string("?key1=val1&key2=val2")); + LT_CHECK_EQ(std::string(req.get_user()), std::string("testuser")); + LT_CHECK_EQ(std::string(req.get_pass()), std::string("testpass")); + LT_CHECK_EQ(std::string(req.get_requestor()), std::string("10.0.0.1")); + LT_CHECK_EQ(req.get_requestor_port(), static_cast(9090)); +LT_END_AUTO_TEST(full_chain) + +// Test path pieces work with test request +LT_BEGIN_AUTO_TEST(create_test_request_suite, build_path_pieces) + auto req = create_test_request() + .path("/api/users/42") + .build(); + auto pieces = req.get_path_pieces(); + LT_CHECK_EQ(pieces.size(), static_cast(3)); + LT_CHECK_EQ(pieces[0], std::string("api")); + LT_CHECK_EQ(pieces[1], std::string("users")); + LT_CHECK_EQ(pieces[2], std::string("42")); +LT_END_AUTO_TEST(build_path_pieces) + +// Test method is uppercased +LT_BEGIN_AUTO_TEST(create_test_request_suite, method_uppercase) + auto req = create_test_request() + .method("post") + .build(); + LT_CHECK_EQ(std::string(req.get_method()), std::string("POST")); +LT_END_AUTO_TEST(method_uppercase) + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV() diff --git a/test/unit/create_webserver_test.cpp b/test/unit/create_webserver_test.cpp new file mode 100644 index 00000000..49bc10ff --- /dev/null +++ b/test/unit/create_webserver_test.cpp @@ -0,0 +1,472 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include +#include +#include + +#include "./httpserver.hpp" +#include "./littletest.hpp" + +using httpserver::create_webserver; +using httpserver::http_request; +using httpserver::http_response; +using httpserver::string_response; + +LT_BEGIN_SUITE(create_webserver_suite) + void set_up() { + } + + void tear_down() { + } +LT_END_SUITE(create_webserver_suite) + +// Test basic port configuration +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_port) + create_webserver cw(8080); + create_webserver cw2 = create_webserver().port(9090); + // Just verify it compiles and runs without error + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_port) + +// Test max_threads builder method +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_max_threads) + create_webserver cw = create_webserver(8080).max_threads(4); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_max_threads) + +// Test max_connections builder method +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_max_connections) + create_webserver cw = create_webserver(8080).max_connections(100); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_max_connections) + +// Test memory_limit builder method +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_memory_limit) + create_webserver cw = create_webserver(8080).memory_limit(1024); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_memory_limit) + +// Test content_size_limit builder method +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_content_size_limit) + create_webserver cw = create_webserver(8080).content_size_limit(1024 * 1024); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_content_size_limit) + +// Test connection_timeout builder method +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_connection_timeout) + create_webserver cw = create_webserver(8080).connection_timeout(30); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_connection_timeout) + +// Test per_IP_connection_limit builder method +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_per_IP_connection_limit) + create_webserver cw = create_webserver(8080).per_IP_connection_limit(10); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_per_IP_connection_limit) + +// Test use_ssl / no_ssl toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_ssl_toggle) + create_webserver cw1 = create_webserver(8080).use_ssl(); + create_webserver cw2 = create_webserver(8080).no_ssl(); + create_webserver cw3 = create_webserver(8080).use_ssl().no_ssl(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_ssl_toggle) + +// Test use_ipv6 / no_ipv6 toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_ipv6_toggle) + create_webserver cw1 = create_webserver(8080).use_ipv6(); + create_webserver cw2 = create_webserver(8080).no_ipv6(); + create_webserver cw3 = create_webserver(8080).use_ipv6().no_ipv6(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_ipv6_toggle) + +// Test use_dual_stack / no_dual_stack toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_dual_stack_toggle) + create_webserver cw1 = create_webserver(8080).use_dual_stack(); + create_webserver cw2 = create_webserver(8080).no_dual_stack(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_dual_stack_toggle) + +// Test debug / no_debug toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_debug_toggle) + create_webserver cw1 = create_webserver(8080).debug(); + create_webserver cw2 = create_webserver(8080).no_debug(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_debug_toggle) + +// Test pedantic / no_pedantic toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_pedantic_toggle) + create_webserver cw1 = create_webserver(8080).pedantic(); + create_webserver cw2 = create_webserver(8080).no_pedantic(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_pedantic_toggle) + +#ifdef HAVE_BAUTH +// Test basic_auth / no_basic_auth toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_basic_auth_toggle) + create_webserver cw1 = create_webserver(8080).basic_auth(); + create_webserver cw2 = create_webserver(8080).no_basic_auth(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_basic_auth_toggle) +#endif // HAVE_BAUTH + +// Test digest_auth / no_digest_auth toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_digest_auth_toggle) + create_webserver cw1 = create_webserver(8080).digest_auth(); + create_webserver cw2 = create_webserver(8080).no_digest_auth(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_digest_auth_toggle) + +// Test deferred / no_deferred toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_deferred_toggle) + create_webserver cw1 = create_webserver(8080).deferred(); + create_webserver cw2 = create_webserver(8080).no_deferred(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_deferred_toggle) + +// Test regex_checking / no_regex_checking toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_regex_checking_toggle) + create_webserver cw1 = create_webserver(8080).regex_checking(); + create_webserver cw2 = create_webserver(8080).no_regex_checking(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_regex_checking_toggle) + +// Test ban_system / no_ban_system toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_ban_system_toggle) + create_webserver cw1 = create_webserver(8080).ban_system(); + create_webserver cw2 = create_webserver(8080).no_ban_system(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_ban_system_toggle) + +// Test post_process / no_post_process toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_post_process_toggle) + create_webserver cw1 = create_webserver(8080).post_process(); + create_webserver cw2 = create_webserver(8080).no_post_process(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_post_process_toggle) + +// Test put_processed_data_to_content toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_put_processed_data_toggle) + create_webserver cw1 = create_webserver(8080).put_processed_data_to_content(); + create_webserver cw2 = create_webserver(8080).no_put_processed_data_to_content(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_put_processed_data_toggle) + +// Test single_resource / no_single_resource toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_single_resource_toggle) + create_webserver cw1 = create_webserver(8080).single_resource(); + create_webserver cw2 = create_webserver(8080).no_single_resource(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_single_resource_toggle) + +// Test generate_random_filename_on_upload toggle +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_random_filename_toggle) + create_webserver cw1 = create_webserver(8080).generate_random_filename_on_upload(); + create_webserver cw2 = create_webserver(8080).no_generate_random_filename_on_upload(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_random_filename_toggle) + +// Test tcp_nodelay +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_tcp_nodelay) + create_webserver cw = create_webserver(8080).tcp_nodelay(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_tcp_nodelay) + +// Test file_upload_target configurations +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_file_upload_target) + create_webserver cw1 = create_webserver(8080) + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_ONLY); + create_webserver cw2 = create_webserver(8080) + .file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY); + create_webserver cw3 = create_webserver(8080) + .file_upload_target(httpserver::FILE_UPLOAD_MEMORY_AND_DISK); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_file_upload_target) + +// Test file_upload_dir +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_file_upload_dir) + create_webserver cw = create_webserver(8080).file_upload_dir("/tmp/uploads"); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_file_upload_dir) + +// Test not_found_resource +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_not_found_resource) + auto not_found_handler = [](const http_request&) { + return std::make_shared("Custom 404", 404); + }; + create_webserver cw = create_webserver(8080).not_found_resource(not_found_handler); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_not_found_resource) + +// Test method_not_allowed_resource +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_method_not_allowed_resource) + auto method_not_allowed_handler = [](const http_request&) { + return std::make_shared("Custom 405", 405); + }; + create_webserver cw = create_webserver(8080).method_not_allowed_resource(method_not_allowed_handler); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_method_not_allowed_resource) + +// Test internal_error_resource +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_internal_error_resource) + auto internal_error_handler = [](const http_request&) { + return std::make_shared("Custom 500", 500); + }; + create_webserver cw = create_webserver(8080).internal_error_resource(internal_error_handler); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_internal_error_resource) + +// Test start_method configurations +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_start_method) + create_webserver cw1 = create_webserver(8080) + .start_method(httpserver::http::http_utils::INTERNAL_SELECT); + create_webserver cw2 = create_webserver(8080) + .start_method(httpserver::http::http_utils::THREAD_PER_CONNECTION); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_start_method) + +// Test default_policy configurations +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_default_policy) + create_webserver cw1 = create_webserver(8080) + .default_policy(httpserver::http::http_utils::ACCEPT); + create_webserver cw2 = create_webserver(8080) + .default_policy(httpserver::http::http_utils::REJECT); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_default_policy) + +// Test cred_type configuration +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_cred_type) + create_webserver cw = create_webserver(8080) + .cred_type(httpserver::http::http_utils::NONE); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_cred_type) + +// Test nonce_nc_size +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_nonce_nc_size) + create_webserver cw = create_webserver(8080).nonce_nc_size(10); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_nonce_nc_size) + +// Test digest_auth_random +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_digest_auth_random) + create_webserver cw = create_webserver(8080).digest_auth_random("random_seed_string"); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_digest_auth_random) + +// Test https_priorities +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_https_priorities) + create_webserver cw = create_webserver(8080).https_priorities("NORMAL:-MD5"); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_https_priorities) + +// Test raw_https_mem_key +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_raw_https_mem_key) + create_webserver cw = create_webserver(8080).raw_https_mem_key("raw key content"); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_raw_https_mem_key) + +// Test raw_https_mem_cert +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_raw_https_mem_cert) + create_webserver cw = create_webserver(8080).raw_https_mem_cert("raw cert content"); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_raw_https_mem_cert) + +// Test raw_https_mem_trust +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_raw_https_mem_trust) + create_webserver cw = create_webserver(8080).raw_https_mem_trust("raw trust content"); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_raw_https_mem_trust) + +// Test bind_socket +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_bind_socket) + create_webserver cw = create_webserver(8080).bind_socket(0); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_bind_socket) + +// Test max_thread_stack_size +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_max_thread_stack_size) + create_webserver cw = create_webserver(8080).max_thread_stack_size(4 * 1024 * 1024); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_max_thread_stack_size) + +// Test log_access callback +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_log_access) + auto log_access_handler = [](const std::string& log_msg) { + // do nothing with the log + (void)log_msg; + }; + create_webserver cw = create_webserver(8080).log_access(log_access_handler); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_log_access) + +// Test log_error callback +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_log_error) + auto log_error_handler = [](const std::string& log_msg) { + // do nothing with the log + (void)log_msg; + }; + create_webserver cw = create_webserver(8080).log_error(log_error_handler); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_log_error) + +// Test validator callback +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_validator) + auto validator_handler = [](const std::string& url) { + (void)url; + return true; + }; + create_webserver cw = create_webserver(8080).validator(validator_handler); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_validator) + +// Test unescaper callback (signature: void(*)(std::string&)) +void test_unescaper(std::string& s) { + // Simple passthrough unescaper + (void)s; +} + +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_unescaper) + create_webserver cw = create_webserver(8080).unescaper(test_unescaper); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_unescaper) + +// Test auth_handler callback +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_auth_handler) + auto auth_handler = [](const http_request&) { + return std::shared_ptr(nullptr); + }; + create_webserver cw = create_webserver(8080).auth_handler(auth_handler); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_auth_handler) + +// Test auth_skip_paths +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_auth_skip_paths) + std::vector skip_paths = {"/public", "/health", "/static/*"}; + create_webserver cw = create_webserver(8080).auth_skip_paths(skip_paths); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_auth_skip_paths) + +// Test file_cleanup_callback +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_file_cleanup_callback) + auto cleanup_handler = [](const std::string& field_name, + const std::string& file_name, + const httpserver::http::file_info& fi) { + (void)field_name; + (void)file_name; + (void)fi; + return true; // return true to delete file + }; + create_webserver cw = create_webserver(8080).file_cleanup_callback(cleanup_handler); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_file_cleanup_callback) + +// Test PSK cred handler callback +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_psk_cred_handler) + auto psk_handler = [](const std::string& identity) { + (void)identity; + return std::string("psk_key"); + }; + create_webserver cw = create_webserver(8080).psk_cred_handler(psk_handler); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_psk_cred_handler) + +// Test copy constructor +LT_BEGIN_AUTO_TEST(create_webserver_suite, copy_constructor) + create_webserver cw1 = create_webserver(8080) + .max_threads(4) + .max_connections(100) + .debug(); + create_webserver cw2(cw1); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(copy_constructor) + +// Test move constructor +LT_BEGIN_AUTO_TEST(create_webserver_suite, move_constructor) + create_webserver cw1 = create_webserver(8080).max_threads(4); + create_webserver cw2(std::move(cw1)); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(move_constructor) + +// Test assignment operator +LT_BEGIN_AUTO_TEST(create_webserver_suite, assignment_operator) + create_webserver cw1 = create_webserver(8080).max_threads(4); + create_webserver cw2 = create_webserver(9090); + cw2 = cw1; + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(assignment_operator) + +// Test move assignment operator +LT_BEGIN_AUTO_TEST(create_webserver_suite, move_assignment_operator) + create_webserver cw1 = create_webserver(8080).max_threads(4); + create_webserver cw2 = create_webserver(9090); + cw2 = std::move(cw1); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(move_assignment_operator) + +// Test method chaining with many options +LT_BEGIN_AUTO_TEST(create_webserver_suite, method_chaining) + create_webserver cw = create_webserver(8080) + .max_threads(4) + .max_connections(100) + .memory_limit(1024) + .content_size_limit(1024 * 1024) + .connection_timeout(30) + .per_IP_connection_limit(10) + .debug() + .pedantic() + .regex_checking() + .ban_system() + .post_process() + .tcp_nodelay(); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(method_chaining) + +// Test default constructor +LT_BEGIN_AUTO_TEST(create_webserver_suite, default_constructor) + create_webserver cw; + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(default_constructor) + +// Test https_mem_key (loads from file path) +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_https_mem_key_file) + // Use the test key file that exists in the test directory + create_webserver cw = create_webserver(8080).https_mem_key("../test/key.pem"); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_https_mem_key_file) + +// Test https_mem_cert (loads from file path) +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_https_mem_cert_file) + // Use the test cert file that exists in the test directory + create_webserver cw = create_webserver(8080).https_mem_cert("../test/cert.pem"); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_https_mem_cert_file) + +// Test https_mem_trust (loads from file path) +LT_BEGIN_AUTO_TEST(create_webserver_suite, builder_https_mem_trust_file) + // Use the test CA file that exists in the test directory + create_webserver cw = create_webserver(8080).https_mem_trust("../test/test_root_ca.pem"); + LT_CHECK_EQ(true, true); +LT_END_AUTO_TEST(builder_https_mem_trust_file) + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV() diff --git a/test/unit/http_endpoint_test.cpp b/test/unit/http_endpoint_test.cpp new file mode 100644 index 00000000..42bfbc1d --- /dev/null +++ b/test/unit/http_endpoint_test.cpp @@ -0,0 +1,616 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include "httpserver/details/http_endpoint.hpp" + +#include +#include +#include + +#include "./littletest.hpp" + +using httpserver::details::http_endpoint; +using std::string; +using std::vector; + +LT_BEGIN_SUITE(http_endpoint_suite) + void set_up() { + } + + void tear_down() { + } +LT_END_SUITE(http_endpoint_suite) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_default) + http_endpoint test_endpoint; + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "/"); + + LT_CHECK_EQ(test_endpoint.get_url_pars().size(), 0); + LT_CHECK_EQ(test_endpoint.get_url_pieces().size(), 0); + LT_CHECK_EQ(test_endpoint.get_chunk_positions().size(), 0); + + LT_CHECK_EQ(test_endpoint.is_family_url(), false); + LT_CHECK_EQ(test_endpoint.is_regex_compiled(), false); +LT_END_AUTO_TEST(http_endpoint_default) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_from_string_registration) + http_endpoint test_endpoint("/path/to/resource", false, true, true); + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/path/to/resource"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "^/path/to/resource$"); + + LT_CHECK_EQ(test_endpoint.get_url_pars().size(), 0); + + string expected_arr[] = { "path", "to", "resource" }; + vector expected_pieces(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_url_pieces().begin(), test_endpoint.get_url_pieces().end(), expected_pieces.begin()); + + LT_CHECK_EQ(test_endpoint.get_chunk_positions().size(), 0); + + LT_CHECK_EQ(test_endpoint.is_family_url(), false); + LT_CHECK_EQ(test_endpoint.is_regex_compiled(), true); +LT_END_AUTO_TEST(http_endpoint_from_string_registration) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_from_string_not_beginning_with_slash) + http_endpoint test_endpoint("path/to/resource", false, true, true); + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/path/to/resource"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "^/path/to/resource$"); + + LT_CHECK_EQ(test_endpoint.get_url_pars().size(), 0); + + string expected_arr[] = { "path", "to", "resource" }; + vector expected_pieces(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_url_pieces().begin(), test_endpoint.get_url_pieces().end(), expected_pieces.begin()); + + LT_CHECK_EQ(test_endpoint.get_chunk_positions().size(), 0); + + LT_CHECK_EQ(test_endpoint.is_family_url(), false); + LT_CHECK_EQ(test_endpoint.is_regex_compiled(), true); +LT_END_AUTO_TEST(http_endpoint_from_string_not_beginning_with_slash) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_from_string_ending_with_slash) + http_endpoint test_endpoint("path/to/resource/", false, true, true); + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/path/to/resource"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "^/path/to/resource$"); + + LT_CHECK_EQ(test_endpoint.get_url_pars().size(), 0); + + string expected_arr[] = { "path", "to", "resource" }; + vector expected_pieces(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_url_pieces().begin(), test_endpoint.get_url_pieces().end(), expected_pieces.begin()); + + LT_CHECK_EQ(test_endpoint.get_chunk_positions().size(), 0); + + LT_CHECK_EQ(test_endpoint.is_family_url(), false); + LT_CHECK_EQ(test_endpoint.is_regex_compiled(), true); +LT_END_AUTO_TEST(http_endpoint_from_string_ending_with_slash) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_from_string_family) + http_endpoint test_endpoint("/path/to/resource", true, true, true); + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/path/to/resource"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "^/path/to/resource$"); + + LT_CHECK_EQ(test_endpoint.get_url_pars().size(), 0); + + string expected_arr[] = { "path", "to", "resource" }; + vector expected_pieces(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_url_pieces().begin(), test_endpoint.get_url_pieces().end(), expected_pieces.begin()); + + LT_CHECK_EQ(test_endpoint.get_chunk_positions().size(), 0); + + LT_CHECK_EQ(test_endpoint.is_family_url(), true); + LT_CHECK_EQ(test_endpoint.is_regex_compiled(), true); +LT_END_AUTO_TEST(http_endpoint_from_string_family) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_default_no_regex) + http_endpoint test_endpoint("/path/to/resource"); + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/path/to/resource"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "/path/to/resource"); + + LT_CHECK_EQ(test_endpoint.get_url_pars().size(), 0); + + string expected_arr[] = { "path", "to", "resource" }; + vector expected_pieces(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_url_pieces().begin(), test_endpoint.get_url_pieces().end(), expected_pieces.begin()); + + LT_CHECK_EQ(test_endpoint.get_chunk_positions().size(), 0); + + LT_CHECK_EQ(test_endpoint.is_family_url(), false); + LT_CHECK_EQ(test_endpoint.is_regex_compiled(), false); +LT_END_AUTO_TEST(http_endpoint_default_no_regex) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_from_string_no_regex) + http_endpoint test_endpoint("/path/to/resource", false, false, false); + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/path/to/resource"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "/path/to/resource"); + + LT_CHECK_EQ(test_endpoint.get_url_pars().size(), 0); + + string expected_arr[] = { "path", "to", "resource" }; + vector expected_pieces(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_url_pieces().begin(), test_endpoint.get_url_pieces().end(), expected_pieces.begin()); + + LT_CHECK_EQ(test_endpoint.get_chunk_positions().size(), 0); + + LT_CHECK_EQ(test_endpoint.is_family_url(), false); + LT_CHECK_EQ(test_endpoint.is_regex_compiled(), false); +LT_END_AUTO_TEST(http_endpoint_from_string_no_regex) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_registration) + http_endpoint test_endpoint("/path/to/resource", false, true, true); + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/path/to/resource"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "^/path/to/resource$"); + + LT_CHECK_EQ(test_endpoint.get_url_pars().size(), 0); + + string expected_arr[] = { "path", "to", "resource" }; + vector expected_pieces(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_url_pieces().begin(), test_endpoint.get_url_pieces().end(), expected_pieces.begin()); + + LT_CHECK_EQ(test_endpoint.get_chunk_positions().size(), 0); + + LT_CHECK_EQ(test_endpoint.is_family_url(), false); + LT_CHECK_EQ(test_endpoint.is_regex_compiled(), true); +LT_END_AUTO_TEST(http_endpoint_registration) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_registration_nested_regex) + http_endpoint test_endpoint("/path/to/resource/with/[0-9]+/to/fetch", false, true, true); + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/path/to/resource/with/[0-9]+/to/fetch"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "^/path/to/resource/with/[0-9]+/to/fetch$"); + + LT_CHECK_EQ(test_endpoint.get_url_pars().size(), 0); + + string expected_arr[] = { "path", "to", "resource", "with", "[0-9]+", "to", "fetch" }; + vector expected_pieces(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_url_pieces().begin(), test_endpoint.get_url_pieces().end(), expected_pieces.begin()); + + LT_CHECK_EQ(test_endpoint.get_chunk_positions().size(), 0); + + LT_CHECK_EQ(test_endpoint.is_family_url(), false); + LT_CHECK_EQ(test_endpoint.is_regex_compiled(), true); +LT_END_AUTO_TEST(http_endpoint_registration_nested_regex) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_registration_arg) + http_endpoint test_endpoint("/path/to/resource/with/{arg}/to/fetch", false, true, true); + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/path/to/resource/with/{arg}/to/fetch"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "^/path/to/resource/with/([^\\/]+)/to/fetch$"); + + string expected_pars_arr[] = { "arg" }; + vector expected_pars(expected_pars_arr, expected_pars_arr + sizeof(expected_pars_arr) / sizeof(expected_pars_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_url_pars().begin(), test_endpoint.get_url_pars().end(), expected_pars.begin()); + + string expected_arr[] = { "path", "to", "resource", "with", "{arg}", "to", "fetch" }; + vector expected_pieces(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_url_pieces().begin(), test_endpoint.get_url_pieces().end(), expected_pieces.begin()); + + int expected_chunk_positions_arr[] = { 4 }; + vector expected_chunk_positions(expected_chunk_positions_arr, expected_chunk_positions_arr + sizeof(expected_chunk_positions_arr) / sizeof(expected_chunk_positions_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_chunk_positions().begin(), test_endpoint.get_chunk_positions().end(), expected_chunk_positions.begin()); + + LT_CHECK_EQ(test_endpoint.is_family_url(), false); + LT_CHECK_EQ(test_endpoint.is_regex_compiled(), true); +LT_END_AUTO_TEST(http_endpoint_registration_arg) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_registration_arg_custom_regex) + http_endpoint test_endpoint("/path/to/resource/with/{arg|([0-9]+)}/to/fetch", false, true, true); + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/path/to/resource/with/{arg|([0-9]+)}/to/fetch"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "^/path/to/resource/with/([0-9]+)/to/fetch$"); + + string expected_pars_arr[] = { "arg" }; + vector expected_pars(expected_pars_arr, expected_pars_arr + sizeof(expected_pars_arr) / sizeof(expected_pars_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_url_pars().begin(), test_endpoint.get_url_pars().end(), expected_pars.begin()); + + string expected_arr[] = { "path", "to", "resource", "with", "{arg|([0-9]+)}", "to", "fetch" }; + vector expected_pieces(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_url_pieces().begin(), test_endpoint.get_url_pieces().end(), expected_pieces.begin()); + + int expected_chunk_positions_arr[] = { 4 }; + vector expected_chunk_positions(expected_chunk_positions_arr, expected_chunk_positions_arr + sizeof(expected_chunk_positions_arr) / sizeof(expected_chunk_positions_arr[0])); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_chunk_positions().begin(), test_endpoint.get_chunk_positions().end(), expected_chunk_positions.begin()); + + LT_CHECK_EQ(test_endpoint.is_family_url(), false); + LT_CHECK_EQ(test_endpoint.is_regex_compiled(), true); +LT_END_AUTO_TEST(http_endpoint_registration_arg_custom_regex) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_registration_invalid_arg) + LT_CHECK_THROW(http_endpoint("/path/to/resource/with/{}/to/fetch", false, true)); +LT_END_AUTO_TEST(http_endpoint_registration_invalid_arg) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_copy_constructor) + http_endpoint a("/path/to/resource/with/{arg|([0-9]+)}/to/fetch", false, true, true); + http_endpoint b(a); + + LT_CHECK_EQ(a.get_url_complete(), b.get_url_complete()); + LT_CHECK_EQ(a.get_url_normalized(), b.get_url_normalized()); + LT_CHECK_COLLECTIONS_EQ(a.get_url_pars().begin(), a.get_url_pars().end(), b.get_url_pars().begin()); + LT_CHECK_COLLECTIONS_EQ(a.get_url_pieces().begin(), a.get_url_pieces().end(), b.get_url_pieces().begin()); + LT_CHECK_COLLECTIONS_EQ(a.get_chunk_positions().begin(), a.get_chunk_positions().end(), b.get_chunk_positions().begin()); + LT_CHECK_EQ(a.is_family_url(), b.is_family_url()); + LT_CHECK_EQ(a.is_regex_compiled(), b.is_regex_compiled()); + + LT_CHECK_EQ(a.match(http_endpoint("/path/to/resource/with/10/to/fetch")), true); + LT_CHECK_EQ(b.match(http_endpoint("/path/to/resource/with/10/to/fetch")), true); +LT_END_AUTO_TEST(http_endpoint_copy_constructor) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_assignment) + http_endpoint a("/path/to/resource/with/{arg|([0-9]+)}/to/fetch", false, true, true); + http_endpoint b("whatever/initial/value"); + + LT_CHECK_NEQ(a.get_url_complete(), b.get_url_complete()); + + std::cout << "before assigning" << std::endl; + b = a; + std::cout << "after assigning" << std::endl; + + LT_CHECK_EQ(a.get_url_complete(), b.get_url_complete()); + LT_CHECK_EQ(a.get_url_normalized(), b.get_url_normalized()); + LT_CHECK_COLLECTIONS_EQ(a.get_url_pars().begin(), a.get_url_pars().end(), b.get_url_pars().begin()); + LT_CHECK_COLLECTIONS_EQ(a.get_url_pieces().begin(), a.get_url_pieces().end(), b.get_url_pieces().begin()); + LT_CHECK_COLLECTIONS_EQ(a.get_chunk_positions().begin(), a.get_chunk_positions().end(), b.get_chunk_positions().begin()); + LT_CHECK_EQ(a.is_family_url(), b.is_family_url()); + LT_CHECK_EQ(a.is_regex_compiled(), b.is_regex_compiled()); + + LT_CHECK_EQ(a.match(http_endpoint("/path/to/resource/with/10/to/fetch")), true); + LT_CHECK_EQ(b.match(http_endpoint("/path/to/resource/with/10/to/fetch")), true); +LT_END_AUTO_TEST(http_endpoint_assignment) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_match_regex) + http_endpoint test_endpoint("/path/to/resource/", false, true, true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to2/resource")), false); +LT_END_AUTO_TEST(http_endpoint_match_regex) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_match_regex_nested) + http_endpoint test_endpoint("/path/to/resource/with/[0-9]+/to/fetch", false, true, true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/with/0/to/fetch")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/with/10/to/fetch")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("path/to/resource/with/1/to/fetch")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/with/1/to/fetch/")), true); +LT_END_AUTO_TEST(http_endpoint_match_regex_nested) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_match_regex_nested_capture) + http_endpoint test_endpoint("/path/to/resource/with/([0-9]+)/to/fetch", false, true, true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/with/0/to/fetch")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/with/10/to/fetch")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("path/to/resource/with/1/to/fetch")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/with/1/to/fetch/")), true); +LT_END_AUTO_TEST(http_endpoint_match_regex_nested_capture) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_match_regex_nested_arg) + http_endpoint test_endpoint("/path/to/resource/with/{arg}/to/fetch", false, true, true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/with/0/to/fetch")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/with/10/to/fetch")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("path/to/resource/with/1/to/fetch")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/with/1/to/fetch/")), true); +LT_END_AUTO_TEST(http_endpoint_match_regex_nested_arg) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_match_regex_nested_custom_arg) + http_endpoint test_endpoint("/path/to/resource/with/{arg|([0-9]+)}/to/fetch", false, true, true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/with/0/to/fetch")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/with/10/to/fetch")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("path/to/resource/with/1/to/fetch")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/with/1/to/fetch/")), true); +LT_END_AUTO_TEST(http_endpoint_match_regex_nested_custom_arg) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_match_regex_family) + http_endpoint test_endpoint("/path/to/resource", true, true, true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("path/to/resource")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("path/to/resource/")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/to/resource/followed/by/anything")), true); + + LT_CHECK_EQ(test_endpoint.match(http_endpoint("path/to2/resource")), false); +LT_END_AUTO_TEST(http_endpoint_match_regex_family) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_match_regex_disabled) + http_endpoint test_endpoint("/path/to/resource", false, true, false); + LT_CHECK_THROW(test_endpoint.match(http_endpoint("/path/to/resource"))); +LT_END_AUTO_TEST(http_endpoint_match_regex_disabled) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_cannot_use_regex_if_not_registering) + LT_CHECK_THROW(http_endpoint("/path/to/resource", false, false, true)); +LT_END_AUTO_TEST(http_endpoint_cannot_use_regex_if_not_registering) + +LT_BEGIN_AUTO_TEST(http_endpoint_suite, comparator) + LT_CHECK_EQ(http_endpoint("/a/b") < http_endpoint("/a/c"), true); + LT_CHECK_EQ(http_endpoint("/a/c") < http_endpoint("/a/b"), false); + + LT_CHECK_EQ(http_endpoint("/a/b") < http_endpoint("/a/b/c"), true); + LT_CHECK_EQ(http_endpoint("/a/b/c") < http_endpoint("/a/b"), false); +LT_END_AUTO_TEST(comparator) + +// Test that invalid regex pattern throws exception (covers lines 114-116) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_invalid_regex_pattern) + // Using unbalanced parentheses which is invalid regex + LT_CHECK_THROW(http_endpoint("/path/(unclosed", false, true, true)); +LT_END_AUTO_TEST(http_endpoint_invalid_regex_pattern) + +// Test operator< when family_url differs (covers line 145) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, comparator_family_difference) + http_endpoint family_ep("/path/to/resource", true, true, true); + http_endpoint non_family_ep("/path/to/resource", false, true, true); + + // Family URL should come before non-family in ordering + LT_CHECK_EQ(family_ep < non_family_ep, true); + LT_CHECK_EQ(non_family_ep < family_ep, false); +LT_END_AUTO_TEST(comparator_family_difference) + +// Test operator< when both are family URLs (covers line 146) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, comparator_same_family) + http_endpoint family_a("/aaa", true, true, true); + http_endpoint family_b("/bbb", true, true, true); + + // Should compare by url_normalized when both are family URLs + LT_CHECK_EQ(family_a < family_b, true); + LT_CHECK_EQ(family_b < family_a, false); +LT_END_AUTO_TEST(comparator_same_family) + +// Test match with family URL and shorter incoming URL (covers line 152) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_match_family_shorter_url) + // Family URL with 3 pieces + http_endpoint family_ep("/path/to/resource", true, true, true); + + // Incoming URL with fewer pieces (covers the || short-circuit) + http_endpoint short_url("/path"); + + // Should still match using regex_match directly + LT_CHECK_EQ(family_ep.match(short_url), false); +LT_END_AUTO_TEST(http_endpoint_match_family_shorter_url) + +// Test match with non-family URL (covers line 153 directly) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_match_non_family) + http_endpoint non_family_ep("/path/to/resource", false, true, true); + http_endpoint incoming("/path/to/resource"); + + // Non-family should use direct regex_match + LT_CHECK_EQ(non_family_ep.match(incoming), true); +LT_END_AUTO_TEST(http_endpoint_match_non_family) + +// Test URL parameter at first position (covers line 84 false branch, line 101 first==true) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_arg_first_position) + http_endpoint test_endpoint("/{arg}/rest/of/path", false, true, true); + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/{arg}/rest/of/path"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "^/([^\\/]+)/rest/of/path$"); + + string expected_pars_arr[] = { "arg" }; + vector expected_pars(expected_pars_arr, expected_pars_arr + 1); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_url_pars().begin(), + test_endpoint.get_url_pars().end(), + expected_pars.begin()); + + int expected_chunk_positions_arr[] = { 0 }; + vector expected_chunk_positions(expected_chunk_positions_arr, expected_chunk_positions_arr + 1); + LT_CHECK_COLLECTIONS_EQ(test_endpoint.get_chunk_positions().begin(), + test_endpoint.get_chunk_positions().end(), + expected_chunk_positions.begin()); + + // Verify it matches correctly + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/value/rest/of/path")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/wrong/path")), false); +LT_END_AUTO_TEST(http_endpoint_arg_first_position) + +// Test custom regex pattern at first position (covers line 85 starting with ^) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_custom_regex_first) + // Note: Custom regex starting with ^ at first position + http_endpoint test_endpoint("/{id|([0-9]+)}/data", false, true, true); + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/{id|([0-9]+)}/data"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "^/([0-9]+)/data$"); + + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/123/data")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/abc/data")), false); +LT_END_AUTO_TEST(http_endpoint_custom_regex_first) + +// Test URL pattern where first part starts with ^ (caret) +// Covers http_endpoint.cpp line 85 (parts[i][0] == '^' branch) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_caret_at_start) + // When first part[0] == '^', the prefix should be cleared + // The regex pattern starting with ^ at the first position + http_endpoint test_endpoint("/^api", false, true, true); + + // The normalized URL should not have double caret (^^ would be wrong) + LT_CHECK_EQ(test_endpoint.get_url_normalized().find("^^") == std::string::npos, true); + // Should start with ^api (not ^/^api) + LT_CHECK_EQ(test_endpoint.get_url_normalized().substr(0, 4), "^api"); +LT_END_AUTO_TEST(http_endpoint_caret_at_start) + +// Test URL with consecutive slashes creating empty parts +// Covers http_endpoint.cpp line 83 (parts[i] == "" condition) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_consecutive_slashes) + // Consecutive slashes create empty parts which should be skipped in processing + // but the original URL is preserved in url_complete + http_endpoint test_endpoint("//path//to//resource", false, true, true); + + // URL is preserved with consecutive slashes (leading / is normalized) + LT_CHECK_EQ(test_endpoint.get_url_complete(), "//path//to//resource"); + + // But url_pieces should only contain non-empty parts + std::vector pieces = test_endpoint.get_url_pieces(); + LT_CHECK_EQ(pieces.size() > 0, true); // At least some pieces + for (const auto& piece : pieces) { + // No empty pieces should be in the result + LT_CHECK_EQ(piece.empty(), false); + } +LT_END_AUTO_TEST(http_endpoint_consecutive_slashes) + +// Test URL part that is just "^" by itself (edge case) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_caret_only_part) + // Part that is just "^" - tests the empty string after ^ edge case + http_endpoint test_endpoint("/api/^/data", false, true, true); + + // Should be handled correctly + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/api/^/data"); +LT_END_AUTO_TEST(http_endpoint_caret_only_part) + +// Test match with family URL where incoming has more pieces (should match) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_match_family_more_pieces) + // Family URL with 3 pieces + http_endpoint family_ep("/api/v1", true, true, true); + + // Incoming URL with more pieces + http_endpoint long_url("/api/v1/users/123/details"); + + // Family URL should match URLs that extend the pattern + LT_CHECK_EQ(family_ep.match(long_url), true); +LT_END_AUTO_TEST(http_endpoint_match_family_more_pieces) + +// Test match with equal pieces but mismatched content +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_match_mismatch_content) + http_endpoint registered("/api/users", false, true, true); + http_endpoint incoming("/api/items"); + + LT_CHECK_EQ(registered.match(incoming), false); +LT_END_AUTO_TEST(http_endpoint_match_mismatch_content) + +// Test multiple URL parameters in sequence +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_multiple_params) + http_endpoint test_endpoint("/{type}/{id}/{action}", false, true, true); + + LT_CHECK_EQ(test_endpoint.get_url_pars().size(), 3); + + // Verify parameter names + auto pars = test_endpoint.get_url_pars(); + LT_CHECK_EQ(pars[0], "type"); + LT_CHECK_EQ(pars[1], "id"); + LT_CHECK_EQ(pars[2], "action"); + + // Should match a URL with three values + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/user/123/edit")), true); +LT_END_AUTO_TEST(http_endpoint_multiple_params) + +// Test URL parameter with custom regex that includes special characters +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_custom_regex_special) + http_endpoint test_endpoint("/files/{filename|([a-zA-Z0-9._-]+)}", false, true, true); + + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/files/test.txt")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/files/my-file_123.json")), true); +LT_END_AUTO_TEST(http_endpoint_custom_regex_special) + +// Test comparator with same URL but different family status +LT_BEGIN_AUTO_TEST(http_endpoint_suite, comparator_same_url_family_diff) + http_endpoint ep1("/path", true, true, true); + http_endpoint ep2("/path", false, true, true); + + // Family endpoints should sort before non-family + bool result1 = ep1 < ep2; + bool result2 = ep2 < ep1; + + // At least one should be true (they should be different) + LT_CHECK_EQ(result1 != result2, true); +LT_END_AUTO_TEST(comparator_same_url_family_diff) + +// Test URL that's just a slash +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_root_only) + http_endpoint test_endpoint("/", false, true, true); + + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/"); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/")), true); +LT_END_AUTO_TEST(http_endpoint_root_only) + +// Test URL with trailing and leading slashes +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_multiple_trailing_slashes) + http_endpoint test_endpoint("/api/", false, true, true); + + // Should normalize and match + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/api")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/api/")), true); +LT_END_AUTO_TEST(http_endpoint_multiple_trailing_slashes) + +// Test complex regex pattern with alternation +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_regex_alternation) + http_endpoint test_endpoint("/{resource|(users|posts|comments)}", false, true, true); + + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/users")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/posts")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/comments")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/other")), false); +LT_END_AUTO_TEST(http_endpoint_regex_alternation) + +// Test that use_regex without registration throws +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_regex_no_registration_throws) + // use_regex=true but registration=false should throw + LT_CHECK_THROW(http_endpoint("/path", false, false, true)); +LT_END_AUTO_TEST(http_endpoint_regex_no_registration_throws) + +// Test non-registration path (use_regex=false, registration=false) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_non_registration) + http_endpoint test_endpoint("/path/to/resource", false, false, false); + + // Non-registration endpoints should still parse correctly + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/path/to/resource"); + LT_CHECK_EQ(test_endpoint.get_url_normalized(), "/path/to/resource"); + LT_CHECK_EQ(test_endpoint.is_regex_compiled(), false); +LT_END_AUTO_TEST(http_endpoint_non_registration) + +// Test with trailing slash (should be removed) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_trailing_slash_removed) + http_endpoint test_endpoint("/path/resource/", false, true, false); + + // Trailing slash should be removed from url_complete + LT_CHECK_EQ(test_endpoint.get_url_complete(), "/path/resource"); +LT_END_AUTO_TEST(http_endpoint_trailing_slash_removed) + +// Test invalid URL parameter format (too short) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_invalid_param_too_short) + // Parameter {} is too short (less than 3 chars including braces) + LT_CHECK_THROW(http_endpoint("/path/{}", false, true, true)); +LT_END_AUTO_TEST(http_endpoint_invalid_param_too_short) + +// Test invalid URL parameter format (only one brace) +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_invalid_param_one_brace) + // Parameter {x is missing closing brace + LT_CHECK_THROW(http_endpoint("/path/{x", false, true, true)); +LT_END_AUTO_TEST(http_endpoint_invalid_param_one_brace) + +// Test URL parameter with bar separator but short +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_param_with_regex) + http_endpoint test_endpoint("/path/{id|[0-9]+}", false, true, true); + + LT_CHECK_EQ(test_endpoint.get_url_pars().size(), 1); + LT_CHECK_EQ(test_endpoint.get_url_pars()[0], "id"); + // Should match numbers only + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/123")), true); + LT_CHECK_EQ(test_endpoint.match(http_endpoint("/path/abc")), false); +LT_END_AUTO_TEST(http_endpoint_param_with_regex) + +// Test invalid regex pattern throws +LT_BEGIN_AUTO_TEST(http_endpoint_suite, http_endpoint_invalid_regex_throws) + // Invalid regex pattern should throw + LT_CHECK_THROW(http_endpoint("/path/{id|[invalid}", false, true, true)); +LT_END_AUTO_TEST(http_endpoint_invalid_regex_throws) + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV() diff --git a/test/unit/http_resource_test.cpp b/test/unit/http_resource_test.cpp new file mode 100644 index 00000000..6cfbe34e --- /dev/null +++ b/test/unit/http_resource_test.cpp @@ -0,0 +1,223 @@ +/* + This file is part of libhttpserver + Copyright (C) 2021 Alexander Dahl + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include + +#include +#include +#include +#include + +#include "./httpserver.hpp" +#include "./littletest.hpp" + +using std::shared_ptr; +using std::sort; +using std::string; +using std::vector; + +using httpserver::http_request; +using httpserver::http_resource; +using httpserver::http_response; +using httpserver::string_response; + +class simple_resource : public http_resource { + public: + shared_ptr render_GET(const http_request&) { + return std::make_shared("OK"); + } +}; + +LT_BEGIN_SUITE(http_resource_suite) + void set_up() { + } + + void tear_down() { + } +LT_END_SUITE(http_resource_suite) + +LT_BEGIN_AUTO_TEST(http_resource_suite, disallow_all_methods) + simple_resource sr; + sr.disallow_all(); + auto allowed_methods = sr.get_allowed_methods(); + LT_CHECK_EQ(allowed_methods.size(), 0); +LT_END_AUTO_TEST(disallow_all_methods) + +LT_BEGIN_AUTO_TEST(http_resource_suite, allow_some_methods) + simple_resource sr; + sr.disallow_all(); + sr.set_allowing(MHD_HTTP_METHOD_GET, true); + sr.set_allowing(MHD_HTTP_METHOD_POST, true); + auto allowed_methods = sr.get_allowed_methods(); + LT_CHECK_EQ(allowed_methods.size(), 2); + // elements in http_resource::method_state are sorted (std::map) + vector some_methods{MHD_HTTP_METHOD_GET, MHD_HTTP_METHOD_POST}; + sort(some_methods.begin(), some_methods.end()); + LT_CHECK_COLLECTIONS_EQ(allowed_methods.cbegin(), allowed_methods.cend(), + some_methods.cbegin()) +LT_END_AUTO_TEST(allow_some_methods) + +LT_BEGIN_AUTO_TEST(http_resource_suite, allow_all_methods) + simple_resource sr; + sr.allow_all(); + auto allowed_methods = sr.get_allowed_methods(); + // elements in http_resource::method_state are sorted (std::map) + vector all_methods{MHD_HTTP_METHOD_GET, MHD_HTTP_METHOD_POST, + MHD_HTTP_METHOD_PUT, MHD_HTTP_METHOD_HEAD, MHD_HTTP_METHOD_DELETE, + MHD_HTTP_METHOD_TRACE, MHD_HTTP_METHOD_CONNECT, + MHD_HTTP_METHOD_OPTIONS, MHD_HTTP_METHOD_PATCH}; + sort(all_methods.begin(), all_methods.end()); + LT_CHECK_COLLECTIONS_EQ(allowed_methods.cbegin(), allowed_methods.cend(), + all_methods.cbegin()) +LT_END_AUTO_TEST(allow_all_methods) + +LT_BEGIN_AUTO_TEST(http_resource_suite, set_allowing_nonexistent_method) + simple_resource sr; + // Try to set allowing for a method not in method_state + // This should be silently ignored (no effect) + sr.set_allowing("NONEXISTENT", true); + auto allowed_methods = sr.get_allowed_methods(); + // Verify that NONEXISTENT is not in the list + LT_CHECK_EQ(std::find(allowed_methods.begin(), allowed_methods.end(), + "NONEXISTENT") == allowed_methods.end(), true); +LT_END_AUTO_TEST(set_allowing_nonexistent_method) + +LT_BEGIN_AUTO_TEST(http_resource_suite, is_allowed_nonexistent_method) + simple_resource sr; + // Check that is_allowed returns false for unknown methods + LT_CHECK_EQ(sr.is_allowed("UNKNOWN_METHOD"), false); + LT_CHECK_EQ(sr.is_allowed("CUSTOM"), false); +LT_END_AUTO_TEST(is_allowed_nonexistent_method) + +LT_BEGIN_AUTO_TEST(http_resource_suite, set_allowing_disable) + simple_resource sr; + // By default, GET is allowed + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_GET), true); + // Disable GET + sr.set_allowing(MHD_HTTP_METHOD_GET, false); + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_GET), false); + // Re-enable GET + sr.set_allowing(MHD_HTTP_METHOD_GET, true); + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_GET), true); +LT_END_AUTO_TEST(set_allowing_disable) + +// Test resource that only overrides render() method +class render_only_resource : public http_resource { + public: + shared_ptr render(const http_request&) { + return std::make_shared("render called", 200); + } +}; + +// Test resource with no overrides at all +class empty_resource : public http_resource { + public: + // No render methods overridden - uses defaults +}; + +LT_BEGIN_AUTO_TEST(http_resource_suite, default_render_returns_empty) + empty_resource er; + // Create a minimal mock request - we need to test that render() returns empty + // Since we can't create a proper http_request without MHD internals, + // we just verify the resource exists and has correct method state + auto allowed = er.get_allowed_methods(); + LT_CHECK_EQ(allowed.size(), 9); // All 9 methods allowed by default +LT_END_AUTO_TEST(default_render_returns_empty) + +LT_BEGIN_AUTO_TEST(http_resource_suite, render_only_resource_methods_allowed) + render_only_resource ror; + // All methods should be allowed by default + LT_CHECK_EQ(ror.is_allowed(MHD_HTTP_METHOD_GET), true); + LT_CHECK_EQ(ror.is_allowed(MHD_HTTP_METHOD_POST), true); + LT_CHECK_EQ(ror.is_allowed(MHD_HTTP_METHOD_PUT), true); + LT_CHECK_EQ(ror.is_allowed(MHD_HTTP_METHOD_HEAD), true); + LT_CHECK_EQ(ror.is_allowed(MHD_HTTP_METHOD_DELETE), true); + LT_CHECK_EQ(ror.is_allowed(MHD_HTTP_METHOD_TRACE), true); + LT_CHECK_EQ(ror.is_allowed(MHD_HTTP_METHOD_CONNECT), true); + LT_CHECK_EQ(ror.is_allowed(MHD_HTTP_METHOD_OPTIONS), true); + LT_CHECK_EQ(ror.is_allowed(MHD_HTTP_METHOD_PATCH), true); +LT_END_AUTO_TEST(render_only_resource_methods_allowed) + +LT_BEGIN_AUTO_TEST(http_resource_suite, resource_init_sets_all_methods) + simple_resource sr; + // Verify all 9 HTTP methods are initialized + auto allowed = sr.get_allowed_methods(); + LT_CHECK_EQ(allowed.size(), 9); +LT_END_AUTO_TEST(resource_init_sets_all_methods) + +LT_BEGIN_AUTO_TEST(http_resource_suite, get_allowed_methods_only_returns_true) + simple_resource sr; + // Disallow some methods + sr.set_allowing(MHD_HTTP_METHOD_POST, false); + sr.set_allowing(MHD_HTTP_METHOD_PUT, false); + sr.set_allowing(MHD_HTTP_METHOD_DELETE, false); + + auto allowed = sr.get_allowed_methods(); + // Should only return 6 methods now (9 - 3) + LT_CHECK_EQ(allowed.size(), 6); + + // Verify POST, PUT, DELETE are not in the list + LT_CHECK_EQ(std::find(allowed.begin(), allowed.end(), + MHD_HTTP_METHOD_POST) == allowed.end(), true); + LT_CHECK_EQ(std::find(allowed.begin(), allowed.end(), + MHD_HTTP_METHOD_PUT) == allowed.end(), true); + LT_CHECK_EQ(std::find(allowed.begin(), allowed.end(), + MHD_HTTP_METHOD_DELETE) == allowed.end(), true); +LT_END_AUTO_TEST(get_allowed_methods_only_returns_true) + +LT_BEGIN_AUTO_TEST(http_resource_suite, is_allowed_known_methods) + simple_resource sr; + // All standard methods should be allowed by default + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_GET), true); + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_POST), true); + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_PUT), true); + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_HEAD), true); + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_DELETE), true); + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_TRACE), true); + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_CONNECT), true); + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_OPTIONS), true); + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_PATCH), true); +LT_END_AUTO_TEST(is_allowed_known_methods) + +LT_BEGIN_AUTO_TEST(http_resource_suite, allow_all_after_disallow_all) + simple_resource sr; + sr.disallow_all(); + LT_CHECK_EQ(sr.get_allowed_methods().size(), 0); + + sr.allow_all(); + LT_CHECK_EQ(sr.get_allowed_methods().size(), 9); +LT_END_AUTO_TEST(allow_all_after_disallow_all) + +LT_BEGIN_AUTO_TEST(http_resource_suite, set_allowing_multiple_times) + simple_resource sr; + // Toggle GET multiple times + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_GET), true); + sr.set_allowing(MHD_HTTP_METHOD_GET, false); + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_GET), false); + sr.set_allowing(MHD_HTTP_METHOD_GET, true); + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_GET), true); + sr.set_allowing(MHD_HTTP_METHOD_GET, false); + sr.set_allowing(MHD_HTTP_METHOD_GET, false); // Double false + LT_CHECK_EQ(sr.is_allowed(MHD_HTTP_METHOD_GET), false); +LT_END_AUTO_TEST(set_allowing_multiple_times) + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV() diff --git a/test/unit/http_response_test.cpp b/test/unit/http_response_test.cpp new file mode 100644 index 00000000..e4ca6750 --- /dev/null +++ b/test/unit/http_response_test.cpp @@ -0,0 +1,331 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include +#include + +#include "./littletest.hpp" +#include "./httpserver.hpp" + +using std::string; +using httpserver::http_response; +using httpserver::string_response; + +LT_BEGIN_SUITE(http_response_suite) + void set_up() { + } + + void tear_down() { + } +LT_END_SUITE(http_response_suite) + +LT_BEGIN_AUTO_TEST(http_response_suite, default_response_code) + http_response resp; + LT_CHECK_EQ(resp.get_response_code(), -1); +LT_END_AUTO_TEST(default_response_code) + +LT_BEGIN_AUTO_TEST(http_response_suite, custom_response_code) + http_response resp(404, "text/plain"); + LT_CHECK_EQ(resp.get_response_code(), 404); +LT_END_AUTO_TEST(custom_response_code) + +LT_BEGIN_AUTO_TEST(http_response_suite, string_response_code) + string_response resp("Not Found", 404, "text/plain"); + LT_CHECK_EQ(resp.get_response_code(), 404); +LT_END_AUTO_TEST(string_response_code) + +LT_BEGIN_AUTO_TEST(http_response_suite, header_operations) + http_response resp(200, "text/plain"); + resp.with_header("X-Custom-Header", "HeaderValue"); + LT_CHECK_EQ(resp.get_header("X-Custom-Header"), "HeaderValue"); +LT_END_AUTO_TEST(header_operations) + +LT_BEGIN_AUTO_TEST(http_response_suite, footer_operations) + http_response resp(200, "text/plain"); + resp.with_footer("X-Footer", "FooterValue"); + LT_CHECK_EQ(resp.get_footer("X-Footer"), "FooterValue"); +LT_END_AUTO_TEST(footer_operations) + +LT_BEGIN_AUTO_TEST(http_response_suite, cookie_operations) + http_response resp(200, "text/plain"); + resp.with_cookie("SessionId", "abc123"); + LT_CHECK_EQ(resp.get_cookie("SessionId"), "abc123"); +LT_END_AUTO_TEST(cookie_operations) + +LT_BEGIN_AUTO_TEST(http_response_suite, get_headers) + http_response resp(200, "text/plain"); + resp.with_header("Header1", "Value1"); + resp.with_header("Header2", "Value2"); + auto headers = resp.get_headers(); + LT_CHECK_EQ(headers.at("Header1"), "Value1"); + LT_CHECK_EQ(headers.at("Header2"), "Value2"); +LT_END_AUTO_TEST(get_headers) + +LT_BEGIN_AUTO_TEST(http_response_suite, get_footers) + http_response resp(200, "text/plain"); + resp.with_footer("Footer1", "Value1"); + resp.with_footer("Footer2", "Value2"); + auto footers = resp.get_footers(); + LT_CHECK_EQ(footers.at("Footer1"), "Value1"); + LT_CHECK_EQ(footers.at("Footer2"), "Value2"); +LT_END_AUTO_TEST(get_footers) + +LT_BEGIN_AUTO_TEST(http_response_suite, get_cookies) + http_response resp(200, "text/plain"); + resp.with_cookie("Cookie1", "Value1"); + resp.with_cookie("Cookie2", "Value2"); + auto cookies = resp.get_cookies(); + LT_CHECK_EQ(cookies.at("Cookie1"), "Value1"); + LT_CHECK_EQ(cookies.at("Cookie2"), "Value2"); +LT_END_AUTO_TEST(get_cookies) + +LT_BEGIN_AUTO_TEST(http_response_suite, shoutcast_response) + string_response resp("OK", 200, "audio/mpeg"); + int original_code = resp.get_response_code(); + resp.shoutCAST(); + // shoutCAST sets the MHD_ICY_FLAG (1 << 31) on response_code + // Verify the flag bit is set (use unsigned comparison) + LT_CHECK_EQ(static_cast(resp.get_response_code()) & 0x80000000u, 0x80000000u); + // Also verify the original code bits are preserved + LT_CHECK_EQ(resp.get_response_code() & 0x7FFFFFFF, original_code); +LT_END_AUTO_TEST(shoutcast_response) + +LT_BEGIN_AUTO_TEST(http_response_suite, string_response_default_constructor) + string_response resp; + // Default constructor should create response with default values + LT_CHECK_EQ(resp.get_response_code(), -1); +LT_END_AUTO_TEST(string_response_default_constructor) + +LT_BEGIN_AUTO_TEST(http_response_suite, string_response_content_only) + string_response resp("Hello World"); + // Should use default response code (200) and content type (text/plain) + LT_CHECK_EQ(resp.get_response_code(), 200); +LT_END_AUTO_TEST(string_response_content_only) + +LT_BEGIN_AUTO_TEST(http_response_suite, ostream_operator_empty) + // Test ostream operator with default response (no headers/footers/cookies) + http_response resp; // Default constructor - no content type header added + std::ostringstream oss; + oss << resp; + string output = oss.str(); + // With empty headers/footers/cookies, only the response code line is output + LT_CHECK_EQ(output.find("Response [response_code:-1]") != string::npos, true); + // Empty maps don't produce any output in dump_header_map + LT_CHECK_EQ(output.find("Headers [") == string::npos, true); + LT_CHECK_EQ(output.find("Footers [") == string::npos, true); + LT_CHECK_EQ(output.find("Cookies [") == string::npos, true); +LT_END_AUTO_TEST(ostream_operator_empty) + +LT_BEGIN_AUTO_TEST(http_response_suite, ostream_operator_full) + // Test ostream operator with headers, footers, and cookies + http_response resp(201, "application/json"); + resp.with_header("X-Header1", "Value1"); + resp.with_header("X-Header2", "Value2"); + resp.with_footer("X-Footer", "FooterVal"); + resp.with_cookie("SessionId", "abc123"); + resp.with_cookie("UserId", "user42"); + + std::ostringstream oss; + oss << resp; + string output = oss.str(); + + LT_CHECK_EQ(output.find("Response [response_code:201]") != string::npos, true); + LT_CHECK_EQ(output.find("X-Header1") != string::npos, true); + LT_CHECK_EQ(output.find("X-Header2") != string::npos, true); + LT_CHECK_EQ(output.find("X-Footer") != string::npos, true); + LT_CHECK_EQ(output.find("SessionId") != string::npos, true); + LT_CHECK_EQ(output.find("UserId") != string::npos, true); +LT_END_AUTO_TEST(ostream_operator_full) + +// Test response code constants +LT_BEGIN_AUTO_TEST(http_response_suite, response_code_200) + string_response resp("OK", 200, "text/plain"); + LT_CHECK_EQ(resp.get_response_code(), 200); +LT_END_AUTO_TEST(response_code_200) + +LT_BEGIN_AUTO_TEST(http_response_suite, response_code_201) + string_response resp("Created", 201, "text/plain"); + LT_CHECK_EQ(resp.get_response_code(), 201); +LT_END_AUTO_TEST(response_code_201) + +LT_BEGIN_AUTO_TEST(http_response_suite, response_code_301) + string_response resp("", 301, "text/plain"); + LT_CHECK_EQ(resp.get_response_code(), 301); +LT_END_AUTO_TEST(response_code_301) + +LT_BEGIN_AUTO_TEST(http_response_suite, response_code_400) + string_response resp("Bad Request", 400, "text/plain"); + LT_CHECK_EQ(resp.get_response_code(), 400); +LT_END_AUTO_TEST(response_code_400) + +LT_BEGIN_AUTO_TEST(http_response_suite, response_code_500) + string_response resp("Internal Server Error", 500, "text/plain"); + LT_CHECK_EQ(resp.get_response_code(), 500); +LT_END_AUTO_TEST(response_code_500) + +// Test get_header with nonexistent key +LT_BEGIN_AUTO_TEST(http_response_suite, get_header_nonexistent) + http_response resp(200, "text/plain"); + string header = resp.get_header("NonExistent"); + LT_CHECK_EQ(header, ""); +LT_END_AUTO_TEST(get_header_nonexistent) + +// Test get_footer with nonexistent key +LT_BEGIN_AUTO_TEST(http_response_suite, get_footer_nonexistent) + http_response resp(200, "text/plain"); + string footer = resp.get_footer("NonExistent"); + LT_CHECK_EQ(footer, ""); +LT_END_AUTO_TEST(get_footer_nonexistent) + +// Test get_cookie with nonexistent key +LT_BEGIN_AUTO_TEST(http_response_suite, get_cookie_nonexistent) + http_response resp(200, "text/plain"); + string cookie = resp.get_cookie("NonExistent"); + LT_CHECK_EQ(cookie, ""); +LT_END_AUTO_TEST(get_cookie_nonexistent) + +// Test multiple headers +LT_BEGIN_AUTO_TEST(http_response_suite, multiple_headers) + http_response resp(200, "text/plain"); + resp.with_header("H1", "V1"); + resp.with_header("H2", "V2"); + resp.with_header("H3", "V3"); + LT_CHECK_EQ(resp.get_header("H1"), "V1"); + LT_CHECK_EQ(resp.get_header("H2"), "V2"); + LT_CHECK_EQ(resp.get_header("H3"), "V3"); +LT_END_AUTO_TEST(multiple_headers) + +// Test multiple footers +LT_BEGIN_AUTO_TEST(http_response_suite, multiple_footers) + http_response resp(200, "text/plain"); + resp.with_footer("F1", "V1"); + resp.with_footer("F2", "V2"); + LT_CHECK_EQ(resp.get_footer("F1"), "V1"); + LT_CHECK_EQ(resp.get_footer("F2"), "V2"); +LT_END_AUTO_TEST(multiple_footers) + +// Test multiple cookies +LT_BEGIN_AUTO_TEST(http_response_suite, multiple_cookies) + http_response resp(200, "text/plain"); + resp.with_cookie("C1", "V1"); + resp.with_cookie("C2", "V2"); + LT_CHECK_EQ(resp.get_cookie("C1"), "V1"); + LT_CHECK_EQ(resp.get_cookie("C2"), "V2"); +LT_END_AUTO_TEST(multiple_cookies) + +// Test overwriting header +LT_BEGIN_AUTO_TEST(http_response_suite, overwrite_header) + http_response resp(200, "text/plain"); + resp.with_header("Key", "Value1"); + LT_CHECK_EQ(resp.get_header("Key"), "Value1"); + resp.with_header("Key", "Value2"); + LT_CHECK_EQ(resp.get_header("Key"), "Value2"); +LT_END_AUTO_TEST(overwrite_header) + +// Test overwriting cookie +LT_BEGIN_AUTO_TEST(http_response_suite, overwrite_cookie) + http_response resp(200, "text/plain"); + resp.with_cookie("Cookie", "OldValue"); + LT_CHECK_EQ(resp.get_cookie("Cookie"), "OldValue"); + resp.with_cookie("Cookie", "NewValue"); + LT_CHECK_EQ(resp.get_cookie("Cookie"), "NewValue"); +LT_END_AUTO_TEST(overwrite_cookie) + +// Test empty headers map (using default constructor to get truly empty headers) +LT_BEGIN_AUTO_TEST(http_response_suite, empty_headers_map) + http_response resp; // Default constructor - no content type header added + auto headers = resp.get_headers(); + LT_CHECK_EQ(headers.empty(), true); +LT_END_AUTO_TEST(empty_headers_map) + +// Test empty footers map +LT_BEGIN_AUTO_TEST(http_response_suite, empty_footers_map) + http_response resp(200, "text/plain"); + auto footers = resp.get_footers(); + LT_CHECK_EQ(footers.empty(), true); +LT_END_AUTO_TEST(empty_footers_map) + +// Test empty cookies map +LT_BEGIN_AUTO_TEST(http_response_suite, empty_cookies_map) + http_response resp(200, "text/plain"); + auto cookies = resp.get_cookies(); + LT_CHECK_EQ(cookies.empty(), true); +LT_END_AUTO_TEST(empty_cookies_map) + +// Test ostream with only headers +LT_BEGIN_AUTO_TEST(http_response_suite, ostream_operator_headers_only) + http_response resp(200, "text/plain"); + resp.with_header("X-Custom", "Value"); + std::ostringstream oss; + oss << resp; + string output = oss.str(); + LT_CHECK_EQ(output.find("X-Custom") != string::npos, true); + LT_CHECK_EQ(output.find("200") != string::npos, true); +LT_END_AUTO_TEST(ostream_operator_headers_only) + +// Test ostream with only footers +LT_BEGIN_AUTO_TEST(http_response_suite, ostream_operator_footers_only) + http_response resp(200, "text/plain"); + resp.with_footer("X-Footer", "FootVal"); + std::ostringstream oss; + oss << resp; + string output = oss.str(); + LT_CHECK_EQ(output.find("X-Footer") != string::npos, true); +LT_END_AUTO_TEST(ostream_operator_footers_only) + +// Test ostream with only cookies +LT_BEGIN_AUTO_TEST(http_response_suite, ostream_operator_cookies_only) + http_response resp(200, "text/plain"); + resp.with_cookie("Session", "abc123"); + std::ostringstream oss; + oss << resp; + string output = oss.str(); + LT_CHECK_EQ(output.find("Session") != string::npos, true); +LT_END_AUTO_TEST(ostream_operator_cookies_only) + +// Test string_response with all parameters +LT_BEGIN_AUTO_TEST(http_response_suite, string_response_full_params) + string_response resp("Body content", 201, "application/json"); + LT_CHECK_EQ(resp.get_response_code(), 201); +LT_END_AUTO_TEST(string_response_full_params) + +// Test http_response with content_type parameter +LT_BEGIN_AUTO_TEST(http_response_suite, http_response_content_type) + http_response resp(200, "application/json"); + LT_CHECK_EQ(resp.get_response_code(), 200); +LT_END_AUTO_TEST(http_response_content_type) + +// Test special characters in header values +LT_BEGIN_AUTO_TEST(http_response_suite, header_special_characters) + http_response resp(200, "text/plain"); + resp.with_header("Content-Disposition", "attachment; filename=\"file.txt\""); + LT_CHECK_EQ(resp.get_header("Content-Disposition"), "attachment; filename=\"file.txt\""); +LT_END_AUTO_TEST(header_special_characters) + +// Test special characters in cookie values +LT_BEGIN_AUTO_TEST(http_response_suite, cookie_special_characters) + http_response resp(200, "text/plain"); + resp.with_cookie("Data", "value=with=equals"); + LT_CHECK_EQ(resp.get_cookie("Data"), "value=with=equals"); +LT_END_AUTO_TEST(cookie_special_characters) + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV() diff --git a/test/unit/http_utils_test.cpp b/test/unit/http_utils_test.cpp index e721f4bd..24b88c54 100644 --- a/test/unit/http_utils_test.cpp +++ b/test/unit/http_utils_test.cpp @@ -1,6 +1,6 @@ /* This file is part of libhttpserver - Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino + Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -18,7 +18,9 @@ USA */ -#if defined(__MINGW32__) || defined(__CYGWIN32__) +#include "httpserver/http_utils.hpp" + +#if defined(_WIN32) && !defined(__CYGWIN__) #define _WINDOWS #undef _WIN32_WINNT #define _WIN32_WINNT 0x600 @@ -28,58 +30,200 @@ #include #endif -#include "littletest.hpp" -#include "http_utils.hpp" - +#include #include +#include +#include +#include +#include + +#include "./littletest.hpp" + +using std::string; +using std::vector; -using namespace httpserver; -using namespace std; +#define STR2(p) #p +#define STR(p) STR2(p) + +#ifdef HTTPSERVER_DATA_ROOT +#define ROOT STR(HTTPSERVER_DATA_ROOT) +#else +#define ROOT "." +#endif // HTTPSERVER_DATA_ROOT LT_BEGIN_SUITE(http_utils_suite) - void set_up() - { + void set_up() { } - void tear_down() - { + void tear_down() { } LT_END_SUITE(http_utils_suite) LT_BEGIN_AUTO_TEST(http_utils_suite, unescape) - char* string_with_plus = (char*) malloc(6 * sizeof(char)); - sprintf(string_with_plus, "%s", "A%20B"); - int expected_size = http::http_unescape(string_with_plus); + std::string string_with_plus = "A%20B"; + int expected_size = httpserver::http::http_unescape(&string_with_plus); + + std::string expected = "A B"; - char* expected = (char*) malloc(4 * sizeof(char)); - sprintf(expected, "%s", "A B"); + std::cout << "|||||" << string_with_plus << "||||" << std::endl; + std::cout << expected << std::endl; - LT_CHECK_EQ(string(string_with_plus), string(expected)); + LT_CHECK_EQ(string_with_plus, expected); LT_CHECK_EQ(expected_size, 3); LT_END_AUTO_TEST(unescape) +LT_BEGIN_AUTO_TEST(http_utils_suite, unescape_plus) + std::string string_with_plus = "A+B"; + int expected_size = httpserver::http::http_unescape(&string_with_plus); + + std::string expected = "A B"; + + LT_CHECK_EQ(string_with_plus, expected); + LT_CHECK_EQ(expected_size, 3); +LT_END_AUTO_TEST(unescape_plus) + +LT_BEGIN_AUTO_TEST(http_utils_suite, unescape_partial_marker) + std::string string_with_marker = "A+B%0"; + int expected_size = httpserver::http::http_unescape(&string_with_marker); + + std::string expected = "A B%0"; + + LT_CHECK_EQ(string_with_marker, expected); + LT_CHECK_EQ(expected_size, 5); +LT_END_AUTO_TEST(unescape_partial_marker) + +LT_BEGIN_AUTO_TEST(http_utils_suite, unescape_lowercase_hex) + // Test lowercase hex digits (%2a -> '*') + std::string str = "test%2avalue"; + int expected_size = httpserver::http::http_unescape(&str); + + LT_CHECK_EQ(str, "test*value"); + LT_CHECK_EQ(expected_size, 10); +LT_END_AUTO_TEST(unescape_lowercase_hex) + +LT_BEGIN_AUTO_TEST(http_utils_suite, unescape_uppercase_hex) + // Test uppercase hex digits (%2A -> '*') + std::string str = "test%2Avalue"; + int expected_size = httpserver::http::http_unescape(&str); + + LT_CHECK_EQ(str, "test*value"); + LT_CHECK_EQ(expected_size, 10); +LT_END_AUTO_TEST(unescape_uppercase_hex) + +LT_BEGIN_AUTO_TEST(http_utils_suite, unescape_invalid_hex) + // Test invalid hex after % - should be left as-is + std::string str = "test%ZZvalue"; + int expected_size = httpserver::http::http_unescape(&str); + + LT_CHECK_EQ(str, "test%ZZvalue"); + LT_CHECK_EQ(expected_size, 12); +LT_END_AUTO_TEST(unescape_invalid_hex) + +LT_BEGIN_AUTO_TEST(http_utils_suite, unescape_percent_at_end) + // Test % at the very end of string + std::string str = "test%"; + int expected_size = httpserver::http::http_unescape(&str); + + LT_CHECK_EQ(str, "test%"); + LT_CHECK_EQ(expected_size, 5); +LT_END_AUTO_TEST(unescape_percent_at_end) + +LT_BEGIN_AUTO_TEST(http_utils_suite, unescape_percent_with_one_char) + // Test % followed by only one character + std::string str = "test%a"; + int expected_size = httpserver::http::http_unescape(&str); + + LT_CHECK_EQ(str, "test%a"); + LT_CHECK_EQ(expected_size, 6); +LT_END_AUTO_TEST(unescape_percent_with_one_char) + +LT_BEGIN_AUTO_TEST(http_utils_suite, unescape_mixed_case_hex) + // Test mixed case hex digits (%aB -> char) + std::string str = "test%aBvalue"; + int expected_size = httpserver::http::http_unescape(&str); + + // 0xAB = 171 which is a valid byte + LT_CHECK_EQ(expected_size, 10); +LT_END_AUTO_TEST(unescape_mixed_case_hex) + +LT_BEGIN_AUTO_TEST(http_utils_suite, unescape_multiple_percent) + // Test multiple percent-encoded values + std::string str = "%20%2B%20"; // space + plus + space + int expected_size = httpserver::http::http_unescape(&str); + + LT_CHECK_EQ(str, " + "); + LT_CHECK_EQ(expected_size, 3); +LT_END_AUTO_TEST(unescape_multiple_percent) + +LT_BEGIN_AUTO_TEST(http_utils_suite, tokenize_url) + string value = "test/this/url/here"; + string expected_arr[] = { "test", "this", "url", "here" }; + vector expected(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + vector actual = httpserver::http::http_utils::tokenize_url(value, '/'); + + LT_CHECK_COLLECTIONS_EQ(expected.begin(), expected.end(), actual.begin()); +LT_END_AUTO_TEST(tokenize_url) + +LT_BEGIN_AUTO_TEST(http_utils_suite, tokenize_url_multiple_spaces) + string value = "test//this//url//here"; + string expected_arr[] = { "test", "this", "url", "here" }; + vector expected(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + vector actual = httpserver::http::http_utils::tokenize_url(value, '/'); + + LT_CHECK_COLLECTIONS_EQ(expected.begin(), expected.end(), actual.begin()); +LT_END_AUTO_TEST(tokenize_url_multiple_spaces) + +LT_BEGIN_AUTO_TEST(http_utils_suite, tokenize_url_end_slash) + string value = "test/this/url/here/"; + string expected_arr[] = { "test", "this", "url", "here" }; + vector expected(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + vector actual = httpserver::http::http_utils::tokenize_url(value, '/'); + + LT_CHECK_COLLECTIONS_EQ(expected.begin(), expected.end(), actual.begin()); +LT_END_AUTO_TEST(tokenize_url_end_slash) + LT_BEGIN_AUTO_TEST(http_utils_suite, standardize_url) string url = "/", result; - http::http_utils::standardize_url(url, result); + result = httpserver::http::http_utils::standardize_url(url); LT_CHECK_EQ(result, "/"); url = "/abc/", result = ""; - http::http_utils::standardize_url(url, result); + result = httpserver::http::http_utils::standardize_url(url); LT_CHECK_EQ(result, "/abc"); url = "/abc", result = ""; - http::http_utils::standardize_url(url, result); + result = httpserver::http::http_utils::standardize_url(url); LT_CHECK_EQ(result, "/abc"); url = "/abc/pqr/", result = ""; - http::http_utils::standardize_url(url, result); + result = httpserver::http::http_utils::standardize_url(url); LT_CHECK_EQ(result, "/abc/pqr"); url = "/abc/pqr", result = ""; - http::http_utils::standardize_url(url, result); + result = httpserver::http::http_utils::standardize_url(url); + LT_CHECK_EQ(result, "/abc/pqr"); + + url = "/abc//pqr", result = ""; + result = httpserver::http::http_utils::standardize_url(url); LT_CHECK_EQ(result, "/abc/pqr"); LT_END_AUTO_TEST(standardize_url) +LT_BEGIN_AUTO_TEST(http_utils_suite, generate_random_upload_filename) + struct stat sb; + string directory = ".", filename = ""; + string expected_output = directory + httpserver::http::http_utils::path_separator + httpserver::http::http_utils::upload_filename_template; + try { + filename = httpserver::http::http_utils::generate_random_upload_filename(directory); + } catch(const httpserver::http::generateFilenameException& e) { + LT_FAIL(e.what()); + } + LT_CHECK_EQ(stat(filename.c_str(), &sb), 0); + // unlink the file again, to not mess up the test directory with files + unlink(filename.c_str()); + // omit the last 6 signs in the check, as the "XXXXXX" is substituted to be random and unique + LT_CHECK_EQ(filename.substr(0, filename.size() - 6), expected_output.substr(0, expected_output.size() - 6)); +LT_END_AUTO_TEST(generate_random_upload_filename) + LT_BEGIN_AUTO_TEST(http_utils_suite, ip_to_str) struct sockaddr_in ip4addr; @@ -87,12 +231,718 @@ LT_BEGIN_AUTO_TEST(http_utils_suite, ip_to_str) ip4addr.sin_port = htons(3490); ip4addr.sin_addr.s_addr = inet_addr("127.0.0.1"); - string result = ""; - http::get_ip_str((struct sockaddr *) &ip4addr, result); + string result = httpserver::http::get_ip_str((struct sockaddr*) &ip4addr); + unsigned short port = httpserver::http::get_port((struct sockaddr*) &ip4addr); LT_CHECK_EQ(result, "127.0.0.1"); + LT_CHECK_EQ(port, htons(3490)); LT_END_AUTO_TEST(ip_to_str) +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_to_str6) + struct sockaddr_in6 ip6addr; + + ip6addr.sin6_family = AF_INET6; + ip6addr.sin6_port = htons(3490); + inet_pton(AF_INET6, "2001:db8:8714:3a90::12", &(ip6addr.sin6_addr)); + + string result = httpserver::http::get_ip_str((struct sockaddr *) &ip6addr); + unsigned short port = httpserver::http::get_port((struct sockaddr*) &ip6addr); + + LT_CHECK_EQ(result, "2001:db8:8714:3a90::12"); + LT_CHECK_EQ(port, htons(3490)); +LT_END_AUTO_TEST(ip_to_str6) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_to_str_invalid_family) + struct sockaddr_in ip4addr; + + ip4addr.sin_family = 55; + ip4addr.sin_port = htons(3490); + ip4addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + + LT_CHECK_THROW(httpserver::http::get_ip_str((struct sockaddr*) &ip4addr)); +LT_END_AUTO_TEST(ip_to_str_invalid_family) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_to_str_null) + LT_CHECK_THROW(httpserver::http::get_ip_str((struct sockaddr*) nullptr)); +LT_END_AUTO_TEST(ip_to_str_null) + +LT_BEGIN_AUTO_TEST(http_utils_suite, get_port_invalid_family) + struct sockaddr_in ip4addr; + + ip4addr.sin_family = 55; + ip4addr.sin_port = htons(3490); + ip4addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + + LT_CHECK_THROW(httpserver::http::get_port((struct sockaddr*) &ip4addr)); +LT_END_AUTO_TEST(get_port_invalid_family) + +LT_BEGIN_AUTO_TEST(http_utils_suite, get_port_null) + LT_CHECK_THROW(httpserver::http::get_port((struct sockaddr*) nullptr)); +LT_END_AUTO_TEST(get_port_null) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation4_str) + httpserver::http::ip_representation test_ip("192.168.5.5"); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV4); + + for (int i = 0; i < 12; i++) { + LT_CHECK_EQ(test_ip.pieces[i], 0); + } + + LT_CHECK_EQ(test_ip.pieces[12], 192); + LT_CHECK_EQ(test_ip.pieces[13], 168); + LT_CHECK_EQ(test_ip.pieces[14], 5); + LT_CHECK_EQ(test_ip.pieces[15], 5); + + LT_CHECK_EQ(test_ip.mask, 0xFFFF); +LT_END_AUTO_TEST(ip_representation4_str) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation4_str_mask) + httpserver::http::ip_representation test_ip("192.168.*.*"); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV4); + + for (int i = 0; i < 12; i++) { + LT_CHECK_EQ(test_ip.pieces[i], 0); + } + + LT_CHECK_EQ(test_ip.pieces[12], 192); + LT_CHECK_EQ(test_ip.pieces[13], 168); + LT_CHECK_EQ(test_ip.pieces[14], 0); + LT_CHECK_EQ(test_ip.pieces[15], 0); + + LT_CHECK_EQ(test_ip.mask, 0x3FFF); +LT_END_AUTO_TEST(ip_representation4_str_mask) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation4_str_invalid) + LT_CHECK_THROW(httpserver::http::ip_representation("192.168.5.5.5")); +LT_END_AUTO_TEST(ip_representation4_str_invalid) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation4_str_beyond255) + LT_CHECK_THROW(httpserver::http::ip_representation("192.168.256.5")); +LT_END_AUTO_TEST(ip_representation4_str_beyond255) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str) + httpserver::http::ip_representation test_ip("2001:db8:8714:3a90::12"); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV6); + + LT_CHECK_EQ(test_ip.pieces[0], 32); + LT_CHECK_EQ(test_ip.pieces[1], 1); + LT_CHECK_EQ(test_ip.pieces[2], 13); + LT_CHECK_EQ(test_ip.pieces[3], 184); + LT_CHECK_EQ(test_ip.pieces[4], 135); + LT_CHECK_EQ(test_ip.pieces[5], 20); + LT_CHECK_EQ(test_ip.pieces[6], 58); + LT_CHECK_EQ(test_ip.pieces[7], 144); + LT_CHECK_EQ(test_ip.pieces[8], 0); + LT_CHECK_EQ(test_ip.pieces[9], 0); + LT_CHECK_EQ(test_ip.pieces[10], 0); + LT_CHECK_EQ(test_ip.pieces[11], 0); + LT_CHECK_EQ(test_ip.pieces[12], 0); + LT_CHECK_EQ(test_ip.pieces[13], 0); + LT_CHECK_EQ(test_ip.pieces[14], 0); + LT_CHECK_EQ(test_ip.pieces[15], 18); + + LT_CHECK_EQ(test_ip.mask, 0xFFFF); +LT_END_AUTO_TEST(ip_representation6_str) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_mask) + httpserver::http::ip_representation test_ip("2001:db8:8714:3a90:*:*"); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV6); + + LT_CHECK_EQ(test_ip.pieces[0], 32); + LT_CHECK_EQ(test_ip.pieces[1], 1); + LT_CHECK_EQ(test_ip.pieces[2], 13); + LT_CHECK_EQ(test_ip.pieces[3], 184); + LT_CHECK_EQ(test_ip.pieces[4], 135); + LT_CHECK_EQ(test_ip.pieces[5], 20); + LT_CHECK_EQ(test_ip.pieces[6], 58); + LT_CHECK_EQ(test_ip.pieces[7], 144); + LT_CHECK_EQ(test_ip.pieces[8], 0); + LT_CHECK_EQ(test_ip.pieces[9], 0); + LT_CHECK_EQ(test_ip.pieces[10], 0); + LT_CHECK_EQ(test_ip.pieces[11], 0); + LT_CHECK_EQ(test_ip.pieces[12], 0); + LT_CHECK_EQ(test_ip.pieces[13], 0); + LT_CHECK_EQ(test_ip.pieces[14], 0); + LT_CHECK_EQ(test_ip.pieces[15], 0); + + LT_CHECK_EQ(test_ip.mask, 0xF0FF); +LT_END_AUTO_TEST(ip_representation6_str_mask) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_nested) + httpserver::http::ip_representation test_ip("::ffff:192.0.2.128"); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV6); + + LT_CHECK_EQ(test_ip.pieces[0], 0); + LT_CHECK_EQ(test_ip.pieces[1], 0); + LT_CHECK_EQ(test_ip.pieces[2], 0); + LT_CHECK_EQ(test_ip.pieces[3], 0); + LT_CHECK_EQ(test_ip.pieces[4], 0); + LT_CHECK_EQ(test_ip.pieces[5], 0); + LT_CHECK_EQ(test_ip.pieces[6], 0); + LT_CHECK_EQ(test_ip.pieces[7], 0); + LT_CHECK_EQ(test_ip.pieces[8], 0); + LT_CHECK_EQ(test_ip.pieces[9], 0); + LT_CHECK_EQ(test_ip.pieces[10], 255); + LT_CHECK_EQ(test_ip.pieces[11], 255); + LT_CHECK_EQ(test_ip.pieces[12], 192); + LT_CHECK_EQ(test_ip.pieces[13], 0); + LT_CHECK_EQ(test_ip.pieces[14], 2); + LT_CHECK_EQ(test_ip.pieces[15], 128); + + LT_CHECK_EQ(test_ip.mask, 0xFFFF); +LT_END_AUTO_TEST(ip_representation6_str_nested) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_nested_deprecated) + LT_CHECK_NOTHROW(httpserver::http::ip_representation("::192.0.2.128")); + httpserver::http::ip_representation test_ip("::192.0.2.128"); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV6); + + LT_CHECK_EQ(test_ip.pieces[0], 0); + LT_CHECK_EQ(test_ip.pieces[1], 0); + LT_CHECK_EQ(test_ip.pieces[2], 0); + LT_CHECK_EQ(test_ip.pieces[3], 0); + LT_CHECK_EQ(test_ip.pieces[4], 0); + LT_CHECK_EQ(test_ip.pieces[5], 0); + LT_CHECK_EQ(test_ip.pieces[6], 0); + LT_CHECK_EQ(test_ip.pieces[7], 0); + LT_CHECK_EQ(test_ip.pieces[8], 0); + LT_CHECK_EQ(test_ip.pieces[9], 0); + LT_CHECK_EQ(test_ip.pieces[10], 0); + LT_CHECK_EQ(test_ip.pieces[11], 0); + LT_CHECK_EQ(test_ip.pieces[12], 192); + LT_CHECK_EQ(test_ip.pieces[13], 0); + LT_CHECK_EQ(test_ip.pieces[14], 2); + LT_CHECK_EQ(test_ip.pieces[15], 128); + + LT_CHECK_EQ(test_ip.mask, 0xFFFF); +LT_END_AUTO_TEST(ip_representation6_str_nested_deprecated) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_ipv4_mask) + httpserver::http::ip_representation test_ip("::ffff:192.0.*.*"); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV6); + + LT_CHECK_EQ(test_ip.pieces[0], 0); + LT_CHECK_EQ(test_ip.pieces[1], 0); + LT_CHECK_EQ(test_ip.pieces[2], 0); + LT_CHECK_EQ(test_ip.pieces[3], 0); + LT_CHECK_EQ(test_ip.pieces[4], 0); + LT_CHECK_EQ(test_ip.pieces[5], 0); + LT_CHECK_EQ(test_ip.pieces[6], 0); + LT_CHECK_EQ(test_ip.pieces[7], 0); + LT_CHECK_EQ(test_ip.pieces[8], 0); + LT_CHECK_EQ(test_ip.pieces[9], 0); + LT_CHECK_EQ(test_ip.pieces[10], 255); + LT_CHECK_EQ(test_ip.pieces[11], 255); + LT_CHECK_EQ(test_ip.pieces[12], 192); + LT_CHECK_EQ(test_ip.pieces[13], 0); + LT_CHECK_EQ(test_ip.pieces[14], 0); + LT_CHECK_EQ(test_ip.pieces[15], 0); + + LT_CHECK_EQ(test_ip.mask, 0x3FFF); +LT_END_AUTO_TEST(ip_representation6_str_ipv4_mask) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_clustered_middle) + httpserver::http::ip_representation test_ip("2001:db8::ff00:42:8329"); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV6); + + LT_CHECK_EQ(test_ip.pieces[0], 32); + LT_CHECK_EQ(test_ip.pieces[1], 1); + LT_CHECK_EQ(test_ip.pieces[2], 13); + LT_CHECK_EQ(test_ip.pieces[3], 184); + LT_CHECK_EQ(test_ip.pieces[4], 0); + LT_CHECK_EQ(test_ip.pieces[5], 0); + LT_CHECK_EQ(test_ip.pieces[6], 0); + LT_CHECK_EQ(test_ip.pieces[7], 0); + LT_CHECK_EQ(test_ip.pieces[8], 0); + LT_CHECK_EQ(test_ip.pieces[9], 0); + LT_CHECK_EQ(test_ip.pieces[10], 255); + LT_CHECK_EQ(test_ip.pieces[11], 0); + LT_CHECK_EQ(test_ip.pieces[12], 0); + LT_CHECK_EQ(test_ip.pieces[13], 66); + LT_CHECK_EQ(test_ip.pieces[14], 131); + LT_CHECK_EQ(test_ip.pieces[15], 41); + + LT_CHECK_EQ(test_ip.mask, 0xFFFF); +LT_END_AUTO_TEST(ip_representation6_str_clustered_middle) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_loopback) + httpserver::http::ip_representation test_ip("::1"); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV6); + + LT_CHECK_EQ(test_ip.pieces[0], 0); + LT_CHECK_EQ(test_ip.pieces[1], 0); + LT_CHECK_EQ(test_ip.pieces[2], 0); + LT_CHECK_EQ(test_ip.pieces[3], 0); + LT_CHECK_EQ(test_ip.pieces[4], 0); + LT_CHECK_EQ(test_ip.pieces[5], 0); + LT_CHECK_EQ(test_ip.pieces[6], 0); + LT_CHECK_EQ(test_ip.pieces[7], 0); + LT_CHECK_EQ(test_ip.pieces[8], 0); + LT_CHECK_EQ(test_ip.pieces[9], 0); + LT_CHECK_EQ(test_ip.pieces[10], 0); + LT_CHECK_EQ(test_ip.pieces[11], 0); + LT_CHECK_EQ(test_ip.pieces[12], 0); + LT_CHECK_EQ(test_ip.pieces[13], 0); + LT_CHECK_EQ(test_ip.pieces[14], 0); + LT_CHECK_EQ(test_ip.pieces[15], 1); + + LT_CHECK_EQ(test_ip.mask, 0xFFFF); +LT_END_AUTO_TEST(ip_representation6_str_loopback) + +// Test IPv6 with exactly 8 parts (full address without ::) +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_full_8_parts) + httpserver::http::ip_representation test_ip("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV6); + + LT_CHECK_EQ(test_ip.pieces[0], 32); + LT_CHECK_EQ(test_ip.pieces[1], 1); + LT_CHECK_EQ(test_ip.pieces[2], 13); + LT_CHECK_EQ(test_ip.pieces[3], 184); + LT_CHECK_EQ(test_ip.pieces[4], 133); + LT_CHECK_EQ(test_ip.pieces[5], 163); + // pieces 6-9 are 0 + LT_CHECK_EQ(test_ip.pieces[10], 138); + LT_CHECK_EQ(test_ip.pieces[11], 46); + LT_CHECK_EQ(test_ip.pieces[12], 3); + LT_CHECK_EQ(test_ip.pieces[13], 112); + LT_CHECK_EQ(test_ip.pieces[14], 115); + LT_CHECK_EQ(test_ip.pieces[15], 52); + + LT_CHECK_EQ(test_ip.mask, 0xFFFF); +LT_END_AUTO_TEST(ip_representation6_full_8_parts) + +// Test IPv6 with leading :: +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_leading_double_colon) + httpserver::http::ip_representation test_ip("::ffff:1234:5678"); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV6); + + // First 10 bytes should be 0 + for (int i = 0; i < 10; i++) { + LT_CHECK_EQ(test_ip.pieces[i], 0); + } + + LT_CHECK_EQ(test_ip.pieces[10], 255); + LT_CHECK_EQ(test_ip.pieces[11], 255); + LT_CHECK_EQ(test_ip.pieces[12], 18); + LT_CHECK_EQ(test_ip.pieces[13], 52); + LT_CHECK_EQ(test_ip.pieces[14], 86); + LT_CHECK_EQ(test_ip.pieces[15], 120); + + LT_CHECK_EQ(test_ip.mask, 0xFFFF); +LT_END_AUTO_TEST(ip_representation6_leading_double_colon) + +// Test IPv6 with trailing :: +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_trailing_double_colon) + httpserver::http::ip_representation test_ip("2001:db8::"); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV6); + + LT_CHECK_EQ(test_ip.pieces[0], 32); + LT_CHECK_EQ(test_ip.pieces[1], 1); + LT_CHECK_EQ(test_ip.pieces[2], 13); + LT_CHECK_EQ(test_ip.pieces[3], 184); + + // Rest should be 0 + for (int i = 4; i < 16; i++) { + LT_CHECK_EQ(test_ip.pieces[i], 0); + } + + LT_CHECK_EQ(test_ip.mask, 0xFFFF); +LT_END_AUTO_TEST(ip_representation6_trailing_double_colon) + +// Test all zeros IPv6 +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_all_zeros) + httpserver::http::ip_representation test_ip("::"); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV6); + + for (int i = 0; i < 16; i++) { + LT_CHECK_EQ(test_ip.pieces[i], 0); + } + + LT_CHECK_EQ(test_ip.mask, 0xFFFF); +LT_END_AUTO_TEST(ip_representation6_all_zeros) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation_weight) + LT_CHECK_EQ(httpserver::http::ip_representation("::1").weight(), 16); + LT_CHECK_EQ(httpserver::http::ip_representation("192.168.0.1").weight(), 16); + LT_CHECK_EQ(httpserver::http::ip_representation("192.168.*.*").weight(), 14); + LT_CHECK_EQ(httpserver::http::ip_representation("::ffff:192.0.*.*").weight(), 14); + LT_CHECK_EQ(httpserver::http::ip_representation("2001:db8:8714:3a90:*:*").weight(), 12); + LT_CHECK_EQ(httpserver::http::ip_representation("2001:db8:8714:3a90:8714:2001:db8:3a90").weight(), 16); + LT_CHECK_EQ(httpserver::http::ip_representation("2001:db8:8714:3a90:8714:2001:*:*").weight(), 12); + LT_CHECK_EQ(httpserver::http::ip_representation("*:*:*:*:*:*:*:*").weight(), 0); +LT_END_AUTO_TEST(ip_representation_weight) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_invalid) + LT_CHECK_THROW(httpserver::http::ip_representation("2001:db8:8714:3a90::12:4:4:4")); +LT_END_AUTO_TEST(ip_representation6_str_invalid) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_block_too_long) + LT_CHECK_THROW(httpserver::http::ip_representation("2001:db8:87214:3a90::12:4:4")); +LT_END_AUTO_TEST(ip_representation6_str_block_too_long) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_invalid_multiple_clusters) + LT_CHECK_THROW(httpserver::http::ip_representation("2001::3a90::12:4:4")); +LT_END_AUTO_TEST(ip_representation6_str_invalid_multiple_clusters) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_invalid_too_long_before_nested) + LT_CHECK_THROW(httpserver::http::ip_representation("2001:db8:8714:3a90:13:12:13:192.0.2.128")); +LT_END_AUTO_TEST(ip_representation6_str_invalid_too_long_before_nested) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_invalid_nested_beyond255) + LT_CHECK_THROW(httpserver::http::ip_representation("::ffff:192.0.256.128")); +LT_END_AUTO_TEST(ip_representation6_str_invalid_nested_beyond255) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_invalid_nested_more_than_4_parts) + LT_CHECK_THROW(httpserver::http::ip_representation("::ffff:192.0.5.128.128")); +LT_END_AUTO_TEST(ip_representation6_str_invalid_nested_more_than_4_parts) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_invalid_nested_not_at_end) + LT_CHECK_THROW(httpserver::http::ip_representation("::ffff:192.0.256.128:ffff")); +LT_END_AUTO_TEST(ip_representation6_str_invalid_nested_not_at_end) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_invalid_nested_starting_non_zero) + LT_CHECK_THROW(httpserver::http::ip_representation("0:0:1::ffff:192.0.5.128")); +LT_END_AUTO_TEST(ip_representation6_str_invalid_nested_starting_non_zero) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_str_invalid_nested_starting_wrong_prefix) + LT_CHECK_THROW(httpserver::http::ip_representation("::ffcc:192.0.5.128")); + LT_CHECK_THROW(httpserver::http::ip_representation("::ccff:192.0.5.128")); +LT_END_AUTO_TEST(ip_representation6_str_invalid_nested_starting_wrong_prefix) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation4_sockaddr) + struct sockaddr_in ip4addr; + + ip4addr.sin_family = AF_INET; + ip4addr.sin_port = htons(3490); + ip4addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + + httpserver::http::ip_representation test_ip(reinterpret_cast(&ip4addr)); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV4); + + for (int i = 0; i < 12; i++) { + LT_CHECK_EQ(test_ip.pieces[i], 0); + } + + LT_CHECK_EQ(test_ip.pieces[12], 127); + LT_CHECK_EQ(test_ip.pieces[13], 0); + LT_CHECK_EQ(test_ip.pieces[14], 0); + LT_CHECK_EQ(test_ip.pieces[15], 1); + + LT_CHECK_EQ(test_ip.mask, 0xFFFF); +LT_END_AUTO_TEST(ip_representation4_sockaddr) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_sockaddr) + struct sockaddr_in6 ip6addr; + + ip6addr.sin6_family = AF_INET6; + ip6addr.sin6_port = htons(3490); + inet_pton(AF_INET6, "2001:db8:8714:3a90::12", &(ip6addr.sin6_addr)); + + httpserver::http::ip_representation test_ip(reinterpret_cast(&ip6addr)); + + LT_CHECK_EQ(test_ip.ip_version, httpserver::http::http_utils::IPV6); + + LT_CHECK_EQ(test_ip.pieces[0], 32); + LT_CHECK_EQ(test_ip.pieces[1], 1); + LT_CHECK_EQ(test_ip.pieces[2], 13); + LT_CHECK_EQ(test_ip.pieces[3], 184); + LT_CHECK_EQ(test_ip.pieces[4], 135); + LT_CHECK_EQ(test_ip.pieces[5], 20); + LT_CHECK_EQ(test_ip.pieces[6], 58); + LT_CHECK_EQ(test_ip.pieces[7], 144); + LT_CHECK_EQ(test_ip.pieces[8], 0); + LT_CHECK_EQ(test_ip.pieces[9], 0); + LT_CHECK_EQ(test_ip.pieces[10], 0); + LT_CHECK_EQ(test_ip.pieces[11], 0); + LT_CHECK_EQ(test_ip.pieces[12], 0); + LT_CHECK_EQ(test_ip.pieces[13], 0); + LT_CHECK_EQ(test_ip.pieces[14], 0); + LT_CHECK_EQ(test_ip.pieces[15], 18); + + LT_CHECK_EQ(test_ip.mask, 0xFFFF); +LT_END_AUTO_TEST(ip_representation6_sockaddr) + +LT_BEGIN_AUTO_TEST(http_utils_suite, load_file) + LT_CHECK_EQ(httpserver::http::load_file(ROOT "/test_content"), "test content of file\n"); +LT_END_AUTO_TEST(load_file) + +LT_BEGIN_AUTO_TEST(http_utils_suite, load_file_invalid) + LT_CHECK_THROW(httpserver::http::load_file("test_content_invalid")); +LT_END_AUTO_TEST(load_file_invalid) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation_less_than) + LT_CHECK_EQ(httpserver::http::ip_representation("127.0.0.1") < httpserver::http::ip_representation("127.0.0.2"), true); + LT_CHECK_EQ(httpserver::http::ip_representation("128.0.0.1") < httpserver::http::ip_representation("127.0.0.2"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("127.0.0.2") < httpserver::http::ip_representation("127.0.0.1"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("127.0.0.1") < httpserver::http::ip_representation("127.0.0.1"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("127.0.0.1") < httpserver::http::ip_representation("127.0.0.1"), false); + + LT_CHECK_EQ(httpserver::http::ip_representation("2001:db8::ff00:42:8329") < httpserver::http::ip_representation("2001:db8::ff00:42:8329"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("2001:db8::ff00:42:8330") < httpserver::http::ip_representation("2001:db8::ff00:42:8329"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("2001:db8::ff00:42:8329") < httpserver::http::ip_representation("2001:db8::ff00:42:8330"), true); + LT_CHECK_EQ(httpserver::http::ip_representation("2002:db8::ff00:42:8329") < httpserver::http::ip_representation("2001:db8::ff00:42:8330"), false); + + LT_CHECK_EQ(httpserver::http::ip_representation("::192.0.2.128") < httpserver::http::ip_representation("::192.0.2.128"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("::192.0.2.129") < httpserver::http::ip_representation("::192.0.2.128"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("::192.0.2.128") < httpserver::http::ip_representation("::192.0.2.129"), true); + + LT_CHECK_EQ(httpserver::http::ip_representation("::ffff:192.0.2.128") < httpserver::http::ip_representation("::ffff:192.0.2.128"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("::ffff:192.0.2.129") < httpserver::http::ip_representation("::ffff:192.0.2.128"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("::ffff:192.0.2.128") < httpserver::http::ip_representation("::ffff:192.0.2.129"), true); + + LT_CHECK_EQ(httpserver::http::ip_representation("::ffff:192.0.2.128") < httpserver::http::ip_representation("::192.0.2.128"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("::ffff:192.0.2.128") < httpserver::http::ip_representation("::192.0.2.128"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("::192.0.2.128") < httpserver::http::ip_representation("::ffff:192.0.2.129"), true); +LT_END_AUTO_TEST(ip_representation_less_than) + +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation_less_than_with_masks) + LT_CHECK_EQ(httpserver::http::ip_representation("127.0.*.*") < httpserver::http::ip_representation("127.0.0.1"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("127.0.0.1") < httpserver::http::ip_representation("127.0.*.*"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("127.0.0.*") < httpserver::http::ip_representation("127.0.*.*"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("127.0.*.1") < httpserver::http::ip_representation("127.0.0.1"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("127.0.0.1") < httpserver::http::ip_representation("127.0.*.1"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("127.1.0.1") < httpserver::http::ip_representation("127.0.*.1"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("127.0.*.1") < httpserver::http::ip_representation("127.1.0.1"), true); + LT_CHECK_EQ(httpserver::http::ip_representation("127.1.*.1") < httpserver::http::ip_representation("127.0.*.1"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("127.0.*.1") < httpserver::http::ip_representation("127.1.*.1"), true); + + LT_CHECK_EQ(httpserver::http::ip_representation("2001:db8::ff00:42:*") < httpserver::http::ip_representation("2001:db8::ff00:42:8329"), false); + LT_CHECK_EQ(httpserver::http::ip_representation("2001:db8::ff00:42:8329") < httpserver::http::ip_representation("2001:db8::ff00:42:*"), false); +LT_END_AUTO_TEST(ip_representation_less_than_with_masks) + +LT_BEGIN_AUTO_TEST(http_utils_suite, dump_header_map) + std::map header_map; + header_map["HEADER_ONE"] = "VALUE_ONE"; + header_map["HEADER_TWO"] = "VALUE_TWO"; + header_map["HEADER_THREE"] = "VALUE_THREE"; + + std::stringstream ss; + httpserver::http::dump_header_map(ss, "prefix", header_map); + LT_CHECK_EQ(ss.str(), " prefix [HEADER_ONE:\"VALUE_ONE\" HEADER_TWO:\"VALUE_TWO\" HEADER_THREE:\"VALUE_THREE\" ]\n"); +LT_END_AUTO_TEST(dump_header_map) + +LT_BEGIN_AUTO_TEST(http_utils_suite, dump_header_map_no_prefix) + std::map header_map; + header_map["HEADER_ONE"] = "VALUE_ONE"; + header_map["HEADER_TWO"] = "VALUE_TWO"; + header_map["HEADER_THREE"] = "VALUE_THREE"; + + std::stringstream ss; + httpserver::http::dump_header_map(ss, "", header_map); + LT_CHECK_EQ(ss.str(), " [HEADER_ONE:\"VALUE_ONE\" HEADER_TWO:\"VALUE_TWO\" HEADER_THREE:\"VALUE_THREE\" ]\n"); +LT_END_AUTO_TEST(dump_header_map_no_prefix) + +LT_BEGIN_AUTO_TEST(http_utils_suite, dump_arg_map) + httpserver::http::arg_view_map arg_map; + arg_map["ARG_ONE"].values.push_back("VALUE_ONE"); + arg_map["ARG_TWO"].values.push_back("VALUE_TWO"); + arg_map["ARG_THREE"].values.push_back("VALUE_THREE"); + + std::stringstream ss; + httpserver::http::dump_arg_map(ss, "prefix", arg_map); + LT_CHECK_EQ(ss.str(), " prefix [ARG_ONE:[\"VALUE_ONE\"] ARG_TWO:[\"VALUE_TWO\"] ARG_THREE:[\"VALUE_THREE\"] ]\n"); +LT_END_AUTO_TEST(dump_arg_map) + +LT_BEGIN_AUTO_TEST(http_utils_suite, dump_arg_map_no_prefix) + httpserver::http::arg_view_map arg_map; + arg_map["ARG_ONE"].values.push_back("VALUE_ONE"); + arg_map["ARG_TWO"].values.push_back("VALUE_TWO"); + arg_map["ARG_THREE"].values.push_back("VALUE_THREE"); + + std::stringstream ss; + httpserver::http::dump_arg_map(ss, "", arg_map); + LT_CHECK_EQ(ss.str(), " [ARG_ONE:[\"VALUE_ONE\"] ARG_TWO:[\"VALUE_TWO\"] ARG_THREE:[\"VALUE_THREE\"] ]\n"); +LT_END_AUTO_TEST(dump_arg_map_no_prefix) + +// Test IPv6 with too many parts (more than 8) +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_too_many_parts) + LT_CHECK_THROW(httpserver::http::ip_representation("2001:db8:8714:3a90:8714:2001:db8:3a90:extra")); +LT_END_AUTO_TEST(ip_representation6_too_many_parts) + +// Test IPv4 with wrong number of parts +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation4_wrong_parts) + LT_CHECK_THROW(httpserver::http::ip_representation("192.168.1")); + LT_CHECK_THROW(httpserver::http::ip_representation("192.168.1.2.3")); +LT_END_AUTO_TEST(ip_representation4_wrong_parts) + +// Test IPv6 with wildcards +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_with_wildcards) + httpserver::http::ip_representation ip1("2001:db8:*:3a90::12"); + LT_CHECK_EQ(ip1.ip_version, httpserver::http::http_utils::IPV6); + // Check that wildcard creates a masked entry + LT_CHECK_EQ(ip1.weight(), 14); // 16 - 2 wildcards + + httpserver::http::ip_representation ip2("*:*:*:*:*:*:*:*"); + LT_CHECK_EQ(ip2.weight(), 0); // All wildcards +LT_END_AUTO_TEST(ip_representation6_with_wildcards) + +// Test IPv6 nested IPv4 with wildcards +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_nested_ipv4_wildcard) + httpserver::http::ip_representation ip1("::ffff:192.168.*.*"); + LT_CHECK_EQ(ip1.ip_version, httpserver::http::http_utils::IPV6); + LT_CHECK_EQ(ip1.weight(), 14); // 16 - 2 wildcards + + httpserver::http::ip_representation ip2("::192.0.*.128"); + LT_CHECK_EQ(ip2.weight(), 15); // 16 - 1 wildcard +LT_END_AUTO_TEST(ip_representation6_nested_ipv4_wildcard) + +// Test comparison of addresses with different ::ffff prefixes +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation_ffff_comparison) + // Test comparing ::ffff addresses with :: addresses + // These should hit the special case at lines 483-486 + httpserver::http::ip_representation a("::ffff:192.168.1.1"); + httpserver::http::ip_representation b("::192.168.1.1"); + + // When scores are equal and both have valid ffff/0000 prefix bytes, return false + LT_CHECK_EQ(a < b, false); + LT_CHECK_EQ(b < a, false); + + // Different addresses should compare correctly + LT_CHECK_EQ(httpserver::http::ip_representation("::ffff:192.168.1.1") < + httpserver::http::ip_representation("::ffff:192.168.1.2"), true); +LT_END_AUTO_TEST(ip_representation_ffff_comparison) + +// Test comparison with different octets in bytes 10 and 11 (::ffff prefix area) +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation_middle_bytes_comparison) + // Test addresses with ::ffff prefix to exercise lines 489-494 + // The middle bytes comparison happens when scores are equal but ffff differs + httpserver::http::ip_representation a("::ffff:192.168.1.1"); + httpserver::http::ip_representation b("::192.168.1.1"); + + // Both have same IP part but different ffff bytes + // scores are same in main loop, so middle bytes comparison runs + bool result = a < b; + // ::ffff has higher value in bytes 10-11, so a > b + LT_CHECK_EQ(result, false); + + // When we compare two ::ffff addresses with different IPs + httpserver::http::ip_representation c("::ffff:10.0.0.1"); + httpserver::http::ip_representation d("::ffff:10.0.0.2"); + LT_CHECK_EQ(c < d, true); +LT_END_AUTO_TEST(ip_representation_middle_bytes_comparison) + +// Test IPv6 single-character blocks (padded to 4 chars) +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation6_short_blocks) + httpserver::http::ip_representation ip1("1:2:3:4:5:6:7:8"); + LT_CHECK_EQ(ip1.ip_version, httpserver::http::http_utils::IPV6); + LT_CHECK_EQ(ip1.pieces[0], 0); + LT_CHECK_EQ(ip1.pieces[1], 1); + LT_CHECK_EQ(ip1.pieces[2], 0); + LT_CHECK_EQ(ip1.pieces[3], 2); +LT_END_AUTO_TEST(ip_representation6_short_blocks) + +// Test URL standardization edge cases +LT_BEGIN_AUTO_TEST(http_utils_suite, standardize_url_single_slash) + // Test single character URL (line 230 branch: n_url_length > 1) + LT_CHECK_EQ(httpserver::http::http_utils::standardize_url("/"), "/"); +LT_END_AUTO_TEST(standardize_url_single_slash) + +// Test URL standardization with multiple consecutive slashes +LT_BEGIN_AUTO_TEST(http_utils_suite, standardize_url_multiple_slashes) + LT_CHECK_EQ(httpserver::http::http_utils::standardize_url("///foo///bar///"), "/foo/bar"); + LT_CHECK_EQ(httpserver::http::http_utils::standardize_url("//"), "/"); +LT_END_AUTO_TEST(standardize_url_multiple_slashes) + +// Test http_unescape with empty string +LT_BEGIN_AUTO_TEST(http_utils_suite, http_unescape_empty) + std::string val = ""; + httpserver::http::http_unescape(&val); + LT_CHECK_EQ(val, ""); +LT_END_AUTO_TEST(http_unescape_empty) + +// Test http_unescape with no escape sequences +LT_BEGIN_AUTO_TEST(http_utils_suite, http_unescape_no_escapes) + std::string val = "hello world"; + httpserver::http::http_unescape(&val); + LT_CHECK_EQ(val, "hello world"); +LT_END_AUTO_TEST(http_unescape_no_escapes) + +// Test http_unescape with multiple escape sequences +LT_BEGIN_AUTO_TEST(http_utils_suite, http_unescape_multiple) + std::string val = "%20%2B%3D"; + httpserver::http::http_unescape(&val); + LT_CHECK_EQ(val, " +="); +LT_END_AUTO_TEST(http_unescape_multiple) + +// Test tokenize_url with empty string +LT_BEGIN_AUTO_TEST(http_utils_suite, tokenize_url_empty) + std::vector result = httpserver::http::http_utils::tokenize_url(""); + LT_CHECK_EQ(result.size(), 0); +LT_END_AUTO_TEST(tokenize_url_empty) + +// Test tokenize_url with root only +LT_BEGIN_AUTO_TEST(http_utils_suite, tokenize_url_root) + std::vector result = httpserver::http::http_utils::tokenize_url("/"); + LT_CHECK_EQ(result.size(), 0); +LT_END_AUTO_TEST(tokenize_url_root) + +// Test tokenize_url with multiple segments +LT_BEGIN_AUTO_TEST(http_utils_suite, tokenize_url_multiple_segments) + std::vector result = httpserver::http::http_utils::tokenize_url("/api/v1/users/123"); + LT_CHECK_EQ(result.size(), 4); + LT_CHECK_EQ(result[0], "api"); + LT_CHECK_EQ(result[1], "v1"); + LT_CHECK_EQ(result[2], "users"); + LT_CHECK_EQ(result[3], "123"); +LT_END_AUTO_TEST(tokenize_url_multiple_segments) + +// Test standardize_url with empty string +LT_BEGIN_AUTO_TEST(http_utils_suite, standardize_url_empty) + // Empty string returns empty string (not "/") + LT_CHECK_EQ(httpserver::http::http_utils::standardize_url(""), ""); +LT_END_AUTO_TEST(standardize_url_empty) + +// Test dump_header_map with empty prefix +LT_BEGIN_AUTO_TEST(http_utils_suite, dump_header_map_empty_prefix) + httpserver::http::header_view_map headers; + headers["Content-Type"] = "application/json"; + headers["Accept"] = "text/html"; + + std::stringstream ss; + httpserver::http::dump_header_map(ss, "", headers); + std::string output = ss.str(); + LT_CHECK_EQ(output.find("Content-Type") != std::string::npos, true); + LT_CHECK_EQ(output.find("Accept") != std::string::npos, true); +LT_END_AUTO_TEST(dump_header_map_empty_prefix) + +// Test get_ip_str with nullptr (edge case) +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation_comparison_equal) + httpserver::http::ip_representation ip1("192.168.1.1"); + httpserver::http::ip_representation ip2("192.168.1.1"); + + // Same addresses should not be less than each other + LT_CHECK_EQ(ip1 < ip2, false); + LT_CHECK_EQ(ip2 < ip1, false); +LT_END_AUTO_TEST(ip_representation_comparison_equal) + +// Test ip_representation with max weight comparison +LT_BEGIN_AUTO_TEST(http_utils_suite, ip_representation_wildcard_weight) + // weight() returns count of non-wildcard bytes in IPv6 representation (16 bytes total) + // For IPv4 addresses stored as IPv6 (::ffff:x.x.x.x), specific octets add to weight + httpserver::http::ip_representation ip1("192.168.*.*"); + LT_CHECK_EQ(ip1.weight(), 14); // 16 - 2 wildcard bytes + + httpserver::http::ip_representation ip2("192.*.*.*"); + LT_CHECK_EQ(ip2.weight(), 13); // 16 - 3 wildcard bytes + + // More specific (higher weight) should be "greater than" less specific + LT_CHECK_EQ(ip1.weight() > ip2.weight(), true); +LT_END_AUTO_TEST(ip_representation_wildcard_weight) + LT_BEGIN_AUTO_TEST_ENV() AUTORUN_TESTS() LT_END_AUTO_TEST_ENV() diff --git a/test/unit/string_utilities_test.cpp b/test/unit/string_utilities_test.cpp new file mode 100644 index 00000000..d94b6f73 --- /dev/null +++ b/test/unit/string_utilities_test.cpp @@ -0,0 +1,210 @@ +/* + This file is part of libhttpserver + Copyright (C) 2011-2019 Sebastiano Merlino + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA +*/ + +#include "httpserver/string_utilities.hpp" + +#include +#include +#include + +#include "./littletest.hpp" + +using std::string; +using std::vector; + +LT_BEGIN_SUITE(string_utilities_suite) + void set_up() { + } + + void tear_down() { + } +LT_END_SUITE(string_utilities_suite) + +LT_BEGIN_AUTO_TEST(string_utilities_suite, to_upper_copy) + LT_CHECK_EQ(httpserver::string_utilities::to_upper_copy("test message"), string("TEST MESSAGE")); + LT_CHECK_EQ(httpserver::string_utilities::to_upper_copy("tEsT mEssAge 245&$"), string("TEST MESSAGE 245&$")); +LT_END_AUTO_TEST(to_upper_copy) + +LT_BEGIN_AUTO_TEST(string_utilities_suite, to_lower_copy) + LT_CHECK_EQ(httpserver::string_utilities::to_lower_copy("TEST MESSAGE"), string("test message")); + LT_CHECK_EQ(httpserver::string_utilities::to_lower_copy("tEsT mEssAge 245&$"), string("test message 245&$")); +LT_END_AUTO_TEST(to_lower_copy) + +LT_BEGIN_AUTO_TEST(string_utilities_suite, split_string) + string value = "test this message here"; + string expected_arr[] = { "test", "this", "message", "here" }; + vector expected(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + vector actual = httpserver::string_utilities::string_split(value, ' ', false); + + LT_CHECK_COLLECTIONS_EQ(expected.begin(), expected.end(), actual.begin()); +LT_END_AUTO_TEST(split_string) + +LT_BEGIN_AUTO_TEST(string_utilities_suite, split_string_multiple_spaces) + string value = "test this message here"; + string expected_arr[] = { "test", "", "this", "", "message", "", "here" }; + vector expected(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + vector actual = httpserver::string_utilities::string_split(value, ' ', false); + + LT_CHECK_COLLECTIONS_EQ(expected.begin(), expected.end(), actual.begin()); +LT_END_AUTO_TEST(split_string_multiple_spaces) + +LT_BEGIN_AUTO_TEST(string_utilities_suite, split_string_multiple_spaces_collapse) + string value = "test this message here"; + string expected_arr[] = { "test", "this", "message", "here" }; + vector expected(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + vector actual = httpserver::string_utilities::string_split(value, ' ', true); + + LT_CHECK_COLLECTIONS_EQ(expected.begin(), expected.end(), actual.begin()); +LT_END_AUTO_TEST(split_string_multiple_spaces_collapse) + +LT_BEGIN_AUTO_TEST(string_utilities_suite, split_string_end_space) + string value = "test this message here "; + string expected_arr[] = { "test", "this", "message", "here" }; + vector expected(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + vector actual = httpserver::string_utilities::string_split(value, ' ', false); + + LT_CHECK_COLLECTIONS_EQ(expected.begin(), expected.end(), actual.begin()); +LT_END_AUTO_TEST(split_string_end_space) + +// Test string_split with empty input +LT_BEGIN_AUTO_TEST(string_utilities_suite, split_string_empty_input) + string value = ""; + vector actual = httpserver::string_utilities::string_split(value, ' ', true); + LT_CHECK_EQ(actual.size(), 0); +LT_END_AUTO_TEST(split_string_empty_input) + +// Test string_split with empty input and no collapse +LT_BEGIN_AUTO_TEST(string_utilities_suite, split_string_empty_input_no_collapse) + string value = ""; + vector actual = httpserver::string_utilities::string_split(value, ' ', false); + LT_CHECK_EQ(actual.size(), 0); +LT_END_AUTO_TEST(split_string_empty_input_no_collapse) + +// Test string_split with only separators and collapse +LT_BEGIN_AUTO_TEST(string_utilities_suite, split_string_only_separators_collapse) + string value = " "; // Only spaces + vector actual = httpserver::string_utilities::string_split(value, ' ', true); + LT_CHECK_EQ(actual.size(), 0); +LT_END_AUTO_TEST(split_string_only_separators_collapse) + +// Test string_split with only separators and no collapse +LT_BEGIN_AUTO_TEST(string_utilities_suite, split_string_only_separators_no_collapse) + string value = " "; // Only spaces + vector actual = httpserver::string_utilities::string_split(value, ' ', false); + // Should have 3 empty strings (between the 3 spaces) but last gets trimmed + LT_CHECK_EQ(actual.size(), 3); + LT_CHECK_EQ(actual[0], ""); + LT_CHECK_EQ(actual[1], ""); + LT_CHECK_EQ(actual[2], ""); +LT_END_AUTO_TEST(split_string_only_separators_no_collapse) + +// Test string_split with leading separator and no collapse +LT_BEGIN_AUTO_TEST(string_utilities_suite, split_string_leading_separator_no_collapse) + string value = " a b"; // Leading space + string expected_arr[] = { "", "a", "b" }; + vector expected(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + vector actual = httpserver::string_utilities::string_split(value, ' ', false); + + LT_CHECK_COLLECTIONS_EQ(expected.begin(), expected.end(), actual.begin()); +LT_END_AUTO_TEST(split_string_leading_separator_no_collapse) + +// Test string_split with leading separator and collapse +LT_BEGIN_AUTO_TEST(string_utilities_suite, split_string_leading_separator_collapse) + string value = " a b"; // Leading space + string expected_arr[] = { "a", "b" }; + vector expected(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + vector actual = httpserver::string_utilities::string_split(value, ' ', true); + + LT_CHECK_COLLECTIONS_EQ(expected.begin(), expected.end(), actual.begin()); +LT_END_AUTO_TEST(split_string_leading_separator_collapse) + +// Test to_upper_copy with empty string +LT_BEGIN_AUTO_TEST(string_utilities_suite, to_upper_copy_empty) + LT_CHECK_EQ(httpserver::string_utilities::to_upper_copy(""), string("")); +LT_END_AUTO_TEST(to_upper_copy_empty) + +// Test to_lower_copy with empty string +LT_BEGIN_AUTO_TEST(string_utilities_suite, to_lower_copy_empty) + LT_CHECK_EQ(httpserver::string_utilities::to_lower_copy(""), string("")); +LT_END_AUTO_TEST(to_lower_copy_empty) + +// Test to_upper_copy with already uppercase +LT_BEGIN_AUTO_TEST(string_utilities_suite, to_upper_copy_already_upper) + LT_CHECK_EQ(httpserver::string_utilities::to_upper_copy("HELLO WORLD"), string("HELLO WORLD")); +LT_END_AUTO_TEST(to_upper_copy_already_upper) + +// Test to_lower_copy with already lowercase +LT_BEGIN_AUTO_TEST(string_utilities_suite, to_lower_copy_already_lower) + LT_CHECK_EQ(httpserver::string_utilities::to_lower_copy("hello world"), string("hello world")); +LT_END_AUTO_TEST(to_lower_copy_already_lower) + +// Test string_split with different separator +LT_BEGIN_AUTO_TEST(string_utilities_suite, split_string_comma_separator) + string value = "a,b,c,d"; + string expected_arr[] = { "a", "b", "c", "d" }; + vector expected(expected_arr, expected_arr + sizeof(expected_arr) / sizeof(expected_arr[0])); + vector actual = httpserver::string_utilities::string_split(value, ',', false); + + LT_CHECK_COLLECTIONS_EQ(expected.begin(), expected.end(), actual.begin()); +LT_END_AUTO_TEST(split_string_comma_separator) + +// Test string_split with single element +LT_BEGIN_AUTO_TEST(string_utilities_suite, split_string_single_element) + string value = "hello"; + vector actual = httpserver::string_utilities::string_split(value, ' ', true); + LT_CHECK_EQ(actual.size(), 1); + LT_CHECK_EQ(actual[0], "hello"); +LT_END_AUTO_TEST(split_string_single_element) + +// Test is_valid_hex with valid strings +LT_BEGIN_AUTO_TEST(string_utilities_suite, is_valid_hex_valid) + LT_CHECK_EQ(httpserver::string_utilities::is_valid_hex("0123456789"), true); + LT_CHECK_EQ(httpserver::string_utilities::is_valid_hex("abcdef"), true); + LT_CHECK_EQ(httpserver::string_utilities::is_valid_hex("ABCDEF"), true); + LT_CHECK_EQ(httpserver::string_utilities::is_valid_hex("0123456789abcdefABCDEF"), true); +LT_END_AUTO_TEST(is_valid_hex_valid) + +// Test is_valid_hex with invalid strings +LT_BEGIN_AUTO_TEST(string_utilities_suite, is_valid_hex_invalid) + LT_CHECK_EQ(httpserver::string_utilities::is_valid_hex("ZZZZ"), false); + LT_CHECK_EQ(httpserver::string_utilities::is_valid_hex("hello"), false); + LT_CHECK_EQ(httpserver::string_utilities::is_valid_hex("12g4"), false); + LT_CHECK_EQ(httpserver::string_utilities::is_valid_hex("12 34"), false); +LT_END_AUTO_TEST(is_valid_hex_invalid) + +// Test is_valid_hex with empty string +LT_BEGIN_AUTO_TEST(string_utilities_suite, is_valid_hex_empty) + LT_CHECK_EQ(httpserver::string_utilities::is_valid_hex(""), true); +LT_END_AUTO_TEST(is_valid_hex_empty) + +// Test hex_char_to_val with digits +LT_BEGIN_AUTO_TEST(string_utilities_suite, hex_char_to_val_digits) + LT_CHECK_EQ(httpserver::string_utilities::hex_char_to_val('0'), 0); + LT_CHECK_EQ(httpserver::string_utilities::hex_char_to_val('9'), 9); + LT_CHECK_EQ(httpserver::string_utilities::hex_char_to_val('a'), 10); + LT_CHECK_EQ(httpserver::string_utilities::hex_char_to_val('f'), 15); + LT_CHECK_EQ(httpserver::string_utilities::hex_char_to_val('A'), 10); + LT_CHECK_EQ(httpserver::string_utilities::hex_char_to_val('F'), 15); + LT_CHECK_EQ(httpserver::string_utilities::hex_char_to_val('z'), 0); +LT_END_AUTO_TEST(hex_char_to_val_digits) + +LT_BEGIN_AUTO_TEST_ENV() + AUTORUN_TESTS() +LT_END_AUTO_TEST_ENV()