Prechádzať zdrojové kódy

Merge branch 'master' of https://github.com/civetweb/civetweb

bel2125 2 mesiacov pred
rodič
commit
32080e0a3a

+ 13 - 0
.devcontainer/Dockerfile

@@ -0,0 +1,13 @@
+# syntax=docker/dockerfile:1
+ARG ubuntu_version=24.04
+
+FROM ubuntu:${ubuntu_version}
+RUN apt-get update && \
+    apt-get install --no-install-recommends -y \
+                build-essential \
+                ca-certificates \
+                cmake \
+                clang \
+                git \
+                nano\
+                openssh-server

+ 16 - 0
.devcontainer/devcontainer.json

@@ -0,0 +1,16 @@
+{
+    "name": "Civetweb",
+    "dockerFile": "Dockerfile",
+    "customizations": {
+      "vscode": {
+        "extensions": [
+          "ms-vscode.cpptools",
+          "eamodio.gitlens"
+        ]
+      }
+    },
+    "mounts": [
+      "type=bind,source=/home/${localEnv:USER}/.ssh,target=/root/.ssh,readonly"
+    ]
+  
+  }

+ 10 - 0
.github/dependabot.yml

@@ -0,0 +1,10 @@
+version: 2
+updates:
+- package-ecosystem: github-actions
+  directory: "/"
+  schedule:
+    interval: weekly
+    day: saturday
+    time: "10:00"
+  open-pull-requests-limit: 10
+  target-branch: master

+ 539 - 0
.github/workflows/cibuild.yml

@@ -0,0 +1,539 @@
+name: CI build
+
+on:
+  push:
+  pull_request:
+  release:
+    types: [published]
+  workflow_dispatch:
+jobs:
+  build-and-test:
+    runs-on: ${{ matrix.os }}
+    name: ${{ matrix.env.NAME }}
+    strategy:
+      fail-fast: true
+      matrix:
+        include:
+          - os: ubuntu-latest
+            compiler: clang
+            env:
+              NAME: Clang-Linux-Minimal-Debug
+              BUILD_TYPE: Debug
+              ENABLE_SSL_DYNAMIC_LOADING: YES
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: NO
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: NO
+              NO_FILES: YES
+              ENABLE_SSL: NO
+              NO_CGI: YES
+              ENABLE_IPV6: NO
+              ENABLE_WEBSOCKETS: NO
+              ENABLE_SERVER_STATS: NO
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: NO
+              ALLOW_WARNINGS: YES
+              RUN_UNITTEST: 1
+          
+          - os: ubuntu-latest
+            compiler: clang
+            env:
+              NAME: Clang-Linux-Default-Release
+              BUILD_TYPE: Release
+              ENABLE_SSL_DYNAMIC_LOADING: YES
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: YES
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: NO
+              NO_FILES: NO
+              ENABLE_SSL: YES
+              NO_CGI: NO
+              ENABLE_IPV6: NO
+              ENABLE_WEBSOCKETS: NO
+              ENABLE_SERVER_STATS: NO
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: NO
+              ALLOW_WARNINGS: YES
+              RUN_UNITTEST: 1
+          
+          - os: ubuntu-latest
+            compiler: gcc 
+            env:
+              NAME: GCC-Linux-Complete-NoLua-Release
+              BUILD_TYPE: Release
+              ENABLE_SSL_DYNAMIC_LOADING: YES
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: YES
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: NO
+              NO_FILES: NO
+              ENABLE_SSL: YES
+              NO_CGI: NO
+              ENABLE_IPV6: YES
+              ENABLE_WEBSOCKETS: YES
+              ENABLE_SERVER_STATS: YES
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: YES
+              ALLOW_WARNINGS: YES
+              RUN_UNITTEST: 1
+          
+          - os: ubuntu-latest
+            compiler: clang
+            env:
+              NAME: CLANG-AnyVersion-Linux-Coverage
+              BUILD_TYPE: Coverage
+              ENABLE_SSL_DYNAMIC_LOADING: YES
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: YES
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: NO
+              NO_FILES: NO
+              ENABLE_SSL: YES
+              NO_CGI: NO
+              ENABLE_IPV6: YES
+              ENABLE_WEBSOCKETS: YES
+              ENABLE_SERVER_STATS: YES
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: NO
+              ALLOW_WARNINGS: YES
+              RUN_UNITTEST: 1
+          
+          - os: ubuntu-latest
+            compiler: clang
+            env:
+              NAME: Clang-Linux-Default-Shared
+              BUILD_TYPE: Debug
+              ENABLE_SSL_DYNAMIC_LOADING: YES
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: YES
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: YES
+              NO_FILES: NO
+              ENABLE_SSL: YES
+              NO_CGI: NO
+              ENABLE_IPV6: NO
+              ENABLE_WEBSOCKETS: NO
+              ENABLE_SERVER_STATS: NO
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: NO
+              ALLOW_WARNINGS: YES
+              RUN_UNITTEST: 1
+          
+          - os: ubuntu-latest
+            compiler: gcc
+            env:
+              NAME: GCCLinuxDefault_RelWithDebInfo
+              BUILD_TYPE: RelWithDebInfo
+              ENABLE_SSL_DYNAMIC_LOADING: YES
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: YES
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: NO
+              NO_FILES: NO
+              ENABLE_SSL: YES
+              NO_CGI: NO
+              ENABLE_IPV6: NO
+              ENABLE_WEBSOCKETS: NO
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: NO
+              ALLOW_WARNINGS: YES
+              RUN_UNITTEST: 1
+          
+          - os: ubuntu-latest
+            compiler: gcc
+            env:
+              NAME: GCCLinuxDefault_MinSizeRel
+              BUILD_TYPE: MinSizeRel
+              ENABLE_SSL_DYNAMIC_LOADING: YES
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: YES
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: NO
+              NO_FILES: NO
+              ENABLE_SSL: YES
+              NO_CGI: NO
+              ENABLE_IPV6: NO
+              ENABLE_WEBSOCKETS: NO
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: NO
+              ALLOW_WARNINGS: YES
+              RUN_UNITTEST: 1
+          
+          - os: ubuntu-latest
+            compiler: gcc
+            env:
+              NAME: GCCLinuxDefault_None
+              BUILD_TYPE: None
+              ENABLE_SSL_DYNAMIC_LOADING: YES
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: YES
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: NO
+              NO_FILES: NO
+              ENABLE_SSL: YES
+              NO_CGI: NO
+              ENABLE_IPV6: NO
+              ENABLE_WEBSOCKETS: NO
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: NO
+              ALLOW_WARNINGS: YES
+              RUN_UNITTEST: 1
+          
+          - os: ubuntu-latest
+            compiler: gcc
+            env:
+              NAME: GCCLinuxDefault_xenial
+              BUILD_TYPE: Release
+              ENABLE_SSL_DYNAMIC_LOADING: YES
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: YES
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: NO
+              NO_FILES: NO
+              ENABLE_SSL: YES
+              NO_CGI: NO
+              ENABLE_IPV6: NO
+              ENABLE_WEBSOCKETS: NO
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: NO
+              ALLOW_WARNINGS: YES
+              RUN_UNITTEST: 1
+          
+          - os: ubuntu-latest
+            compiler: gcc
+            env:
+              NAME: GCCLinuxDefault
+              BUILD_TYPE: Release
+              ENABLE_SSL_DYNAMIC_LOADING: YES
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: YES
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: NO
+              NO_FILES: NO
+              ENABLE_SSL: YES
+              NO_CGI: NO
+              ENABLE_IPV6: NO
+              ENABLE_WEBSOCKETS: NO
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: NO
+              ALLOW_WARNINGS: YES
+              RUN_UNITTEST: 1
+         
+          - os: ubuntu-latest
+            compiler: gcc
+            env:
+              NAME: GCCLinuxDefault_OpenSSL_3_0
+              BUILD_TYPE: Release
+              ENABLE_SSL_DYNAMIC_LOADING: YES
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: NO
+              OpenSSL_3_0: YES
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: NO
+              NO_FILES: NO
+              ENABLE_SSL: YES
+              NO_CGI: NO
+              ENABLE_IPV6: NO
+              ENABLE_WEBSOCKETS: NO
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: NO
+              ALLOW_WARNINGS: YES
+              RUN_UNITTEST: 1
+
+          # Disable Lua build, until someone knows how to fix the CMake files
+          # see https://github.com/civetweb/civetweb/issues/543
+          # - os: ubuntu-lastest
+          #   compiler: clang
+          #   env:
+          #     NAME: Clang-Linux-Complete-WithLua-Debug
+          #     BUILD_TYPE: Debug
+          #     ENABLE_SSL_DYNAMIC_LOADING: YES
+          #     OPENSSL_1_0: NO
+          #     OPENSSL_1_1: YES
+          #     ENABLE_CXX: NO
+          #     C_STANDARD: auto
+          #     CXX_STANDARD: auto
+          #     BUILD_SHARED: NO
+          #     NO_FILES: NO
+          #     ENABLE_SSL: YES
+          #     NO_CGI: NO
+          #     ENABLE_IPV6: YES
+          #     ENABLE_WEBSOCKETS: YES
+          #     ENABLE_SERVER_STATS: YES
+          #     ENABLE_LUA: YES
+          #     ENABLE_LUA_SHARED: YES
+          #     ENABLE_DUKTAPE: NO
+          #     NO_CACHING: YES
+          #     ALLOW_WARNINGS: YES
+          #     RUN_UNITTEST: 1
+
+          - os: macos-latest
+            compiler: clang
+            env:
+              NAME: Clang-OSX-Complete-NoLua-Release-OpenSSL_1_1_NoDynLoad
+              BUILD_TYPE: Release
+              ENABLE_SSL_DYNAMIC_LOADING: NO
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: YES
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: NO
+              NO_FILES: NO
+              ENABLE_SSL: YES
+              NO_CGI: NO
+              ENABLE_IPV6: YES
+              ENABLE_WEBSOCKETS: YES
+              ENABLE_SERVER_STATS: YES
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: YES
+              ALLOW_WARNINGS: YES
+              RUN_UNITTEST: 1
+
+          - os: macos-latest
+            compiler: clang
+            env:
+              NAME: OSX-Package_OpenSSL_1_1
+              BUILD_TYPE: Release
+              ENABLE_SSL_DYNAMIC_LOADING: YES
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: YES
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: NO
+              NO_FILES: NO
+              ENABLE_SSL: YES
+              NO_CGI: NO
+              ENABLE_IPV6: YES
+              ENABLE_WEBSOCKETS: YES
+              ENABLE_SERVER_STATS: NO
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: NO
+              ALLOW_WARNINGS: YES
+              MACOSX_PACKAGE: 1
+              RUN_UNITTEST: 1
+
+          - os: macos-latest
+            compiler: clang
+            env:
+              NAME: OSX-Package_OpenSSL_3_0
+              BUILD_TYPE: Release
+              ENABLE_SSL_DYNAMIC_LOADING: YES
+              OPENSSL_1_0: NO
+              OPENSSL_1_1: NO
+              OPENSSL_3_0: YES
+              ENABLE_CXX: NO
+              ENABLE_LUA_SHARED: NO
+              C_STANDARD: auto
+              CXX_STANDARD: auto
+              BUILD_SHARED: NO
+              NO_FILES: NO
+              ENABLE_SSL: YES
+              NO_CGI: NO
+              ENABLE_IPV6: YES
+              ENABLE_WEBSOCKETS: YES
+              ENABLE_SERVER_STATS: NO
+              ENABLE_LUA: NO
+              ENABLE_DUKTAPE: NO
+              NO_CACHING: NO
+              ALLOW_WARNINGS: YES
+              MACOSX_PACKAGE: 1
+              RUN_UNITTEST: 1
+
+
+    steps:
+        - name: Checkout code
+          uses: actions/checkout@v4.1.7
+
+        - name: Export number of CPUs
+          run: |
+            if [ "$RUNNER_OS" == "Linux" ]; then
+              echo "cores=$(nproc)" >> $GITHUB_ENV
+            fi
+            if [ "$RUNNER_OS" == "macOS" ]; then
+              echo "cores=$(sysctl -n hw.logicalcpu)" >> $GITHUB_ENV
+            fi
+            
+        - name: Install clang on Linux
+          if: matrix.compiler == 'clang' && startsWith(matrix.os,'ubuntu')
+          run: |
+            sudo apt-get install --no-install-recommends -y clang
+            sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100
+            sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
+
+        - name: Set up OpenSSL 1.1 on modern MacOS
+          # OpenSSL 1.1 is installed by default, so we just need to set the paths
+          if: startsWith(matrix.os,'macos') && matrix.env.OPENSSL_1_1 == 'YES'
+          run: |
+            OPENSSL_ROOT_DIR=$(brew --prefix openssl@1.1)
+            LDFLAGS=-L${OPENSSL_ROOT_DIR}/lib
+            CFLAGS=-I${OPENSSL_ROOT_DIR}/include
+            ADDITIONAL_CMAKE_ARGS="-DCMAKE_SHARED_LINKER_FLAGS=${LDFLAGS} -DMAKE_C_FLAGS=${CFLAGS}"
+            PKG_CONFIG_PATH=${OPENSSL_ROOT_DIR}/lib/pkgconfig
+            DYLD_LIBRARY_PATH=${OPENSSL_ROOT_DIR}/lib
+            
+            echo "LDFLAGS=${LDFLAGS}" >> $GITHUB_ENV
+            echo "CFLAGS=${CFLAGS}" >> $GITHUB_ENV
+            echo "${OPENSSL_ROOT_DIR}/bin" >> $GITHUB_PATH
+            echo "ADDITIONAL_CMAKE_ARGS=${ADDITIONAL_CMAKE_ARGS}" >> $GITHUB_ENV
+            echo "PKG_CONFIG_PATH=${PKG_CONFIG_PATH}" >> $GITHUB_ENV
+            echo "DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}" >> $GITHUB_ENV
+
+        - name: Install OpenSSL 3.0 on modern MacOS
+          # OpenSSL 1.1 is installed by default, so we need to install 3.0 manually
+          if: startsWith(matrix.os,'macos') && matrix.env.OPENSSL_3_0 == 'YES'            
+          run: |
+            brew install openssl@3.0
+             
+            OPENSSL_ROOT_DIR=$(brew --prefix openssl@3.0)
+            LDFLAGS=-L${OPENSSL_ROOT_DIR}/lib
+            CFLAGS=-I${OPENSSL_ROOT_DIR}/include
+            ADDITIONAL_CMAKE_ARGS="-DCMAKE_SHARED_LINKER_FLAGS=${LDFLAGS} -DMAKE_C_FLAGS=${CFLAGS}"
+            PKG_CONFIG_PATH=${OPENSSL_ROOT_DIR}/lib/pkgconfig
+            DYLD_LIBRARY_PATH=${OPENSSL_ROOT_DIR}/lib
+
+            echo "LDFLAGS=${LDFLAGS}" >> $GITHUB_ENV
+            echo "CFLAGS=${CFLAGS}" >> $GITHUB_ENV
+            echo "${OPENSSL_ROOT_DIR}/bin" >> $GITHUB_PATH
+            echo "ADDITIONAL_CMAKE_ARGS=${ADDITIONAL_CMAKE_ARGS}" >> $GITHUB_ENV
+            echo "PKG_CONFIG_PATH=${PKG_CONFIG_PATH}" >> $GITHUB_ENV
+            echo "DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}" >> $GITHUB_ENV
+       
+        - name: Install OpenSSL 1.1 on modern Linux
+          # Needed for recent versions of Linux as they ship  with OpenSSL 3.0 by default
+          if: startsWith(matrix.os,'ubuntu') && matrix.env.OPENSSL_1_1 == 'YES'
+          run: |
+            curl -O -L https://github.com/openssl/openssl/releases/download/OpenSSL_1_1_1w/openssl-1.1.1w.tar.gz
+            tar -xzf openssl-1.1.1w.tar.gz
+            cd openssl-1.1.1w
+            ./config --prefix=/usr/local/ssl1.1 --openssldir=/usr/local/ssl1.1 shared
+            make depend
+            make -j ${{ env.cores }}
+            sudo make install_sw -j ${{ env.cores }}
+            sudo ldconfig
+
+            OPENSSL_ROOT_DIR=/usr/local/ssl1.1
+            LDFLAGS=-L${OPENSSL_ROOT_DIR}/lib
+            CFLAGS=-I${OPENSSL_ROOT_DIR}/include
+            ADDITIONAL_CMAKE_ARGS="-DCMAKE_SHARED_LINKER_FLAGS=${LDFLAGS} -DMAKE_C_FLAGS=${CFLAGS}"
+            PKG_CONFIG_PATH=${OPENSSL_ROOT_DIR}/lib/pkgconfig
+            
+            echo "LDFLAGS=${LDFLAGS}" >> $GITHUB_ENV
+            echo "CFLAGS=${CFLAGS}" >> $GITHUB_ENV
+            echo "${OPENSSL_ROOT_DIR}/bin" >> $GITHUB_PATH
+            echo "ADDITIONAL_CMAKE_ARGS=${ADDITIONAL_CMAKE_ARGS}" >> $GITHUB_ENV
+            echo "PKG_CONFIG_PATH=${PKG_CONFIG_PATH}" >> $GITHUB_ENV
+            echo "LD_LIBRARY_PATH=${OPENSSL_ROOT_DIR}/lib" >> $GITHUB_ENV
+        
+        - name: Print tool version information
+          run: |
+            openssl version
+            cc --version
+            cmake --version
+            clang --version  
+        
+        - name: Run CMake
+          run: |
+            cmake -S . -B output\
+              -DCMAKE_BUILD_TYPE=${{ matrix.env.BUILD_TYPE }}\
+              -DBUILD_SHARED_LIBS=${{ matrix.env.BUILD_SHARED }}\
+              -DCIVETWEB_THIRD_PARTY_DIR=../src/third-party\
+              -DCIVETWEB_ENABLE_THIRD_PARTY_OUTPUT=YES\
+              -DCIVETWEB_ENABLE_SSL=${{ matrix.env.ENABLE_SSL }}\
+              -DCIVETWEB_DISABLE_CGI=${{ matrix.env.NO_CGI }}\
+              -DCIVETWEB_SERVE_NO_FILES=${{ matrix.env.NO_FILES }}\
+              -DCIVETWEB_ENABLE_SSL_DYNAMIC_LOADING=${{ matrix.env.ENABLE_SSL_DYNAMIC_LOADING }}\
+              -DCIVETWEB_SSL_OPENSSL_API_1_0=${{ matrix.env.OPENSSL_1_0 }}\
+              -DCIVETWEB_SSL_OPENSSL_API_1_1=${{ matrix.env.OPENSSL_1_1 }}\
+              -DCIVETWEB_SSL_OPENSSL_API_3_0=${{ matrix.env.OPENSSL_3_0 }}\
+              -DCIVETWEB_ENABLE_WEBSOCKETS=${{ matrix.env.ENABLE_WEBSOCKETS }}\
+              -DCIVETWEB_ENABLE_CXX=${{ matrix.env.ENABLE_CXX }}\
+              -DCIVETWEB_ENABLE_SERVER_STATS=${{ matrix.env.ENABLE_SERVER_STATS }}\
+              -DCIVETWEB_ENABLE_LUA=${{ matrix.env.ENABLE_LUA }}\
+              -DCIVETWEB_ENABLE_LUA_SHARED=${{ matrix.env.ENABLE_LUA_SHARED }}\
+              -DCIVETWEB_ENABLE_DUKTAPE=${{ matrix.env.ENABLE_DUKTAPE }}\
+              -DCIVETWEB_DISABLE_CACHING=${{ matrix.env.NO_CACHING }}\
+              -DCIVETWEB_C_STANDARD=${{ matrix.env.C_STANDARD }}\
+              -DCIVETWEB_CXX_STANDARD=${{ matrix.env.CXX_STANDARD }}\
+              -DCIVETWEB_ALLOW_WARNINGS=${{ matrix.env.ALLOW_WARNINGS }}\
+              -DCIVETWEB_ENABLE_IPV6=${{ matrix.env.ENABLE_IPV6 }}\
+              ${{ env.ADDITIONAL_CMAKE_ARGS }}
+                
+        - name: Build MacOS Package
+          if: matrix.env.MACOSX_PACKAGE == 1
+          run: |
+            make -f Makefile.osx package -j ${{ env.cores }}
+        
+        - name: Build executable
+          run: |
+            cmake --build output -- -j ${{ env.cores }}
+          
+        - name: Check executable
+          run: |
+            ./output/src/civetweb -I
+        
+        - name: Run unit tests
+          if: matrix.env.RUN_UNITTEST == 1
+          run: |
+            # kill processes that are using port 8084, which is used in the unit tests
+            # Currently, this affects linux only, where 'mono' is using this port
+            pid_8084=$(sudo lsof -t -i:8084 || true;)
+                
+            if [[ -n ${pid_8084} ]]; then 
+              echo "Killing process using port 8084: ${pid_8084}"     
+              sudo kill -9 ${pid_8084}
+            fi
+
+            # Run unit tests
+            gcc unittest/cgi_test.c -o output/cgi_test.cgi
+            cd output
+            CTEST_OUTPUT_ON_FAILURE=1  CK_FORK=yes  make all test -j ${{ env.cores }}

