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/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 index 97b081e5..17ae2955 100644 --- a/.github/workflows/verify-build.yml +++ b/.github/workflows/verify-build.yml @@ -20,6 +20,9 @@ jobs: BUILD_TYPE: ${{ matrix.build-type }} CC: ${{ matrix.c-compiler }} CXX: ${{ matrix.cc-compiler }} + defaults: + run: + shell: ${{ matrix.shell }} strategy: fail-fast: false matrix: @@ -33,6 +36,7 @@ jobs: coverage: [coverage, nocoverage] linking: [dynamic, static] build-type: [classic] + shell: [bash] exclude: - os: ubuntu-latest os-type: mac @@ -58,229 +62,305 @@ jobs: os-type: ubuntu build-type: asan 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: msan - compiler-family: clang - c-compiler: clang-6.0 - cc-compiler: clang++-6.0 + 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-6.0 - cc-compiler: clang++-6.0 + 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-6.0 - cc-compiler: clang++-6.0 + 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-6.0 - cc-compiler: clang++-6.0 + c-compiler: clang-18 + cc-compiler: clang++-18 debug: debug coverage: nocoverage + shell: bash - test-group: extra - os: ubuntu-18.04 + os: ubuntu-latest os-type: ubuntu build-type: none compiler-family: gcc - c-compiler: gcc-5 - cc-compiler: g++-5 + c-compiler: gcc-9 + cc-compiler: g++-9 debug: nodebug coverage: nocoverage + shell: bash - test-group: extra - os: ubuntu-18.04 + os: ubuntu-latest os-type: ubuntu build-type: none compiler-family: gcc - c-compiler: gcc-6 - cc-compiler: g++-6 + 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-7 - cc-compiler: g++-7 + 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-8 - cc-compiler: g++-8 + 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-9 - cc-compiler: g++-9 + 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-10 - cc-compiler: g++-10 + c-compiler: gcc-14 + cc-compiler: g++-14 debug: nodebug coverage: nocoverage + shell: bash - test-group: extra - os: ubuntu-18.04 + os: ubuntu-22.04 os-type: ubuntu build-type: none compiler-family: clang - c-compiler: clang-3.9 - cc-compiler: clang++-3.9 + c-compiler: clang-11 + cc-compiler: clang++-11 debug: nodebug coverage: nocoverage + shell: bash - test-group: extra - os: ubuntu-18.04 + os: ubuntu-22.04 os-type: ubuntu build-type: none compiler-family: clang - c-compiler: clang-4.0 - cc-compiler: clang++-4.0 + c-compiler: clang-12 + cc-compiler: clang++-12 debug: nodebug coverage: nocoverage + shell: bash - test-group: extra - os: ubuntu-18.04 + os: ubuntu-22.04 os-type: ubuntu build-type: none compiler-family: clang - c-compiler: clang-5.0 - cc-compiler: clang++-5.0 + 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-6.0 - cc-compiler: clang++-6.0 + 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-7 - cc-compiler: clang++-7 + 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-8 - cc-compiler: clang++-8 + 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-9 - cc-compiler: clang++-9 + 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: none - compiler-family: clang - c-compiler: clang-10 - cc-compiler: clang++-10 + 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: valgrind + build-type: no-dauth compiler-family: gcc - c-compiler: gcc-7 - cc-compiler: g++-7 + 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-9 - cc-compiler: clang++-9 + 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-7 - cc-compiler: g++-7 + 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-7 - cc-compiler: g++-7 + 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-7 - cc-compiler: g++-7 + 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-7 - cc-compiler: g++-7 + 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@v2 + 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. @@ -289,8 +369,29 @@ jobs: # 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 ; @@ -309,8 +410,18 @@ jobs: 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 valgrind-dbg + run: sudo apt-get install valgrind if: ${{ matrix.build-type == 'valgrind' && matrix.os-type == 'ubuntu' }} - name: Install cpplint if needed @@ -319,16 +430,12 @@ jobs: - name: Install IWYU dependencies if needed run: | - # Use same deps used by iwyu in their setup for travis - sudo apt-get install llvm-9-dev llvm-9-tools libclang-9-dev ; - # Use same CMAKE used by iwyu in their setup for travis - wget -O cmake.sh https://cmake.org/files/v3.10/cmake-3.10.0-Linux-x86_64.sh ; - sudo sh cmake.sh --skip-license --exclude-subdir --prefix=/usr/local ; + 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@v2 + uses: actions/cache@v4 with: path: include-what-you-use key: ${{ matrix.os }}-${{ matrix.c-compiler }}-include-what-you-use-pre-built @@ -337,11 +444,11 @@ jobs: # 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-9 --prefix` ; - CLANG_BIN_PATH=`llvm-config-9 --bindir` ; - curl "https://libhttpserver.s3.amazonaws.com/travis_stuff/include-what-you-use-clang-9.tgz" -o "include-what-you-use-clang-9.tgz" ; - tar -xzf "include-what-you-use-clang-9.tgz" ; + 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++ ../ ; @@ -357,10 +464,10 @@ jobs: - name: CURL from cache (for testing) id: cache-CURL - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: curl-7.75.0 - key: ${{ matrix.os }}-CURL-pre-built + key: ${{ matrix.os }}-CURL-pre-built-v2 if: ${{ matrix.os == 'macos-latest' }} - name: Build CURL (for testing) @@ -368,7 +475,7 @@ jobs: 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 ; - if [ "$matrix.os-type" = "ubuntu" ]; then ./configure ; else ./configure --with-darwinssl ; fi + ./configure --with-darwinssl --without-ssl ; make ; if: ${{ matrix.os == 'macos-latest' && steps.cache-CURL.outputs.cache-hit != 'true' }} @@ -384,33 +491,103 @@ jobs: run: brew install autoconf automake libtool if: ${{ matrix.os == 'macos-latest' }} - - name: Setup coverage dependencies (only on linux) + - name: Setup dependencies (only on linux) run: | sudo apt-get install info install-info ; - sudo pip install codecov ; - sudo pip install gcovr ; 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@v2 + uses: actions/cache@v4 with: - path: libmicrohttpd-0.9.64 - key: ${{ matrix.os }}-${{ matrix.c-compiler }}-libmicrohttpd-pre-built + 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.64.tar.gz -o libmicrohttpd-0.9.64.tar.gz ; - tar -xzf libmicrohttpd-0.9.64.tar.gz ; - cd libmicrohttpd-0.9.64 ; + 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: steps.cache-libmicrohttpd.outputs.cache-hit != 'true' - + 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.64 ; sudo make install ; - + 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' }} @@ -429,7 +606,7 @@ jobs: 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 + if [ "${{ matrix.os }}" = "macos-latest" ]; then export CFLAGS='-mtune=generic' ; export IPV6_TESTS_ENABLED="true" ; fi @@ -437,7 +614,16 @@ jobs: ./bootstrap ; mkdir build ; cd build ; - if [ "$LINKING" = "static" ]; then + 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; @@ -451,6 +637,25 @@ jobs: ../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: | @@ -480,26 +685,35 @@ jobs: run: | cd build ; make check; - if: ${{ matrix.build-type != 'iwyu' }} - + 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' }} + if: ${{ failure() && matrix.build-type != 'iwyu' && matrix.compiler-family != 'arm-cross' }} - name: Run Valgrind checks run: | cd build ; make check-valgrind ; - cat test/test-suite-memcheck.log ; 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' }} + if: ${{ matrix.os-type == 'ubuntu' && matrix.compiler-family != 'arm-cross' }} - name: Run performance tests (select) run: | @@ -525,8 +739,16 @@ jobs: sleep 5 && ab -n 1000000 -c 100 127.0.0.1:8080/plaintext if: ${{ matrix.build-type == 'threads' }} - - name: Push code coverage data + - name: Generate coverage report run: | - cd build ; - bash <(curl -s https://codecov.io/bash) ; + 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/AUTHORS b/AUTHORS index a30f6b9e..ff66cfa2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,6 +13,9 @@ 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 @@ -36,7 +39,7 @@ martamoreton (Github: https://github.com/martamoreton) - Memory leaks rdiazmartin -- Cleanup of multiple parts of the code +- Cleanup of multiple parts of the code. Improved testing. Fixed issues with family urls. bcsgh (Github: https://github.com/bcsgh) - Management of large uploads @@ -46,7 +49,8 @@ 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. +- 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/CONTRIBUTING.md b/CONTRIBUTING.md index ee018af3..d923f747 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -127,6 +127,22 @@ Please follow these steps to have your contribution considered by the maintainer 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 diff --git a/ChangeLog b/ChangeLog index 3b40a2c5..6e9532e3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,13 +1,42 @@ -Sun Mar 07 20:02:10 2021 -0800 +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. - -Thu Feb 25 20:27:12 2021 -0800 - Simplified dependency management for libmicrohttpd - -Sat Nov 21 07:20:00 2020 -0800 + Simplified dependency management for libmicrohttpd. Added support on build for CodeQL security checks. - Moved builds to travis.com + 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. @@ -17,37 +46,34 @@ Sat Nov 21 07:20:00 2020 -0800 Moved windows builds to AppVeyor. Made the library compatible with libmicrohttpd v0.9.71 and above. -Sat Jun 6 10:21:05 2020 -0800 +Version 0.18.1 - 2020-06-06 + Prevent use of regex in http_endpoint outside of registration which could allow DOS attacks. -Sat May 16 07:20:00 2020 -0800 +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 - -Sat Aug 10 18:34:07 2019 -0800 - Added support for TCP-NODELAY - Changed set_path on http_request to have lazy behavior - -Tue Aug 06 22:22:14 2019 -0800 + 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 + Added support for PATCH method. + +Version 0.17.5 - 2019-01-28 -Sat Jan 27 21:59:11 2019 -0800 - libhttpserver now includes set of examples to demonstrate the main capabilities of the library + 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 + 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 + 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. - -Sat Jan 12 00:51:00 2019 -0800 Removed the support for integrated COMET logic. Removed the support for caching logic. Added integ tests. @@ -55,255 +81,129 @@ Sat Jan 12 00:51:00 2019 -0800 Improved interface of the http_response object. Deprecated http_response_builder object. -Thu Dec 26 10:00:30 2018 -0800 - Fixed IPV6 parsing logic. - Added tests to support IP parsing, URL parsing and utilities - -Thu Nov 22 20:58:00 2018 -0800 - Solved problem with the server not being able to start on mac os +Version 0.16.0 - 2018-12-26 -Sun Nov 04 19:28:00 2018 -0800 - Moved http_endpoint as a sub-class of webserver. This avoids usage of friends. + Fixed IPV6 parsing logic. + Added tests to support IP parsing, URL parsing and utilities. -Wed Feb 26 21:31:00 2017 +0000 - Fixed problem with segfault when copying http_response object +Version 0.15.0 - 2018-11-23 -Wed Feb 12 13:14:01 2017 +0000 - Updated to libmicrohttpd 0.9.52 + Solved problem with the server not being able to start on mac os. -Wed Jul 13 02:23:11 2016 +0100 - Fixed problems with large payloads - Fixed memory leak in http_response_ptr +Version 0.14.0 - 2018-11-05 -Tue Dec 29 18:56:31 2015 +0100 - 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 + Moved http_endpoint as a sub-class of webserver. This avoids usage of friends. -Sun Dec 27 19:39:01 2015 +0100 - Removed POLL start configuration (THREAD now defaults to POLL or EPOLL on Linux) - Use TCP_FASTOPEN on linux >= 3.6 +Version 0.13.0 - 2017-02-26 -Sat Dec 26 15:08:22 2015 +0100 - Changed http_resource to use classic C++ polymorphism using virtual instead of CRTP + Fixed problem with segfault when copying http_response object. -Fri Jul 17 21:38:54 2015 +0000 - Removed build dependency on pkg-config +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. -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 +Version 0.11.1 - 2016-07-13 -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 + Fixed problems with large payloads. + Fixed memory leak in http_response_ptr. -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 +Version 0.11.0 - 2015-12-26 -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 + 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 14:23:57 2012 +0100 - Changed dependency download method - Sebastiano Merlino +Version 0.9.0 - 2015-04-15 -Wed Oct 31 14:13:49 2012 +0100 - Added dependency to travis - 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:07:30 2012 +0100 - Changed travis build path - Sebastiano Merlino +Version 0.8.0 - 2014-07-23 -Wed Oct 31 14:02:59 2012 +0100 - Added travis conf to repo - Sebastiano Merlino + Support for building on MinGW/Cygwin systems. + min libmicrohttpd version moved to 0.9.37. -Tue Oct 30 16:13:10 2012 +0100 - Changed the buggy debian changelog - Sebastiano Merlino +Version 0.7.2 - 2014-03-23 -Tue Oct 30 16:06:26 2012 +0100 - Changed version to v0.5.4 - Sebastiano Merlino + Continue the cleanup reducing webserver.cpp responsibilities. + Deep work on documentation. -Tue Oct 30 15:59:45 2012 +0100 - Adjusted debian build rules - Sebastiano Merlino +Version 0.7.0 - 2014-01-25 -Tue Oct 30 12:52:04 2012 +0100 - Changed version to 0.5.3 - Added grow method to http_request - 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 23 12:46:48 2012 +0200 - Changed version from 0.5.1 to 0.5.2 - Sebastiano Merlino +Version 0.5.4 - 2012-10-30 -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 + 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. -Mon Oct 22 12:13:11 2012 +0200 - Modified version number and changelog in order to prepare tag - Sebastiano Merlino +Version 0.5.3 - 2012-10-30 -Fri Oct 19 17:11:21 2012 +0200 - Added response constructor with byte - Sebastiano Merlino + Added grow method to http_request. -Mon Oct 15 11:16:22 2012 +0200 - Removed unuseful dependency from libuuid - Sebastiano Merlino +Version 0.5.2 - 2012-10-23 -Fri Oct 12 15:42:21 2012 +0200 - Solved a bug that made impossible to parse post data - Sebastiano Merlino + Changed default log behaviour to print nothing. + Added getters and setters for dynamic components of WS. -Wed Oct 10 17:19:25 2012 +0200 - Moved to version 0.5.1 - Sebastiano Merlino +Version 0.5.1 - 2012-10-10 -Wed Oct 10 17:16:26 2012 +0200 - Added querystring to request attributes - 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. -Fri Oct 5 18:00:38 2012 +0200 - Merge branch 'master' of https://github.com/etr/libhttpserver - Conflicts: - src/webserver.cpp - Sebastiano Merlino +Version 0.5.0 - 2012-10-05 -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 + 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. -Mon Sep 24 15:29:28 2012 +0200 - Modified webserver in order to accept comet calls - Added ignored patters in gitignore - Sebastiano Merlino +Version 0.4.0 - 2012-08-26 -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 - -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 - -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 a46882fd..02121fde 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,7 +38,7 @@ endif endif -EXTRA_DIST = libhttpserver.pc.in $(DX_CONFIG) +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 diff --git a/README.md b/README.md index 07021ed9..b9d96f37 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ Copyright (C) 2011-2019 Sebastiano Merlino. # The libhttpserver reference manual ![GA: Build Status](https://github.com/etr/libhttpserver/actions/workflows/verify-build.yml/badge.svg) -[![Build status](https://ci.appveyor.com/api/projects/status/ktoy6ewkrf0q1hw6/branch/master?svg=true)](https://ci.appveyor.com/project/etr/libhttpserver/branch/master) [![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) @@ -32,6 +31,7 @@ libhttpserver is built upon [libmicrohttpd](https://www.gnu.org/software/libmic - 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 @@ -67,7 +67,7 @@ The mission of this library is to support all possible HTTP features directly an 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 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. +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. @@ -79,6 +79,7 @@ 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/). @@ -118,16 +119,88 @@ Here are listed the libhttpserver specific options (the canonical configure opti [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: + +``` +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: - const std::shared_ptr render(const http_request&) { + std::shared_ptr render(const http_request&) { return std::shared_ptr(new string_response("Hello, World!")); } }; @@ -141,7 +214,7 @@ The most basic example of creating a server and handling a requests for the path return 0; } - +``` To test the above example, you could run the following command from a terminal: curl -XGET -v http://localhost:8080/hello @@ -166,9 +239,9 @@ You can also check this example on [github](https://github.com/etr/libhttpserver ## 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 @@ -182,6 +255,8 @@ For example, if your connection limit is “1”, a browser may open a first con * _.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. @@ -197,6 +272,7 @@ For example, if your connection limit is “1”, a browser may open a first con * `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. @@ -214,21 +290,22 @@ In all these 3 cases libhttpserver would provide a standard HTTP response to the * _.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; - const std::shared_ptr not_found_custom(const http_request& req) { + std::shared_ptr not_found_custom(const http_request& req) { return std::shared_ptr(new string_response("Not found custom", 404, "text/plain")); } - const std::shared_ptr not_allowed_custom(const http_request& req) { + 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: - const std::shared_ptr render(const http_request&) { + std::shared_ptr render(const http_request&) { return std::shared_ptr(new string_response("Hello, World!")); } }; @@ -246,7 +323,7 @@ In all these 3 cases libhttpserver would provide a standard HTTP response to the return 0; } - +``` To test the above example, you can run the following command from a terminal: curl -XGET -v http://localhost:8080/hello @@ -262,6 +339,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver * _.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 @@ -273,7 +351,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver class hello_world_resource : public http_resource { public: - const std::shared_ptr render(const http_request&) { + std::shared_ptr render(const http_request&) { return std::shared_ptr(new string_response("Hello, World!")); } }; @@ -288,7 +366,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver return 0; } - +``` To test the above example, you can run the following command from a terminal: curl -XGET -v http://localhost:8080/hello @@ -310,15 +388,18 @@ You can also check this example on [github](https://github.com/etr/libhttpserver * _.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: - const std::shared_ptr render(const http_request&) { + std::shared_ptr render(const http_request&) { return std::shared_ptr(new string_response("Hello, World!")); } }; @@ -335,13 +416,66 @@ You can also check this example on [github](https://github.com/etr/libhttpserver 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. @@ -355,10 +489,10 @@ You should calculate the value of NC_SIZE based on the number of connections per * _.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_ssl() + .no_ipv6() .no_debug() .no_pedantic() .no_basic_auth() @@ -367,12 +501,14 @@ You should calculate the value of NC_SIZE based on the number of connections per .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. @@ -386,28 +522,29 @@ Once a webserver is created, you can manage its execution through the following 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`): -* _**const std::shared_ptr** http_resource::render_GET(**const http_request&** req):_ Invoked on an HTTP GET request. -* _**const std::shared_ptr** http_resource::render_POST(**const http_request&** req):_ Invoked on an HTTP POST request. -* _**const std::shared_ptr** http_resource::render_PUT(**const http_request&** req):_ Invoked on an HTTP PUT request. -* _**const std::shared_ptr** http_resource::render_HEAD(**const http_request&** req):_ Invoked on an HTTP HEAD request. -* _**const std::shared_ptr** http_resource::render_DELETE(**const http_request&** req):_ Invoked on an HTTP DELETE request. -* _**const std::shared_ptr** http_resource::render_TRACE(**const http_request&** req):_ Invoked on an HTTP TRACE request. -* _**const std::shared_ptr** http_resource::render_OPTIONS(**const http_request&** req):_ Invoked on an HTTP OPTIONS request. -* _**const std::shared_ptr** http_resource::render_CONNECT(**const http_request&** req):_ Invoked on an HTTP CONNECT request. -* _**const 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`. +* _**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: - const std::shared_ptr render_GET(const http_request&) { + std::shared_ptr render_GET(const http_request&) { return std::shared_ptr(new string_response("GET: Hello, World!")); } - const std::shared_ptr render(const http_request&) { + std::shared_ptr render(const http_request&) { return std::shared_ptr(new string_response("OTHER: Hello, World!")); } }; @@ -421,7 +558,7 @@ Given this, the `http_resource` class contains the following extensible methods 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. @@ -436,13 +573,14 @@ The base `http_resource` class has a set of methods that can be used to allow an * _**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: - const std::shared_ptr render(const http_request&) { + std::shared_ptr render(const http_request&) { return std::shared_ptr(new string_response("Hello, World!")); } }; @@ -458,7 +596,7 @@ The base `http_resource` class has a set of methods that can be used to allow an return 0; } - +``` To test the above example, you can run the following command from a terminal: curl -XGET -v http://localhost:8080/hello @@ -482,29 +620,31 @@ There are essentially four ways to specify an endpoint string: * **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: - const std::shared_ptr render(const http_request&) { + std::shared_ptr render(const http_request&) { return std::shared_ptr(new string_response("Hello, World!")); } }; class handling_multiple_resource : public http_resource { public: - const std::shared_ptr render(const http_request& req) { + 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: - const std::shared_ptr render(const http_request& req) { - return std::shared_ptr(new string_response("ARGS: " + req.get_arg("arg1") + " and " + req.get_arg("arg2"))); + 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)); } }; @@ -526,7 +666,7 @@ There are essentially four ways to specify an endpoint string: 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. @@ -552,14 +692,16 @@ The `http_request` class has a set of methods you will have access to when imple * _**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. -* _**const std::string** get_header(**const std::string&** key) **const**:_ Returns the header with name equal to `key` if present in the HTTP request. Returns an `empty string` otherwise. -* _**const std::string** get_cookie(**const std::string&** key) **const**:_ Returns the cookie with name equal to `key` if present in the HTTP request. Returns an `empty string` otherwise. -* _**const std::string** get_footer(**const std::string&** 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. -* _**const std::string** get_arg(**const std::string&** 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). -* _**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). +* _**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. @@ -571,8 +713,16 @@ The `http_request` class has a set of methods you will have access to when imple * _**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. -* _**gnutls_session_t** get_tls_session() **const**:_ Tests if there is am underlying TLS state of the current request. +* _**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. @@ -581,15 +731,55 @@ Details on the `http::file_info` structure. * _**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: - const std::shared_ptr render(const http_request& req) { - return std::shared_ptr(new string_response("Hello: " + req.get_arg("name"))); + std::shared_ptr render(const http_request& req) { + return std::shared_ptr(new string_response("Hello: " + std::string(req.get_arg("name")))); } }; @@ -602,7 +792,7 @@ Details on the `http::file_info` structure. 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" @@ -617,7 +807,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver 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. +* _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. @@ -634,13 +824,14 @@ The `http_response` class offers an additional set of methods to "decorate" your * _**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: - const std::shared_ptr render(const http_request&) { + 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; @@ -656,7 +847,7 @@ The `http_response` class offers an additional set of methods to "decorate" your return 0; } - +``` To test the above example, you could run the following command from a terminal: curl -XGET -v "http://localhost:8080/hello" @@ -665,6 +856,41 @@ 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 @@ -675,7 +901,7 @@ The system supports both IPV4 and IPV6 and manages them transparently. The only 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 `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 @@ -691,13 +917,14 @@ Examples of valid IPs include: * `"::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: - const std::shared_ptr render(const http_request&) { + std::shared_ptr render(const http_request&) { return std::shared_ptr(new string_response("Hello, World!")); } }; @@ -714,7 +941,7 @@ Examples of valid IPs include: return 0; } - +``` To test the above example, you could run the following command from a terminal: curl -XGET -v "http://localhost:8080/hello" @@ -733,13 +960,14 @@ Digest authentication uses a one-way authentication method based on MD5 hash alg 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: - const std::shared_ptr render_GET(const http_request& req) { + 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")); } @@ -756,7 +984,7 @@ Client certificate authentication uses a X.509 certificate from the client. This 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" @@ -766,6 +994,7 @@ You will receive back the user and password you passed in input. Try to pass the 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" @@ -774,7 +1003,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver class digest_resource : public httpserver::http_resource { public: - const std::shared_ptr render_GET(const http_request& req) { + 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)); } @@ -797,32 +1026,225 @@ You can also check this example on [github](https://github.com/etr/libhttpserver 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 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 redudant to list them here; so, please, consult the list directly [here](https://github.com/etr/libhttpserver/blob/master/src/httpserver/http_utils.hpp). +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: - const std::shared_ptr render_GET(const http_request& req) { + std::shared_ptr render_GET(const http_request& req) { return std::shared_ptr(new file_response("test_content", 200, "text/plain")); } }; @@ -836,7 +1258,7 @@ libhttpserver provides a set of constants to help you develop your HTTP server. return 0; } - +``` To test the above example, you can run the following command from a terminal: curl -XGET -v localhost:8080/hello @@ -844,6 +1266,7 @@ To test the above example, you can run the following command from a terminal: 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; @@ -864,7 +1287,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver class deferred_resource : public http_resource { public: - const std::shared_ptr render_GET(const http_request& req) { + std::shared_ptr render_GET(const http_request& req) { return std::shared_ptr >(new deferred_response(test_callback, nullptr, "cycle callback response")); } }; @@ -878,7 +1301,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver return 0; } - +``` To test the above example, you can run the following command from a terminal: curl -XGET -v localhost:8080/hello @@ -886,6 +1309,7 @@ To test the above example, you can run the following command from a terminal: 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 @@ -920,7 +1344,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver class deferred_resource : public http_resource { public: - const std::shared_ptr render_GET(const http_request& req) { + 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")); } @@ -935,7 +1359,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver return 0; } - +``` To test the above example, you can run the following command from a terminal: curl -XGET -v localhost:8080/hello diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index bc2fb9e7..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,27 +0,0 @@ -platform: x64 - -environment: - matrix: - - compiler: msys2 - MINGW_CHOST: x86_64-w64-mingw32 - MSYS2_ARCH: x86_64 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 -init: - - 'echo Building libhttpserver %version% for Windows' - - 'echo System architecture: %PLATFORM%' - - 'echo Repo build branch is: %APPVEYOR_REPO_BRANCH%' - - 'echo Build folder is: %APPVEYOR_BUILD_FOLDER%' - - 'echo Repo build commit is: %APPVEYOR_REPO_COMMIT%' - - 'echo Cygwin root is: %CYG_ROOT%' - - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) -install: - - 'if "%compiler%"=="msys2" C:\msys64\msys2_shell.cmd -defterm -no-start -msys2 -c "pacman --noconfirm -S --needed mingw-w64-$MSYS2_ARCH-{libtool,make,pkg-config,libsystre,doxygen,gnutls,graphviz,curl}"' - - 'if "%compiler%"=="msys2" C:\msys64\msys2_shell.cmd -defterm -no-start -msys2 -c "pacman --noconfirm -S --needed autotools"' - - 'if "%compiler%"=="msys2" C:\msys64\msys2_shell.cmd -defterm -no-start -mingw64 -full-path -here -c "cd $APPVEYOR_BUILD_FOLDER && curl https://s3.amazonaws.com/libhttpserver/libmicrohttpd_releases/libmicrohttpd-0.9.64.tar.gz -o libmicrohttpd-0.9.64.tar.gz"' - - 'if "%compiler%"=="msys2" C:\msys64\msys2_shell.cmd -defterm -no-start -mingw64 -full-path -here -c "cd $APPVEYOR_BUILD_FOLDER && tar -xzf libmicrohttpd-0.9.64.tar.gz"' - - 'if "%compiler%"=="msys2" C:\msys64\msys2_shell.cmd -defterm -no-start -mingw64 -full-path -here -c "cd $APPVEYOR_BUILD_FOLDER/libmicrohttpd-0.9.64 && ./configure --disable-examples --enable-poll=no --prefix /C/msys64 && make && make install"' - - 'if "%compiler%"=="msys2" C:\msys64\msys2_shell.cmd -defterm -no-start -mingw64 -full-path -here -c "cd $APPVEYOR_BUILD_FOLDER && ./bootstrap"' - - 'if "%compiler%"=="msys2" C:\msys64\msys2_shell.cmd -defterm -no-start -mingw64 -full-path -here -c "cd $APPVEYOR_BUILD_FOLDER && mkdir build && cd build && MANIFEST_TOOL=no; ../configure --disable-fastopen --prefix /C/msys64 CXXFLAGS=-I/C/msys64/include LDFLAGS=-L/C/msys64/lib; make"' -build_script: - - 'if "%compiler%"=="msys2" C:\msys64\msys2_shell.cmd -defterm -no-start -mingw64 -full-path -here -c "cd $APPVEYOR_BUILD_FOLDER/build && make check"' - - 'if "%compiler%"=="msys2" C:\msys64\msys2_shell.cmd -defterm -no-start -mingw64 -full-path -here -c "cd $APPVEYOR_BUILD_FOLDER/build && cat test/test-suite.log"' diff --git a/codecov.yml b/codecov.yml index a6e1de74..97a08e6a 100644 --- a/codecov.yml +++ b/codecov.yml @@ -18,3 +18,6 @@ comment: layout: "reach,diff,flags,files,footer" behavior: default require_changes: no + +ignore: + - "test" diff --git a/configure.ac b/configure.ac index 23adba36..003170c6 100644 --- a/configure.ac +++ b/configure.ac @@ -21,7 +21,7 @@ AC_PREREQ(2.57) m4_define([libhttpserver_MAJOR_VERSION],[0])dnl -m4_define([libhttpserver_MINOR_VERSION],[19])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 @@ -44,7 +44,7 @@ AC_LANG([C++]) AC_SYS_LARGEFILE # Minimal feature-set required -AX_CXX_COMPILE_STDCXX([14]) +AX_CXX_COMPILE_STDCXX([17]) native_srcdir=$srcdir @@ -57,7 +57,7 @@ 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 @@ -71,6 +71,16 @@ case "$host" in 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="arpa/inet.h" ADDITIONAL_LIBS="-lpthread -no-undefined" @@ -83,7 +93,6 @@ case "$host" in 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")]) @@ -140,6 +149,16 @@ fi 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], @@ -149,14 +168,12 @@ AC_ARG_ENABLE([fastopen], AC_MSG_RESULT([$fastopen]) is_fastopen_supported=no; -if test x"$fastopen" = x"yes"; then - if test x"$is_windows" = x"no"; then - if test `uname -r |cut -d. -f1` -ge 3; then - if test `uname -r |cut -d. -f2` -ge 7; then - CXXFLAGS="-DUSE_FASTOPEN $CXXFLAGS"; - is_fastopen_supported=yes; - fi - fi +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 @@ -197,7 +214,7 @@ 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++14 -Wno-unused-command-line-argument -O0" + 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]) @@ -245,11 +262,27 @@ 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) @@ -275,9 +308,16 @@ 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]) @@ -292,13 +332,17 @@ AC_OUTPUT( examples/Makefile ) -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/examples/Makefile.am b/examples/Makefile.am index 0fe116af..148fa944 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -19,7 +19,7 @@ LDADD = $(top_builddir)/src/libhttpserver.la AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/ METASOURCES = AUTO -noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg setting_headers custom_access_log basic_authentication digest_authentication minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator file_upload +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 @@ -28,10 +28,9 @@ 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 -basic_authentication_SOURCES = basic_authentication.cpp -digest_authentication_SOURCES = digest_authentication.cpp minimal_https_SOURCES = minimal_https.cpp minimal_file_response_SOURCES = minimal_file_response.cpp minimal_deferred_SOURCES = minimal_deferred.cpp @@ -42,3 +41,22 @@ 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 index a0578f5e..50efa4fd 100644 --- a/examples/allowing_disallowing_methods.cpp +++ b/examples/allowing_disallowing_methods.cpp @@ -18,11 +18,13 @@ USA */ +#include + #include class hello_world_resource : public httpserver::http_resource { public: - const std::shared_ptr render(const httpserver::http_request&) { + std::shared_ptr render(const httpserver::http_request&) { return std::shared_ptr(new httpserver::string_response("Hello, World!")); } }; 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 index cc90f55b..661bbb3c 100644 --- a/examples/basic_authentication.cpp +++ b/examples/basic_authentication.cpp @@ -18,16 +18,19 @@ USA */ +#include +#include + #include class user_pass_resource : public httpserver::http_resource { public: - const std::shared_ptr render_GET(const httpserver::http_request& req) { + 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(req.get_user() + " " + req.get_pass(), 200, "text/plain")); + return std::shared_ptr(new httpserver::string_response(std::string(req.get_user()) + " " + std::string(req.get_pass()), 200, "text/plain")); } }; diff --git a/examples/benchmark_nodelay.cpp b/examples/benchmark_nodelay.cpp index c1a6c1e7..96c2f570 100755 --- a/examples/benchmark_nodelay.cpp +++ b/examples/benchmark_nodelay.cpp @@ -32,7 +32,7 @@ class hello_world_resource : public httpserver::http_resource { resp(resp) { } - const std::shared_ptr render(const httpserver::http_request&) { + std::shared_ptr render(const httpserver::http_request&) { return resp; } diff --git a/examples/benchmark_select.cpp b/examples/benchmark_select.cpp index 1edc1c00..ef5cd089 100755 --- a/examples/benchmark_select.cpp +++ b/examples/benchmark_select.cpp @@ -32,7 +32,7 @@ class hello_world_resource : public httpserver::http_resource { resp(resp) { } - const std::shared_ptr render(const httpserver::http_request&) { + std::shared_ptr render(const httpserver::http_request&) { return resp; } diff --git a/examples/benchmark_threads.cpp b/examples/benchmark_threads.cpp index 1afe4dfb..db376168 100755 --- a/examples/benchmark_threads.cpp +++ b/examples/benchmark_threads.cpp @@ -32,7 +32,7 @@ class hello_world_resource : public httpserver::http_resource { resp(resp) { } - const std::shared_ptr render(const httpserver::http_request&) { + std::shared_ptr render(const httpserver::http_request&) { return resp; } 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 index dd2e4aa8..8f596c90 100644 --- a/examples/custom_access_log.cpp +++ b/examples/custom_access_log.cpp @@ -19,6 +19,8 @@ */ #include +#include +#include #include @@ -28,7 +30,7 @@ void custom_access_log(const std::string& url) { class hello_world_resource : public httpserver::http_resource { public: - const std::shared_ptr render(const httpserver::http_request&) { + std::shared_ptr render(const httpserver::http_request&) { return std::shared_ptr(new httpserver::string_response("Hello, World!")); } }; diff --git a/examples/custom_error.cpp b/examples/custom_error.cpp index 8e720835..c38fb169 100644 --- a/examples/custom_error.cpp +++ b/examples/custom_error.cpp @@ -18,19 +18,21 @@ USA */ +#include + #include -const std::shared_ptr not_found_custom(const httpserver::http_request&) { +std::shared_ptr not_found_custom(const httpserver::http_request&) { return std::shared_ptr(new httpserver::string_response("Not found custom", 404, "text/plain")); } -const std::shared_ptr not_allowed_custom(const httpserver::http_request&) { +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: - const std::shared_ptr render(const httpserver::http_request&) { + std::shared_ptr render(const httpserver::http_request&) { return std::shared_ptr(new httpserver::string_response("Hello, World!")); } }; diff --git a/examples/deferred_with_accumulator.cpp b/examples/deferred_with_accumulator.cpp index a55e4cd8..a4367773 100644 --- a/examples/deferred_with_accumulator.cpp +++ b/examples/deferred_with_accumulator.cpp @@ -18,10 +18,14 @@ 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 @@ -56,7 +60,7 @@ ssize_t test_callback(std::shared_ptr > closure_data, char* buf class deferred_resource : public httpserver::http_resource { public: - const std::shared_ptr render_GET(const httpserver::http_request&) { + 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")); } diff --git a/examples/digest_authentication.cpp b/examples/digest_authentication.cpp index c919cf0e..fb87cd4b 100644 --- a/examples/digest_authentication.cpp +++ b/examples/digest_authentication.cpp @@ -18,13 +18,15 @@ USA */ +#include + #include #define MY_OPAQUE "11733b200778ce33060f31c9af70a870ba96ddd4" class digest_resource : public httpserver::http_resource { public: - const std::shared_ptr render_GET(const httpserver::http_request& req) { + 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 { diff --git a/examples/file_upload.cpp b/examples/file_upload.cpp index 2c82e2a5..0916a4fc 100644 --- a/examples/file_upload.cpp +++ b/examples/file_upload.cpp @@ -19,11 +19,14 @@ */ #include +#include +#include + #include class file_upload_resource : public httpserver::http_resource { public: - const std::shared_ptr render_GET(const httpserver::http_request&) { + std::shared_ptr render_GET(const httpserver::http_request&) { std::string get_response = "\n"; get_response += " \n"; get_response += "
\n"; @@ -40,7 +43,7 @@ class file_upload_resource : public httpserver::http_resource { return std::shared_ptr(new httpserver::string_response(get_response, 200, "text/html")); } - const std::shared_ptr render_POST(const httpserver::http_request& req) { + std::shared_ptr render_POST(const httpserver::http_request& req) { std::string post_response = "\n"; post_response += "\n"; post_response += "