+ 1 - 1
.github/workflows/cifuzz.yml

@@ -18,7 +18,7 @@ jobs:
         fuzz-seconds: 600
         dry-run: false
     - name: Upload Crash
-      uses: actions/upload-artifact@v1
+      uses: actions/upload-artifact@v4
       if: failure() && steps.build.outcome == 'success'
       with:
         name: artifacts

+ 0 - 66
.github/workflows/codeql-analysis.yml

@@ -1,66 +0,0 @@
-name: "CodeQL"
-
-on:
-  push:
-    branches: [master]
-  pull_request:
-    # The branches below must be a subset of the branches above
-    branches: [master]
-  schedule:
-    - cron: '0 19 * * 4'
-
-jobs:
-  analyze:
-    name: Analyze
-    runs-on: ubuntu-latest
-
-    strategy:
-      fail-fast: false
-      matrix:
-        # Override automatic language detection by changing the below list
-        # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
-        language: ['cpp']
-        # Learn more...
-        # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
-
-    steps:
-    - name: Checkout repository
-      uses: actions/checkout@v2
-      with:
-        # We must fetch at least the immediate parents so that if this is
-        # a pull request then we can checkout the head.
-        fetch-depth: 2
-
-    # If this run was triggered by a pull request event, then checkout
-    # the head of the pull request instead of the merge commit.
-    - run: git checkout HEAD^2
-      if: ${{ github.event_name == 'pull_request' }}
-
-    # Initializes the CodeQL tools for scanning.
-    - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
-      with:
-        languages: ${{ matrix.language }}
-        # If you wish to specify custom queries, you can do so here or in a config file.
-        # By default, queries listed here will override any specified in a config file. 
-        # Prefix the list here with "+" to use these queries and those in the config file.
-        # queries: ./path/to/local/query, your-org/your-repo/queries@main
-
-    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
-    # If this step fails, then you should remove it and run the build manually (see below)
-    - name: Autobuild
-      uses: github/codeql-action/autobuild@v1
-
-    # ℹ️ Command-line programs to run using the OS shell.
-    # 📚 https://git.io/JvXDl
-
-    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
-    #    and modify them (or add more) to build your code if your project
-    #    uses a compiled language
-
-    #- run: |
-    #   make bootstrap
-    #   make release
-
-    - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v1

+ 0 - 3
.github/workflows/codeql-buildscript.sh

@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-
-make build WITH_ALL=1

+ 9 - 9
.github/workflows/codeql.yml

@@ -12,8 +12,8 @@
 name: "CodeQL"
 
 on:
-  # push:
-  #   branches: [ "main", "master" ]
+  push:
+    branches: [ "master" ]
   schedule:
     - cron: '0 0 * * *'
   pull_request:
@@ -27,7 +27,7 @@ jobs:
     #   - https://gh.io/supported-runners-and-hardware-resources
     #   - https://gh.io/using-larger-runners
     # Consider using larger runners for possible analysis time improvements.
-    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-20.04' }}
+    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
     timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
     permissions:
       actions: read
@@ -45,13 +45,13 @@ jobs:
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
         submodules: recursive
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v2
+      uses: github/codeql-action/init@v3
       with:
         languages: ${{ matrix.language }}
         # If you wish to specify custom queries, you can do so here or in a config file.
@@ -75,10 +75,10 @@ jobs:
     #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
 
     - run: |
-        ./.github/workflows/codeql-buildscript.sh
+        make build WITH_ALL=1
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v2
+      uses: github/codeql-action/analyze@v3
       with:
         category: "/language:${{matrix.language}}"
         upload: false
@@ -107,14 +107,14 @@ jobs:
         output: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif
 
     - name: Upload CodeQL results to code scanning
-      uses: github/codeql-action/upload-sarif@v2
+      uses: github/codeql-action/upload-sarif@v3
       with:
         sarif_file: ${{ steps.step1.outputs.sarif-output }}
         category: "/language:${{matrix.language}}"
 
     - name: Upload CodeQL results as an artifact
       if: success() || failure()
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
         name: codeql-results
         path: ${{ steps.step1.outputs.sarif-output }}

+ 1 - 0
.gitignore

@@ -27,6 +27,7 @@ log.out
 /CMakeCache.txt
 /CMakeFiles
 /mingw-builds
+/output
 
 #################
 ## Eclipse

+ 1 - 518
.travis.yml

@@ -9,60 +9,17 @@ cache:
   directories:
   - $HOME/third-party
 
-osx_image: xcode9
-
-addons:
-  apt:
-    packages:
-      - cmake
-      - openssl
-      - libssl-dev
-      - gdb
-    sources:
-      - kubuntu-backports
-
-
 before_install:
-  - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
-      mkdir $HOME/usr;
-      export PATH="$HOME/usr/bin:$PATH";
-      wget https://cmake.org/files/v3.7/cmake-3.7.2-Linux-x86_64.sh --no-check-certificate;
-      chmod +x cmake-3.7.2-Linux-x86_64.sh;
-      ./cmake-3.7.2-Linux-x86_64.sh --prefix=$HOME/usr --exclude-subdir --skip-license;
-    fi
   - cmake --version
 
-
 install:
-  - if [ "${BUILD_TYPE}" == "Coverage" -a "${TRAVIS_OS_NAME}" == "linux" ]; then
-      PATH=~/.local/bin:${PATH};
-      pip install --user --upgrade pip;
-      pip install --user cpp-coveralls;
-      pip install --user codecov;
-      pip install --user coverage;
-    fi
 
 before_script:
-  # Add an IPv6 config - see the corresponding Travis issue
-  # https://github.com/travis-ci/travis-ci/issues/8361
-  - if [ "${ENABLE_IPV6}" == "YES" -a "${TRAVIS_OS_NAME}" == "linux" ]; then
-      echo "Activating IPv6 on Travis";
-      sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6';
-    fi
   # Check some settings of the build server (operating system, IPv6 availability, directory)
   - uname -a
-  - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
-      lsb_release -a;
-      cat /etc/network/interfaces || true;
-    fi
   - ifconfig
   - pwd
   - ls -la
-  - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
-      apt-cache search gcc | grep "GNU C compiler";
-      apt-cache search clang | grep compiler;
-    fi
-  - if [[ "${BUILD_TYPE}" == "OSX_OPENSSL_1_1" ]]; then HOMEBREW_NO_AUTO_UPDATE=1 brew install openssl@1.1 ;fi
   # Generate the build scripts with CMake
   - mkdir output
   - openssl version
@@ -130,16 +87,7 @@ after_failure:
   - if [[ -f "$COREFILE" ]]; then gdb -c "$COREFILE" example -ex "thread apply all bt" -ex "set pagination 0" -batch; fi
 
 
-# Modifications due to Travis IPv6 issues:
-# https://github.com/travis-ci/travis-ci/issues/8711
-# https://github.com/travis-ci/travis-ci/issues/8361
-# DCIVETWEB_ENABLE_IPV6=${ENABLE_IPV6} or =NO
-
 script:
-  - if [ "${MACOSX_PACKAGE}" == "1" ]; then
-      cd "${TRAVIS_BUILD_DIR}";
-      make -f Makefile.osx package;
-    fi
   - if [ "${RUN_UNITTEST}" == "1" ]; then
       CTEST_OUTPUT_ON_FAILURE=1  CK_FORK=yes  make all test;
     fi
@@ -152,16 +100,7 @@ script:
     fi
   - echo "Build and test script DONE"
 
-# Coveralls options: https://github.com/eddyxu/cpp-coveralls/blob/master/README.md
 after_success:
-  - if [ "${BUILD_TYPE}" == "Coverage" -a "${TRAVIS_OS_NAME}" == "linux" ]; then
-      echo "Preparing coverage tests";
-      echo "Creating coveralls coverage report";
-      coveralls --include src --exclude src/main.c --exclude src/third_party --include include --gcov-options '\-lp' --root .. --build-root .;
-      echo "Creating codecov coverage report";
-      bash <(curl -s https://codecov.io/bash);
-      echo "All coverage reports created";
-    fi
 
 
 #########################################################################################
@@ -173,350 +112,6 @@ after_success:
 matrix:
   fast_finish: true
   include:
-
-
-#########################################################################################
-#####   TRUSTY   ########################################################################
-#########################################################################################
-
-  - dist: trusty
-    sudo: false
-    os: linux
-    compiler: clang
-    addons:
-      apt:
-        sources:
-          - ubuntu-toolchain-r-test
-          - llvm-toolchain-precise-3.8
-        packages:
-          - clang-3.8
-    env:
-      idx=1
-      N=Clang3.8-Linux-Minimal-Debug
-      MATRIX_EVAL="CC=clang-3.8 && CXX=clang++-3.8"
-      BUILD_TYPE=Debug
-      ENABLE_SSL_DYNAMIC_LOADING=YES
-      OPENSSL_1_0=NO
-      OPENSSL_1_1=NO
-      ENABLE_CXX=NO
-      ENABLE_LUA_SHARED=NO
-      C_STANDARD=auto
-      CXX_STANDARD=auto
-      BUILD_SHARED=NO
-      NO_FILES=YES
-      ENABLE_SSL=NO
-      NO_CGI=YES
-      ENABLE_IPV6=NO
-      ENABLE_WEBSOCKETS=NO
-      ENABLE_SERVER_STATS=NO
-      ENABLE_LUA=NO
-      ENABLE_DUKTAPE=NO
-      NO_CACHING=NO
-      ALLOW_WARNINGS=YES
-
-  - dist: trusty
-    sudo: false
-    os: linux
-    compiler: clang
-    addons:
-      apt:
-        sources:
-          - ubuntu-toolchain-r-test
-          - llvm-toolchain-precise-3.8
-        packages:
-          - clang-3.8
-    env:
-      idx=3
-      N=Clang3.8-Linux-Default-Release
-      MATRIX_EVAL="CC=clang-3.8 && CXX=clang++-3.8"
-      BUILD_TYPE=Release
-      ENABLE_SSL_DYNAMIC_LOADING=YES
-      OPENSSL_1_0=YES
-      OPENSSL_1_1=NO
-      ENABLE_CXX=NO
-      ENABLE_LUA_SHARED=NO
-      C_STANDARD=auto
-      CXX_STANDARD=auto
-      BUILD_SHARED=NO
-      NO_FILES=NO
-      ENABLE_SSL=YES
-      NO_CGI=NO
-      ENABLE_IPV6=NO
-      ENABLE_WEBSOCKETS=NO
-      ENABLE_SERVER_STATS=NO
-      ENABLE_LUA=NO
-      ENABLE_DUKTAPE=NO
-      NO_CACHING=NO
-      ALLOW_WARNINGS=YES
-
-  - dist: trusty
-    sudo: required
-    os: linux
-    compiler: gcc
-    addons:
-      apt:
-        sources:
-          - ubuntu-toolchain-r-test
-        packages:
-          - g++-5
-    env:
-      idx=5
-      N=GCC5-Linux-Complete-NoLua-Release
-      MATRIX_EVAL="CC=gcc-5 && CXX=g++-5"
-      BUILD_TYPE=Release
-      ENABLE_SSL_DYNAMIC_LOADING=YES
-      OPENSSL_1_0=YES
-      OPENSSL_1_1=NO
-      ENABLE_CXX=NO
-      ENABLE_LUA_SHARED=NO
-      C_STANDARD=auto
-      CXX_STANDARD=auto
-      BUILD_SHARED=NO
-      NO_FILES=NO
-      ENABLE_SSL=YES
-      NO_CGI=NO
-      ENABLE_IPV6=YES
-      ENABLE_WEBSOCKETS=YES
-      ENABLE_SERVER_STATS=YES
-      ENABLE_LUA=NO
-      ENABLE_DUKTAPE=NO
-      NO_CACHING=YES
-      ALLOW_WARNINGS=YES
-      RUN_UNITTEST=1
-
-
-#########################################################################################
-#####   COVERAGE   ######################################################################
-#########################################################################################
-
-  - os: linux
-    sudo: required
-    compiler: clang
-    env:
-      idx=6
-      N=GCCAnyVersion-Linux-Coverage
-      BUILD_TYPE=Coverage
-      ENABLE_SSL_DYNAMIC_LOADING=YES
-      OPENSSL_1_0=YES
-      OPENSSL_1_1=NO
-      ENABLE_CXX=NO
-      ENABLE_LUA_SHARED=NO
-      C_STANDARD=auto
-      CXX_STANDARD=auto
-      BUILD_SHARED=NO
-      NO_FILES=NO
-      ENABLE_SSL=YES
-      NO_CGI=NO
-      ENABLE_IPV6=YES
-      ENABLE_WEBSOCKETS=YES
-      ENABLE_SERVER_STATS=YES
-      ENABLE_LUA=NO
-      ENABLE_DUKTAPE=NO
-      NO_CACHING=NO
-      ALLOW_WARNINGS=YES
-      RUN_UNITTEST=1
-
-#########################################################################################
-#####   SHARED   ########################################################################
-#########################################################################################
-
-  - sudo: false
-    os: linux
-    compiler: clang
-    env:
-      idx=9
-      N=Clang-Linux-Default-Shared
-      BUILD_TYPE=Debug
-      ENABLE_SSL_DYNAMIC_LOADING=YES
-      OPENSSL_1_0=NO
-      OPENSSL_1_1=YES
-      ENABLE_CXX=NO
-      ENABLE_LUA_SHARED=NO
-      C_STANDARD=auto
-      CXX_STANDARD=auto
-      BUILD_SHARED=YES
-      NO_FILES=NO
-      ENABLE_SSL=YES
-      NO_CGI=NO
-      ENABLE_IPV6=NO
-      ENABLE_WEBSOCKETS=NO
-      ENABLE_SERVER_STATS=NO
-      ENABLE_LUA=NO
-      ENABLE_DUKTAPE=NO
-      NO_CACHING=NO
-      ALLOW_WARNINGS=YES
-
-
-#########################################################################################
-#####   BUILD TYPES   ###################################################################
-#########################################################################################
-
-# According to CMakeLists, options are:
-# None Debug Release RelWithDebInfo MinSizeRel Coverage
-
-  -
-    os: linux
-    compiler: gcc
-    env:
-      idx=15
-      N=GCCLinuxDefault_RelWithDebInfo
-      BUILD_TYPE=RelWithDebInfo
-      ENABLE_SSL_DYNAMIC_LOADING=YES
-      OPENSSL_1_0=YES
-      OPENSSL_1_1=NO
-      ENABLE_CXX=NO
-      ENABLE_LUA_SHARED=NO
-      C_STANDARD=auto
-      CXX_STANDARD=auto
-      BUILD_SHARED=NO
-      NO_FILES=NO
-      ENABLE_SSL=YES
-      NO_CGI=NO
-      ENABLE_IPV6=NO
-      ENABLE_WEBSOCKETS=NO
-      ENABLE_LUA=NO
-      ENABLE_DUKTAPE=NO
-      NO_CACHING=NO
-      ALLOW_WARNINGS=YES
-
-  -
-    os: linux
-    compiler: gcc
-    env:
-      idx=16
-      N=GCCLinuxDefault_MinSizeRel
-      BUILD_TYPE=MinSizeRel
-      ENABLE_SSL_DYNAMIC_LOADING=YES
-      OPENSSL_1_0=YES
-      OPENSSL_1_1=NO
-      ENABLE_CXX=NO
-      ENABLE_LUA_SHARED=NO
-      C_STANDARD=auto
-      CXX_STANDARD=auto
-      BUILD_SHARED=NO
-      NO_FILES=NO
-      ENABLE_SSL=YES
-      NO_CGI=NO
-      ENABLE_IPV6=NO
-      ENABLE_WEBSOCKETS=NO
-      ENABLE_LUA=NO
-      ENABLE_DUKTAPE=NO
-      NO_CACHING=NO
-      ALLOW_WARNINGS=YES
-
-  -
-    os: linux
-    compiler: gcc
-    env:
-      idx=17
-      N=GCCLinuxDefault_None
-      BUILD_TYPE=None
-      ENABLE_SSL_DYNAMIC_LOADING=YES
-      OPENSSL_1_0=YES
-      OPENSSL_1_1=NO
-      ENABLE_CXX=NO
-      ENABLE_LUA_SHARED=NO
-      C_STANDARD=auto
-      CXX_STANDARD=auto
-      BUILD_SHARED=NO
-      NO_FILES=NO
-      ENABLE_SSL=YES
-      NO_CGI=NO
-      ENABLE_IPV6=NO
-      ENABLE_WEBSOCKETS=NO
-      ENABLE_LUA=NO
-      ENABLE_DUKTAPE=NO
-      NO_CACHING=NO
-      ALLOW_WARNINGS=YES
-
-#########################################################################################
-#####   XENIAL, BIONIC, FOCAL   #########################################################
-#########################################################################################
-
-  -
-    os: linux
-    compiler: gcc
-    dist: xenial
-    env:
-      idx=20
-      N=GCCLinuxDefault_xenial
-      BUILD_TYPE=Release
-      ENABLE_SSL_DYNAMIC_LOADING=YES
-      OPENSSL_1_0=YES
-      OPENSSL_1_1=NO
-      ENABLE_CXX=NO
-      ENABLE_LUA_SHARED=NO
-      C_STANDARD=auto
-      CXX_STANDARD=auto
-      BUILD_SHARED=NO
-      NO_FILES=NO
-      ENABLE_SSL=YES
-      NO_CGI=NO
-      ENABLE_IPV6=NO
-      ENABLE_WEBSOCKETS=NO
-      ENABLE_LUA=NO
-      ENABLE_DUKTAPE=NO
-      NO_CACHING=NO
-      ALLOW_WARNINGS=YES
-
-  -
-    os: linux
-    compiler: gcc
-    dist: bionic
-    env:
-      idx=21
-      N=GCCLinuxDefault_bionic
-      BUILD_TYPE=Release
-      ENABLE_SSL_DYNAMIC_LOADING=YES
-      OPENSSL_1_0=NO
-      OPENSSL_1_1=YES
-      ENABLE_CXX=NO
-      ENABLE_LUA_SHARED=NO
-      C_STANDARD=auto
-      CXX_STANDARD=auto
-      BUILD_SHARED=NO
-      NO_FILES=NO
-      ENABLE_SSL=YES
-      NO_CGI=NO
-      ENABLE_IPV6=NO
-      ENABLE_WEBSOCKETS=NO
-      ENABLE_LUA=NO
-      ENABLE_DUKTAPE=NO
-      NO_CACHING=NO
-      ALLOW_WARNINGS=YES
-
-  -
-    os: linux
-    compiler: gcc
-    dist: focal
-    addons:
-      apt:
-        packages:
-          - lsb-core
-    env:
-      idx=23
-      N=GCCLinuxDefault_focal
-      BUILD_TYPE=Release
-      ENABLE_SSL_DYNAMIC_LOADING=YES
-      OPENSSL_1_0=NO
-      OPENSSL_1_1=YES
-      ENABLE_CXX=NO
-      ENABLE_LUA_SHARED=NO
-      C_STANDARD=auto
-      CXX_STANDARD=auto
-      BUILD_SHARED=NO
-      NO_FILES=NO
-      ENABLE_SSL=YES
-      NO_CGI=NO
-      ENABLE_IPV6=NO
-      ENABLE_WEBSOCKETS=NO
-      ENABLE_LUA=NO
-      ENABLE_DUKTAPE=NO
-      NO_CACHING=NO
-      ALLOW_WARNINGS=YES
-      RUN_UNITTEST=1
-
-
 #########################################################################################
 #####   FREEBSD BUILD   ###########=#####################################################
 #########################################################################################
@@ -547,116 +142,4 @@ matrix:
       ENABLE_DUKTAPE=NO
       NO_CACHING=NO
       ALLOW_WARNINGS=YES
-      RUN_UNITTEST=1
-
-
-#########################################################################################
-#####   OSX BUILD   #####################################################################
-#########################################################################################
-
-  -
-    os: osx
-    sudo: required
-    compiler: clang
-    env:
-      idx=8
-      N=Clang-OSX-Complete-NoLua-Release-OpenSSL_1_1_NoDynLoad
-      BUILD_TYPE=Release
-      ENABLE_SSL_DYNAMIC_LOADING=NO
-      OPENSSL_1_0=NO
-      OPENSSL_1_1=YES
-      ENABLE_CXX=NO
-      ENABLE_LUA_SHARED=NO
-      C_STANDARD=auto
-      CXX_STANDARD=auto
-      BUILD_SHARED=NO
-      NO_FILES=NO
-      ENABLE_SSL=YES
-      NO_CGI=NO
-      ENABLE_IPV6=YES
-      ENABLE_WEBSOCKETS=YES
-      ENABLE_SERVER_STATS=YES
-      ENABLE_LUA=NO
-      ENABLE_DUKTAPE=NO
-      NO_CACHING=YES
-      ALLOW_WARNINGS=YES
-      OPENSSL_ROOT_DIR="/usr/local/opt/openssl@1.1"
-      LDFLAGS="-L${OPENSSL_ROOT_DIR}/lib"
-      CFLAGS="-I${OPENSSL_ROOT_DIR}/include"
-      ADDITIONAL_CMAKE_ARGS="-DCMAKE_SHARED_LINKER_FLAGS=${LDFLAGS} -DCMAKE_C_FLAGS=${CFLAGS}"
-      PATH="${OPENSSL_ROOT_DIR}/bin:$PATH"
-      DYLD_LIBRARY_PATH="${OPENSSL_ROOT_DIR}/lib:${DYLD_LIBRARY_PATH}"
-      RUN_UNITTEST=1
-
-  -
-    os: osx
-    sudo: required
-    compiler: clang
-    env:
-      idx=11
-      N=OSX-Package
-      BUILD_TYPE=Release
-      ENABLE_SSL_DYNAMIC_LOADING=YES
-      OPENSSL_1_0=YES
-      OPENSSL_1_1=NO
-      ENABLE_CXX=NO
-      ENABLE_LUA_SHARED=NO
-      C_STANDARD=auto
-      CXX_STANDARD=auto
-      BUILD_SHARED=NO
-      NO_FILES=NO
-      ENABLE_SSL=YES
-      NO_CGI=NO
-      ENABLE_IPV6=YES
-      ENABLE_WEBSOCKETS=YES
-      ENABLE_SERVER_STATS=NO
-      ENABLE_LUA=NO
-      ENABLE_DUKTAPE=NO
-      NO_CACHING=NO
-      ALLOW_WARNINGS=YES
-      MACOSX_PACKAGE=1
-
-#########################################################################################
-#########################################################################################
-#####   END OF BUILD MATRIX   ###########################################################
-#########################################################################################
-#########################################################################################
-
-# Remove Lua build, until someone knows how to fix the CMake files
-#
-#  - dist: trusty
-#    sudo: required
-#    os: linux
-#    compiler: clang
-#    addons:
-#      apt:
-#        sources:
-#          - ubuntu-toolchain-r-test
-#          - llvm-toolchain-precise-3.8
-#        packages:
-#          - clang-3.8
-#          - lua5.2
-#    env:
-#      idx=99
-#      N=Clang3.8-Linux-Complete-WithLua-Debug
-#      MATRIX_EVAL="CC=clang-3.8 && CXX=clang++-3.8"
-#      BUILD_TYPE=Debug
-#      ENABLE_SSL_DYNAMIC_LOADING=YES
-#      OPENSSL_1_0=NO
-#      OPENSSL_1_1=YES
-#      ENABLE_CXX=NO
-#      ENABLE_LUA_SHARED=YES
-#      C_STANDARD=auto
-#      CXX_STANDARD=auto
-#      BUILD_SHARED=NO
-#      NO_FILES=NO
-#      ENABLE_SSL=YES
-#      NO_CGI=NO
-#      ENABLE_IPV6=YES
-#      ENABLE_WEBSOCKETS=YES
-#      ENABLE_SERVER_STATS=YES
-#      ENABLE_LUA=YES
-#      ENABLE_LUA_SHARED=YES
-#      ENABLE_DUKTAPE=NO
-#      NO_CACHING=YES
-#      ALLOW_WARNINGS=YES
+      RUN_UNITTEST=1

+ 16 - 8
CMakeLists.txt

@@ -1,8 +1,5 @@
-# Use at least CMake 3.3
-cmake_minimum_required (VERSION 3.3.0)
-cmake_policy(VERSION 3.2.2)
-cmake_policy(SET CMP0054 NEW)
-cmake_policy(SET CMP0057 NEW)
+cmake_minimum_required(VERSION 3.10)
+cmake_policy(VERSION 3.10)
 
 # Set up the project
 project (civetweb VERSION 1.16.0)
@@ -80,6 +77,10 @@ message(STATUS "Disable caching support - ${CIVETWEB_DISABLE_CACHING}")
 option(CIVETWEB_ENABLE_CXX "Enables the C++ wrapper library" OFF)
 message(STATUS "C++ wrappers - ${CIVETWEB_ENABLE_CXX}")
 
+# HTTP2 Support
+option(CIVETWEB_ENABLE_HTTP2 "Enables HTTP2 support" OFF)
+message(STATUS "Use HTPP2 - ${CIVETWEB_ENABLE_HTTP2}")
+
 # IP Version 6
 option(CIVETWEB_ENABLE_IPV6 "Enables the IP version 6 support" ON)
 message(STATUS "IP Version 6 - ${CIVETWEB_ENABLE_IPV6}")
@@ -184,7 +185,7 @@ if (CIVETWEB_ENABLE_LUA)
   mark_as_advanced(CIVETWEB_LUA_SQLITE_VERSION)
 
   # Lua SQLite Verification Hash
-  set(CIVETWEB_LUA_SQLITE_MD5_HASH 43234ae08197dfce6da02482ed14ec92 CACHE STRING
+  set(CIVETWEB_LUA_SQLITE_MD5_HASH ff7abd4aa8bd549eb18298fb954612f8 CACHE STRING
     "The hash of Lua SQLite archive to be downloaded")
   set_property(CACHE CIVETWEB_LUA_SQLITE_MD5_HASH PROPERTY VALUE ${CIVETWEB_LUA_SQLITE_MD5_HASH})
   mark_as_advanced(CIVETWEB_LUA_SQLITE_MD5_HASH)
@@ -236,8 +237,11 @@ message(STATUS "Compile for OpenSSL 1.1 API - ${CIVETWEB_SSL_OPENSSL_API_1_1}")
 option(CIVETWEB_SSL_OPENSSL_API_3_0 "Use the OpenSSL 3.0 API" OFF)
 message(STATUS "Compile for OpenSSL 3.0 API - ${CIVETWEB_SSL_OPENSSL_API_3_0}")
 
+option(CIVETWEB_ENABLE_GNUTLS "Use the GnuTls" OFF)
+message(STATUS "SSL support (GnuTLS)  - ${CIVETWEB_ENABLE_GNUTLS}")
+
 option(CIVETWEB_ENABLE_MBEDTLS "Use the MbedTls" OFF)
-message(STATUS "SSL support - ${CIVETWEB_ENABLE_MBEDTLS}")
+message(STATUS "SSL support (MbedTLS) - ${CIVETWEB_ENABLE_MBEDTLS}")
 
 # Dynamically load or link the SSL libraries
 cmake_dependent_option(
@@ -496,6 +500,9 @@ if (${CMAKE_BUILD_TYPE} MATCHES "[Dd]ebug")
   add_definitions(-O0)
   add_definitions(-g)
 endif()
+if (CIVETWEB_ENABLE_HTTP2)
+  add_definitions(-DUSE_HTTP2)
+endif()
 if (CIVETWEB_ENABLE_IPV6)
   add_definitions(-DUSE_IPV6)
 endif()
@@ -534,6 +541,8 @@ if (CIVETWEB_ENABLE_MEMORY_DEBUGGING)
 endif()
 if (NOT CIVETWEB_ENABLE_SSL)
   add_definitions(-DNO_SSL)
+elseif (CIVETWEB_ENABLE_GNUTLS)
+  add_definitions(-DUSE_GNUTLS)
 elseif (CIVETWEB_ENABLE_MBEDTLS)
   add_definitions(-DUSE_MBEDTLS)
 elseif (NOT CIVETWEB_ENABLE_SSL_DYNAMIC_LOADING)
@@ -699,4 +708,3 @@ set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_PACKAGE_DEPENDS}")
 
 # Finalize CPack settings
 include(CPack)
-

+ 11 - 2
Makefile

@@ -71,7 +71,11 @@ ifdef WITH_CFLAGS
   CFLAGS += $(WITH_CFLAGS)
 endif
 
-LIBS = -lpthread -lm $(LOPT)
+LIBS =
+ifneq ($(TARGET_OS), RTEMS)
+LIBS += -lpthread
+endif
+LIBS += -lm $(LOPT)
 
 ifdef WITH_DEBUG
   CFLAGS += -g -DDEBUG
@@ -93,6 +97,9 @@ endif
 
 ifdef NO_SSL
   CFLAGS += -DNO_SSL
+else ifdef WITH_GNUTLS
+  CFLAGS += -DUSE_GNUTLS
+  LIBS += -lgnutls -lhogweed -lgmp -lnettle
 else ifdef WITH_MBEDTLS
   CFLAGS += -DUSE_MBEDTLS
   LIBS += -lmbedcrypto -lmbedtls -lmbedx509
@@ -178,7 +185,9 @@ ifdef WITH_COMPRESSION
 endif
 
 ifdef WITH_ZLIB
+ifneq ($(TARGET_OS), RTEMS)
   LIBS += -lz
+endif
   CFLAGS += -DUSE_ZLIB
 endif
 
@@ -297,6 +306,7 @@ help:
 	@echo "   WITH_CPP=1            build library with c++ classes"
 	@echo "   WITH_EXPERIMENTAL=1   build with experimental features"
 	@echo "   WITH_DAEMONIZE=1      build with daemonize."
+	@echo "   WITH_GNUTLS=1         build with GnuTLS support."
 	@echo "   WITH_MBEDTLS=1        build with mbedTLS support."
 	@echo "   WITH_OPENSSL_API_1_0=1  build with OpenSSL 1.0.x support."
 	@echo "   WITH_OPENSSL_API_1_1=1  build with OpenSSL 1.1.x support."
@@ -446,4 +456,3 @@ indent:
 	astyle --suffix=none --style=linux --indent=spaces=4 --lineend=linux  include/*.h src/*.c src/*.cpp src/*.inl examples/*/*.c  examples/*/*.cpp
 
 .PHONY: all help build install clean lib so
-

+ 1 - 1
Makefile.osx

@@ -13,7 +13,7 @@ WITH_LUA = 1
 PACKAGE = Civetweb
 BUILD_DIR = out
 
-CFLAGS += -DUSE_COCOA -DENABLE_CREATE_CONFIG_FILE -mmacosx-version-min=10.4 -ObjC -arch i386 -arch x86_64
+CFLAGS += -DUSE_COCOA -DENABLE_CREATE_CONFIG_FILE -mmacosx-version-min=10.6 -ObjC -arch x86_64 -arch arm64
 LDFLAGS += -framework Cocoa
 
 DMG_DIR = $(BUILD_DIR)/dmg

+ 2 - 0
README.md

@@ -134,6 +134,8 @@ simplicity by a carefully selected list of features:
 
 [Mbed TLS](https://github.com/ARMmbed/mbedtls)
 
+[GNU TLS](https://gnutls.org)
+
 
 Support
 -------

+ 3 - 2
docs/Building.md

@@ -179,8 +179,9 @@ make build COPT="-DNDEBUG -DNO_CGI"
 | `SSL_ALREADY_INITIALIZED`    | do not initialize libcrypto                                         |
 | `OPENSSL_API_1_0`            | Use OpenSSL V1.0.x interface                                        |
 | `OPENSSL_API_1_1`            | Use OpenSSL V1.1.x interface                                        |
-| `OPENSSL_API_3_0`            | Use OpenSSL V3.0.x interface                                          |
-| `USE_MBEDTLS`                | Use MbedTLS (cannot be combined with OPENSSL_API_*)                 |
+| `OPENSSL_API_3_0`            | Use OpenSSL V3.0.x interface                                        |
+| `USE_GNUTLS`                 | Use GnuTLS (cannot be combined with OPENSSL_API_* or USE_MBEDTLS)   |
+| `USE_MBEDTLS`                | Use MbedTLS (cannot be combined with OPENSSL_API_* or USE_GNUTLS)   |
 |                              |                                                                     |
 | `BUILD_DATE`                 | define as a string to be used as build id instead of __DATE__       |
 |                              |                                                                     |

+ 8 - 3
docs/UserManual.md

@@ -440,11 +440,15 @@ of 1000.
 ### listening\_ports `8080`
 Comma-separated list of ports to listen on. If the port is SSL, a
 letter `s` must be appended, for example, `80,443s` will open
-port 80 and port 443, and connections on port 443 will be SSL-ed.
+port 80 and port 443, and connections on port 443 will be SSL-ed. If the port
+should be optional the letter `o` must be appended, for example with `80o,443s`
+the server will not exit if binding to port 80 is not possible during startup.
 For non-SSL ports, it is allowed to append letter `r`, meaning 'redirect'.
 Redirect ports will redirect all their traffic to the first configured
 SSL port. For example, if `listening_ports` is `80r,443s`, then all
 HTTP traffic coming at port 80 will be redirected to HTTPS port 443.
+For ports with redirection configured `authentication_domain` will
+be used as host component of the redirection url.
 
 It is possible to specify an IP address to bind to. In this case,
 an IP address and a colon must be prepended to the port number.
@@ -514,7 +518,7 @@ The script can define callbacks to be notified when the server starts
 or stops. Furthermore, it can be used for log filtering or formatting. 
 The Lua state remains open until the server is stopped.
 
-For a detailed descriotion of available Lua callbacks see section
+For a detailed description of available Lua callbacks see section
 "Lua background script" below.
 
 ### lua\_background\_script\_params
@@ -688,7 +692,8 @@ The OpenSSL cipher string uses different cipher names than IANA
 (see [this mapping](https://testssl.sh/openssl-iana.mapping.html)).
 
 In case CivetWeb is built with a TLS library other than OpenSSL 
-(e.g., [mbedTLS](https://tls.mbed.org/supported-ssl-ciphersuites)), 
+(e.g., [mbedTLS](https://tls.mbed.org/supported-ssl-ciphersuites)
+or [GnuTLS](https://www.gnutls.org/manual/html_node/Supported-ciphersuites.html)), 
 the cipher names may be different.
 
 ### ssl\_default\_verify\_paths `yes`

+ 1 - 1
docs/api/mg_form_data_handler.md

@@ -9,7 +9,7 @@
 |**`field_found`**|**`int field_found( const char *key, const char *filename, char *path, size_t pathlen, void *user_data )`**;|
 ||The callback function `field_found()` is called when a new field has been found. The return value of this callback is used to define how the field should be processed. The parameters contain the following information:|
 ||**`key`** - The name of the field as it was named with the `name` tag in the HTML source.|
-||**`filename`** - The name of the file to upload. Please not that this parameter is only valid when the input type was set to `file`. Otherwise this parameter has the value `NULL`.|
+||**`filename`** - The name of the file to upload. Please note that this parameter is only valid when the input type was set to `file`. Otherwise this parameter has the value `NULL`.|
 ||**`path`** - This is an output parameter used to store the full name of the file including the path to store an incoming file at the computer. This parameter must be provided by the application to Civetweb when a form field of type `file` is found. Please not that together with setting this parameter, the callback function must return `FORM_FIELD_STORAGE_STORE`.i With any other return value the contents of the `path` buffer is ignored by Civetweb.|
 ||**`pathlen`** - The length of the buffer where the output path can be stored.|
 ||**`user_data`** - A pointer to the value of the field `user_data` of the structure `struct mg_form_data_handler`.|

+ 2 - 2
docs/api/mg_server_port.md

@@ -10,8 +10,8 @@
 |**`port`**|`int`|The port number on which the service listens|
 |**`is_ssl`**|`int`|**0** for `HTTP` communication, **1** for `HTTPS`|
 |**`is_redirect`**|`int`|**1** if all requests are redirected, otherwise **0**|
-|**`_reserved1`**|`int`|Reserved for internal use|
-|**`_reserved2`**|`int`|Reserved for internal use|
+|**`is_optional`**|`int`|**1** if port is optional, otherwise **0**|
+|**`is_bound`**|`int`|**1** if the port is bound, otherwise **0**|
 |**`_reserved3`**|`int`|Reserved for internal use|
 |**`_reserved4`**|`int`|Reserved for internal use|
 

+ 20 - 0
docs/gnutls.md

@@ -0,0 +1,20 @@
+#### Use GnuTLS instead of OpenSSL
+=====
+
+1 [Build libgmp](https://gmplib.org)
+
+ - 1.1 [Download source](https://gmplib.org/#DOWNLOAD)
+ - 1.2 ./configure && make && make install
+
+2 [Build libhogweed and libnettle](https://www.lysator.liu.se/~nisse/nettle/)
+
+ - 2.1 [Download source](https://ftp.gnu.org/gnu/nettle/)
+ - 2.2 ./configure && make && make install
+
+3 Build civetweb
+
+ - make build WITH_GNUTLS=1
+
+4 Run civetweb
+ - export LD_LIBRARY_PATH=/usr/local/lib/:$LD_LIBRARY_PATH
+ - ./civetweb  -listening_ports 8443s  -ssl_certificate resources/cert/server.pem  -document_root ./test/htmldir/

+ 1 - 1
examples/linux_ws_server_cpp/CMakeLists.txt

@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.5.1)
+cmake_minimum_required(VERSION 3.10)
 project(linux_ws_server)
 
 set(TARGET_NAME ${PROJECT_NAME})

+ 1 - 0
format.bat

@@ -17,6 +17,7 @@ clang-format -i src/handle_form.inl
 clang-format -i src/response.inl
 clang-format -i src/http2.inl
 clang-format -i src/mod_mbedtls.inl
+clang-format -i src/mod_gnutls.inl
 
 clang-format -i src/third_party/civetweb_lua.h
 

+ 34 - 26
fuzztest/fuzzmain.c

@@ -45,9 +45,6 @@ unsigned short PORT_NUM_HTTP = 0; /* set dynamically */
 	}
 
 
-static uint64_t call_count = 0;
-
-
 /********************************************************/
 /* Init CivetWeb server ... test with mock client       */
 /********************************************************/
@@ -110,6 +107,17 @@ civetweb_init(void)
 	atexit(civetweb_exit);
 }
 
+int LLVMFuzzerInitialize(int *argc, char ***argv);
+
+int
+LLVMFuzzerInitialize(int *argc, char ***argv) {
+  // Silence unused args warning.
+	(void)(argc);
+	(void)(argv);
+
+	civetweb_init();
+  return 0;
+}
 
 #if defined(TEST_FUZZ1)
 static int
@@ -202,19 +210,12 @@ test_civetweb_client(const char *server,
 	return 0;
 }
 
-
 static int
 LLVMFuzzerTestOneInput_URI(const uint8_t *data, size_t size)
 {
 	static char URI[1024 * 64]; /* static, to avoid stack overflow */
 
-	if (call_count == 0) {
-		memset(URI, 0, sizeof(URI));
-		civetweb_init();
-	}
-	call_count++;
-
-	if (size < sizeof(URI)) {
+	if (size+1 < sizeof(URI)) {
 		memcpy(URI, data, size);
 		URI[size] = 0;
 	} else {
@@ -230,11 +231,6 @@ LLVMFuzzerTestOneInput_URI(const uint8_t *data, size_t size)
 static int
 LLVMFuzzerTestOneInput_REQUEST(const uint8_t *data, size_t size)
 {
-	if (call_count == 0) {
-		civetweb_init();
-	}
-	call_count++;
-
 	int r;
 	SOCKET sock = socket(AF_INET, SOCK_STREAM, 6);
 	if (sock == -1) {
@@ -446,15 +442,22 @@ mock_server_init(void)
 	atexit(mock_server_exit);
 }
 
+int LLVMFuzzerInitialize(int *argc, char ***argv);
+
+int
+LLVMFuzzerInitialize(int *argc, char ***argv) {
+  // Silence unused args warning.
+	(void)(argc);
+	(void)(argv);
+
+	mock_server_init();
+  return 0;
+}
+
 
 static int
 LLVMFuzzerTestOneInput_RESPONSE(const uint8_t *data, size_t size)
 {
-	if (call_count == 0) {
-		mock_server_init();
-	}
-	call_count++;
-
 	if (size > sizeof(RESPONSE.data)) {
 		return 1;
 	}
@@ -497,21 +500,26 @@ LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 {
 #if defined(TEST_FUZZ1)
 	/* fuzz target 1: different URI for HTTP/1 server */
-	return LLVMFuzzerTestOneInput_URI(data, size);
+	LLVMFuzzerTestOneInput_URI(data, size);
+	return 0;
 #elif defined(TEST_FUZZ2)
 	/* fuzz target 2: different requests for HTTP/1 server */
-	return LLVMFuzzerTestOneInput_REQUEST(data, size);
+	LLVMFuzzerTestOneInput_REQUEST(data, size);
+	return 0;
 #elif defined(TEST_FUZZ3)
 	/* fuzz target 3: different responses for HTTP/1 client */
-	return LLVMFuzzerTestOneInput_RESPONSE(data, size);
+	LLVMFuzzerTestOneInput_RESPONSE(data, size);
+	return 0;
 #elif defined(TEST_FUZZ4)
 #error "Only useful in HTTP/2 feature branch"
 	/* fuzz target 4: different requests for HTTP/2 server */
-	return LLVMFuzzerTestOneInput_REQUEST_HTTP2(data, size);
+	LLVMFuzzerTestOneInput_REQUEST_HTTP2(data, size);
+	return 0;
 #elif defined(TEST_FUZZ5)
 	/* fuzz target 5: calling an internal server test function,
 	 *                bypassing network sockets */
-	return LLVMFuzzerTestOneInput_process_new_connection(data, size);
+	LLVMFuzzerTestOneInput_process_new_connection(data, size);
+	return 0;
 #else
 /* planned targets */
 #error "Unknown fuzz target"

+ 2 - 2
include/civetweb.h

@@ -714,8 +714,8 @@ struct mg_server_port {
 	int port;        /* port number */
 	int is_ssl;      /* https port: 0 = no, 1 = yes */
 	int is_redirect; /* redirect all requests: 0 = no, 1 = yes */
-	int _reserved1;
-	int _reserved2;
+	int is_optional; /* optional: 0 = no, 1 = yes */
+	int is_bound;    /* bound: 0 = no, 1 = yes, relevant for optional ports */
 	int _reserved3;
 	int _reserved4;
 };

+ 6 - 1
src/CMakeLists.txt

@@ -47,7 +47,12 @@ endif()
 
 # We need to link OpenSSL if not dynamically loading
 if (CIVETWEB_ENABLE_SSL)
-  if (CIVETWEB_ENABLE_MBEDTLS)
+  if (CIVETWEB_ENABLE_GNUTLS)
+    find_package(GnuTLS)
+    include_directories(${GNUTLS_INCLUDE_DIR})
+    message(STATUS "GnuTLS include directory: ${GNUTLS_INCLUDE_DIR}")
+    target_link_libraries(civetweb-c-library ${GNUTLS_LIBRARIES})
+  elseif (CIVETWEB_ENABLE_MBEDTLS)
     find_package(MbedTLS)
     include_directories(${MbedTLS_INCLUDE_DIR})
     message(STATUS "MbedTLS include directory: ${MbedTLS_INCLUDE_DIR}")

+ 204 - 21
src/civetweb.c

@@ -183,6 +183,10 @@ mg_static_assert(sizeof(void *) >= sizeof(int), "data type size check");
 #error "Symbian is no longer maintained. CivetWeb no longer supports Symbian."
 #endif /* __SYMBIAN32__ */
 
+#if defined(__rtems__)
+#include <rtems/version.h>
+#endif
+
 #if defined(__ZEPHYR__)
 #include <ctype.h>
 #include <fcntl.h>
@@ -885,7 +889,9 @@ typedef unsigned short int in_port_t;
 #include <string.h>
 #include <sys/socket.h>
 #include <sys/time.h>
+#if !defined(__rtems__)
 #include <sys/utsname.h>
+#endif
 #include <sys/wait.h>
 #include <time.h>
 #include <unistd.h>
@@ -901,8 +907,22 @@ typedef unsigned short int in_port_t;
 #endif
 
 #if defined(__MACH__) && defined(__APPLE__)
-#define SSL_LIB "libssl.dylib"
-#define CRYPTO_LIB "libcrypto.dylib"
+
+#if defined(OPENSSL_API_3_0)
+#define SSL_LIB "libssl.3.dylib"
+#define CRYPTO_LIB "libcrypto.3.dylib"
+#endif
+
+#if defined(OPENSSL_API_1_1)
+#define SSL_LIB "libssl.1.1.dylib"
+#define CRYPTO_LIB "libcrypto.1.1.dylib"
+#endif /* OPENSSL_API_1_1 */
+
+#if defined(OPENSSL_API_1_0)
+#define SSL_LIB "libssl.1.0.dylib"
+#define CRYPTO_LIB "libcrypto.1.0.dylib"
+#endif /* OPENSSL_API_1_0 */
+
 #else
 #if !defined(SSL_LIB)
 #define SSL_LIB "libssl.so"
@@ -963,7 +983,7 @@ count_leap(int y)
 	return (y - 1969) / 4 - (y - 1901) / 100 + (y - 1601) / 400;
 }
 
-time_t
+static time_t
 timegm(struct tm *tm)
 {
 	static const unsigned short ydays[] = {
@@ -1551,11 +1571,13 @@ static void mg_snprintf(const struct mg_connection *conn,
 #if defined(vsnprintf)
 #undef vsnprintf
 #endif
+#if !defined(NDEBUG)
 #define malloc DO_NOT_USE_THIS_FUNCTION__USE_mg_malloc
 #define calloc DO_NOT_USE_THIS_FUNCTION__USE_mg_calloc
 #define realloc DO_NOT_USE_THIS_FUNCTION__USE_mg_realloc
 #define free DO_NOT_USE_THIS_FUNCTION__USE_mg_free
 #define snprintf DO_NOT_USE_THIS_FUNCTION__USE_mg_snprintf
+#endif
 #if defined(_WIN32)
 /* vsnprintf must not be used in any system,
  * but this define only works well for Windows. */
@@ -1572,8 +1594,9 @@ static int mg_init_library_called = 0;
 static int mg_openssl_initialized = 0;
 #endif
 #if !defined(OPENSSL_API_1_0) && !defined(OPENSSL_API_1_1)                     \
-    && !defined(OPENSSL_API_3_0) && !defined(USE_MBEDTLS)
-#error "Please define OPENSSL_API_#_# or USE_MBEDTLS"
+    && !defined(OPENSSL_API_3_0) && !defined(USE_MBEDTLS)                      \
+	&& !defined(USE_GNUTLS)
+#error "Please define OPENSSL_API_#_# or USE_MBEDTLS or USE_GNUTLS"
 #endif
 #if defined(OPENSSL_API_1_0) && defined(OPENSSL_API_1_1)
 #error "Multiple OPENSSL_API versions defined"
@@ -1586,7 +1609,10 @@ static int mg_openssl_initialized = 0;
 #endif
 #if (defined(OPENSSL_API_1_0) || defined(OPENSSL_API_1_1)                      \
      || defined(OPENSSL_API_3_0))                                              \
-    && defined(USE_MBEDTLS)
+    && (defined(USE_MBEDTLS) || defined(USE_GNUTLS))
+#error "Multiple SSL libraries defined"
+#endif
+#if defined(USE_MBEDTLS) && defined(USE_GNUTLS)
 #error "Multiple SSL libraries defined"
 #endif
 #endif
@@ -1751,11 +1777,15 @@ typedef int socklen_t;
 #endif
 
 
-/* SSL: mbedTLS vs. no-ssl vs. OpenSSL */
+/* SSL: mbedTLS vs. GnuTLS vs. no-ssl vs. OpenSSL */
 #if defined(USE_MBEDTLS)
 /* mbedTLS */
 #include "mod_mbedtls.inl"
 
+#elif defined(USE_GNUTLS)
+/* GnuTLS */
+#include "mod_gnutls.inl"
+
 #elif defined(NO_SSL)
 /* no SSL */
 typedef struct SSL SSL; /* dummy for SSL argument to push/pull */
@@ -2544,7 +2574,6 @@ struct mg_connection {
 	                 * versions. For the current definition, see
 	                 * mg_get_connection_info_impl */
 #endif
-
 	SSL *ssl;               /* SSL descriptor */
 	struct socket client;   /* Connected client */
 	time_t conn_birth_time; /* Time (wall clock) when connection was
@@ -3310,6 +3339,8 @@ mg_get_server_ports(const struct mg_context *ctx,
 		    ntohs(USA_IN_PORT_UNSAFE(&(ctx->listening_sockets[i].lsa)));
 		ports[cnt].is_ssl = ctx->listening_sockets[i].is_ssl;
 		ports[cnt].is_redirect = ctx->listening_sockets[i].ssl_redir;
+		ports[cnt].is_optional = ctx->listening_sockets[i].is_optional;
+		ports[cnt].is_bound = ctx->listening_sockets[i].sock != INVALID_SOCKET;
 
 		if (ctx->listening_sockets[i].lsa.sa.sa_family == AF_INET) {
 			/* IPv4 */
@@ -6111,7 +6142,7 @@ push_inner(struct mg_context *ctx,
 		return -2;
 	}
 
-#if defined(NO_SSL) && !defined(USE_MBEDTLS)
+#if defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS)
 	if (ssl) {
 		return -2;
 	}
@@ -6137,6 +6168,16 @@ push_inner(struct mg_context *ctx,
 				err = 0;
 			}
 		} else
+#elif defined(USE_GNUTLS)
+		if (ssl != NULL) {
+			n = gtls_ssl_write(ssl, (const unsigned char *)buf, (size_t) len);
+			if (n < 0) {
+				fprintf(stderr, "SSL write failed (%d): %s", n, gnutls_strerror(n));
+				return -2;
+			} else {
+				err = 0;
+			}
+		} else
 #elif !defined(NO_SSL)
 		if (ssl != NULL) {
 			ERR_clear_error();
@@ -6393,6 +6434,62 @@ pull_inner(FILE *fp,
 			nread = 0;
 		}
 
+#elif defined(USE_GNUTLS)
+	} else if (conn->ssl != NULL) {
+		struct mg_pollfd pfd[2];
+		size_t to_read;
+		int pollres;
+		unsigned int num_sock = 1;
+
+		to_read = gnutls_record_check_pending(conn->ssl->sess);
+
+		if (to_read > 0) {
+			/* We already know there is no more data buffered in conn->buf
+			 * but there is more available in the SSL layer. So don't poll
+			 * conn->client.sock yet. */
+
+			pollres = 1;
+			if (to_read > (size_t)len)
+				to_read = (size_t)len;
+		} else {
+			pfd[0].fd = conn->client.sock;
+			pfd[0].events = POLLIN;
+
+			if (conn->phys_ctx->context_type == CONTEXT_SERVER) {
+				pfd[num_sock].fd =
+				    conn->phys_ctx->thread_shutdown_notification_socket;
+				pfd[num_sock].events = POLLIN;
+				num_sock++;
+			}
+
+			to_read = (size_t)len;
+
+			pollres = mg_poll(pfd,
+			                  num_sock,
+			                  (int)(timeout * 1000.0),
+			                  &(conn->phys_ctx->stop_flag));
+
+			if (!STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag)) {
+				return -2;
+			}
+		}
+
+		if (pollres > 0) {
+			nread = gtls_ssl_read(conn->ssl, (unsigned char *)buf, to_read);
+			if (nread < 0) {
+				fprintf(stderr, "SSL read failed (%d): %s", nread, gnutls_strerror(nread));
+				return -2;
+			} else {
+				err = 0;
+			}
+		} else if (pollres < 0) {
+			/* Error */
+			return -2;
+		} else {
+			/* pollres = 0 means timeout */
+			nread = 0;
+		}
+
 #elif !defined(NO_SSL)
 	} else if (conn->ssl != NULL) {
 		int ssl_pending;
@@ -9535,7 +9632,7 @@ connect_socket(
 		return 0;
 	}
 
-#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(NO_SSL_DL)
+#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS) && !defined(NO_SSL_DL)
 #if defined(OPENSSL_API_1_1) || defined(OPENSSL_API_3_0)
 	if (use_ssl && (TLS_client_method == NULL)) {
 		if (error != NULL) {
@@ -10981,7 +11078,7 @@ parse_http_headers(char **buf, struct mg_header hdr[MG_MAX_HEADERS])
 		}
 
 		/* here *dp is either 0 or '\n' */
-		/* in any case, we have a new header */
+		/* in any case, we have found a complete header */
 		num_headers = i + 1;
 
 		if (*dp) {
@@ -10990,9 +11087,11 @@ parse_http_headers(char **buf, struct mg_header hdr[MG_MAX_HEADERS])
 			*buf = dp;
 
 			if ((dp[0] == '\r') || (dp[0] == '\n')) {
-				/* This is the end of the header */
+				/* We've had CRLF twice in a row
+				 * This is the end of the headers */
 				break;
 			}
+			/* continue within the loop, find the next header */
 		} else {
 			*buf = dp;
 			break;
@@ -16057,6 +16156,10 @@ set_ports_option(struct mg_context *phys_ctx)
 			mg_cry_ctx_internal(phys_ctx,
 			                    "cannot create socket (entry %i)",
 			                    portsTotal);
+			if (so.is_optional) {
+				portsOk++; /* it's okay if we couldn't create a socket,
+						this port is optional anyway */
+			}
 			continue;
 		}
 
@@ -16142,6 +16245,10 @@ set_ports_option(struct mg_context *phys_ctx)
 #else
 			mg_cry_ctx_internal(phys_ctx, "%s", "IPv6 not available");
 			closesocket(so.sock);
+			if (so.is_optional) {
+				portsOk++; /* it's okay if we couldn't set the socket option,
+				              this port is optional anyway */
+			}
 			so.sock = INVALID_SOCKET;
 			continue;
 #endif
@@ -16638,6 +16745,37 @@ mg_sslctx_init(struct mg_context *phys_ctx, struct mg_domain_context *dom_ctx)
 	           : 0;
 }
 
+#elif defined(USE_GNUTLS)
+/* Check if SSL is required.
+ * If so, set up ctx->ssl_ctx pointer. */
+static int
+mg_sslctx_init(struct mg_context *phys_ctx, struct mg_domain_context *dom_ctx)
+{
+	if (!phys_ctx) {
+		return 0;
+	}
+
+	if (!dom_ctx) {
+		dom_ctx = &(phys_ctx->dd);
+	}
+
+	if (!is_ssl_port_used(dom_ctx->config[LISTENING_PORTS])) {
+		/* No SSL port is set. No need to setup SSL. */
+		return 1;
+	}
+
+	dom_ctx->ssl_ctx = (SSL_CTX *)mg_calloc(1, sizeof(*dom_ctx->ssl_ctx));
+	if (dom_ctx->ssl_ctx == NULL) {
+		fprintf(stderr, "ssl_ctx malloc failed\n");
+		return 0;
+	}
+
+	return gtls_sslctx_init(dom_ctx->ssl_ctx, dom_ctx->config[SSL_CERTIFICATE])
+	               == 0
+	           ? 1
+	           : 0;
+}
+
 #elif !defined(NO_SSL)
 
 static int ssl_use_pem_file(struct mg_context *phys_ctx,
@@ -17913,7 +18051,7 @@ uninitialize_openssl(void)
 #endif /* OPENSSL_API_1_1 || OPENSSL_API_3_0 */
 	}
 }
-#endif /* !defined(NO_SSL) && !defined(USE_MBEDTLS) */
+#endif /* !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS) */
 
 
 #if !defined(NO_FILESYSTEMS)
@@ -18185,6 +18323,11 @@ close_connection(struct mg_connection *conn)
 		mbed_ssl_close(conn->ssl);
 		conn->ssl = NULL;
 	}
+#elif defined(USE_GNUTLS)
+	if (conn->ssl != NULL) {
+		gtls_ssl_close(conn->ssl);
+		conn->ssl = NULL;
+	}
 #elif !defined(NO_SSL)
 	if (conn->ssl != NULL) {
 		/* Run SSL_shutdown twice to ensure completely close SSL connection
@@ -18256,7 +18399,7 @@ mg_close_connection(struct mg_connection *conn)
 
 	close_connection(conn);
 
-#if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client
+#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS) // TODO: mbedTLS client
 	if (((conn->phys_ctx->context_type == CONTEXT_HTTP_CLIENT)
 	     || (conn->phys_ctx->context_type == CONTEXT_WS_CLIENT))
 	    && (conn->phys_ctx->dd.ssl_ctx != NULL)) {
@@ -18358,7 +18501,7 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 		return NULL;
 	}
 
-#if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client
+#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS) // TODO: mbedTLS client
 #if (defined(OPENSSL_API_1_1) || defined(OPENSSL_API_3_0))                     \
     && !defined(NO_SSL_DL)
 
@@ -18435,7 +18578,7 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 			            error->text_buffer_size,
 			            "Can not create mutex");
 		}
-#if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client
+#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS) // TODO: mbedTLS client
 		SSL_CTX_free(conn->dom_ctx->ssl_ctx);
 #endif
 		closesocket(sock);
@@ -18443,7 +18586,7 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 		return NULL;
 	}
 
-#if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client
+#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS) // TODO: mbedTLS client
 	if (use_ssl) {
 		/* TODO: Check ssl_verify_peer and ssl_ca_path here.
 		 * SSL_CTX_set_verify call is needed to switch off server
@@ -19530,6 +19673,9 @@ mg_connect_websocket_client(const char *host,
 	memset(&client_options, 0, sizeof(client_options));
 	client_options.host = host;
 	client_options.port = port;
+	if (use_ssl) {
+		client_options.host_name = host;
+	}
 
 	return mg_connect_websocket_client_impl(&client_options,
 	                                        use_ssl,
@@ -20161,6 +20307,24 @@ worker_thread_run(struct mg_connection *conn)
 				close_connection(conn);
 			}
 
+#elif defined(USE_GNUTLS)
+			/* HTTPS connection */
+			if (gtls_ssl_accept(&(conn->ssl),
+			                    conn->dom_ctx->ssl_ctx,
+			                    conn->client.sock,
+			                    conn->phys_ctx)
+			    == 0) {
+				/* conn->dom_ctx is set in get_request */
+				/* process HTTPS connection */
+				init_connection(conn);
+				conn->connection_type = CONNECTION_TYPE_REQUEST;
+				conn->protocol_type = PROTOCOL_TYPE_HTTP1;
+				process_new_connection(conn);
+			} else {
+				/* make sure the connection is cleaned up on SSL failure */
+				close_connection(conn);
+			}
+
 #elif !defined(NO_SSL)
 			/* HTTPS connection */
 			if (sslize(conn, SSL_accept, NULL)) {
@@ -20668,6 +20832,13 @@ free_context(struct mg_context *ctx)
 		ctx->dd.ssl_ctx = NULL;
 	}
 
+#elif defined(USE_GNUTLS)
+	if (ctx->dd.ssl_ctx != NULL) {
+		gtls_sslctx_uninit(ctx->dd.ssl_ctx);
+		mg_free(ctx->dd.ssl_ctx);
+		ctx->dd.ssl_ctx = NULL;
+	}
+
 #elif !defined(NO_SSL)
 	/* Deallocate SSL context */
 	if (ctx->dd.ssl_ctx != NULL) {
@@ -20784,6 +20955,8 @@ get_system_name(char **sysName)
 
 	*sysName = mg_strdup(name);
 
+#elif defined(__rtems__)
+	*sysName = mg_strdup("RTEMS");
 #elif defined(__ZEPHYR__)
 	*sysName = mg_strdup("Zephyr OS");
 #else
@@ -21356,7 +21529,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 	}
 #endif
 
-#if defined(USE_MBEDTLS)
+#if defined(USE_MBEDTLS) || defined(USE_GNUTLS)
 	if (!mg_sslctx_init(ctx, NULL)) {
 		const char *err_msg = "Error initializing SSL context";
 		/* Fatal error - abort start. */
@@ -21841,7 +22014,7 @@ mg_start_domain2(struct mg_context *ctx,
 	new_dom->shared_lua_websockets = NULL;
 #endif
 
-#if !defined(NO_SSL) && !defined(USE_MBEDTLS)
+#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS)
 	if (!init_ssl_ctx(ctx, new_dom)) {
 		/* Init SSL failed */
 		if (error != NULL) {
@@ -21920,7 +22093,7 @@ mg_check_feature(unsigned feature)
 #if !defined(NO_FILES)
 	                                    | MG_FEATURES_FILES
 #endif
-#if !defined(NO_SSL) || defined(USE_MBEDTLS)
+#if !defined(NO_SSL) || defined(USE_MBEDTLS) || defined(USE_GNUTLS)
 	                                    | MG_FEATURES_SSL
 #endif
 #if !defined(NO_CGI)
@@ -22077,13 +22250,23 @@ mg_get_system_info(char *buffer, int buflen)
 		            (unsigned)si.dwNumberOfProcessors,
 		            (unsigned)si.dwActiveProcessorMask);
 		system_info_length += mg_str_append(&buffer, end, block);
-#elif defined(__ZEPHYR__)
+#elif defined(__rtems__)
 		mg_snprintf(NULL,
 		            NULL,
 		            block,
 		            sizeof(block),
 		            ",%s\"os\" : \"%s %s\"",
 		            eol,
+		           "RTEMS",
+		            rtems_version());
+		system_info_length += mg_str_append(&buffer, end, block);
+#elif defined(__ZEPHYR__)
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            ",%s\"os\" : \"%s\"",
+		            eol,
 		            "Zephyr OS",
 		            ZEPHYR_VERSION);
 		system_info_length += mg_str_append(&buffer, end, block);

+ 75 - 39
src/handle_form.inl

@@ -162,14 +162,17 @@ search_boundary(const char *buf,
                 const char *boundary,
                 size_t boundary_len)
 {
-	/* We must do a binary search here, not a string search, since the buffer
-	 * may contain '\x00' bytes, if binary data is transferred. */
-	int clen = (int)buf_len - (int)boundary_len - 4;
+	char *boundary_start = "\r\n--";
+	size_t boundary_start_len = strlen(boundary_start);
+
+	/* We must do a binary search here, not a string search, since the
+	 * buffer may contain '\x00' bytes, if binary data is transferred. */
+	int clen = (int)buf_len - (int)boundary_len - boundary_start_len;
 	int i;
 
 	for (i = 0; i <= clen; i++) {
-		if (!memcmp(buf + i, "\r\n--", 4)) {
-			if (!memcmp(buf + i + 4, boundary, boundary_len)) {
+		if (!memcmp(buf + i, boundary_start, boundary_start_len)) {
+			if (!memcmp(buf + i + boundary_start_len, boundary, boundary_len)) {
 				return buf + i;
 			}
 		}
@@ -185,7 +188,7 @@ mg_handle_form_request(struct mg_connection *conn,
 	char path[512];
 	char buf[MG_BUF_LEN]; /* Must not be smaller than ~900 */
 	int field_storage;
-	int buf_fill = 0;
+	size_t buf_fill = 0;
 	int r;
 	int field_count = 0;
 	struct mg_file fstore = STRUCT_FILE_INITIALIZER;
@@ -394,10 +397,10 @@ mg_handle_form_request(struct mg_connection *conn,
 			int end_of_key_value_pair_found = 0;
 			int get_block;
 
-			if ((size_t)buf_fill < (sizeof(buf) - 1)) {
+			if (buf_fill < (sizeof(buf) - 1)) {
 
-				size_t to_read = sizeof(buf) - 1 - (size_t)buf_fill;
-				r = mg_read(conn, buf + (size_t)buf_fill, to_read);
+				size_t to_read = sizeof(buf) - 1 - buf_fill;
+				r = mg_read(conn, buf + buf_fill, to_read);
 				if ((r < 0) || ((r == 0) && all_data_read)) {
 					/* read error */
 					return -1;
@@ -526,11 +529,11 @@ mg_handle_form_request(struct mg_connection *conn,
 					        buf + (size_t)used,
 					        sizeof(buf) - (size_t)used);
 					next = buf;
-					buf_fill -= (int)used;
-					if ((size_t)buf_fill < (sizeof(buf) - 1)) {
+					buf_fill -= used;
+					if (buf_fill < (sizeof(buf) - 1)) {
 
-						size_t to_read = sizeof(buf) - 1 - (size_t)buf_fill;
-						r = mg_read(conn, buf + (size_t)buf_fill, to_read);
+						size_t to_read = sizeof(buf) - 1 - buf_fill;
+						r = mg_read(conn, buf + buf_fill, to_read);
 						if ((r < 0) || ((r == 0) && all_data_read)) {
 #if !defined(NO_FILESYSTEMS)
 							/* read error */
@@ -589,7 +592,7 @@ mg_handle_form_request(struct mg_connection *conn,
 			/* Proceed to next entry */
 			used = next - buf;
 			memmove(buf, buf + (size_t)used, sizeof(buf) - (size_t)used);
-			buf_fill -= (int)used;
+			buf_fill -= used;
 		}
 
 		return field_count;
@@ -624,6 +627,7 @@ mg_handle_form_request(struct mg_connection *conn,
 		}
 
 		/* Copy boundary string to variable "boundary" */
+		/* fbeg is pointer to start of value of boundary */
 		fbeg = content_type + bl + 9;
 		bl = strlen(fbeg);
 		boundary = (char *)mg_malloc(bl + 1);
@@ -678,12 +682,12 @@ mg_handle_form_request(struct mg_connection *conn,
 		for (part_no = 0;; part_no++) {
 			size_t towrite, fnlen, n;
 			int get_block;
-			size_t to_read = sizeof(buf) - 1 - (size_t)buf_fill;
+			size_t to_read = sizeof(buf) - 1 - buf_fill;
 
 			/* Unused without filesystems */
 			(void)n;
 
-			r = mg_read(conn, buf + (size_t)buf_fill, to_read);
+			r = mg_read(conn, buf + buf_fill, to_read);
 			if ((r < 0) || ((r == 0) && all_data_read)) {
 				/* read error */
 				mg_free(boundary);
@@ -701,43 +705,75 @@ mg_handle_form_request(struct mg_connection *conn,
 				return -1;
 			}
 
+			/* @see https://www.rfc-editor.org/rfc/rfc2046.html#section-5.1.1
+			 *
+			 * multipart-body := [preamble CRLF]
+			 *     dash-boundary transport-padding CRLF
+			 *     body-part *encapsulation
+			 *     close-delimiter transport-padding
+			 *     [CRLF epilogue]
+			 */
+
 			if (part_no == 0) {
-				int d = 0;
-				while ((d < buf_fill) && (buf[d] != '-')) {
-					d++;
+				size_t preamble_length = 0;
+				/* skip over the preamble until we find a complete boundary
+				 * limit the preamble length to prevent abuse */
+				/* +2 for the -- preceding the boundary */
+				while (preamble_length < 1024
+				       && (preamble_length < buf_fill - bl)
+				       && strncmp(buf + preamble_length + 2, boundary, bl)) {
+					preamble_length++;
 				}
-				if ((d > 0) && (buf[d] == '-')) {
-					memmove(buf, buf + d, (unsigned)buf_fill - (unsigned)d);
-					buf_fill -= d;
+				/* reset the start of buf to remove the preamble */
+				if (0 == strncmp(buf + preamble_length + 2, boundary, bl)) {
+					memmove(buf,
+					        buf + preamble_length,
+					        (unsigned)buf_fill - (unsigned)preamble_length);
+					buf_fill -= preamble_length;
 					buf[buf_fill] = 0;
 				}
 			}
 
-			if (buf[0] != '-' || buf[1] != '-') {
+			/* either it starts with a boundary and it's fine, or it's malformed
+			 * because:
+			 * - the preamble was longer than accepted
+			 * - couldn't find a boundary at all in the body
+			 * - didn't have a terminating boundary */
+			if (buf_fill < (bl + 2) || strncmp(buf, "--", 2)
+			    || strncmp(buf + 2, boundary, bl)) {
 				/* Malformed request */
 				mg_free(boundary);
 				return -1;
 			}
-			if (0 != strncmp(buf + 2, boundary, bl)) {
-				/* Malformed request */
-				mg_free(boundary);
-				return -1;
+
+			/* skip the -- */
+			char *boundary_start = buf + 2;
+			size_t transport_padding = 0;
+			while (boundary_start[bl + transport_padding] == ' '
+			       || boundary_start[bl + transport_padding] == '\t') {
+				transport_padding++;
 			}
-			if (buf[bl + 2] != '\r' || buf[bl + 3] != '\n') {
-				/* Every part must end with \r\n, if there is another part.
-				 * The end of the request has an extra -- */
-				if (((size_t)buf_fill != (size_t)(bl + 6))
-				    || (strncmp(buf + bl + 2, "--\r\n", 4))) {
+			char *boundary_end = boundary_start + bl + transport_padding;
+
+			/* after the transport padding, if the boundary isn't
+			 * immediately followed by a \r\n then it is either... */
+			if (strncmp(boundary_end, "\r\n", 2))
+			{
+				/* ...the final boundary, and it is followed by --, (in which
+				 * case it's the end of the request) or it's a malformed
+				 * request */
+				if (strncmp(boundary_end, "--", 2)) {
 					/* Malformed request */
 					mg_free(boundary);
 					return -1;
 				}
-				/* End of the request */
+				/* Ingore any epilogue here */
 				break;
 			}
 
+			/* skip the \r\n */
+			hbuf = boundary_end + 2;
 			/* Next, we need to get the part header: Read until \r\n\r\n */
-			hbuf = buf + bl + 4;
 			hend = strstr(hbuf, "\r\n\r\n");
 			if (!hend) {
 				/* Malformed request */
@@ -965,12 +1001,12 @@ mg_handle_form_request(struct mg_connection *conn,
 #endif /* NO_FILESYSTEMS */
 
 				memmove(buf, hend + towrite, bl + 4);
-				buf_fill = (int)(bl + 4);
+				buf_fill = bl + 4;
 				hend = buf;
 
 				/* Read new data */
-				to_read = sizeof(buf) - 1 - (size_t)buf_fill;
-				r = mg_read(conn, buf + (size_t)buf_fill, to_read);
+				to_read = sizeof(buf) - 1 - buf_fill;
+				r = mg_read(conn, buf + buf_fill, to_read);
 				if ((r < 0) || ((r == 0) && all_data_read)) {
 #if !defined(NO_FILESYSTEMS)
 					/* read error */
@@ -989,7 +1025,7 @@ mg_handle_form_request(struct mg_connection *conn,
 				/* buf_fill is at least 8 here */
 
 				/* Find boundary */
-				next = search_boundary(buf, (size_t)buf_fill, boundary, bl);
+				next = search_boundary(buf, buf_fill, boundary, bl);
 
 				if (!next && (r == 0)) {
 					/* incomplete request */
@@ -1064,7 +1100,7 @@ mg_handle_form_request(struct mg_connection *conn,
 			if (next) {
 				used = next - buf + 2;
 				memmove(buf, buf + (size_t)used, sizeof(buf) - (size_t)used);
-				buf_fill -= (int)used;
+				buf_fill -= used;
 			} else {
 				buf_fill = 0;
 			}

+ 1 - 1
src/main.c

@@ -41,7 +41,7 @@
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wreserved-id-macro"
 #endif
-#if !defined(_XOPEN_SOURCE)
+#if !defined(_XOPEN_SOURCE) && !defined(USE_COCOA)
 #define _XOPEN_SOURCE 600 /* For PATH_MAX on linux */
 /* This should also be sufficient for "realpath", according to
  * http://man7.org/linux/man-pages/man3/realpath.3.html, but in

+ 240 - 0
src/mod_gnutls.inl

@@ -0,0 +1,240 @@
+#if defined(USE_GNUTLS) // USE_GNUTLS used with NO_SSL
+
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+typedef struct {
+	gnutls_session_t sess;
+} SSL;
+typedef struct {
+	gnutls_certificate_credentials_t cred;
+	gnutls_priority_t prio;
+} SSL_CTX;
+
+
+/* public api */
+CIVETWEB_API int gtls_sslctx_init(SSL_CTX *ctx, const char *crt);
+CIVETWEB_API void gtls_sslctx_uninit(SSL_CTX *ctx);
+CIVETWEB_API void gtls_ssl_close(SSL *ssl);
+CIVETWEB_API int gtls_ssl_accept(SSL **ssl,
+                    SSL_CTX *ssl_ctx,
+                    int sock,
+                    struct mg_context *phys_ctx);
+CIVETWEB_API int gtls_ssl_read(SSL *ssl, unsigned char *buf, size_t len);
+CIVETWEB_API int gtls_ssl_write(SSL *ssl, const unsigned char *buf, size_t len);
+
+
+CIVETWEB_API int
+gtls_sslctx_init(SSL_CTX *ctx, const char *crt)
+{
+	int rc;
+
+	if (ctx == NULL || crt == NULL) {
+		return -1;
+	}
+
+	DEBUG_TRACE("%s", "Initializing GnuTLS SSL");
+
+	rc = gnutls_certificate_allocate_credentials(&ctx->cred);
+	if (rc != GNUTLS_E_SUCCESS) {
+		DEBUG_TRACE("Failed to allocate credentials (%d): %s",
+		            rc,
+		            gnutls_strerror(rc));
+		goto failed;
+	}
+
+	rc = gnutls_priority_init(&ctx->prio, NULL, NULL);
+	if (rc != GNUTLS_E_SUCCESS) {
+		DEBUG_TRACE("Failed to allocate priority cache (%d): %s",
+		            rc,
+		            gnutls_strerror(rc));
+		goto failed;
+	}
+
+	rc = gnutls_certificate_set_x509_key_file2(ctx->cred,
+	                                           crt,
+	                                           crt,
+	                                           GNUTLS_X509_FMT_PEM,
+	                                           NULL,
+	                                           GNUTLS_PKCS_PLAIN
+	                                               | GNUTLS_PKCS_NULL_PASSWORD);
+	if (rc != GNUTLS_E_SUCCESS) {
+		DEBUG_TRACE("TLS parse crt/key file failed (%d): %s",
+		            rc,
+		            gnutls_strerror(rc));
+		goto failed;
+	}
+
+	return 0;
+
+failed:
+	gtls_sslctx_uninit(ctx);
+
+	return -1;
+}
+
+
+CIVETWEB_API void
+gtls_sslctx_uninit(SSL_CTX *ctx)
+{
+	if (ctx != NULL) {
+		gnutls_certificate_free_credentials(ctx->cred);
+		gnutls_priority_deinit(ctx->prio);
+		ctx->cred = NULL;
+		ctx->prio = NULL;
+	}
+}
+
+
+CIVETWEB_API int
+gtls_ssl_accept(SSL **ssl,
+                SSL_CTX *ssl_ctx,
+                int sock,
+                struct mg_context *phys_ctx)
+{
+	int rc;
+
+	if (ssl == NULL || ssl_ctx == NULL) {
+		return -1;
+	}
+
+	DEBUG_TRACE("TLS accept processing %p", ssl);
+
+	*ssl = (SSL *)mg_calloc_ctx(1, sizeof(SSL), phys_ctx);
+	if (*ssl == NULL) {
+		DEBUG_TRACE("Failed to allocate memory for session %zu", sizeof(SSL));
+		return -1;
+	}
+
+	rc = gnutls_init(&(*ssl)->sess, GNUTLS_SERVER);
+	if (rc != GNUTLS_E_SUCCESS) {
+		DEBUG_TRACE("Failed to initialize session (%d): %s",
+		            rc,
+		            gnutls_strerror(rc));
+		goto failed;
+	}
+
+	rc = gnutls_priority_set((*ssl)->sess, ssl_ctx->prio);
+	if (rc != GNUTLS_E_SUCCESS) {
+		DEBUG_TRACE("TLS set priortities failed (%d): %s",
+		            rc,
+		            gnutls_strerror(rc));
+		goto failed;
+	}
+
+	rc = gnutls_credentials_set((*ssl)->sess,
+	                            GNUTLS_CRD_CERTIFICATE,
+	                            ssl_ctx->cred);
+	if (rc != GNUTLS_E_SUCCESS) {
+		DEBUG_TRACE("TLS set credentials failed (%d): %s",
+		            rc,
+		            gnutls_strerror(rc));
+		goto failed;
+	}
+
+	gnutls_certificate_send_x509_rdn_sequence((*ssl)->sess, 1);
+	gnutls_certificate_server_set_request((*ssl)->sess, GNUTLS_CERT_IGNORE);
+	gnutls_handshake_set_timeout((*ssl)->sess,
+	                             GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+	gnutls_transport_set_int((*ssl)->sess, sock);
+
+	while ((rc = gnutls_handshake((*ssl)->sess)) != GNUTLS_E_SUCCESS) {
+		if (gnutls_error_is_fatal(rc)) {
+			if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED) {
+				DEBUG_TRACE("TLS fatal alert received: %s",
+				            gnutls_alert_get_name(
+				                gnutls_alert_get((*ssl)->sess)));
+			} else {
+				DEBUG_TRACE("TLS handshake failed (%d): %s",
+				            rc,
+				            gnutls_strerror(rc));
+			}
+
+			goto failed;
+		}
+	}
+
+	DEBUG_TRACE("TLS connection %p accepted", *ssl);
+
+	return 0;
+
+failed:
+	gnutls_deinit((*ssl)->sess);
+	mg_free(*ssl);
+	*ssl = NULL;
+
+	return -1;
+}
+
+
+CIVETWEB_API void
+gtls_ssl_close(SSL *ssl)
+{
+	int rc;
+
+	if (ssl == NULL) {
+		return;
+	}
+
+	while ((rc = gnutls_bye(ssl->sess, GNUTLS_SHUT_RDWR)) != GNUTLS_E_SUCCESS) {
+		switch (rc) {
+		case GNUTLS_E_AGAIN: /* fall through */
+		case GNUTLS_E_INTERRUPTED:
+			continue;
+		default: /* should actually never happen */
+			break;
+		}
+	}
+
+	DEBUG_TRACE("TLS connection %p closed", ssl);
+	gnutls_deinit(ssl->sess);
+	mg_free(ssl);
+}
+
+
+CIVETWEB_API int
+gtls_ssl_read(SSL *ssl, unsigned char *buf, size_t len)
+{
+	ssize_t rc;
+
+	if (ssl == NULL) {
+		return GNUTLS_E_INVALID_SESSION;
+	}
+
+	while ((rc = gnutls_record_recv(ssl->sess, buf, len)) < 0) {
+		switch (rc) {
+		case GNUTLS_E_AGAIN: /* fall through */
+		case GNUTLS_E_INTERRUPTED:
+			continue;
+		default:
+			break;
+		}
+	}
+	/* DEBUG_TRACE("gnutls_record_recv: %d", rc); */
+	return (int)rc;
+}
+
+
+CIVETWEB_API int
+gtls_ssl_write(SSL *ssl, const unsigned char *buf, size_t len)
+{
+	ssize_t rc;
+
+	if (ssl == NULL) {
+		return GNUTLS_E_INVALID_SESSION;
+	}
+
+	while ((rc = gnutls_record_send(ssl->sess, buf, len)) < 0) {
+		switch (rc) {
+		case GNUTLS_E_AGAIN: /* fall through */
+		case GNUTLS_E_INTERRUPTED:
+			continue;
+		default:
+			break;
+		}
+	}
+	/* DEBUG_TRACE("gnutls_record_send: %d", rc); */
+	return (int)rc;
+}
+
+#endif /* USE_GNUTLS */

+ 64 - 16
src/mod_lua.inl

@@ -10,6 +10,9 @@
 #include "civetweb_lua.h"
 #include "civetweb_private_lua.h"
 
+/* Prototypes */
+static int
+lua_error_handler(lua_State *L);
 
 #if defined(_WIN32)
 static void *
@@ -641,14 +644,21 @@ run_lsp_kepler(struct mg_connection *conn,
 		/* Only send a HTML header, if this is the top level page.
 		 * If this page is included by some mg.include calls, do not add a
 		 * header. */
-		mg_printf(conn, "HTTP/1.1 200 OK\r\n");
+
+		/* Initialize a new HTTP response, either with some-predefined
+		 * status code (e.g. 404 if this is called from an error
+		 * handler) or with 200 OK */
+		mg_response_header_start(conn, conn->status_code > 0 ? conn->status_code : 200);
+
+		/* Add additional headers */
 		send_no_cache_header(conn);
 		send_additional_header(conn);
-		mg_printf(conn,
-		          "Date: %s\r\n"
-		          "Connection: close\r\n"
-		          "Content-Type: text/html; charset=utf-8\r\n\r\n",
-		          date);
+
+		/* Add content type */
+		mg_response_header_add(conn, "Content-Type", "text/html; charset=utf-8", -1);
+
+		/* Send the HTTP response (status and all headers) */
+		mg_response_header_send(conn);
 	}
 
 	data.begin = p;
@@ -662,11 +672,19 @@ run_lsp_kepler(struct mg_connection *conn,
 		/* Syntax error or OOM.
 		 * Error message is pushed on stack. */
 		lua_pcall(L, 1, 0, 0);
-		lua_cry(conn, lua_ok, L, "LSP", "execute"); /* XXX TODO: everywhere ! */
+		lua_cry(conn, lua_ok, L, "LSP Kepler", "execute");
+		lua_error_handler(L);
+		return 1;
 
 	} else {
 		/* Success loading chunk. Call it. */
-		lua_pcall(L, 0, 0, 1);
+		lua_ok = lua_pcall(L, 0, 0, 0);
+		if(lua_ok != LUA_OK)
+		{
+			lua_cry(conn, lua_ok, L, "LSP Kepler", "call");
+			lua_error_handler(L);
+			return 1;
+		}
 	}
 	return 0;
 }
@@ -780,9 +798,18 @@ run_lsp_civetweb(struct mg_connection *conn,
 						/* Syntax error or OOM.
 						 * Error message is pushed on stack. */
 						lua_pcall(L, 1, 0, 0);
+						lua_cry(conn, lua_ok, L, "LSP", "execute");
+						lua_error_handler(L);
+						return 1;
 					} else {
 						/* Success loading chunk. Call it. */
-						lua_pcall(L, 0, 0, 1);
+						lua_ok = lua_pcall(L, 0, 0, 0);
+						if(lua_ok != LUA_OK)
+						{
+							lua_cry(conn, lua_ok, L, "LSP", "call");
+							lua_error_handler(L);
+							return 1;
+						}
 					}
 
 					/* Progress until after the Lua closing tag. */
@@ -2769,18 +2796,39 @@ lua_error_handler(lua_State *L)
 
 	lua_getglobal(L, "mg");
 	if (!lua_isnil(L, -1)) {
-		lua_getfield(L, -1, "write"); /* call mg.write() */
+		/* Write the error message to the error log */
+		lua_getfield(L, -1, "write");
 		lua_pushstring(L, error_msg);
 		lua_pushliteral(L, "\n");
-		lua_call(L, 2, 0);
-		IGNORE_UNUSED_RESULT(
-		    luaL_dostring(L, "mg.write(debug.traceback(), '\\n')"));
+		lua_call(L, 2, 0); /* call mg.write(error_msg + \n) */
+		lua_pop(L, 1); /* pop mg */
+
+		/* Get Lua traceback */
+		lua_getglobal(L, "debug");
+		lua_getfield(L, -1, "traceback");
+		lua_call(L, 0, 1); /* call debug.traceback() */
+		lua_remove(L, -2); /* remove debug */
+
+		/* Write the Lua traceback to the error log */
+		lua_getglobal(L, "mg");
+		lua_getfield(L, -1, "write");
+		lua_pushvalue(L, -3); /* push the traceback */
+
+		/* Only print the traceback if it is not empty */
+		if (strcmp(lua_tostring(L, -1), "stack traceback:") != 0) {
+			lua_pushliteral(L, "\n"); /* append a newline */
+			lua_call(L, 2, 0); /* call mg.write(traceback + \n) */
+			lua_pop(L, 2); /* pop mg and traceback */
+		} else {
+			lua_pop(L, 3); /* pop mg, traceback and error message */
+		}
+
 	} else {
 		printf("Lua error: [%s]\n", error_msg);
 		IGNORE_UNUSED_RESULT(
 		    luaL_dostring(L, "print(debug.traceback(), '\\n')"));
 	}
-	/* TODO(lsm, low): leave the stack balanced */
+	lua_pop(L, 1); /* pop error message */
 
 	return 0;
 }
@@ -3679,7 +3727,7 @@ lua_init_optional_libraries(void)
 	lua_shared_init();
 
 /* UUID library */
-#if !defined(_WIN32)
+#if !defined(_WIN32) && !defined(NO_DLOPEN)
 	lib_handle_uuid = dlopen("libuuid.so", RTLD_LAZY);
 	pf_uuid_generate.p =
 	    (lib_handle_uuid ? dlsym(lib_handle_uuid, "uuid_generate") : 0);
@@ -3693,7 +3741,7 @@ static void
 lua_exit_optional_libraries(void)
 {
 /* UUID library */
-#if !defined(_WIN32)
+#if !defined(_WIN32) && !defined(NO_DLOPEN)
 	if (lib_handle_uuid) {
 		dlclose(lib_handle_uuid);
 	}

+ 12 - 0
src/mod_mbedtls.inl

@@ -88,6 +88,18 @@ mbed_sslctx_init(SSL_CTX *ctx, const char *crt)
 	mbedtls_ctr_drbg_init(&ctx->ctr);
 	mbedtls_x509_crt_init(&ctx->cert);
 
+#ifdef MBEDTLS_PSA_CRYPTO_C
+	/* Initialize PSA crypto (mandatory with TLS 1.3)
+	 * This must be done before calling any other PSA Crypto
+	 * functions or they will fail with PSA_ERROR_BAD_STATE
+	 */
+	const psa_status_t status = psa_crypto_init();
+	if (status != PSA_SUCCESS) {
+		DEBUG_TRACE("Failed to initialize PSA crypto, returned %d\n", (int) status);
+		return -1;
+	}
+#endif
+
 	rc = mbedtls_ctr_drbg_seed(&ctx->ctr,
 	                           mbedtls_entropy_func,
 	                           &ctx->entropy,

+ 858 - 4
unittest/public_server.c

@@ -457,10 +457,14 @@ test_mg_start_stop_http_server_impl(int ipv6, int bound)
 	ck_assert_int_eq(portinfo[0].port, 0);
 	ck_assert_int_eq(portinfo[0].is_ssl, 0);
 	ck_assert_int_eq(portinfo[0].is_redirect, 0);
+	ck_assert_int_eq(portinfo[0].is_optional, 0);
+	ck_assert_int_eq(portinfo[0].is_bound, 0);
 	ck_assert_int_eq(portinfo[1].protocol, 0);
 	ck_assert_int_eq(portinfo[1].port, 0);
 	ck_assert_int_eq(portinfo[1].is_ssl, 0);
 	ck_assert_int_eq(portinfo[1].is_redirect, 0);
+	ck_assert_int_eq(portinfo[1].is_optional, 0);
+	ck_assert_int_eq(portinfo[1].is_bound, 0);
 
 	ret = mg_get_server_ports(ctx, 4, portinfo);
 	ck_assert_int_eq(ret, 1);
@@ -472,10 +476,14 @@ test_mg_start_stop_http_server_impl(int ipv6, int bound)
 	ck_assert_int_eq(portinfo[0].port, 8080);
 	ck_assert_int_eq(portinfo[0].is_ssl, 0);
 	ck_assert_int_eq(portinfo[0].is_redirect, 0);
+	ck_assert_int_eq(portinfo[0].is_optional, 0);
+	ck_assert_int_eq(portinfo[0].is_bound, 1);
 	ck_assert_int_eq(portinfo[1].protocol, 0);
 	ck_assert_int_eq(portinfo[1].port, 0);
 	ck_assert_int_eq(portinfo[1].is_ssl, 0);
 	ck_assert_int_eq(portinfo[1].is_redirect, 0);
+	ck_assert_int_eq(portinfo[1].is_optional, 0);
+	ck_assert_int_eq(portinfo[1].is_bound, 0);
 
 	test_sleep(1);
 
@@ -649,7 +657,7 @@ START_TEST(test_mg_start_stop_https_server)
 	OPTIONS[opt_idx++] = ".";
 #endif
 	OPTIONS[opt_idx++] = "listening_ports";
-	OPTIONS[opt_idx++] = "8080r,8443s";
+	OPTIONS[opt_idx++] = "8080r,8443os";
 	OPTIONS[opt_idx++] = "ssl_certificate";
 	OPTIONS[opt_idx++] = ssl_cert;
 
@@ -674,10 +682,14 @@ START_TEST(test_mg_start_stop_https_server)
 	ck_assert_int_eq(portinfo[0].port, 0);
 	ck_assert_int_eq(portinfo[0].is_ssl, 0);
 	ck_assert_int_eq(portinfo[0].is_redirect, 0);
+	ck_assert_int_eq(portinfo[0].is_optional, 0);
+	ck_assert_int_eq(portinfo[0].is_bound, 0);
 	ck_assert_int_eq(portinfo[1].protocol, 0);
 	ck_assert_int_eq(portinfo[1].port, 0);
 	ck_assert_int_eq(portinfo[1].is_ssl, 0);
 	ck_assert_int_eq(portinfo[1].is_redirect, 0);
+	ck_assert_int_eq(portinfo[1].is_optional, 0);
+	ck_assert_int_eq(portinfo[1].is_bound, 0);
 
 	ret = mg_get_server_ports(ctx, 4, portinfo);
 	ck_assert_int_eq(ret, 2);
@@ -685,14 +697,20 @@ START_TEST(test_mg_start_stop_https_server)
 	ck_assert_int_eq(portinfo[0].port, 8080);
 	ck_assert_int_eq(portinfo[0].is_ssl, 0);
 	ck_assert_int_eq(portinfo[0].is_redirect, 1);
+	ck_assert_int_eq(portinfo[0].is_optional, 0);
+	ck_assert_int_eq(portinfo[0].is_bound, 1);
 	ck_assert_int_eq(portinfo[1].protocol, 1);
 	ck_assert_int_eq(portinfo[1].port, 8443);
 	ck_assert_int_eq(portinfo[1].is_ssl, 1);
 	ck_assert_int_eq(portinfo[1].is_redirect, 0);
+	ck_assert_int_eq(portinfo[1].is_optional, 1);
+	ck_assert_int_eq(portinfo[1].is_bound, 1);
 	ck_assert_int_eq(portinfo[2].protocol, 0);
 	ck_assert_int_eq(portinfo[2].port, 0);
 	ck_assert_int_eq(portinfo[2].is_ssl, 0);
 	ck_assert_int_eq(portinfo[2].is_redirect, 0);
+	ck_assert_int_eq(portinfo[2].is_optional, 0);
+	ck_assert_int_eq(portinfo[2].is_bound, 0);
 
 	test_sleep(1);
 
@@ -771,7 +789,7 @@ START_TEST(test_mg_server_and_client_tls)
 	OPTIONS[opt_idx++] = ".";
 #endif
 	OPTIONS[opt_idx++] = "listening_ports";
-	OPTIONS[opt_idx++] = "8080r,8443s";
+	OPTIONS[opt_idx++] = "8080r,8443os";
 	OPTIONS[opt_idx++] = "ssl_certificate";
 	OPTIONS[opt_idx++] = server_cert;
 	OPTIONS[opt_idx++] = "ssl_verify_peer";
@@ -800,14 +818,20 @@ START_TEST(test_mg_server_and_client_tls)
 	ck_assert_int_eq(ports[0].port, 8080);
 	ck_assert_int_eq(ports[0].is_ssl, 0);
 	ck_assert_int_eq(ports[0].is_redirect, 1);
+	ck_assert_int_eq(ports[0].is_optional, 0);
+	ck_assert_int_eq(ports[0].is_bound, 1);
 	ck_assert_int_eq(ports[1].protocol, 1);
 	ck_assert_int_eq(ports[1].port, 8443);
 	ck_assert_int_eq(ports[1].is_ssl, 1);
 	ck_assert_int_eq(ports[1].is_redirect, 0);
+	ck_assert_int_eq(ports[1].is_optional, 1);
+	ck_assert_int_eq(ports[1].is_bound, 1);
 	ck_assert_int_eq(ports[2].protocol, 0);
 	ck_assert_int_eq(ports[2].port, 0);
 	ck_assert_int_eq(ports[2].is_ssl, 0);
 	ck_assert_int_eq(ports[2].is_redirect, 0);
+	ck_assert_int_eq(ports[2].is_optional, 0);
+	ck_assert_int_eq(ports[2].is_bound, 0);
 
 	test_sleep(1);
 
@@ -823,7 +847,7 @@ START_TEST(test_mg_server_and_client_tls)
 	 * while Ubuntu Xenial, Ubuntu Trusty and Windows test containers at
 	 * Travis CI do not. Maybe it is OpenSSL version specific.
 	 */
-#if defined(OPENSSL_API_1_1)
+#if defined(OPENSSL_API_1_1) || defined(OPENSSL_API_3_0) 
 	if (client_conn) {
 		/* Connect succeeds, but the connection is unusable. */
 		mg_printf(client_conn, "GET / HTTP/1.0\r\n\r\n");
@@ -1316,7 +1340,7 @@ START_TEST(test_request_handlers)
 	char cmd_buf[1024];
 	char *cgi_env_opt;
 
-	const char *server_host = "localhost"; //"test.domain";
+	const char *server_host = "test.domain";
 
 	mark_point();
 
@@ -2609,6 +2633,36 @@ FormGet(struct mg_connection *conn, void *cbdata)
 
 
 static int
+FormError(struct mg_connection *conn, void *cbdata)
+{
+	const struct mg_request_info *req_info = mg_get_request_info(conn);
+	int ret;
+	struct mg_form_data_handler fdh = {NULL, NULL, NULL, NULL};
+
+	(void)cbdata;
+
+	ck_assert(req_info != NULL);
+
+	mg_printf(conn, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n");
+	fdh.user_data = (void *)&g_field_found_return;
+
+	/* Call the form handler */
+	g_field_step = 0;
+	g_field_found_return = MG_FORM_FIELD_STORAGE_GET;
+	ret = mg_handle_form_request(conn, &fdh);
+	g_field_found_return = -888;
+	ck_assert_int_eq(ret, -1);
+	ck_assert_int_eq(g_field_step, 0);
+	mg_printf(conn, "%i\r\n", ret);
+	g_field_step = 1000;
+
+	mark_point();
+
+	return 1;
+}
+
+
+static int
 FormStore(struct mg_connection *conn,
           void *cbdata,
           int ret_expected,
@@ -2727,6 +2781,7 @@ START_TEST(test_handle_form)
 	ck_assert_str_eq(opt, "8884");
 
 	mg_set_request_handler(ctx, "/handle_form", FormGet, NULL);
+	mg_set_request_handler(ctx, "/handle_form_error", FormError, NULL);
 	mg_set_request_handler(ctx, "/handle_form_store", FormStore1, NULL);
 	mg_set_request_handler(ctx, "/handle_form_store2", FormStore2, NULL);
 
@@ -2797,6 +2852,23 @@ START_TEST(test_handle_form)
 	ck_assert_int_eq(client_ri->status_code, 200);
 	mg_close_connection(client_conn);
 
+	/*
+	 * https://datatracker.ietf.org/doc/html/rfc2046#section-5.1
+	 *
+	 * multipart-body := [preamble CRLF]
+	 *     dash-boundary transport-padding CRLF
+	 *     body-part *encapsulation
+	 *     close-delimiter transport-padding
+	 *     [CRLF epilogue]
+	 *
+	 * preamble := discard-text
+	 * epilogue := discard-text
+	 *
+	 * discard-text := *(*text CRLF) *text
+	 *
+	 * text := <any CHAR, including bare CR & bare LF, but NOT including CRLF>
+	 */
+
 	/* Handle form: "POST multipart/form-data" */
 	multipart_body =
 	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
@@ -2924,6 +2996,7 @@ START_TEST(test_handle_form)
 
 
 	/* Handle form: "POST multipart/form-data" with chunked transfer encoding */
+	/* use the most universal possible (no edge cases) body*/
 	client_conn =
 	    mg_download("localhost",
 	                8884,
@@ -3016,6 +3089,787 @@ START_TEST(test_handle_form)
 	ck_assert_int_eq(client_ri->status_code, 200);
 	mg_close_connection(client_conn);
 
+	/* Handle form: "POST multipart/form-data" without trailing CRLF*/
+	multipart_body =
+		"--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"textin\"\r\n"
+	    "\r\n"
+	    "text\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"passwordin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"radio1\"\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=radio2\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"check1\"\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"numberin\"\r\n"
+	    "\r\n"
+	    "1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datein\"\r\n"
+	    "\r\n"
+	    "1.1.2016\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"colorin\"\r\n"
+	    "\r\n"
+	    "#80ff00\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"rangein\"\r\n"
+	    "\r\n"
+	    "3\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"monthin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"weekin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"timein\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datetimen\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datetimelocalin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"emailin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"searchin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"telin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"urlin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"filein\"; filename=\"\"\r\n"
+	    "Content-Type: application/octet-stream\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=filesin; filename=\r\n"
+	    "Content-Type: application/octet-stream\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"selectin\"\r\n"
+	    "\r\n"
+	    "opt1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"message\"\r\n"
+	    "\r\n"
+	    "Text area default text.\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388--";
+	body_len = strlen(multipart_body);
+	ck_assert_uint_eq(body_len, 2366); /* not required */
+
+	client_conn =
+	    mg_download("localhost",
+	                8884,
+	                0,
+	                ebuf,
+	                sizeof(ebuf),
+	                "POST /handle_form HTTP/1.1\r\n"
+	                "Host: localhost:8884\r\n"
+	                "Connection: close\r\n"
+	                "Content-Type: multipart/form-data; "
+	                "boundary=multipart-form-data-boundary--see-RFC-2388\r\n"
+	                "Content-Length: %u\r\n"
+	                "\r\n%s",
+	                (unsigned int)body_len,
+	                multipart_body);
+
+	ck_assert(client_conn != NULL);
+	for (sleep_cnt = 0; sleep_cnt < 30; sleep_cnt++) {
+		test_sleep(1);
+		if (g_field_step == 1000) {
+			break;
+		}
+	}
+	client_ri = mg_get_response_info(client_conn);
+
+	ck_assert(client_ri != NULL);
+	ck_assert_int_eq(client_ri->status_code, 200);
+	mg_close_connection(client_conn);
+
+	/* Handle form: "POST multipart/form-data" with epilogue*/
+	multipart_body =
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"textin\"\r\n"
+	    "\r\n"
+	    "text\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"passwordin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"radio1\"\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=radio2\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"check1\"\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"numberin\"\r\n"
+	    "\r\n"
+	    "1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datein\"\r\n"
+	    "\r\n"
+	    "1.1.2016\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"colorin\"\r\n"
+	    "\r\n"
+	    "#80ff00\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"rangein\"\r\n"
+	    "\r\n"
+	    "3\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"monthin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"weekin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"timein\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datetimen\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datetimelocalin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"emailin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"searchin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"telin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"urlin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"filein\"; filename=\"\"\r\n"
+	    "Content-Type: application/octet-stream\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=filesin; filename=\r\n"
+	    "Content-Type: application/octet-stream\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"selectin\"\r\n"
+	    "\r\n"
+	    "opt1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"message\"\r\n"
+	    "\r\n"
+	    "Text area default text.\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388--\r\n"
+	    "epilogue\r\n"
+	    "epilogue\r\n"
+	    "\r\n"
+	    "1234567890-=!@£$%^&*()_+[]{};'\\:\"|,./<>?`~§\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "epilogue\r\n";
+	body_len = strlen(multipart_body);
+	ck_assert_uint_eq(body_len, 2453); /* not required */
+
+	client_conn =
+	    mg_download("localhost",
+	                8884,
+	                0,
+	                ebuf,
+	                sizeof(ebuf),
+	                "POST /handle_form HTTP/1.1\r\n"
+	                "Host: localhost:8884\r\n"
+	                "Connection: close\r\n"
+	                "Content-Type: multipart/form-data; "
+	                "boundary=multipart-form-data-boundary--see-RFC-2388\r\n"
+	                "Content-Length: %u\r\n"
+	                "\r\n%s",
+	                (unsigned int)body_len,
+	                multipart_body);
+
+	ck_assert(client_conn != NULL);
+	for (sleep_cnt = 0; sleep_cnt < 30; sleep_cnt++) {
+		test_sleep(1);
+		if (g_field_step == 1000) {
+			break;
+		}
+	}
+	client_ri = mg_get_response_info(client_conn);
+
+	ck_assert(client_ri != NULL);
+	ck_assert_int_eq(client_ri->status_code, 200);
+	mg_close_connection(client_conn);
+
+	/* Handle form: "POST multipart/form-data" with preamble*/
+	/*
+	 * https://datatracker.ietf.org/doc/html/rfc2046#section-5.1
+	 *
+	 * multipart-body := [preamble CRLF]
+	 *     dash-boundary transport-padding CRLF
+	 *     body-part *encapsulation
+	 *     close-delimiter transport-padding
+	 *     [CRLF epilogue]
+	 *
+	 * preamble := discard-text
+	 *
+	 * discard-text := *(*text CRLF) *text
+	 *
+	 * text := <any CHAR, including bare CR & bare LF, but NOT including CRLF>
+	 */
+	multipart_body =
+	    "\r\n"
+	    "\r\npreamble"
+	    "\r\npreamble"
+	    "\r\npreamble"
+	    "\r\n"
+	    "\r\npreamble"
+	    "\r\n"
+	    "1234567890-=!@£$%^&*()_+[]{};'\\:\"|,./<>?`~§\r\n"
+	    "\r\n"
+	    "\r\n\t\t\t   \t\t\t"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"textin\"\r\n"
+	    "\r\n"
+	    "text\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"passwordin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"radio1\"\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=radio2\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"check1\"\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"numberin\"\r\n"
+	    "\r\n"
+	    "1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datein\"\r\n"
+	    "\r\n"
+	    "1.1.2016\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"colorin\"\r\n"
+	    "\r\n"
+	    "#80ff00\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"rangein\"\r\n"
+	    "\r\n"
+	    "3\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"monthin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"weekin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"timein\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datetimen\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datetimelocalin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"emailin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"searchin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"telin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"urlin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"filein\"; filename=\"\"\r\n"
+	    "Content-Type: application/octet-stream\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=filesin; filename=\r\n"
+	    "Content-Type: application/octet-stream\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"selectin\"\r\n"
+	    "\r\n"
+	    "opt1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"message\"\r\n"
+	    "\r\n"
+	    "Text area default text.\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388--\r\n";
+	body_len = strlen(multipart_body);
+	ck_assert_uint_eq(body_len, 2478); /* not required */
+
+	client_conn =
+	    mg_download("localhost",
+	                8884,
+	                0,
+	                ebuf,
+	                sizeof(ebuf),
+	                "POST /handle_form HTTP/1.1\r\n"
+	                "Host: localhost:8884\r\n"
+	                "Connection: close\r\n"
+	                "Content-Type: multipart/form-data; "
+	                "boundary=multipart-form-data-boundary--see-RFC-2388\r\n"
+	                "Content-Length: %u\r\n"
+	                "\r\n%s",
+	                (unsigned int)body_len,
+	                multipart_body);
+
+	ck_assert(client_conn != NULL);
+	for (sleep_cnt = 0; sleep_cnt < 30; sleep_cnt++) {
+		test_sleep(1);
+		if (g_field_step == 1000) {
+			break;
+		}
+	}
+	client_ri = mg_get_response_info(client_conn);
+
+	ck_assert(client_ri != NULL);
+	ck_assert_int_eq(client_ri->status_code, 200);
+	mg_close_connection(client_conn);
+
+	/* Handle form: "POST multipart/form-data" with transport padding*/
+	multipart_body =
+	    "--multipart-form-data-boundary--see-RFC-2388           \r\n"
+	    "Content-Disposition: form-data; name=\"textin\"\r\n"
+	    "\r\n"
+	    "text\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\t\t\t\r\n"
+	    "Content-Disposition: form-data; name=\"passwordin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"radio1\"\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=radio2\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"check1\"\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"numberin\"\r\n"
+	    "\r\n"
+	    "1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datein\"\r\n"
+	    "\r\n"
+	    "1.1.2016\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"colorin\"\r\n"
+	    "\r\n"
+	    "#80ff00\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"rangein\"\r\n"
+	    "\r\n"
+	    "3\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"monthin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"weekin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"timein\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datetimen\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datetimelocalin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"emailin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"searchin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"telin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"urlin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"filein\"; filename=\"\"\r\n"
+	    "Content-Type: application/octet-stream\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=filesin; filename=\r\n"
+	    "Content-Type: application/octet-stream\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"selectin\"\r\n"
+	    "\r\n"
+	    "opt1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"message\"\r\n"
+	    "\r\n"
+	    "Text area default text.\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388--\r\n";
+	body_len = strlen(multipart_body);
+	ck_assert_uint_eq(body_len, 2382); /* not required */
+
+	client_conn =
+	    mg_download("localhost",
+	                8884,
+	                0,
+	                ebuf,
+	                sizeof(ebuf),
+	                "POST /handle_form HTTP/1.1\r\n"
+	                "Host: localhost:8884\r\n"
+	                "Connection: close\r\n"
+	                "Content-Type: multipart/form-data; "
+	                "boundary=multipart-form-data-boundary--see-RFC-2388\r\n"
+	                "Content-Length: %u\r\n"
+	                "\r\n%s",
+	                (unsigned int)body_len,
+	                multipart_body);
+
+	ck_assert(client_conn != NULL);
+	for (sleep_cnt = 0; sleep_cnt < 30; sleep_cnt++) {
+		test_sleep(1);
+		if (g_field_step == 1000) {
+			break;
+		}
+	}
+	client_ri = mg_get_response_info(client_conn);
+
+	ck_assert(client_ri != NULL);
+	ck_assert_int_eq(client_ri->status_code, 200);
+	mg_close_connection(client_conn);
+
+	/* Handle form: "POST multipart/form-data" with custom name fields in the
+	 * Content-Disposition */
+	multipart_body =
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; "
+		"custom1name=\"1\"; "
+		"custom2name=\"2\"; "
+		"custom3name=\"3\"; "
+		"custom4name=\"4\"; "
+		"name=\"textin\"\r\n"
+	    "\r\n"
+	    "text\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\t\t\t\r\n"
+	    "Content-Disposition: form-data; name=\"passwordin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"radio1\"\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=radio2\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"check1\"\r\n"
+	    "\r\n"
+	    "val1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"numberin\"\r\n"
+	    "\r\n"
+	    "1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datein\"\r\n"
+	    "\r\n"
+	    "1.1.2016\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"colorin\"\r\n"
+	    "\r\n"
+	    "#80ff00\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"rangein\"\r\n"
+	    "\r\n"
+	    "3\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"monthin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"weekin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"timein\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datetimen\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"datetimelocalin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"emailin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"searchin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"telin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"urlin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"filein\"; filename=\"\"\r\n"
+	    "Content-Type: application/octet-stream\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=filesin; filename=\r\n"
+	    "Content-Type: application/octet-stream\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"selectin\"\r\n"
+	    "\r\n"
+	    "opt1\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n"
+	    "Content-Disposition: form-data; name=\"message\"\r\n"
+	    "\r\n"
+	    "Text area default text.\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388--\r\n";
+	body_len = strlen(multipart_body);
+	ck_assert_uint_eq(body_len, 2439); /* not required */
+
+	client_conn =
+	    mg_download("localhost",
+	                8884,
+	                0,
+	                ebuf,
+	                sizeof(ebuf),
+	                "POST /handle_form HTTP/1.1\r\n"
+	                "Host: localhost:8884\r\n"
+	                "Connection: close\r\n"
+	                "Content-Type: multipart/form-data; "
+	                "boundary=multipart-form-data-boundary--see-RFC-2388\r\n"
+	                "Content-Length: %u\r\n"
+	                "\r\n%s",
+	                (unsigned int)body_len,
+	                multipart_body);
+
+	ck_assert(client_conn != NULL);
+	for (sleep_cnt = 0; sleep_cnt < 30; sleep_cnt++) {
+		test_sleep(1);
+		if (g_field_step == 1000) {
+			break;
+		}
+	}
+	client_ri = mg_get_response_info(client_conn);
+
+	ck_assert(client_ri != NULL);
+	ck_assert_int_eq(client_ri->status_code, 200);
+	mg_close_connection(client_conn);
+
+	/* Handle form error cases */
+	/* Handle form: "POST multipart/form-data" empty body */
+	multipart_body = "";
+	body_len = strlen(multipart_body);
+	ck_assert_uint_eq(body_len, 0); /* not required */
+
+	client_conn =
+	    mg_download("localhost",
+	                8884,
+	                0,
+	                ebuf,
+	                sizeof(ebuf),
+	                "POST /handle_form_error HTTP/1.1\r\n"
+	                "Host: localhost:8884\r\n"
+	                "Connection: close\r\n"
+	                "Content-Type: multipart/form-data; "
+	                "boundary=multipart-form-data-boundary--see-RFC-2388\r\n"
+	                "Content-Length: %u\r\n"
+	                "\r\n%s",
+	                (unsigned int)body_len,
+	                multipart_body);
+
+	ck_assert(client_conn != NULL);
+	for (sleep_cnt = 0; sleep_cnt < 30; sleep_cnt++) {
+		test_sleep(1);
+		if (g_field_step == 1000) {
+			break;
+		}
+	}
+	client_ri = mg_get_response_info(client_conn);
+
+	ck_assert(client_ri != NULL);
+	ck_assert_int_eq(client_ri->status_code, 200);
+	mg_close_connection(client_conn);
+
+	/* Handle form: "POST multipart/form-data" very long preamble */
+	multipart_body =
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "preamblepreamblepreamblepreamblepreamble\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388\r\n";
+	    "Content-Disposition: form-data; name=\"passwordin\"\r\n"
+	    "\r\n"
+	    "\r\n"
+	    "--multipart-form-data-boundary--see-RFC-2388--\r\n";
+
+	body_len = strlen(multipart_body);
+	ck_assert_uint_eq(body_len, 1768); /* not required */
+
+	client_conn =
+	    mg_download("localhost",
+	                8884,
+	                0,
+	                ebuf,
+	                sizeof(ebuf),
+	                "POST /handle_form_error HTTP/1.1\r\n"
+	                "Host: localhost:8884\r\n"
+	                "Connection: close\r\n"
+	                "Content-Type: multipart/form-data; "
+	                "boundary=multipart-form-data-boundary--see-RFC-2388\r\n"
+	                "Content-Length: %u\r\n"
+	                "\r\n%s",
+	                (unsigned int)body_len,
+	                multipart_body);
+
+	ck_assert(client_conn != NULL);
+	for (sleep_cnt = 0; sleep_cnt < 30; sleep_cnt++) {
+		test_sleep(1);
+		if (g_field_step == 1000) {
+			break;
+		}
+	}
+	client_ri = mg_get_response_info(client_conn);
+
+	ck_assert(client_ri != NULL);
+	ck_assert_int_eq(client_ri->status_code, 200);
+	mg_close_connection(client_conn);
 
 	/* Now test form_store */