New Upstream Release - civetweb

Ready changes

Summary

Merged new upstream version: 1.16+dfsg (was: 1.15+dfsg).

Diff

diff --git a/.travis.yml b/.travis.yml
index a994668..8dabd5b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,6 +17,7 @@ addons:
       - cmake
       - openssl
       - libssl-dev
+      - gdb
     sources:
       - kubuntu-backports
 
@@ -120,6 +121,12 @@ before_script:
     ${ADDITIONAL_CMAKE_ARGS}
     ..
   - ls -la
+  - ulimit -c unlimited -S
+
+after_failure:
+  - COREFILE=$(find . -maxdepth 1 -name "core*" | head -n 1) # find core file
+  - 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
@@ -132,7 +139,7 @@ script:
       make -f Makefile.osx package;
     fi
   - if [ "${RUN_UNITTEST}" == "1" ]; then
-      CTEST_OUTPUT_ON_FAILURE=1 make all test;
+      CTEST_OUTPUT_ON_FAILURE=1  CK_FORK=yes  make all test;
     fi
   - pwd
   - ls -la unittest
@@ -154,40 +161,6 @@ after_success:
       echo "All coverage reports created";
     fi
 
-conan-linux: &conan-linux
-    os: linux
-    dist: xenial
-    language: python
-    python: "3.7"
-    services:
-      - docker
-    if: tag IS present
-    before_install:
-      - true
-    install:
-      - ./conan/travis/install.sh
-    before_script:
-      - true
-    script:
-      - ./conan/travis/build.sh
-    after_success:
-      - true
-
-conan-osx: &conan-osx
-    os: osx
-    language: generic
-    if: tag IS present
-    before_install:
-      - true
-    install:
-      - ./conan/travis/install.sh
-    before_script:
-      - true
-    script:
-      - ./conan/travis/build.sh
-    after_success:
-      - true
-
 
 #########################################################################################
 #########################################################################################
@@ -542,6 +515,39 @@ matrix:
       RUN_UNITTEST=1
 
 
+#########################################################################################
+#####   FREEBSD BUILD   ###########=#####################################################
+#########################################################################################
+
+  -
+    os: freebsd
+    sudo: required
+    compiler: clang
+    env:
+      idx=20
+      N=Clang-FREEBSD-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=NO
+      ALLOW_WARNINGS=YES
+      RUN_UNITTEST=1
+
+
 #########################################################################################
 #####   OSX BUILD   #####################################################################
 #########################################################################################
@@ -553,7 +559,7 @@ matrix:
     env:
       idx=8
       N=Clang-OSX-Complete-NoLua-Release-OpenSSL_1_1_NoDynLoad
-      BUILD_TYPE=OSX_OPENSSL_1_1
+      BUILD_TYPE=Release
       ENABLE_SSL_DYNAMIC_LOADING=NO
       OPENSSL_1_0=NO
       OPENSSL_1_1=YES
@@ -614,37 +620,6 @@ matrix:
 #########################################################################################
 #########################################################################################
 
-  - <<: *conan-linux
-    env: CONAN_GCC_VERSIONS=4.9 CONAN_DOCKER_IMAGE=conanio/gcc49 CONAN_DOCKER_32_IMAGES=1
-  - <<: *conan-linux
-    env: CONAN_GCC_VERSIONS=5 CONAN_DOCKER_IMAGE=conanio/gcc5 CONAN_DOCKER_32_IMAGES=1
-  - <<: *conan-linux
-    env: CONAN_GCC_VERSIONS=6 CONAN_DOCKER_IMAGE=conanio/gcc6 CONAN_DOCKER_32_IMAGES=1
-  - <<: *conan-linux
-    env: CONAN_GCC_VERSIONS=7 CONAN_DOCKER_IMAGE=conanio/gcc7 CONAN_DOCKER_32_IMAGES=1
-  - <<: *conan-linux
-    env: CONAN_GCC_VERSIONS=8 CONAN_DOCKER_IMAGE=conanio/gcc8 CONAN_DOCKER_32_IMAGES=1
-  - <<: *conan-linux
-    env: CONAN_CLANG_VERSIONS=3.9 CONAN_DOCKER_IMAGE=conanio/clang39 CONAN_DOCKER_32_IMAGES=1
-  - <<: *conan-linux
-    env: CONAN_CLANG_VERSIONS=4.0 CONAN_DOCKER_IMAGE=conanio/clang40 CONAN_DOCKER_32_IMAGES=1
-  - <<: *conan-linux
-    env: CONAN_CLANG_VERSIONS=5.0 CONAN_DOCKER_IMAGE=conanio/clang50 CONAN_DOCKER_32_IMAGES=1
-  - <<: *conan-linux
-    env: CONAN_CLANG_VERSIONS=6.0 CONAN_DOCKER_IMAGE=conanio/clang60 CONAN_DOCKER_32_IMAGES=1
-  - <<: *conan-osx
-    osx_image: xcode8.3
-    env: CONAN_APPLE_CLANG_VERSIONS=8.1
-  - <<: *conan-osx
-    osx_image: xcode9
-    env: CONAN_APPLE_CLANG_VERSIONS=9.0
-  - <<: *conan-osx
-    osx_image: xcode9.4
-    env: CONAN_APPLE_CLANG_VERSIONS=9.1
-  - <<: *conan-osx
-    osx_image: xcode10.1
-    env: CONAN_APPLE_CLANG_VERSIONS=10.0
-
 # Remove Lua build, until someone knows how to fix the CMake files
 #
 #  - dist: trusty
@@ -683,5 +658,3 @@ matrix:
 #      ENABLE_DUKTAPE=NO
 #      NO_CACHING=YES
 #      ALLOW_WARNINGS=YES
-
-
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1eb391f..c5368c0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,6 +2,7 @@
 cmake_minimum_required (VERSION 3.3.0)
 cmake_policy(VERSION 3.2.2)
 cmake_policy(SET CMP0054 NEW)
+cmake_policy(SET CMP0057 NEW)
 
 # Set up the project
 project (civetweb)
@@ -80,7 +81,7 @@ option(CIVETWEB_ENABLE_CXX "Enables the C++ wrapper library" OFF)
 message(STATUS "C++ wrappers - ${CIVETWEB_ENABLE_CXX}")
 
 # IP Version 6
-option(CIVETWEB_ENABLE_IPV6 "Enables the IP version 6 support" OFF)
+option(CIVETWEB_ENABLE_IPV6 "Enables the IP version 6 support" ON)
 message(STATUS "IP Version 6 - ${CIVETWEB_ENABLE_IPV6}")
 
 # Websocket support
@@ -226,6 +227,10 @@ message(STATUS "Compile for OpenSSL 1.0 API - ${CIVETWEB_SSL_OPENSSL_API_1_0}")
 option(CIVETWEB_SSL_OPENSSL_API_1_1 "Use the OpenSSL 1.1 API" ON)
 message(STATUS "Compile for OpenSSL 1.1 API - ${CIVETWEB_SSL_OPENSSL_API_1_1}")
 
+# OpenSSL 1.1 API
+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}")
+
 # Dynamically load or link the SSL libraries
 cmake_dependent_option(
   CIVETWEB_ENABLE_SSL_DYNAMIC_LOADING "Dynamically loads the SSL library rather than linking it" ON
@@ -535,9 +540,19 @@ endif()
 if(CIVETWEB_SSL_OPENSSL_API_1_1)
   add_definitions(-DOPENSSL_API_1_1)
 endif()
+if(CIVETWEB_SSL_OPENSSL_API_3_0)
+  add_definitions(-DOPENSSL_API_3_0)
+endif()
 if(CIVETWEB_SSL_OPENSSL_API_1_0 AND CIVETWEB_SSL_OPENSSL_API_1_1)
   message(FATAL_ERROR "Multiple SSL API versions defined")
 endif()
+if(CIVETWEB_SSL_OPENSSL_API_1_0 AND CIVETWEB_SSL_OPENSSL_API_3_0)
+  message(FATAL_ERROR "Multiple SSL API versions defined")
+endif()
+if(CIVETWEB_SSL_OPENSSL_API_1_1 AND CIVETWEB_SSL_OPENSSL_API_3_0)
+  message(FATAL_ERROR "Multiple SSL API versions defined")
+endif()
+
 
 add_definitions(-DUSE_STACK_SIZE=${CIVETWEB_THREAD_STACK_SIZE})
 
@@ -596,7 +611,31 @@ configure_package_config_file(
   ${PROJECT_NAME}-config.cmake
   INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
   NO_CHECK_REQUIRED_COMPONENTS_MACRO
-  PATH_VARS CMAKE_INSTALL_INCLUDEDIR
+  PATH_VARS CMAKE_INSTALL_INCLUDEDIR CMAKE_INSTALL_LIBDIR CIVETWEB_ENABLE_CXX
+)
+
+configure_file(
+  cmake/${PROJECT_NAME}.pc.in
+  ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.pc
+  @ONLY
+)
+
+configure_file(
+  cmake/${PROJECT_NAME}-cpp.pc.in
+  ${PROJECT_BINARY_DIR}/${PROJECT_NAME}-cpp.pc
+  @ONLY
+)
+
+install(
+  FILES 
+    "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.pc"
+    DESTINATION "${CMAKE_INSTALL_DATADIR}/pkgconfig"
+)
+
+install(
+  FILES 
+    "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-cpp.pc"
+    DESTINATION "${CMAKE_INSTALL_DATADIR}/pkgconfig"
 )
 
 write_basic_package_version_file(${PROJECT_NAME}-config-version.cmake
diff --git a/CREDITS.md b/CREDITS.md
index dbf952c..fc98b63 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -4,6 +4,7 @@
 * Abramo Bagnara
 * Adam Bailey
 * Adam Hunyadi
+* Adam Rowell
 * Alan Somers
 * Alberto Bignotti
 * Alex Kozlov
@@ -24,10 +25,12 @@
 * cdbishop
 * celeron55
 * Charles Olivi
+* Chen Yufei
 * Chris Han
 * Chris Jones
 * Chris Rehn
 * Christian Mauderer
+* Christopher Friedt
 * Christopher Galas
 * cjh
 * Colden Cullen
@@ -39,14 +42,16 @@
 * Dave Brower
 * daveelton
 * David Arnold
+* David Korczynski
 * David Loffredo
 * DavidKorczynski
-* Dialga
 * dennis
+* Dialga
 * DL6ER
 * Domenico Di Iorio
 * dprandle
 * Drew Wells
+* drew-wells
 * duong2179
 * ehlertjd
 * Elan P. Kugelmass
@@ -60,8 +65,10 @@
 * Fabrice Fontaine
 * feneuilflo
 * Fernando G. Aranda
+* forworldm
 * Frank Hilliger
-* Gábor Csárdi
+* gajanak
+* Gerard Marull-Paretas
 * Girish Joshi
 * goodmenzy
 * Grahack
@@ -71,11 +78,13 @@
 * guangqing.chen
 * Guilherme Amadio
 * Gustavo Romero
+* Gábor Csárdi
 * hansipie
 * HariKamath Kamath
 * Henry Chang
 * Herumb Shandilya
 * Herve Codina
+* huangminhang
 * Iain Morton
 * ImgBotApp
 * Ivan Dlugos
@@ -97,6 +106,8 @@
 * Joel Gallant
 * Johan De Taeye
 * John Faith
+* Jonas Hahnfeld
+* Jonas Rembser
 * Jordan
 * Jordan Shelley
 * Joshua Boyd
@@ -127,11 +138,13 @@
 * Maarten Fremouw
 * makrsmark
 * marco
+* Mario Trangoni
 * Mark Lakata
 * Martin Gaida
 * Mateusz Gralka
 * Matt Clarkson
 * Mellnik
+* MHU-valantic
 * Mike Crowe
 * mingodad
 * Morgan McGuire
@@ -143,7 +156,9 @@
 * Nick Hildebrant
 * Nigel Stewart
 * nihildeb
+* Niklas Fiekas
 * No Face Press
+* Olliver Schinagl
 * palortoff
 * Patrick Drechsler
 * Patrick Trinkle
@@ -153,6 +168,7 @@
 * PavelVozenilek
 * Perttu Ahola
 * Peter Foerster
+* Peter Huber
 * Philipp Friedenberger
 * Philipp Hasper
 * Pieter Cardoen
@@ -161,7 +177,9 @@
 * Ponnuvel Palaniyappan
 * qinch
 * qinchao
+* r-j-s
 * Radoslaw Zarzynski
+* Rajdeep Roy Chowdhury
 * Red54
 * Retallack Mark mark.retallack
 * Richard Screene
@@ -171,6 +189,8 @@
 * Sage Weil
 * Sangwhan Moon
 * Saumitra Vikram
+* sbruceheart
+* Scott Fennell
 * Scott Nations
 * Sebastien Jodogne
 * Sergey Linev
@@ -178,6 +198,8 @@
 * shantanugadgil
 * Sherwyn Sen
 * shreyajaggi8
+* Silas Parker
+* silverches
 * Simon Hailes
 * slidertom
 * SpaceIm
@@ -213,6 +235,7 @@
 * Yehuda Sadeh
 * Yury Z
 * zhen.wang
+* Zopolis4
 
 and others.
 
diff --git a/LICENSE.md b/LICENSE.md
index 02f089a..a74a0fe 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -66,6 +66,10 @@ http://www.lua.org/license.html
 > THE SOFTWARE.
 
 
+Additional components Copyright (C) Lua.org, PUC-Rio, with MIT license: 
+http://www.inf.puc-rio.br/~roberto/struct/
+
+
 SQLite3 License
 ------
 
diff --git a/README.md b/README.md
index 1793575..1d1c82b 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,13 @@
-![CivetWeb](https://raw.githubusercontent.com/civetweb/civetweb/master/resources/civetweb_64x64.png "CivetWeb") CivetWeb
+![CivetWeb](/resources/civetweb_64x64.png "CivetWeb") CivetWeb
 =======
 
-**The official home of CivetWeb is [https://github.com/civetweb/civetweb](https://github.com/civetweb/civetweb)**
+**The official home of CivetWeb is on GitHub [https://github.com/civetweb/civetweb](https://github.com/civetweb/civetweb)**
 
-[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
+[![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT)
 [![GitHub contributors](https://img.shields.io/github/contributors/civetweb/civetweb.svg)](https://github.com/civetweb/civetweb/blob/master/CREDITS.md)
+[![Stargazers](https://img.shields.io/github/stars/civetweb/civetweb.svg)](https://github.com/civetweb/civetweb/stargazers)
+[![Forks](https://img.shields.io/github/forks/civetweb/civetweb.svg)](https://github.com/civetweb/civetweb/network/members)
+[![Latest Release](https://img.shields.io/github/v/release/civetweb/civetweb.svg)](https://github.com/civetweb/civetweb/releases)
 
 Continuous integration for Linux and macOS ([Travis CI](https://app.travis-ci.com/github/civetweb/civetweb)):
 
@@ -18,15 +21,11 @@ Test coverage check ([coveralls](https://coveralls.io/github/civetweb/civetweb),
 
 [![Coveralls](https://img.shields.io/coveralls/civetweb/civetweb.svg?maxAge=3600)]()
 [![Coverage Status](https://coveralls.io/repos/github/civetweb/civetweb/badge.svg?branch=master)](https://coveralls.io/github/civetweb/civetweb?branch=master)
-
 [![codecov](https://codecov.io/gh/civetweb/civetweb/branch/master/graph/badge.svg)](https://codecov.io/gh/civetweb/civetweb)
 
+Static source code analysis ([Coverity](https://scan.coverity.com/projects/5784)): [![Coverity Scan Build Status](https://scan.coverity.com/projects/5784/badge.svg)](https://scan.coverity.com/projects/5784)
 
-
-Static source code analysis ([Coverity](https://scan.coverity.com/projects/5784)):
-
-[![Coverity Scan Build Status](https://scan.coverity.com/projects/5784/badge.svg)](https://scan.coverity.com/projects/5784)
-
+CodeQL semantic code analysis: [![CodeQL](https://github.com/civetweb/civetweb/workflows/CodeQL/badge.svg)](https://github.com/civetweb/civetweb/actions/workflows/codeql-analysis.yml)
 
 
 Project Mission
@@ -43,7 +42,7 @@ It can also be used by end users as a stand-alone web server running on a Window
 Where to find the official version?
 -----------------------------------
 
-End users can download CivetWeb binaries / releases from SourceForge
+End users can download CivetWeb binaries / releases from here on GitHub [https://github.com/civetweb/civetweb/releases](https://github.com/civetweb/civetweb/releases) or SourceForge
 [https://sourceforge.net/projects/civetweb/](https://sourceforge.net/projects/civetweb/)
 
 Developers can contribute to CivetWeb via GitHub
@@ -52,10 +51,10 @@ Developers can contribute to CivetWeb via GitHub
 Due to a [bug in Git for Windows V2.24](https://github.com/git-for-windows/git/issues/2435)
 CivetWeb must be used with an earlier or later version (see also [here](https://github.com/civetweb/civetweb/issues/812)).
 
-Trouble tickets should be filed on GitHub
+Bugs and requests should be filed on GitHub
 [https://github.com/civetweb/civetweb/issues](https://github.com/civetweb/civetweb/issues)
 
-New releases are announced at Google Groups
+New releases are announced on Google Groups
 [https://groups.google.com/d/forum/civetweb](https://groups.google.com/d/forum/civetweb)
 
 Formerly some support question and discussion threads have been at [Google groups](https://groups.google.com/d/forum/civetweb).
@@ -65,15 +64,7 @@ Source releases can be found on GitHub
 [https://github.com/civetweb/civetweb/releases](https://github.com/civetweb/civetweb/releases)
 
 A very brief overview can be found on GitHub Pages
-[http://civetweb.github.io/civetweb/](http://civetweb.github.io/civetweb/)
-
-
-Getting The Source
-------------------
-Download the source code by running the following code in your command prompt:
-
-$ git clone https://github.com/civetweb/civetweb.git
-or simply grab a copy of the source code as a ZIP or TGZ file.
+[https://civetweb.github.io/civetweb/](https://civetweb.github.io/civetweb/)
 
 
 Quick start documentation
@@ -96,29 +87,29 @@ Overview
 CivetWeb keeps the balance between functionality and
 simplicity by a carefully selected list of features:
 
-- Liberal, commercial-friendly, permissive,
-  [MIT license](http://en.wikipedia.org/wiki/MIT_License)
-- Free from copy-left licenses, like GPL, because you should innovate without
-  restrictions.
 - Forked from [Mongoose](https://code.google.com/p/mongoose/) in 2013, before
   it changed the licence from MIT to commercial + GPL. A lot of enhancements
-  have been added since that time, see
+  have been added since then, see
   [RELEASE_NOTES.md](https://github.com/civetweb/civetweb/blob/master/RELEASE_NOTES.md).
-- Works on Windows, Mac, Linux, UNIX, iPhone, Android, Buildroot, and many
+- Maintains the liberal, permissive, commercial-friendly,
+  [MIT license](https://en.wikipedia.org/wiki/MIT_License)
+- Project is free from copy-left licenses, like GPL, because you should innovate without
+  restrictions.
+- Works on Windows, Mac, Linux, UNIX, IOS, Android, Buildroot, and many
   other platforms.
-- Scripting and database support (CGI, SQLite database, Lua Server Pages,
-  Server side Lua scripts, Server side JavaScript).
+- Scripting and database support (CGI, Lua Server Pages, Server side Lua scripts, Lua SQLite database,
+  Server side JavaScript).
   This provides a ready to go, powerful web development platform in a one
-  single-click executable with **no dependencies**.0
-- Support for CGI, SSI, HTTP digest (MD5) authorization, WebSocket,
-  WebDAV.
+  single-click executable with **no dependencies**. 0
+- Support for CGI, SSI, HTTP digest (MD5) authorization, WebSocket, WebDAV.
+- Experimental HTTP/2 support.
 - HTTPS (SSL/TLS) support using [OpenSSL](https://www.openssl.org/).
 - Optional support for authentication using client side X.509 certificates.
 - Resumed download, URL rewrite, file blacklist, IP-based ACL.
-- May run as Windows service.
+- Can run as a Windows service or systemd service.
 - Download speed limit based on client subnet or URI pattern.
 - Simple and clean embedding API.
-- The source is in single file to make things easy.
+- The source is in single file for drop in compilation.
 - Embedding examples included.
 - HTTP client capable of sending arbitrary HTTP/HTTPS requests.
 - Websocket client functionality available (WS/WSS).
@@ -126,27 +117,21 @@ simplicity by a carefully selected list of features:
 
 ### Optionally included software
 
-[![Lua](https://raw.githubusercontent.com/civetweb/civetweb/master/resources/lua-logo.jpg "Lua Logo")](http://lua.org)
+[![Lua](/resources/lua-logo.jpg "Lua Logo")](https://lua.org)
+[![LuaFileSystem](/resources/luafilesystem-logo.jpg "LuaFileSystem Logo")](https://keplerproject.github.io/luafilesystem/)
+[![LuaSQLite3](/resources/luasqlite-logo.jpg "LuaSQLite3 Logo")](https://lua.sqlite.org/index.cgi/index)
+[![Sqlite3](/resources/sqlite3-logo.jpg "Sqlite3 Logo")](https://sqlite.org)
+[![LuaXML](/resources/luaxml-logo.jpg "LuaXML Logo")](https://github.com/n1tehawk/LuaXML)
+[![Duktape](/resources/duktape-logo.png "Duktape Logo")](https://duktape.org)
 
-[![Sqlite3](https://raw.githubusercontent.com/civetweb/civetweb/master/resources/sqlite3-logo.jpg "Sqlite3 Logo")](http://sqlite.org)
 
-[![LuaFileSystem](https://raw.githubusercontent.com/civetweb/civetweb/master/resources/luafilesystem-logo.jpg "LuaFileSystem Logo")](http://keplerproject.github.io/luafilesystem/)
+### Optional dependencies
 
-[![LuaSQLite3](https://raw.githubusercontent.com/civetweb/civetweb/master/resources/luasqlite-logo.jpg "LuaSQLite3 Logo")](http://lua.sqlite.org/index.cgi/index)
+[zlib](https://zlib.net)
 
-[![LuaXML](https://raw.githubusercontent.com/civetweb/civetweb/master/resources/luaxml-logo.jpg "LuaXML Logo")](https://github.com/n1tehawk/LuaXML)
-
-[![Duktape](https://raw.githubusercontent.com/civetweb/civetweb/master/resources/duktape-logo.png "Duktape Logo")](http://duktape.org)
-
-
-### Optional depencencies
-
-[![zlib](https://raw.githubusercontent.com/civetweb/civetweb/master/resources/zlib3d-b1.png "zlib Logo")](https://zlib.net)
-
-[![OpenSSL](https://raw.githubusercontent.com/civetweb/civetweb/master/resources/OpenSSL_logo.png "OpenSSL Logo")](https://www.openssl.org/)
-
-[![Mbed TLS](https://raw.githubusercontent.com/civetweb/civetweb/master/resources/mbedTLS_logo.png "mbedTLS Logo")](https://github.com/ARMmbed/mbedtls)
+[OpenSSL](https://www.openssl.org/)
 
+[Mbed TLS](https://github.com/ARMmbed/mbedtls)
 
 
 Support
@@ -169,14 +154,13 @@ Any link provided in this project (including source and documentation) is provid
 However, we cannot accept any responsibility for any content on an external page.
 
 
-
 Contributions
 -------------
 
 Contributions are welcome provided all contributions carry the MIT license.
 
 DO NOT APPLY fixes copied from Mongoose to this project to prevent GPL tainting.
-Since 2013, CivetWeb and Mongoose are developed independently.
+Since 2013, CivetWeb and Mongoose have been developed independently.
 By now the code base differs, so patches cannot be safely transferred in either direction.
 
 Some guidelines can be found in [docs/Contribution.md](https://github.com/civetweb/civetweb/blob/master/docs/Contribution.md).
@@ -185,17 +169,18 @@ Some guidelines can be found in [docs/Contribution.md](https://github.com/civetw
 Authors
 -------
 
-CivetWeb has been forked from the last MIT version of Mongoose in 2013.
+CivetWeb was forked from the last MIT version of Mongoose in August 2013.
 Since then, CivetWeb has seen many improvements from various authors
 (Copyright (c) 2013-2021 the CivetWeb developers, MIT license).
 
 A list of authors can be found in [CREDITS.md](https://github.com/civetweb/civetweb/blob/master/CREDITS.md).
 
-CivetWeb is based on the Mongoose project.  The original author of Mongoose was
-Sergey Lyubka (Copyright (c) 2004-2013 Sergey Lyubka, MIT license).
-However, on August 16, 2013, the [license of Mongoose has been changed](https://groups.google.com/forum/#!topic/mongoose-users/aafbOnHonkI)
-after writing and distributing the original code this project is based on.
-The license change and CivetWeb used to be mentioned on the Mongoose
+CivetWeb is based on the [Mongoose project](https://github.com/cesanta/mongoose). The original author of Mongoose was
+Sergey Lyubka(2004-2013) who released it under the MIT license.
+However, on August 16, 2013,
+[Mongoose was relicensed to a dual GPL V2 + commercial license](https://groups.google.com/forum/#!topic/mongoose-users/aafbOnHonkI)
+and CiwetWeb was created by Thomas Davis (sunsetbrew) as "the MIT fork of mongoose".
+The license change and CivetWeb fork was mentioned on the Mongoose
 [Wikipedia](https://en.wikipedia.org/wiki/Mongoose_(web_server))
 page as well, but it's getting deleted (and added again) there every
 now and then.
@@ -203,4 +188,3 @@ now and then.
 Using the CivetWeb project ensures the MIT licenses terms are applied and
 GPL cannot be imposed on any of this code, as long as it is sourced from
 here. This code will remain free with the MIT license protection.
-
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 2824677..354061e 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,3 +1,33 @@
+Release Notes v1.16
+===
+### Objectives: *bug fixes, documentation, WebDAV*
+
+Changes
+-------
+
+- Enable IPv6 as default when using CMake
+- Define error codes for mg_start2, mg_start_domain2, mg_connect_client2
+- Fixes for OpenSSL 3.0 support
+- Add support for Mbed TLS v3.0.0
+- WebDAV should understand Windows File Explorer (experimental)
+- Accept HTTP basic authentication
+- FreeBSD support, including CI and tests
+- Make pattern matching function availible in the public interface
+- Make base64 encoding and decoding functions available
+- Various fixes for HTTP/2 support
+- Additional examples
+- Fixes and updates to existing examples
+- Fix spelling errors in code and documentation
+- Remove Conan support
+- Update version number
+
+Known Issues
+-----
+
+- The WebDAV support when using the Windows Explorer as client has various limitations when renaming or moving files and folders.
+- In particular file names in non-latin characters may break when using WebDAV with the Windows Explorer.
+
+
 Release Notes v1.15
 ===
 ### Objectives: *bug fixes, remove legacy interfaces*
@@ -408,7 +438,7 @@ Changes
 -------
 
 - Corrected bad mask flag/opcode passing to websocket callback (William Greathouse)
-- Moved CEVITWEB_VERSION define into civetweb.h
+- Moved CIVETWEB_VERSION define into civetweb.h
 - Added new simple zip deployment build for Windows.
 - Removed windows install package build.
 - Fixes page violation in mod_lua.inl (apkbox)
@@ -514,7 +544,7 @@ Changes
 - Added new C++ abstraction class CivetServer
 - Added thread safety for and fixed websocket defects (Morgan McGuire)
 - Created PKGBUILD to use Arch distribution (Daniel Oaks)
-- Created new documentation on Embeddeding, Building and yaSSL (see docs/).
+- Created new documentation on Embedding, Building and yaSSL (see docs/).
 - Updated License file to include all licenses.
 - Replaced MD5 implementation due to questionable license.
      + This requires new source file md5.inl
@@ -537,7 +567,7 @@ Known Issues
 - Build support for VS6 and some other has been deprecated.
     + This does not impact embedded programs, just the stand-alone build.
     + The old Makefile was renamed to Makefile.deprecated.
-    + This is partcially do to lack fo testing.
+    + This is partcially do to lack of testing.
     + Need to find out what is actually in demand.
 - Build changes may impact current users.
     + As with any change of this type, changes may impact some users.
diff --git a/SECURITY.md b/SECURITY.md
index a7efc51..b1781af 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -6,10 +6,18 @@ The current "head" version ("master" branch) undergoes some automatic tests, but
 All development branches may be in an intermediate, untested state.
 
 For released versions, additional tests are performed, including manual tests, static source code analysis and fuzz testing.
+Except for features marked as "experimental". Experimental features are deactivated in the default configuration.
 
 Defects will be fixed in the current head version.
 Selected, critical defects are fixed in the latest release as well.
 
+Note that different security policies apply to different files/folders in this project:
+- The core components are include/civetweb.h, src/civetweb.c and src/*.inl.  These files are part of every server instance in production. Therefore they have to undergo the most intensive security tests and reviews.
+- The src/main.c file is used by the standalone server. It is used in various tests in combination with the aforementioned core components. Applications embedding civetweb will not use main.c and, thus, do not suffer from any vulnerabilities therein.
+- The example folders contain different usage examples in different maintenance state. This is explained in detail in the README file there.
+- The content of all test folders (test, unittest, fuzztest) is used to test the server functionality. These tests are not designed with security in mind - on the contrary, some tests contain scripts or settings that even introduce security leaks on purpose. All tests are only meant to be run in a test environment. You should not use the content of any test folder in production. Also certificates in "resources/cert" are only meant to be used in test environments and must never be used in production.
+
+
 ## Reporting a Vulnerability
 
 Please send vulnerability reports by email to bel2125 at gmail com.
@@ -17,4 +25,5 @@ Vulnerability with low severity can be sent directly by email.
 
 For high severity vulnerabilities, you can get an individual gpg key to encrypt your detailed description of vulnerabilities you want to report.
 
-If you do not get any response within on week, your email might have been lost (e.g., deleted as false positive by a spam filter). In this case, please open a GitHub issue.
+If you do not get any response within one week, your email might have been lost (e.g., deleted as false positive by a spam filter). In this case, please open a GitHub issue.
+
diff --git a/appveyor.yml b/appveyor.yml
index 7f8996e..41cde75 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -320,16 +320,6 @@ environment:
       configuration: Release
       platform: x64
       image: Ubuntu2004
-    # Conan builds
-    - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
-      CONAN_VISUAL_VERSIONS: 12
-      PYTHON: "C:\\Python37"
-    - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
-      CONAN_VISUAL_VERSIONS: 14
-      PYTHON: "C:\\Python37"
-    - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
-      CONAN_VISUAL_VERSIONS: 15
-      PYTHON: "C:\\Python37"
 
 install:
   # Derive some extra information
@@ -471,7 +461,7 @@ before_build:
     -DCIVETWEB_CXX_STANDARD=%cxx_standard%
     -DCIVETWEB_SSL_OPENSSL_API_1_0=NO
     -DCIVETWEB_SSL_OPENSSL_API_1_1=YES
-    "%source_path%"    
+    "%source_path%"
   - powershell Push-AppveyorArtifact CMakeCache.txt
   - cd "%source_path%"
 
@@ -542,14 +532,3 @@ for:
       only:
         - configuration: Release
       fast_finish: false
-  -
-    matrix:
-      only:
-        - PYTHON: "C:\\Python37"
-    skip_non_tags: true
-    install:
-      - set PATH=%PATH%;%PYTHON%/Scripts/
-      - pip.exe install conan conan_package_tools
-    build: false
-    test_script:
-      - python conan/build.py
diff --git a/build b/build
index 75eebc0..12efb54 100755
--- a/build
+++ b/build
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/usr/bin/env bash
 set -euo pipefail
 IFS=$'\n\t'
 
diff --git a/cmake/AddCCompilerFlag.cmake b/cmake/AddCCompilerFlag.cmake
index 5eaf890..a1a28a6 100644
--- a/cmake/AddCCompilerFlag.cmake
+++ b/cmake/AddCCompilerFlag.cmake
@@ -24,14 +24,15 @@ function(add_c_compiler_flag FLAG)
   string(REPLACE "+" "X" SANITIZED_FLAG ${SANITIZED_FLAG})
   string(REGEX REPLACE "[^A-Za-z_0-9]" "_" SANITIZED_FLAG ${SANITIZED_FLAG})
   string(REGEX REPLACE "_+" "_" SANITIZED_FLAG ${SANITIZED_FLAG})
-  check_c_compiler_flag(${SANITIZED_FLAG} NO_DIAGNOSTICS_PRODUCED)
-  if(${NO_DIAGNOSTICS_PRODUCED})
+  check_c_compiler_flag("${FLAG}" ${SANITIZED_FLAG})
+  if(${${SANITIZED_FLAG}})
+    set(${SANITIZED_FLAG} ON PARENT_SCOPE )
     set(VARIANT ${ARGV1})
     if(ARGV1)
       string(REGEX REPLACE "[^A-Za-z_0-9]" "_" VARIANT "${VARIANT}")
       string(TOUPPER "_${VARIANT}" VARIANT)
     endif()
-    set(CMAKE_C_FLAGS${VARIANT} "${CMAKE_C_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE)
-  endif()
+      set(CMAKE_C_FLAGS${VARIANT} "${CMAKE_C_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE)
+	endif()
 endfunction()
 
diff --git a/cmake/AddCXXCompilerFlag.cmake b/cmake/AddCXXCompilerFlag.cmake
index 6607c62..dbb4bf2 100644
--- a/cmake/AddCXXCompilerFlag.cmake
+++ b/cmake/AddCXXCompilerFlag.cmake
@@ -24,14 +24,14 @@ function(add_cxx_compiler_flag FLAG)
   string(REPLACE "+" "X" SANITIZED_FLAG ${SANITIZED_FLAG})
   string(REGEX REPLACE "[^A-Za-z_0-9]" "_" SANITIZED_FLAG ${SANITIZED_FLAG})
   string(REGEX REPLACE "_+" "_" SANITIZED_FLAG ${SANITIZED_FLAG})
-  check_cxx_compiler_flag(${SANITIZED_FLAG} NO_DIAGNOSTICS_PRODUCED)
-  if(${NO_DIAGNOSTICS_PRODUCED})
+  check_cxx_compiler_flag( "${FLAG}" ${SANITIZED_FLAG} )
+  if(${${SANITIZED_FLAG}})
     set(VARIANT ${ARGV1})
     if(ARGV1)
       string(REGEX REPLACE "[^A-Za-z_0-9]" "_" VARIANT "${VARIANT}")
       string(TOUPPER "_${VARIANT}" VARIANT)
     endif()
-    set(CMAKE_CXX_FLAGS${VARIANT} "${CMAKE_CXX_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE)
-  endif()
+      set(CMAKE_CXX_FLAGS${VARIANT} "${CMAKE_CXX_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE)
+	endif()
 endfunction()
 
diff --git a/cmake/FindWinSock.cmake b/cmake/FindWinSock.cmake
index 25c4f2f..b83bf4e 100644
--- a/cmake/FindWinSock.cmake
+++ b/cmake/FindWinSock.cmake
@@ -39,6 +39,7 @@ endmacro(REMOVE_DUPLICATE_PATHS)
 
 set(WINSOCK_INCLUDE_PATHS "${WINSOCK_ROOT}/include/")
 if(MINGW)
+  file(TOUCH ${CMAKE_BINARY_DIR}/nul)
   execute_process(
     COMMAND ${CMAKE_C_COMPILER} -xc -E -v -
     RESULT_VARIABLE RESULT
@@ -71,7 +72,16 @@ if(MINGW)
   )
   if (NOT RESULT)
     string(REGEX MATCH "libraries: =([^\r\n]*)" OUT "${OUT}")
-    list(APPEND WINSOCK_LIBRARY_PATHS "${CMAKE_MATCH_1}")
+    if(CMAKE_HOST_WIN32)
+      message(STATUS "not replacing ${_SEARCH_PATHS}")
+      list(APPEND WINSOCK_LIBRARY_PATHS "${CMAKE_MATCH_1}")
+    else()
+      # On Unix-like host systems, search dirs are separated by ':'.
+      # Thus we need to replace ':' with ';' to make a valid cmake list.
+      string(REPLACE ":" ";" _SEARCH_PATHS "${CMAKE_MATCH_1}")
+      list(APPEND WINSOCK_LIBRARY_PATHS "${_SEARCH_PATHS}")
+      unset(_SEARCH_PATHS)
+    endif()
   endif()
 endif()
 if ("${CMAKE_HOST_SYSTEM_PROCESSOR}" STREQUAL "AMD64" AND "${CMAKE_SIZEOF_VOID_P}" EQUAL 4)
diff --git a/cmake/check/c82fe8888aacfe784476112edd3878256d2e30bc.patch b/cmake/check/c82fe8888aacfe784476112edd3878256d2e30bc.patch
deleted file mode 100644
index e16ea1f..0000000
--- a/cmake/check/c82fe8888aacfe784476112edd3878256d2e30bc.patch
+++ /dev/null
@@ -1,31 +0,0 @@
-From c82fe8888aacfe784476112edd3878256d2e30bc Mon Sep 17 00:00:00 2001
-From: Joshua Boyd <jdboyd@Joshua-Boyds-Mac-mini.local>
-Date: Wed, 23 Mar 2016 17:54:41 -0400
-Subject: [PATCH] Detect missing itimerspec on OSX.
-
-Set define to compiler accordingly.
-
-This fixes cmake on osx support.
----
- CMakeLists.txt | 8 ++++++++
- 1 file changed, 8 insertions(+)
-
-diff --git a/CMakeLists.txt b/CMakeLists.txt
-index e271e31..1d413e8 100644
---- a/CMakeLists.txt
-+++ b/CMakeLists.txt
-@@ -193,6 +193,14 @@ if(NOT HAVE_SYS_TIME_H)
-     endif(MSVC)
- endif(NOT HAVE_SYS_TIME_H)
- 
-+# OSX has sys/time.h, but it still lacks itimerspec
-+if(HAVE_SYS_TIME_H)
-+    check_struct_member("struct itimerspec" it_value "sys/time.h" HAVE_STRUCT_ITIMERSPEC_IT_VALUE)
-+    if(NOT HAVE_STRUCT_ITIMERSPEC_IT_VALUE)
-+        add_definitions(-DSTRUCT_ITIMERSPEC_DEFINITION_MISSING=1)
-+        set(STRUCT_ITIMERSPEC_DEFINITION_MISSING 1)
-+    endif(NOT HAVE_STRUCT_ITIMERSPEC_IT_VALUE)
-+endif(HAVE_SYS_TIME_H)
- 
- ###############################################################################
- # Check for integer types
diff --git a/cmake/check/check_run.patch b/cmake/check/check_run.patch
deleted file mode 100644
index 6d1cf09..0000000
--- a/cmake/check/check_run.patch
+++ /dev/null
@@ -1,27 +0,0 @@
---- check_run.c.orig	2020-01-12 03:06:59.992434700 +0100
-+++ check_run.c.new	2020-01-12 04:40:42.224635800 +0100
-@@ -28,6 +28,8 @@
- #include <stdarg.h>
- #include <signal.h>
- #include <setjmp.h>
-+#include <sys/stat.h>
-+#include <fcntl.h>
- 
- #include "check.h"
- #include "check_error.h"
-@@ -486,6 +488,15 @@
-         eprintf("Error in call to fork:", __FILE__, __LINE__ - 2);
-     if(pid == 0)
-     {
-+char fn[256];
-+int fd1, fd2;
-+sprintf(fn, "%s.stdout", srunner_log_fname(sr));
-+fd1 = open(fn, O_WRONLY|O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
-+dup2(fd1, 1);
-+sprintf(fn, "%s.stderr", srunner_log_fname(sr));
-+fd2 = open(fn, O_WRONLY|O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
-+dup2(fd2, 2);
-+
-         setpgid(0, 0);
-         group_pid = getpgrp();
-         tr = tcase_run_checked_setup(sr, tc);
diff --git a/cmake/check/patch.cmake b/cmake/check/patch.cmake
deleted file mode 100644
index 472d392..0000000
--- a/cmake/check/patch.cmake
+++ /dev/null
@@ -1,12 +0,0 @@
-message(STATUS "Patching check ${VERSION} ${SOURCE_DIR}")
-
-# Patch checks for MinGW
-# https://sourceforge.net/p/check/patches/53/
-set(CMAKE_LISTS_LOCATION "${SOURCE_DIR}/CMakeLists.txt")
-file(READ ${CMAKE_LISTS_LOCATION} CMAKE_LISTS)
-string(REGEX REPLACE
-  "(check_type_size\\((clock|clockid|timer)_t [A-Z_]+\\)[\r\n]+[^\r\n]+[\r\n]+[^\r\n]+[\r\n]+endif\\(NOT HAVE[A-Z_]+\\))"
-  "set(CMAKE_EXTRA_INCLUDE_FILES time.h)\n\\1\nunset(CMAKE_EXTRA_INCLUDE_FILES)"
-  CMAKE_LISTS "${CMAKE_LISTS}")
-file(WRITE ${CMAKE_LISTS_LOCATION} "${CMAKE_LISTS}")
-message(STATUS "Patched ${CMAKE_LISTS_LOCATION}")
diff --git a/cmake/civetweb-config.cmake.in b/cmake/civetweb-config.cmake.in
index 5e4afc7..8053a14 100644
--- a/cmake/civetweb-config.cmake.in
+++ b/cmake/civetweb-config.cmake.in
@@ -2,6 +2,13 @@
 include(CMakeFindDependencyMacro)
 
 set_and_check(civetweb_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
+set_and_check(civetweb_INCLUDE_DIRS "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
+set_and_check(civetweb_LIBRARY_DIRS "@PACKAGE_CMAKE_INSTALL_LIBDIR@")
+set(civetweb_LIBRARIES civetweb)
+
+if(@CIVETWEB_ENABLE_CXX@)
+  set(civetweb-cpp_LIBRARIES civetweb-cpp)
+endif()
 
 find_dependency(Threads)
 
diff --git a/cmake/civetweb-cpp.pc.in b/cmake/civetweb-cpp.pc.in
new file mode 100644
index 0000000..ca1232c
--- /dev/null
+++ b/cmake/civetweb-cpp.pc.in
@@ -0,0 +1,12 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
+libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
+
+Name: @PROJECT_NAME@-cpp
+Description: generic graph library
+Version: @PROJECT_VERSION@
+Requires:
+Libs: -L${libdir} -l@PROJECT_NAME@-cpp
+Cflags: -I${includedir}
+
diff --git a/cmake/civetweb.pc.in b/cmake/civetweb.pc.in
new file mode 100644
index 0000000..27cea8f
--- /dev/null
+++ b/cmake/civetweb.pc.in
@@ -0,0 +1,13 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
+libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
+
+Name: @PROJECT_NAME@
+Description: generic graph library
+Version: @PROJECT_VERSION@
+Requires:
+Libs: -L${libdir} -l@PROJECT_NAME@
+Cflags: -I${includedir}
+
+
diff --git a/conan/build.py b/conan/build.py
deleted file mode 100644
index 96a7e86..0000000
--- a/conan/build.py
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import os
-import re
-from cpt.packager import ConanMultiPackager
-from cpt.ci_manager import CIManager
-from cpt.printer import Printer
-
-
-class BuilderSettings(object):
-
-    @property
-    def branch(self):
-        """ Get branch name
-        """
-        printer = Printer(None)
-        ci_manager = CIManager(printer)
-        return ci_manager.get_branch()
-
-    @property
-    def username(self):
-        """ Set civetweb as package's owner
-        """
-        return os.getenv("CONAN_USERNAME", "civetweb")
-
-    @property
-    def upload(self):
-        """ Set civetweb repository to be used on upload.
-            The upload server address could be customized by env var
-            CONAN_UPLOAD. If not defined, the method will check the branch name.
-            Only master or CONAN_STABLE_BRANCH_PATTERN will be accepted.
-            The master branch will be pushed to testing channel, because it does
-            not match the stable pattern. Otherwise it will upload to stable
-            channel.
-        """
-        if os.getenv("CONAN_UPLOAD", None) is not None:
-            return os.getenv("CONAN_UPLOAD")
-
-        prog = re.compile(self.stable_branch_pattern)
-        if self.branch and prog.match(self.branch):
-            return "https://api.bintray.com/conan/civetweb/conan"
-
-        return None
-
-    @property
-    def upload_only_when_stable(self):
-        """ Force to upload when match stable pattern branch
-        """
-        return os.getenv("CONAN_UPLOAD_ONLY_WHEN_STABLE", True)
-
-    @property
-    def stable_branch_pattern(self):
-        """ Only upload the package the branch name is like a tag
-        """
-        return os.getenv("CONAN_STABLE_BRANCH_PATTERN", r"v(\d+\.\d+)")
-
-    @property
-    def version(self):
-        regex = re.compile(self.stable_branch_pattern)
-        match = regex.match(self.branch)
-        if match:
-            return match.group(1)
-        return "latest"
-
-    @property
-    def reference(self):
-        """ Read project version from branch name to create Conan referece
-        """
-        return os.getenv("CONAN_REFERENCE", "civetweb/{}".format(self.version))
-
-if __name__ == "__main__":
-    settings = BuilderSettings()
-    builder = ConanMultiPackager(
-        reference=settings.reference,
-        username=settings.username,
-        upload=settings.upload,
-        upload_only_when_stable=settings.upload_only_when_stable,
-        stable_branch_pattern=settings.stable_branch_pattern,
-        test_folder=os.path.join("conan", "test_package"))
-    builder.add_common_builds(pure_c=False)
-    builder.run()
diff --git a/conan/test_package/CMakeLists.txt b/conan/test_package/CMakeLists.txt
deleted file mode 100644
index e5034b5..0000000
--- a/conan/test_package/CMakeLists.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-cmake_minimum_required(VERSION 2.8.11)
-project(test_package CXX)
-
-set(CMAKE_VERBOSE_MAKEFILE ON)
-
-include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
-conan_basic_setup()
-
-add_executable(${PROJECT_NAME} test_package.cpp)
-target_link_libraries(${PROJECT_NAME} ${CONAN_LIBS})
-set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11)
diff --git a/conan/test_package/conanfile.py b/conan/test_package/conanfile.py
deleted file mode 100644
index 778ba6a..0000000
--- a/conan/test_package/conanfile.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import os
-from conans import ConanFile, CMake
-
-
-class TestPackageConan(ConanFile):
-    settings = "os", "compiler", "build_type", "arch"
-    generators = "cmake"
-
-    def build(self):
-        cmake = CMake(self)
-        cmake.configure()
-        cmake.build()
-
-    def test(self):
-        assert os.path.isfile(os.path.join(self.deps_cpp_info["civetweb"].rootpath, "licenses", "LICENSE.md"))
-        bin_path = os.path.join("bin", "test_package")
-        self.run(bin_path, run_environment=True)
diff --git a/conan/test_package/test_package.cpp b/conan/test_package/test_package.cpp
deleted file mode 100644
index 551caad..0000000
--- a/conan/test_package/test_package.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (c) 2018-2020 the CivetWeb developers
- * MIT License
- */
-
-/* Simple demo of a REST callback. */
-#ifdef _WIN32
-#include <windows.h>
-#else
-#include <unistd.h>
-#endif
-
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-
-#include "civetweb.h"
-
-#define PORT "8080"
-#define HOST_INFO "http://localhost:8080"
-#define EXAMPLE_URI "/example"
-
-static int exitNow = 0;
-
-static int
-ExampleGET(struct mg_connection* conn)
-{
-    mg_send_http_ok(conn, "text/plain", 10);
-    exitNow = 1;
-	return 200;
-}
-
-static int
-ExampleHandler(struct mg_connection *conn, void *cbdata)
-{
-	const struct mg_request_info *ri = mg_get_request_info(conn);
-	(void)cbdata; /* currently unused */
-
-	if (0 == strcmp(ri->request_method, "GET")) {
-		return ExampleGET(conn);
-	}
-	return 405;
-}
-
-int
-log_message(const struct mg_connection *conn, const char *message)
-{
-	puts(message);
-	return 1;
-}
-
-int
-main(int argc, char *argv[])
-{
-	struct mg_callbacks callbacks;
-	struct mg_context *ctx;
-	time_t start_t;
-	time_t end_t;
-	double diff_t;
-	int err = 0;
-
-	mg_init_library(0);
-
-	/* Callback will print error messages to console */
-	memset(&callbacks, 0, sizeof(callbacks));
-	callbacks.log_message = log_message;
-
-	/* Start CivetWeb web server */
-	ctx = mg_start(&callbacks, 0, NULL);
-
-	/* Check return value: */
-	if (ctx == NULL) {
-		fprintf(stderr, "Cannot start CivetWeb - mg_start failed.\n");
-		return EXIT_FAILURE;
-	}
-
-	/* Add handler EXAMPLE_URI, to explain the example */
-	mg_set_request_handler(ctx, EXAMPLE_URI, ExampleHandler, 0);
-
-	/* Show sone info */
-	printf("Start example: %s%s\n", HOST_INFO, EXAMPLE_URI);
-
-
-	/* Wait until the server should be closed */
-	time(&start_t);
-	while (!exitNow) {
-#ifdef _WIN32
-		Sleep(1000);
-#else
-		sleep(1);
-#endif
-        time(&end_t);
-		diff_t = difftime(end_t, start_t);
-		if (diff_t > 3.0) {
-			break;
-		}
-	}
-
-	/* Stop the server */
-	mg_stop(ctx);
-	return EXIT_SUCCESS;
-}
diff --git a/conan/travis/build.sh b/conan/travis/build.sh
deleted file mode 100755
index 069ced2..0000000
--- a/conan/travis/build.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/bash
-
-set -e
-set -x
-
-if [[ "$(uname -s)" == 'Darwin' ]]; then
-    if which pyenv > /dev/null; then
-        eval "$(pyenv init -)"
-    fi
-    pyenv activate conan
-fi
-
-conan user
-python conan/build.py
diff --git a/conan/travis/install.sh b/conan/travis/install.sh
deleted file mode 100755
index dbd2274..0000000
--- a/conan/travis/install.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-
-set -ex
-
-if [[ "$(uname -s)" == 'Darwin' ]]; then
-    brew update || brew update
-    brew outdated pyenv || brew upgrade pyenv
-    brew install pyenv-virtualenv
-    brew install cmake || true
-
-    if which pyenv > /dev/null; then
-        eval "$(pyenv init -)"
-    fi
-
-    pyenv install 3.7.1
-    pyenv virtualenv 3.7.1 conan
-    pyenv rehash
-    pyenv activate conan
-fi
-
-pip install conan==1.10.2
-pip install conan_package_tools
diff --git a/conanfile.py b/conanfile.py
deleted file mode 100644
index 72af8c7..0000000
--- a/conanfile.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*
-from conans import ConanFile, tools, CMake
-
-
-class civetwebConan(ConanFile):
-    name = "civetweb"
-    license = "MIT"
-    url = "https://github.com/civetweb/civetweb"
-    description = "Embedded C/C++ web server"
-    author = "Bernhard Lehner <bel2125@gmail.com>"
-    topics = ("conan", "civetweb", "web-server", "embedded")
-    exports = ("LICENSE.md", "README.md")
-    exports_sources = ("src/*", "cmake/*", "include/*", "CMakeLists.txt")
-    generators = "cmake"
-    settings = "os", "compiler", "build_type", "arch"
-    options = {
-        "shared"            : [True, False],
-        "fPIC"              : [True, False],
-        "enable_ssl"        : [True, False],
-        "enable_websockets" : [True, False],
-        "enable_ipv6"       : [True, False],
-        "enable_cxx"        : [True, False]
-    }
-    default_options = {
-        "shared"            : False,
-        "fPIC"              : True,
-        "enable_ssl"        : True,
-        "enable_websockets" : True,
-        "enable_ipv6"       : True,
-        "enable_cxx"        : True
-    }
-
-    def config_options(self):
-        if self.settings.os == 'Windows':
-            del self.options.fPIC
-
-    def configure(self):
-        if not self.options.enable_cxx:
-            del self.settings.compiler.libcxx
-
-    def requirements(self):
-        if self.options.enable_ssl:
-            self.requires("OpenSSL/1.0.2q@conan/stable")
-
-    def _configure_cmake(self):
-        cmake = CMake(self)
-        cmake.verbose = True
-        cmake.definitions["CIVETWEB_ENABLE_SSL"] = self.options.enable_ssl
-        cmake.definitions["CIVETWEB_ENABLE_WEBSOCKETS"] = self.options.enable_websockets
-        cmake.definitions["CIVETWEB_ENABLE_IPV6"] = self.options.enable_ipv6
-        cmake.definitions["CIVETWEB_ENABLE_CXX"] = self.options.enable_cxx
-        cmake.definitions["CIVETWEB_BUILD_TESTING"] = False
-        cmake.definitions["CIVETWEB_ENABLE_ASAN"] = False
-        cmake.configure(build_dir="build_subfolder")
-        return cmake
-
-    def build(self):
-        tools.replace_in_file(file_path="CMakeLists.txt",
-                              search="project (civetweb)",
-                              replace="""project (civetweb)
-                                 include(conanbuildinfo.cmake)
-                                 conan_basic_setup()""")
-        cmake = self._configure_cmake()
-        cmake.build()
-
-    def package(self):
-        self.copy("LICENSE.md", dst="licenses")
-        cmake = self._configure_cmake()
-        cmake.install()
-
-    def package_info(self):
-        self.cpp_info.libs = tools.collect_libs(self)
-        if self.settings.os == "Linux":
-            self.cpp_info.libs.extend(["dl", "rt", "pthread"])
-            if self.options.enable_cxx:
-                self.cpp_info.libs.append("m")
-        elif self.settings.os == "Macos":
-            self.cpp_info.exelinkflags.append("-framework Cocoa")
-            self.cpp_info.sharedlinkflags = self.cpp_info.exelinkflags
-            self.cpp_info.defines.append("USE_COCOA")
-        elif self.settings.os == "Windows":
-            self.cpp_info.libs.append("Ws2_32")
-        if self.options.enable_websockets:
-            self.cpp_info.defines.append("USE_WEBSOCKET")
-        if self.options.enable_ipv6:
-            self.cpp_info.defines.append("USE_IPV6")
-        if not self.options.enable_ssl:
-            self.cpp_info.defines.append("NO_SSL")
diff --git a/debian/changelog b/debian/changelog
index 7ae022c..68b1158 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+civetweb (1.16+dfsg-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Tue, 02 May 2023 22:13:19 -0000
+
 civetweb (1.15+dfsg-4) unstable; urgency=medium
 
   * Fix watch file
diff --git a/debian/patches/soversion b/debian/patches/soversion
index 11c546a..23e84ee 100644
--- a/debian/patches/soversion
+++ b/debian/patches/soversion
@@ -2,9 +2,11 @@ Description: Configuration option to set the SOVERSION of the shared library
 Author: Sebastien Jodogne <s.jodogne@orthanc-labs.com>
 ---
 This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
---- a/CMakeLists.txt
-+++ b/CMakeLists.txt
-@@ -37,6 +37,7 @@ include(DetermineTargetArchitecture)
+Index: civetweb.git/CMakeLists.txt
+===================================================================
+--- civetweb.git.orig/CMakeLists.txt
++++ civetweb.git/CMakeLists.txt
+@@ -38,6 +38,7 @@ include(DetermineTargetArchitecture)
  include(CMakeDependentOption)
  
  set(CIVETWEB_VERSION "1.15.0" CACHE STRING "The version of the civetweb library")
@@ -12,8 +14,10 @@ This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
  string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" CIVETWEB_VERSION_MATCH "${CIVETWEB_VERSION}")
  if ("${CIVETWEB_VERSION_MATCH}" STREQUAL "")
    message(FATAL_ERROR "Must specify a semantic version: major.minor.patch")
---- a/src/CMakeLists.txt
-+++ b/src/CMakeLists.txt
+Index: civetweb.git/src/CMakeLists.txt
+===================================================================
+--- civetweb.git.orig/src/CMakeLists.txt
++++ civetweb.git/src/CMakeLists.txt
 @@ -8,7 +8,7 @@ set_target_properties(civetweb-c-library
    OUTPUT_NAME "civetweb"
    EXPORT_NAME "civetweb"
diff --git a/debian/patches/webdav-uploads b/debian/patches/webdav-uploads
index 327af4b..b7745c5 100644
--- a/debian/patches/webdav-uploads
+++ b/debian/patches/webdav-uploads
@@ -2,9 +2,11 @@ Description: Introduce function for performance of keep-alive and to allow WebDA
 Origin: https://hg.orthanc-server.com/orthanc/file/default/OrthancFramework/Resources/Patches/civetweb-1.13.patch
 ---
 This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
---- a/include/civetweb.h
-+++ b/include/civetweb.h
-@@ -1705,6 +1705,9 @@ CIVETWEB_API int mg_start_domain2(struct
+Index: civetweb.git/include/civetweb.h
+===================================================================
+--- civetweb.git.orig/include/civetweb.h
++++ civetweb.git/include/civetweb.h
+@@ -1826,6 +1826,9 @@ CIVETWEB_API int mg_start_domain2(struct
                                    struct mg_error_data *error);
  
  
@@ -14,9 +16,11 @@ This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
  #ifdef __cplusplus
  }
  #endif /* __cplusplus */
---- a/src/civetweb.c
-+++ b/src/civetweb.c
-@@ -10540,6 +10540,11 @@ static const struct mg_http_method_info
+Index: civetweb.git/src/civetweb.c
+===================================================================
+--- civetweb.git.orig/src/civetweb.c
++++ civetweb.git/src/civetweb.c
+@@ -10859,6 +10859,11 @@ static const struct mg_http_method_info
      /* + MicroSoft extensions
       * https://msdn.microsoft.com/en-us/library/aa142917.aspx */
  
@@ -28,7 +32,7 @@ This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
      /* REPORT method (RFC 3253) */
      {"REPORT", 1, 1, 1, 1, 1},
      /* REPORT method only allowed for CGI/Lua/LSP and callbacks. */
-@@ -21472,4 +21477,12 @@ mg_exit_library(void)
+@@ -22516,4 +22521,12 @@ mg_exit_library(void)
  }
  
  
diff --git a/docs/Building.md b/docs/Building.md
index bc0ca7e..ed30bd7 100644
--- a/docs/Building.md
+++ b/docs/Building.md
@@ -163,7 +163,7 @@ make build COPT="-DNDEBUG -DNO_CGI"
 |                              |                                                                     |
 | `USE_ALPN`                   | enable Application-Level-Protocol-Negotiation, required for HTTP2   |
 | `USE_DUKTAPE`                | enable server-side JavaScript (using Duktape library)               |
-| `USE_HTTP2`                  | enable HTTP2 support (experimental, not reccomended for production) |
+| `USE_HTTP2`                  | enable HTTP2 support (experimental, not recommended for production) |
 | `USE_IPV6`                   | enable IPv6 support                                                 |
 | `USE_LUA`                    | enable Lua support                                                  |
 | `USE_SERVER_STATS`           | enable server statistics support                                    |
@@ -179,6 +179,8 @@ 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.x interface                                          |
+| `USE_MBEDTLS`                | Use MbedTLS (cannot be combined with OPENSSL_API_*)                 |
 |                              |                                                                     |
 | `BUILD_DATE`                 | define as a string to be used as build id instead of __DATE__       |
 |                              |                                                                     |
diff --git a/docs/Contribution.md b/docs/Contribution.md
index 75a50e9..47600ca 100644
--- a/docs/Contribution.md
+++ b/docs/Contribution.md
@@ -7,7 +7,7 @@ Contributions to CivetWeb are welcome, provided all contributions carry the MIT
 - If you know how to fix the issue, please create a pull request on GitHub. Please take care your modifications pass the continuous integration checks. These checks are performed automatically when you create a pull request, but it may take some hours until all tests are completed. Please provide a description for every pull request (see below).
 - Alternatively, you can post a patch or describe the required modifications in a GitHub issue. However, a pull request would be preferred.
 
-- Improvments to documentation, tests and examples are welcome as well.
+- Improvements to documentation, tests and examples are welcome as well.
 
 - Contributor names are listed in [CREDITS.md](https://github.com/civetweb/civetweb/blob/master/CREDITS.md), unless you explicitly state you don't want your name to be listed there. This file is occasionally updated, adding new contributors, using author names from git commits and GitHub comments.
 
diff --git a/docs/Embedding.md b/docs/Embedding.md
index 570eead..0a0fa33 100644
--- a/docs/Embedding.md
+++ b/docs/Embedding.md
@@ -3,8 +3,9 @@ Embedding CivetWeb
 
 CivetWeb is primarily designed so applications can easily add HTTP and HTTPS server as well as WebSocket (WS and WSS) server functionality.
 For example, a C/C++ application could use CivetWeb to enable a web service and configuration interface, to add a HTML5 data visualization interface, for automation or remote control, as a protocol gateway or as a HTTP/WebSocket client for firewall traversal.
+Often the easiest way to embed CivetWeb is to add the civetweb.c file into your existing C project (see below).
 
-It can also be used as a stand-alone executable. It can deliver static files and offers built-in server side Lua, JavaScript and CGI support. Some instructions how to build the stand-alone server can be found in [Building.md](https://github.com/civetweb/civetweb/blob/master/docs/Building.md).
+CivetWeb can also be used as a stand-alone executable. It can deliver static files and offers built-in server side Lua, JavaScript and CGI support. Some instructions how to build the stand-alone server can be found in [Building.md](https://github.com/civetweb/civetweb/blob/master/docs/Building.md).
 
 
 Files
@@ -30,7 +31,10 @@ but all functions required to run a HTTP server.
     - src/sha1.inl (SHA calculation)
     - src/handle\_form.inl (HTML form handling functions)
     - src/response.inl (helper for generating HTTP response headers)
+    - src/sort.inl (sorting, qsort_r alternative)
+    - src/match.inl (pattern matching)
     - src/timer.inl (optional timer support)
+    - src/http2.inl (optional HTTP2 support)
   - Optional: C++ wrapper
     - include/CivetServer.h (C++ interface)
     - src/CivetServer.cpp (C++ wrapper implementation)
@@ -180,6 +184,8 @@ Lua is a server side include functionality.  Files ending in .lua will be proces
   - src/third\_party/lsqlite3.c
   - src/third\_party/lfs.c
   - src/third\_party/lfs.h
+  - src/third\_party/lua_struct.c
+  
 
 This build is valid for Lua version Lua 5.2. It is also possible to build with Lua 5.1 (including LuaJIT), Lua 5.3 or Lua 5.4.
 
@@ -286,7 +292,7 @@ Initializing a HTTP server
 }
 ```
 
-A simple callback (HTTP/1.x and HTTP/2):
+A simple callback (new structure supporting HTTP/1.x and HTTP/2):
 ```C
 static int
 handler(struct mg_connection *conn, void *ignored)
@@ -302,7 +308,8 @@ handler(struct mg_connection *conn, void *ignored)
 }
 ```
 
-A simple callback supporting HTTP/1.x only:
+A simple callback (deprecated structure supporting HTTP/1.x only):
+(Note: While some older examples use this pattern, it is not recommended to it in new code.)
 ```C
 static int
 handler(struct mg_connection *conn, void *ignored)
diff --git a/docs/Installing.md b/docs/Installing.md
index 7fa06ce..7a6b003 100644
--- a/docs/Installing.md
+++ b/docs/Installing.md
@@ -8,7 +8,7 @@ The latest source code version is available at [https://github.com/civetweb/cive
 Windows
 ---
 
-This pre-built version comes pre-built wit Lua support. Libraries for SSL support are not included due to licensing restrictions;
+This pre-built version comes pre-built with Lua support. Libraries for SSL support are not included due to licensing restrictions;
 however, users may add an SSL library themselves.
 Instructions for adding SSL support can be found in [https://github.com/civetweb/civetweb/tree/master/docs](https://github.com/civetweb/civetweb/tree/master/docs)
 
diff --git a/docs/README.md b/docs/README.md
index 940f581..54ed712 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -43,7 +43,7 @@ Documentation
 - [Building.md](Building.md) - Building the Server (quick start guide)
 - [Embedding.md](Embedding.md) - Embedding (how to add HTTP support to an existing application)
 - [OpenSSL.md](OpenSSL.md) - Adding HTTPS (SSL/TLS) support using OpenSSL.
-- [API documentation](api) - Additional documentation on the civetweb application programming interface ([civetweb.h](https://github.com/civetweb/civetweb/blob/master/include/civetweb.h)).
+- [APIReference.md](APIReference.md) - Additional documentation on the civetweb application programming interface ([civetweb.h](https://github.com/civetweb/civetweb/blob/master/include/civetweb.h)).
 
 [Authors](https://github.com/civetweb/civetweb/blob/master/CREDITS.md)
 
diff --git a/docs/UserManual.md b/docs/UserManual.md
index bdda485..df9b368 100644
--- a/docs/UserManual.md
+++ b/docs/UserManual.md
@@ -92,16 +92,22 @@ Pattern match starts at the beginning of the string, so essentially
 patterns are prefix patterns. Syntax is as follows:
 
      **      Matches everything
-     *       Matches everything but slash character, '/'
-     ?       Matches any character
+     *       Matches everything but the slash character ('/')
+     ?       Matches any character but the slash character ('/')
      $       Matches the end of the string
      |       Matches if pattern on the left side or the right side matches.
 
 All other characters in the pattern match themselves. Examples:
 
-    **.cgi$      Any string that ends with .cgi
-    /foo         Any string that begins with /foo
-    **a$|**b$    Any string that ends with a or b
+    **.cgi$          Any string that ends with .cgi
+    /foo             Any string that begins with /foo
+    **a$|**b$        Any string that ends with a or b
+
+    /data/????.css$  Matches css files with 4 letter names in "/data" folder.
+    /data/*.js$      Matches all js file names in "/data" folder.
+    /api/*/*.cgi$    Matches "/api/resourcetype/resourcename.cgi"
+    /*.jpg$|/*.jpeg$ JPG and JPEG files in root folder
+    **.jpg$|**.jpeg$ JPG and JPEG files anywhere
 
 
 ## Options from `civetweb.c`
@@ -144,7 +150,7 @@ See the [Wikipedia page on CORS](http://en.wikipedia.org/wiki/Cross-origin_resou
 An Access Control List (ACL) allows restrictions to be put on the list of IP
 addresses which have access to the web server. In the case of the CivetWeb
 web server, the ACL is a comma separated list of IP subnets, where each
-subnet is pre-pended by either a `-` or a `+` sign. A plus sign means allow,
+subnet is prepended by either a `-` or a `+` sign. A plus sign means allow,
 where a minus sign means deny. If a subnet mask is omitted, such as `-1.2.3.4`,
 this means to deny only that single IP address.
 
@@ -260,6 +266,16 @@ Maximum allowed runtime for CGI scripts.  CGI processes are terminated by
 the server after this time.  The default is "no timeout", so scripts may
 run or block for undefined time.
 
+### cgi\_buffering `yes`
+Allow buffering response of CGI program before sending to the client.
+When buffering is enabled content created by CGI scripts is collected in 
+a buffer and forwarded to the client in larger blocks, improving efficiency.
+If partial content has to be sent to the client, try setting 
+`cgi_buffering` to `no`, `allow_sendfile_call` to `no` 
+and `tcp_nodelay` to `1`. This will cost some performance, but not guarantee
+there is no buffering between CGI program and client code, since intermediate 
+proxies or browsers may also buffer data.
+
 ### decode\_query\_string `no`
 URL decode all query strings in the server. 
 If you set this option to `yes`, all callbacks and scripts will only see the already
@@ -310,6 +326,11 @@ but this configuration is redundant. In a future version, the keep\_alive
 configuration option might be removed and automatically set to `yes` if
 a timeout > 0 is set.
 
+### enable\_webdav `no`
+Set this configuration option to `yes` to handle WebDAV specific HTTP methods:
+PROPFIND, PROPPATCH, LOCK, UNLOCK, MOVE, COPY.
+These methods are not allowed if the configuration option is set to `no`.
+
 ### enable\_websocket\_ping\_pong `no`
 If this configuration value is set to `yes`, the server will send a
 websocket PING message to a websocket client, once the timeout set by
@@ -417,7 +438,7 @@ 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.
 
 It is possible to specify an IP address to bind to. In this case,
-an IP address and a colon must be pre-pended to the port number.
+an IP address and a colon must be prepended to the port number.
 For example, to bind to a loopback interface on port 80 and to
 all interfaces on HTTPS port 443, use `127.0.0.1:80,443s`.
 
@@ -667,7 +688,7 @@ TLS1.1+TLS1.2+TLS1.3 | 3
 TLS1.2+TLS1.3 | 4
 TLS1.3 | 5
 
-TLS version 1.3 is only available if you are using an up-to-date TLS libary.
+TLS version 1.3 is only available if you are using an up-to-date TLS library.
 The default setting has been changed from 0 to 4 in CivetWeb 1.14.
 
 ### ssl\_short\_trust `no`
@@ -956,8 +977,8 @@ mg (table):
     mg.get_mime_type(filename)  -- get MIME type of a file
     mg.get_option(name)         -- get configuration option value from name
     mg.get_response_code_text(n)-- get response code text for n, nil otherwise
-    mg.get_var(str, varname, [occurance])  -- extract the first occurance of variable from (query) string
-                                --     otherwise the nth occurance if supplied, nil if not found
+    mg.get_var(str, varname, [occurrence])  -- extract the first occurrence of variable from (query) string
+                                --     otherwise the nth occurrence if supplied, nil if not found
     mg.send_file(filename)      -- send a file, including all required HTTP headers
     mg.send_file_body(filename) -- send a file, excluding HTTP headers
     mg.send_http_error(n,str)   -- send http error code n with string body
@@ -996,7 +1017,7 @@ mg (table):
          .https                 -- true if accessed by https://, false otherwise
          .remote_user           -- user name if authenticated, nil otherwise
          .auth_type             -- Digest
-         .client_cert           -- Table with ssl certificate infomation
+         .client_cert           -- Table with ssl certificate information
               .subject          -- Certificate subject
               .issuer           -- Certificate issuer
               .serial           -- Certificate serial number
@@ -1104,7 +1125,7 @@ some features of the "mg" library are not available yet. Use the "start()" callb
 function instead.
 
 A Lua background script may define the following functions:
-    `start()`        -- called wnen the server is started
+    `start()`        -- called when the server is started
     `stop()`         -- called when the server is stopped
     `log(req, res)`  -- called when an access log entry is created
 
diff --git a/docs/api/mg_base64_X.md b/docs/api/mg_base64_X.md
new file mode 100644
index 0000000..f58db2b
--- /dev/null
+++ b/docs/api/mg_base64_X.md
@@ -0,0 +1,36 @@
+# Civetweb API Reference
+
+### `mg_base64_encode( src, src_len, dst, dst_len );`
+### `mg_base64_decode( src, src_len, dst, dst_len );`
+
+### Parameters `base64_encode`
+
+| Parameter | Type | Description |
+| :--- | :--- | :--- |
+|**`src`**|`const unsigned char *`|Source buffer containing bytes to be encoded into BASE-64 code.|
+|**`src_len`**|`size_t`|Number of bytes in source buffer to be encoded.|
+|**`dst`**|`char *`|Destination string buffer.|
+|**`dst_len`**|`size_t *`|Pointer to a variable containing the available size of the destination buffer in bytes.|
+
+### Parameters `base64_decode`
+
+| Parameter | Type | Description |
+| :--- | :--- | :--- |
+|**`src`**|`const char *`|Source string containing BASE-64 encoded data.|
+|**`src_len`**|`size_t`|Number of bytes in the source buffer to be decoded.|
+|**`dst`**|`unsigned char *`|Destination byte buffer.|
+|**`dst_len`**|`size_t *`|Pointer to a variable containing the available size of the destination buffer in bytes.|
+
+### Return Value
+
+| Type | Description |
+| :--- | :--- |
+|`int`|Error information. -1 indicates success.|
+
+### Description
+
+The function `mg_base64_encode()` encodes the source buffer into the destination buffer using BASE-64 encoding.
+The function `mg_base64_decode()` reads a BASE-64 encoded source buffer and decodes it into the destination buffer.
+Both functions return -1 on success.
+If the destination buffer is not big enough, the functions return 0.
+If the source buffer supplied to `mg_base64_decode()` contains invalid characters, the return value is the position of this character. 
diff --git a/docs/api/mg_callbacks.md b/docs/api/mg_callbacks.md
index 81ce438..13eabf8 100644
--- a/docs/api/mg_callbacks.md
+++ b/docs/api/mg_callbacks.md
@@ -8,30 +8,34 @@
 | :--- | :--- |
 |**`begin_request`**|**`int (*begin_request)( struct mg_connection *conn );`**|
 | |The `begin_request()` callback function is called when CivetWeb has received a new HTTP request. If the callback function does not process the request, it should return 0. In that case CivetWeb will handle the request with the default callback routine. If the callback function returns a value between 1 and 999, CivetWeb does nothing and the callback function should do all the processing, including sending the proper HTTP headers etc. Starting at CivetWeb version 1.7, the function `begin_request()` is called before any authorization is done. If an authorization check is required, `request_handler()` should be used instead. The return value of the callback function is not only used to signal CivetWeb to not further process the request. The returned value is also stored as HTTP status code in the access log. |
+|**`end_request`**|**`void (*end_request)(const struct mg_connection *conn, int reply_status_code);`**|
+| |The callback function `end_request()` is called by CivetWeb when a request has been completely processed. It sends the reply status code which was sent to the client to the application.|
+|**`log_message`**|**`int (*log_message)( const struct mg_connection *conn, const char *message );`**|
+| |The callback function `log_message()` is called when CivetWeb is about to log a message. If the callback function returns 0, CivetWeb will use the default internal log routines to log the message. If a non-zero value is returned CivetWeb assumes that logging has already been done and no further action is performed.|
+|**`log_access`**|**`int (*log_access)( const struct mg_connection *conn, const char *message );`**|
+| |The callback function `log_access()` is called when CivetWeb is about to log a message. If the callback function returns 0, CivetWeb will use the default internal access log routines to log the access. If a non-zero value is returned, CivetWeb assumes that access logging has already been done and no further action is performed.|
+|**`init_ssl`**|**`int (*init_ssl)( void *ssl_ctx, void *user_data );`**|
+| |The callback function `init_ssl()` is called when CivetWeb initializes the SSL library. The `ssl_ctx` parameter is a pointer to the SSL context being configure. The parameter `user_data` contains a pointer to the data which was provided to `mg_start()` when the server was started. The callback function can return 0 to signal that CivetWeb should setup the SSL certificate. With a return value of 1 the callback function signals CivetWeb that the certificate has already been setup and no further processing is necessary. The value -1 should be returned when the SSL initialization fails.|
+|**`init_ssl_domain`**|**`int (*init_ssl_domain)( const char *server_domain, void *ssl_ctx, void *user_data );`**|
+| |The callback function `init_ssl_domain()` is called when CivetWeb initializes the SSL library. The parameter `server_domain` is a pointer to the `authentication_domain` config parameter of the domain being configured. The `ssl_ctx` parameter is a pointer to the SSL context being configure. The parameter `user_data` contains a pointer to the data which was provided to `mg_start()` when the server was started. The callback function can return 0 to signal that CivetWeb should setup the SSL certificate. With a return value of 1 the callback function signals CivetWeb that the certificate has already been setup and no further processing is necessary. The value -1 should be returned when the SSL initialization fails.|
+|**`external_ssl_ctx`**|**`int (*external_ssl_ctx)( void **ssl_ctx, void *user_data );`**|
+| |The callback function `external_ssl_ctx()` is called when civetweb is about to create (`*ssl_ctx` is `NULL`) or free (`*ssl_ctx` is not `NULL`) a SSL context. The parameter `user_data` contains a pointer to the data which was provided to `mg_start()` when the server was started. The callback function can return 0 to signal that CivetWeb should setup the SSL context. With a return value of 1 the callback function signals CivetWeb that the SSL context has already been setup and no further processing is necessary. Also with a return value of 1 other callback functions `init_ssl()` and `init_ssl_domain()` are not called. The value -1 should be returned when the SSL context initialization fails.|
+|**`external_ssl_ctx_domain`**|**`int (*external_ssl_ctx_domain)( const char *server_domain, void **ssl_ctx, void *user_data );`**|
+| |The callback function `external_ssl_ctx_domain()` is called when civetweb is about to create (`*ssl_ctx` is `NULL`) or free (`*ssl_ctx` is not `NULL`) a SSL context. The parameter `server_domain` is a pointer to the `authentication_domain` config parameter of the domain being created or freed. The parameter `user_data` contains a pointer to the data which was provided to `mg_start()` when the server was started. The callback function can return 0 to signal that CivetWeb should setup the SSL context. With a return value of 1 the callback function signals CivetWeb that the SSL context has already been setup and no further processing is necessary. Also with a return value of 1 other callback functions `init_ssl()` and `init_ssl_domain()` are not called. The value -1 should be returned when the SSL context initialization fails.|
 |**`connection_close`**|**`void (*connection_close)( const struct mg_connection *conn );`**|
 | |The callback function `connection_close()` is called before CivetWeb closes a connection. The per-context mutex is locked when the callback function is invoked. The function is primarily useful for noting when a websocket is closing and removing it from any application-maintained list of clients. *Using this callback for websocket connections is deprecated. Use* `mg_set_websocket_handler()` *instead.*|
 |**`connection_closed`**|**`void (*connection_closed)( const struct mg_connection *conn );`**|
 | |The callback function `connection_closed()` is called after CivetWeb has closed a connection (after SSL shutdown and after closing the socket). The per-context mutex is locked when the callback function is invoked.|
-|**`end_request`**|**`void (*end_request)(const struct mg_connection *conn, int reply_status_code);`**|
-| |The callback function `end_request()` is called by CivetWeb when a request has been completely processed. It sends the reply status code which was sent to the client to the application.|
-|**`exit_context`**|**`void (*exit_context)( const struct mg_context *ctx );`**|
-| |The callback function `exit_context()` is called by CivetWeb when the server is stopped. It allows the application to do some cleanup on the application side.|
-|**`http_error`**|**`int (*http_error)( struct mg_connection *conn, int status, const char *msg );`**|
-| |The callback function `http_error()` is called by CivetWeb just before an HTTP error is to be sent to the client. The function allows the application to send a custom error page. The status code of the error is provided as a parameter. If the application sends their own error page, it must return 0 to signal CivetWeb that no further processing is needed. If the returned value is not 0, CivetWeb will send an error page to the client.|
-|**`init_context`**|**`void (*init_context)( const struct mg_context *ctx );`**|
-| |The callback function `init_context()` is called after the CivetWeb server has been started and initialized, but before any requests are served. This allowes the application to perform some initialization activities before the first requests are handled.|
 |**`init_lua`**|**`void (*init_lua)( const struct mg_connection *conn, void *lua_context, unsigned context_flags );`**|
 | |The callback function `init_lua()` is called just before a Lua server page is to be served. Lua page serving must have been enabled at compile time for this callback function to be called. The parameter `lua_context` is a `lua_State *` pointer. The parameter `context_flags` indicate the type of Lua environment. |
 |**`exit_lua`**|**`void (*init_lua)( const struct mg_connection *conn, void *lua_context, unsigned context_flags );`**|
 | |The callback function `exit_lua()` is called when a Lua state is about to be closed. Lua page serving must have been enabled at compile time for this callback function to be called. The parameters are identical to `lua_init()`.|
-|**`external_ssl_ctx`**|**`int (*external_ssl_ctx)( void **ssl_ctx, void *user_data );`**|
-| |The callback function `external_ssl_ctx()` is called when civetweb is about to create (`*ssl_ctx` is `NULL`) or free (`*ssl_ctx` is not `NULL`) a SSL context. The parameter `user_data` contains a pointer to the data which was provided to `mg_start()` when the server was started. The callback function can return 0 to signal that CivetWeb should setup the SSL context. With a return value of 1 the callback function signals CivetWeb that the SSL context has already been setup and no further processing is necessary. Also with a return value of 1 other callback functions `init_ssl()` and `init_ssl_domain()` are not called. The value -1 should be returned when the SSL context initialization fails.|
-|**`external_ssl_ctx_domain`**|**`int (*external_ssl_ctx_domain)( const char *server_domain, void **ssl_ctx, void *user_data );`**|
-| |The callback function `external_ssl_ctx_domain()` is called when civetweb is about to create (`*ssl_ctx` is `NULL`) or free (`*ssl_ctx` is not `NULL`) a SSL context. The parameter `server_domain` is a pointer to the `authentication_domain` config parameter of the domain being created or freed. The parameter `user_data` contains a pointer to the data which was provided to `mg_start()` when the server was started. The callback function can return 0 to signal that CivetWeb should setup the SSL context. With a return value of 1 the callback function signals CivetWeb that the SSL context has already been setup and no further processing is necessary. Also with a return value of 1 other callback functions `init_ssl()` and `init_ssl_domain()` are not called. The value -1 should be returned when the SSL context initialization fails.|
-|**`init_ssl`**|**`int (*init_ssl)( void *ssl_ctx, void *user_data );`**|
-| |The callback function `init_ssl()` is called when CivetWeb initializes the SSL library. The `ssl_ctx` parameter is a pointer to the SSL context being configure. The parameter `user_data` contains a pointer to the data which was provided to `mg_start()` when the server was started. The callback function can return 0 to signal that CivetWeb should setup the SSL certificate. With a return value of 1 the callback function signals CivetWeb that the certificate has already been setup and no further processing is necessary. The value -1 should be returned when the SSL initialization fails.|
-|**`init_ssl_domain`**|**`int (*init_ssl_domain)( const char *server_domain, void *ssl_ctx, void *user_data );`**|
-| |The callback function `init_ssl_domain()` is called when CivetWeb initializes the SSL library. The parameter `server_domain` is a pointer to the `authentication_domain` config parameter of the domain being configured. The `ssl_ctx` parameter is a pointer to the SSL context being configure. The parameter `user_data` contains a pointer to the data which was provided to `mg_start()` when the server was started. The callback function can return 0 to signal that CivetWeb should setup the SSL certificate. With a return value of 1 the callback function signals CivetWeb that the certificate has already been setup and no further processing is necessary. The value -1 should be returned when the SSL initialization fails.|
+|**`http_error`**|**`int (*http_error)( struct mg_connection *conn, int status, const char *msg );`**|
+| |The callback function `http_error()` is called by CivetWeb just before an HTTP error is to be sent to the client. The function allows the application to send a custom error page. The status code of the error is provided as a parameter. If the application sends their own error page, it must return 0 to signal CivetWeb that no further processing is needed. If the returned value is not 0, CivetWeb will send an error page to the client.|
+|**`init_context`**|**`void (*init_context)( const struct mg_context *ctx );`**|
+| |The callback function `init_context()` is called after the CivetWeb server has been started and initialized, but before any requests are served. This allows the application to perform some initialization activities before the first requests are handled.|
+|**`exit_context`**|**`void (*exit_context)( const struct mg_context *ctx );`**|
+| |The callback function `exit_context()` is called by CivetWeb when the server is stopped. It allows the application to do some cleanup on the application side.|
 |**`init_thread`**|**`void * (*init_thread)( const struct mg_context *ctx, int thread_type );`**|
 | |The callback function `init_thread()` is called when a new thread is created by CivetWeb. It is always called from the newly created thread and can be used to initialize thread local storage data. The `thread_type` parameter indicates which type of thread has been created. following thread types are recognized:|
 | |**0** - The master thread is created |
@@ -41,26 +45,9 @@
 | |The returned `void *` is stored as user defined pointer in the thread local storage.|
 |**`exit_thread`**|**`void (*exit_thread)( const struct mg_context *ctx, int thread_type, void * user_ptr);`**|
 | |The callback function `exit_thread()` is called when a thread is about to exit. The parameters correspond to `init_thread`, with `user_ptr` being the return value.|
-|**`log_access`**|**`int (*log_access)( const struct mg_connection *conn, const char *message );`**|
-| |The callback function `log_access()` is called when CivetWeb is about to log a message. If the callback function returns 0, CivetWeb will use the default internal access log routines to log the access. If a non-zero value is returned, CivetWeb assumes that access logging has already been done and no further action is performed.|
-|**`log_message`**|**`int (*log_message)( const struct mg_connection *conn, const char *message );`**|
-| |The callback function `log_message()` is called when CivetWeb is about to log a message. If the callback function returns 0, CivetWeb will use the default internal log routines to log the message. If a non-zero value is returned CivetWeb assumes that logging has already been done and no further action is performed.|
-|**`open_file`**|**`const char *(*open_file)( const struct mg_connection *conn, const char *path, size_t *data_len );`**|
-| |The callback function `open_file()` is called when a file is to be opened by CivetWeb. The callback can return a pointer to a memory location and set the memory block size in the variable pointed to by `data_len` to signal CivetWeb that the file should not be loaded from disk, but that instead a stored version in memory should be used. If the callback function returns NULL, CivetWeb will open the file from disk. This callback allows caching to be implemented at the application side, or to serve specific files from static memory instead of from disk.|
-|~~`upload`~~|**`void (*upload)( struct mg_connection * conn, const char *file_name );`**|
-| |*Deprecated. Use* `mg_handle_form_request()` *instead.* The callback function `upload()` is called when CivetWeb has uploaded a file to a temporary directory as result of a call to `mg_upload()`. The parameter `file_name` contains the full file name including path to the uploaded file.|
-|~~`websocket_connect`~~|**`int (*websocket_connect)( const struct mg_connection *conn );`**|
-| |*Deprecated. Use* `mg_set_websocket_handler()` *instead.* The callback function `websocket_connect()` is called when a websocket request is received, before the actual websocket handshake has taken place. The callback function can signal to CivetWeb if it should accept or deny the incoming request with one of the following return values: |
-| |**0** - CivetWeb can proceed with the handshake to accept the connection |
-| |**1** - CivetWeb must close the connection immediately without performing a handshake |
-|~~`websocket_data`~~|**`int (*websocket_data)( struct mg_connection *conn, int bits, char *data, size_t data_len );`**|
-| |*Deprecated. Use* `mg_set_websocket_handler()` *instead.* The callback function `websocket_data()` is called when a data frame has been received from the client. The parameters contain the following information: |
-| | **`bits`** - The first byte of the websocket frame. See [RFC-6455](http://tools.ietf.org/html/rfc6455) at section 5.2 for more information. |
-| | **`data`** - The pointer to the received data block. Masks--if any--have already been applied. |
-| | **`data_len`** - The length of the received data block |
-| | If the application wants to keep the websocket open to receive more data, the callback function should return the value **1**. If the value **0** is returned by the callback function, CivetWeb will close the websocket connection and no more frames will be received.|
-|~~`websocket_ready`~~|**`int (*websocket_ready)( struct mg_connection *conn );`**|
-| |*Deprecated. Use* `mg_set_websocket_handler()` *instead.* The callback function `websocket_ready()` is called after the handshake of a websocket connection has succeeded successfully to signal the application that the connection is ready for use. |
+|**`init_connection`**|**`int (*init_connection)( struct mg_cconnection *conn, void ** conn_data);`**|
+| |The callback function `init_connection()` is called when a new connection is created. It can be used to set user defined connection data (type `void *`) by setting `*conn_data`.|
+
 
 ### Description
 
diff --git a/docs/api/mg_check_feature.md b/docs/api/mg_check_feature.md
index 1da1b07..9a71c60 100644
--- a/docs/api/mg_check_feature.md
+++ b/docs/api/mg_check_feature.md
@@ -31,7 +31,7 @@ The following parameter values can be used:
 | **64** | USE_DUKTAPE | *Support for server side JavaScript*. Server side JavaScript can be used for dynamic page generation if the proper options have been set at compile time. Please note that client side JavaScript execution is always available if it has been enabled in the connecting browser. |
 | **128** | NO_CACHING | *Support for caching*. The webserver will support caching, if it has not been disabled while compiling the library. |
 
-Parameter values other than the values mentioned above will give undefined results. Therefore&mdash;although the parameter values for the `mg_check_feature()` function are effectively bitmasks, you should't assume that combining two of those values with an OR to a new value will give any meaningful results when the function returns.
+Parameter values other than the values mentioned above will give undefined results. Therefore&mdash;although the parameter values for the `mg_check_feature()` function are effectively bitmasks, you shouldn't assume that combining two of those values with an OR to a new value will give any meaningful results when the function returns.
 
 ### See Also
 
diff --git a/docs/api/mg_error_data.md b/docs/api/mg_error_data.md
index 71425a4..5287fc1 100644
--- a/docs/api/mg_error_data.md
+++ b/docs/api/mg_error_data.md
@@ -6,13 +6,24 @@
 
 | Field | Type | Description |
 | :--- | :--- | :--- |
-|**`code`**|`unsigned *`| A pointer to an `unsigned` variable, to store the error code. |
+|**`code`**|`unsigned *`| Error code (see `MG_ERROR_DATA_CODE_*`). |
+|**`code_sub`**|`unsigned *`| Error sub code, depending on error code. |
 |**`text`**|`char *`| A text buffer to store the error text. |
 |**`text_buffer_size`**|`size_t`| Size of the text buffer. |
 
 ### Description
 
-The structure `mg_error_data` is used in [`mg_start2()`](mg_start.md), [`mg_start_domain2();`](mg_start_domain2.md), [`mg_connect_client2();`](mg_connect_client2.md) and [`mg_get_response2();`](mg_get_response2.md) to return error information.
+The structure `mg_error_data` is used to return error information.
+The `code` number will be set to one of the `MG_ERROR_DATA_CODE_*` values defined in civetweb.h.
+
+The meaning of the `code_sub` number will depend on the value of `code`.
+The `code_sub` member is experimental and may change in future versions.
+
+The optional pointer `text` can be used to provide storage for a textual error message.
+The size of the provided `text` pointer must be set in `text_buffer_size`.
+If no textual error message is required and no buffer is probided, `text_buffer_size` must be set to 0.
+
+Currently `struct mg_error_data` is used by the functions [`mg_start2()`](mg_start.md), [`mg_start_domain2();`](mg_start_domain2.md), [`mg_connect_client2();`](mg_connect_client2.md) and [`mg_get_response2();`](mg_get_response2.md).
 
 ### See Also
 
diff --git a/docs/api/mg_exit_library.md b/docs/api/mg_exit_library.md
index 2643403..8379f02 100644
--- a/docs/api/mg_exit_library.md
+++ b/docs/api/mg_exit_library.md
@@ -15,7 +15,8 @@ none
 ### Description
 
 The function `mg_exit_library()` should be called from an application program, when the library should be unloaded.
-It must be called only from one thread (it is not guaranteed to be thread safe).
+It can be called multiple times (`mg_init_library` and `mg_exit_library` are reference counting).
+However, the caller must make sure it is not called in parallel (it is not guaranteed to be thread safe).
 
 Only use `mg_exit_library( );` when you used [`mg_init_library( feature );`](api/mg_init_library.md) before.
 
diff --git a/docs/api/mg_get_user_connection_data.md b/docs/api/mg_get_user_connection_data.md
index cf388e7..1be270f 100644
--- a/docs/api/mg_get_user_connection_data.md
+++ b/docs/api/mg_get_user_connection_data.md
@@ -16,7 +16,7 @@
 
 ### Description
 
-The function `mg_get_user_connection_data()` returns the user data associated with a connection. This user data is represented with a pointer which has been prevously registered with a call to [`mg_set_user_connection_data();`](mg_set_user_connection_data.md). With this function it is possible to pass state information between callback functions referring to a specific connection.
+The function `mg_get_user_connection_data()` returns the user data associated with a connection. This user data is represented with a pointer which has been previously registered with a call to [`mg_set_user_connection_data();`](mg_set_user_connection_data.md). With this function it is possible to pass state information between callback functions referring to a specific connection.
 
 ### See Also
 
diff --git a/docs/api/mg_init_library.md b/docs/api/mg_init_library.md
index b6a69f9..8b70469 100644
--- a/docs/api/mg_init_library.md
+++ b/docs/api/mg_init_library.md
@@ -17,7 +17,8 @@
 ### Description
 
 The function `mg_init_library()` should be called from an application program before using any other function.
-It must be called only from one thread (it is not guaranteed to be thread safe).
+It can be called multiple times (`mg_init_library` and `mg_exit_library` are reference counting).
+However, the caller must make sure it is not called in parallel (it is not guaranteed to be thread safe).
 
 This function is new in version 1.9 (as dummy implementation) and effective only from version 1.10.
 For compatibility reasons, other functions (such as [`mg_start();`](mg_start.md)) will initialize the required features as well,
@@ -25,18 +26,23 @@ but they will no longer do a de-initialization, leaving a memory leak when the l
 
 The following parameter values can be used:
 
-| Value | Compilation option | Description |
-| :---: | :---: | :--- |
-| **1** | NO_FILES | *Able to serve files*.  If this feature is available, the webserver is able to serve files directly from a directory tree. |
-| **2** | NO_SSL | *Support for HTTPS*. If this feature is available, the webserver can use encryption in the client-server connection. SSLv2, SSLv3, TLSv1.0, TLSv1.1 and TLSv1.2 are supported depending on the SSL library CivetWeb has been compiled with, but which protocols are used effectively when the server is running is dependent on the options used when the server is started. |
-| **4** | NO_CGI | *Support for CGI*. If this feature is available, external CGI scripts can be called by the webserver. |
-| **8** | USE_IPV6 | *Support IPv6*. The CivetWeb library is capable of communicating over both IPv4 and IPv6, but IPv6 support is only available if it has been enabled at compile time. |
-| **16** | USE_WEBSOCKET | Support for web sockets. WebSockets support is available in the CivetWeb library if the proper options has been used during cimpile time. |
-| **32** | USE_LUA | *Support for Lua scripts and Lua server pages*. CivetWeb supports server side scripting through the Lua language, if that has been enabled at compile time. Lua is an efficient scripting language which is less resource heavy than for example PHP. |
-| **64** | USE_DUKTAPE | *Support for server side JavaScript*. Server side JavaScript can be used for dynamic page generation if the proper options have been set at compile time. Please note that client side JavaScript execution is always available if it has been enabled in the connecting browser. |
-| **128** | NO_CACHING | *Support for caching*. The webserver will support caching, if it has not been disabled while compiling the library. |
-
-The parameters can added using bitwise or. Values above 255 are reserved, the behavior of the function is undefined if any unknown bit is set.
+| Value | Compilation option | Feature Flags | Description |
+| :---: | :---: | :--- | :--- |
+| **1** | !NO_FILES | MG_FEATURES_FILES | *Able to serve files*.  If this feature is available, the webserver is able to serve files directly from a directory tree. |
+| **2** | !NO_SSL | MG_FEATURES_TLS | *Support for HTTPS*. If this feature is available, the webserver can use encryption in the client-server connection. SSLv2, SSLv3, TLSv1.0, TLSv1.1 and TLSv1.2 are supported depending on the SSL library CivetWeb has been compiled with, but which protocols are used effectively when the server is running is dependent on the options used when the server is started. |
+| **4** | !NO_CGI | MG_FEATURES_CGI | *Support for CGI*. If this feature is available, external CGI scripts can be called by the webserver. |
+| **8** | USE_IPV6 | MG_FEATURES_IPV6 | *Support IPv6*. The CivetWeb library is capable of communicating over both IPv4 and IPv6, but IPv6 support is only available if it has been enabled at compile time. |
+| **16** | USE_WEBSOCKET | MG_FEATURES_WEBSOCKET | *Support for web sockets*. WebSockets support is available in the CivetWeb library if the proper options has been used during cimpile time. |
+| **32** | USE_LUA | MG_FEATURES_LUA | *Support for Lua scripts and Lua server pages*. CivetWeb supports server side scripting through the Lua language, if that has been enabled at compile time. Lua is an efficient scripting language which is less resource heavy than for example PHP. |
+| **64** | USE_DUKTAPE | MG_FEATURES_SSJS | *Support for server side JavaScript*. Server side JavaScript can be used for dynamic page generation if the proper options have been set at compile time. Please note that client side JavaScript execution is always available if it has been enabled in the connecting browser. |
+| **128** | !NO_CACHING | MG_FEATURES_CACHE | *Support for caching*. The web server will support caching, if it has not been disabled while compiling the library. |
+| **256** | USE_SERVER_STATS | MG_FEATURES_STATS | *Support server statistics*. The web server will collect data for server statistics. |
+| **512** | USE_ZLIB | MG_FEATURES_COMPRESSION | *Support for on the fly compression*. The web server may use ZLIB for on the fly data compression. |
+| **1024** | USE_HTTP2 | MG_FEATURES_HTTP2 | *Support for HTTP/2 protocol*. The web server will accept HTTP/2 connections over HTTPS. |
+| **2048** | USE_X_DOM_SOCKET | MG_FEATURES_X_DOMAIN_SOCKET | *Support for UNIX domain sockets*. The web server will allow to bind to domain sockets, in addition to TCP sockets. |
+| **65535** | | MG_FEATURES_ALL | All feature flags. |
+
+The parameters can added using bitwise or. Values above 65535 are reserved, the behavior of the function is undefined if any unknown bit is set.
 
 ### See Also
 
diff --git a/docs/api/mg_match.md b/docs/api/mg_match.md
new file mode 100644
index 0000000..7cadc80
--- /dev/null
+++ b/docs/api/mg_match.md
@@ -0,0 +1,63 @@
+# Civetweb API Reference
+
+### `struct mg_match_element;`
+### `struct mg_match_context;`
+### `mg_match( pat, str, mcx );`
+
+### Parameters of `mg_match()`
+
+| Parameter | Type | Description |
+| :--- | :--- | :--- |
+|**`pat`**|`const char *`|A pattern string to search for.|
+|**`str`**|`const char *`|The pattern will be searched in this string.|
+|**`mcx`**|`struct mg_match_context *`|Pointer to a "match context" structure. Optional: can be NULL.|
+
+### Fields of `struct mg_match_context`
+
+| Field | Type | Description |
+| :--- | :--- | :--- |
+|**`case_sensitive`**|`int`| Input: 1 for case sensitive or for 0 insensitive match. |
+|**`num_matches`**|`size_t`| Output: Number of wildcard matches found in `str`. |
+|**`match`**|`struct mg_match_element[]`| Output: This array is filled with matches. |
+
+In case `mcx` is not provided to `mg_match()` (a NULL pointer is provided), case insensitive pattern matching will be performed. The wildcard matches will not be stored.
+
+The size of the `match` array is defined by the compile time constant `MG_MATCH_CONTEXT_MAX_MATCHES`.
+
+### Fields of `struct mg_match_element`
+
+| Field | Type | Description |
+| :--- | :--- | :--- |
+|**`str`**|`const char *`| Pointer to the begin of a wildcard match in `str`. |
+|**`len`**|`size_t`| Length of the wildcard match. |
+
+### Return Value
+
+| Type | Description |
+| :--- | :--- |
+|`ptrdiff_t`| Number of characters matched, counting from the beginning or `str`. If the pattern does not match -1 is returned. Note that 0 may be a valid match for some patterns. |
+
+### Description
+
+Pattern matching function. Try to find characters matching pattern `pat` in string `str`.
+A definition of valid pattern can be found in the [User Manual](../UserManual.md).
+
+E.g.:
+`mg_match("**.cgi$", "anywhere/anyname.cgi", NULL)` will return 20 (`strlen(str)`).
+
+`mg_match("**.cgi$", "anywhere/anyname.cgi", &mcx)` will return 20 (`strlen(str)`). 
+Furthermore will set `mcx.match[0].str` to point to the first match "anywhere/anyname" (that is `str+0`)
+and `mcx.match[0].len` to 16 (`strlen("anywhere/anyname")`), but it will not terminate the string.
+The value of `mcx.num_matches` is 1, since the only wildcard pattern `**` matches everything.
+
+`mg_match("a?*/?*g", "ABC/DEFG", &mcx)` with `mcx.case_sensitive = 0` will return 8 (`strlen(str)`).
+The value of `mcx.num_matches` is 2, one for each `?*` wildcard.
+The first match `mcx.match[0].str` will point to "BC" (`str+1`) and `mcx.match[0].len == 2`.
+The second match `mcx.match[1].str` will point to "DEF" (`str+4`) and `mcx.match[1].len == 3`.
+
+Note: This is an experimental interface. 
+Structure definitions may change in future versions.
+
+### See Also
+
+* Pattern Matching in the [User Manual](../UserManual.md).
diff --git a/docs/api/mg_md5.md b/docs/api/mg_md5.md
index 067a6a2..4cf44bb 100644
--- a/docs/api/mg_md5.md
+++ b/docs/api/mg_md5.md
@@ -17,7 +17,7 @@
 
 ### Description
 
-The function `mg_md5()` caluclates the MD5 checksum of a NULL terminated list of NUL terminated ASCII strings. The MD5 checksum is returned in human readable format as an MD5 string in a caller supplied buffer.
+The function `mg_md5()` calculates the MD5 checksum of a NULL terminated list of NUL terminated ASCII strings. The MD5 checksum is returned in human readable format as an MD5 string in a caller supplied buffer.
 
 The function returns a pointer to the supplied result buffer.
 
diff --git a/docs/api/mg_modify_passwords_file.md b/docs/api/mg_modify_passwords_file.md
index 34cc903..1f462de 100644
--- a/docs/api/mg_modify_passwords_file.md
+++ b/docs/api/mg_modify_passwords_file.md
@@ -21,7 +21,7 @@
 
 The function `mg_modify_passwords_file()` allows an application to manipulate .htpasswd files on the fly by adding, deleting and changing user records. This is one of the several ways to implement authentication on the server side.
 
-If the password parameter is not `NULL` an entry is added to the password file. An existing records is modified in that case. If `NULL` is used as the password the enrty is removed from the file.
+If the password parameter is not `NULL` an entry is added to the password file. An existing records is modified in that case. If `NULL` is used as the password the entry is removed from the file.
 
 The function returns 1 when successful and 0 if an error occurs.
 
diff --git a/docs/api/mg_set_auth_handler.md b/docs/api/mg_set_auth_handler.md
index 302324a..632b6cd 100644
--- a/docs/api/mg_set_auth_handler.md
+++ b/docs/api/mg_set_auth_handler.md
@@ -23,8 +23,17 @@ The function `mg_set_auth_handler()` hooks an authorization function to an URI t
 
 The callback function can return **0** to deny access, and **1** to allow access.
 
+To allow maximum flexibility in the HTTP response status code and message when denying access, the callback should
+send the HTTP response itself. 
+The callback may send use [`mg_send_digest_access_authentication_request`](mg_send_digest_access_authentication_request.md) to ask for http digest authentication,
+it may send a http 403 status code using [`mg_send_http_error`](mg_set_request_handler.md), 
+or a 303 redirect to a login page using [`mg_send_http_redirect`](mg_send_http_redirect.md) 
+or any other response.
+
 The `mg_set_auth_handler()` function is very similar in use to [`mg_set_request_handler()`](mg_set_request_handler.md).
 
 ### See Also
 
 * [`mg_set_request_handler();`](mg_set_request_handler.md)
+* [`mg_send_http_error();`](mg_set_request_handler.md)
+* [`mg_send_http_redirect();`](mg_send_http_redirect.md)
diff --git a/docs/yaSSL.md b/docs/yaSSL.md
index e7524bc..cbdfcc1 100644
--- a/docs/yaSSL.md
+++ b/docs/yaSSL.md
@@ -15,7 +15,7 @@ Getting Started
 
 - Download Cayssl at https://www.wolfssl.com (formerly http://www.yassl.com/)
 - Extract the zip file
-    - To make this seemless, extract to a directory parallel to with Civetweb is
+    - To make this seamless, extract to a directory parallel to with Civetweb is
 
 ### Example Project
 
diff --git a/examples/README.md b/examples/README.md
index c4e7f51..83f7131 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -19,6 +19,11 @@ The ([https](https://github.com/civetweb/civetweb/tree/master/examples/https)) e
 a HTTPS server with improved security settings.
 It does not hold any source, but only a configuration file and some documentation how to use it.
 
+The ([embed_certificate](https://github.com/civetweb/civetweb/tree/master/examples/embed_certificate)) example 
+shows embed civetweb in a C application without using additional files on the disk. The HTTPS certificate is
+directly embedded in the code and all content is generated by a callback. It is a template for using civetweb
+on (embedded) devices without a file system - or for systems that do not want CivetWeb to access the disk.
+
 The [multidomain](https://github.com/civetweb/civetweb/tree/master/examples/multidomain) example demonstrates 
 how to host multiple domains with different HTTPS certificates. 
 It uses the standalone server (civetweb.c + main.c) and existing certificates.
diff --git a/examples/client/Makefile b/examples/client/Makefile
new file mode 100644
index 0000000..1178647
--- /dev/null
+++ b/examples/client/Makefile
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2021 CivetWeb Developers
+# License http://opensource.org/licenses/mit-license.php MIT License
+
+TOP = ../..
+
+PROG = client
+SRC = client.c $(TOP)/src/civetweb.c
+
+CFLAGS = -I$(TOP)/include -DOPENSSL_API_1_1 -DCRYPTO_LIB=\"libcrypto.so.1.1\" -DSSL_LIB=\"libssl.so.1.1\" $(COPT)
+LIBS = -lpthread
+
+include $(TOP)/resources/Makefile.in-os
+
+ifeq ($(TARGET_OS),LINUX)
+	LIBS += -ldl
+endif
+
+all: $(PROG)
+
+$(PROG): $(CIVETWEB_LIB) $(SRC)
+	$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $(SRC) $(CIVETWEB_LIB) $(LIBS)
+
+$(CIVETWEB_LIB):
+	$(MAKE) -C $(TOP) WITH_IPV6=1 clean lib
+	cp $(TOP)/$(CIVETWEB_LIB) .
+
+clean:
+	rm -f $(CIVETWEB_LIB) $(PROG)
+
+.PHONY: all clean
+
diff --git a/examples/client/client.c b/examples/client/client.c
new file mode 100644
index 0000000..c305ae8
--- /dev/null
+++ b/examples/client/client.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2018 the CivetWeb developers
+ * MIT License
+ */
+
+/* Simple client demo. */
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "civetweb.h"
+
+
+int
+main(int argc, char *argv[])
+{
+	if (argc < 2) {
+		fprintf(stderr, "Requires server name as argument\n");
+		return EXIT_FAILURE;
+	}
+
+	/* Init libcivetweb. */
+	mg_init_library(MG_FEATURES_TLS);
+
+	if (mg_check_feature(MG_FEATURES_TLS) != MG_FEATURES_TLS) {
+		fprintf(stderr, "TLS is not available\n");
+		return EXIT_FAILURE;
+	}
+
+	/* Connect client */
+	char errbuf[256] = {0};
+	struct mg_client_options opt = {0};
+	opt.host = argv[1];       /* Host name from command line */
+	opt.port = 443;           /* Default HTTPS port */
+	opt.client_cert = NULL;   /* Client certificate, if required */
+	opt.server_cert = NULL;   /* Server certificate to verify */
+	opt.host_name = opt.host; /* Host name for SNI */
+	struct mg_connection *cli =
+	    mg_connect_client_secure(&opt, errbuf, sizeof(errbuf));
+
+	/* Check return value: */
+	if (cli == NULL) {
+		fprintf(stderr, "Cannot connect client: %s\n", errbuf);
+		return EXIT_FAILURE;
+	}
+
+	printf("cli: %p\n", cli);
+
+	mg_printf(cli, "GET / HTTP/1.1\r\n");
+	mg_printf(cli, "Host: %s\r\n", opt.host);
+	mg_printf(cli, "Connection: close\r\n\r\n");
+
+	int ret = mg_get_response(cli, errbuf, sizeof(errbuf), 10000);
+	if (ret < 0) {
+		fprintf(stderr, "Download failed: %s\n", errbuf);
+		mg_close_connection(cli);
+		return EXIT_FAILURE;
+	}
+
+	const struct mg_response_info *ri = mg_get_response_info(cli);
+	if (ri == NULL) {
+		fprintf(stderr, "mg_response_info failed\n");
+		mg_close_connection(cli);
+		return EXIT_FAILURE;
+	}
+
+	printf("Status: %i %s\n", ri->status_code, ri->status_text);
+	printf("HTTP Version: %s\n", ri->http_version);
+	printf("Content-Length: %lli\n", ri->content_length);
+	printf("Headers:\n");
+	int is_chunked = 0;
+	for (int i = 0; i < ri->num_headers; i++) {
+		printf("  %s: %s\n",
+		       ri->http_headers[i].name,
+		       ri->http_headers[i].value);
+		if (!strcasecmp(ri->http_headers[i].name, "Transfer-Encoding")
+		    && !strcasecmp(ri->http_headers[i].value, "chunked")) {
+			is_chunked = 1;
+		}
+	}
+
+	long long cont = ri->content_length;
+	if (cont > 0) {
+		/* Read regular content */
+		printf("Content:\n");
+		while (cont > 0) {
+			char buf[1024];
+			int ret = mg_read(cli, buf, sizeof(buf));
+			if (ret <= 0) {
+				printf("read error\n");
+				break;
+			}
+			cont -= ret;
+			fwrite(buf, 1, ret, stdout);
+		}
+	} else {
+		/* Read chunked content (or content without content length) */
+		char buf[1024];
+		for (;;) {
+			int ret = mg_read(cli, buf, sizeof(buf));
+			if (ret <= 0)
+				break;
+			fwrite(buf, 1, ret, stdout);
+		}
+	}
+
+	mg_close_connection(cli);
+	return EXIT_SUCCESS;
+}
diff --git a/examples/embed_certificate/build.sh b/examples/embed_certificate/build.sh
new file mode 100644
index 0000000..defe39f
--- /dev/null
+++ b/examples/embed_certificate/build.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+[ ! -e ec_server ] || rm ec_server
+
+echo "Building ec_server"
+gcc ec_example.c ../../src/civetweb.c -I ../../include/ -o ec_server -DNO_SSL_DL -DOPENSSL_API_1_1 -lpthread -lssl -lcrypto -DNO_FILES -DNO_FILESYSTEM
+[  -e ec_server ] || echo "Failed to build"
+[  -e ec_server ] || exit 1
+
+echo "Allowing ec_server to bind to port 443. Requires sudo:"
+sudo setcap CAP_NET_BIND_SERVICE=+eip ec_server
diff --git a/examples/embed_certificate/ec_example.c b/examples/embed_certificate/ec_example.c
new file mode 100644
index 0000000..8fa863c
--- /dev/null
+++ b/examples/embed_certificate/ec_example.c
@@ -0,0 +1,368 @@
+/* Example for embedding a server including the HTTPS server key and
+ * certificate. All response is generated from a callback. The server
+ * does not access the file system at all. */
+/* Building: change into the examples/embed_certificate folder and
+ * use "./build.sh" on Linux (no, you don't need a Makefile). */
+
+#include <openssl/ssl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "civetweb.h"
+
+/* This array corresponds to the content of the file
+ * /resources/cert/server.crt converted from BASE64 to HEX.
+ * When using this code, you need to REPLACE IT BY YOUR OWN CERTIFICATE!
+ */
+const uint8_t SSL_CERT_ASN1[] = {
+    0x30, 0x82, 0x04, 0x40, 0x30, 0x82, 0x03, 0x28, 0xa0, 0x03, 0x02, 0x01,
+    0x02, 0x02, 0x14, 0x49, 0x65, 0x5b, 0x35, 0xce, 0x42, 0x20, 0x15, 0xa7,
+    0xc4, 0x8a, 0x29, 0xe6, 0x44, 0x58, 0x6c, 0x11, 0xe6, 0x8c, 0xa7, 0x30,
+    0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b,
+    0x05, 0x00, 0x30, 0x71, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+    0x06, 0x13, 0x02, 0x41, 0x41, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55,
+    0x04, 0x08, 0x0c, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73,
+    0x74, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x09,
+    0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x31, 0x12, 0x30,
+    0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x09, 0x6c, 0x6f, 0x63, 0x61,
+    0x6c, 0x68, 0x6f, 0x73, 0x74, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55,
+    0x04, 0x0b, 0x0c, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73,
+    0x74, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x09,
+    0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x30, 0x1e, 0x17,
+    0x0d, 0x32, 0x31, 0x30, 0x34, 0x30, 0x34, 0x31, 0x38, 0x33, 0x39, 0x35,
+    0x34, 0x5a, 0x17, 0x0d, 0x33, 0x31, 0x30, 0x34, 0x30, 0x32, 0x31, 0x38,
+    0x33, 0x39, 0x35, 0x34, 0x5a, 0x30, 0x71, 0x31, 0x0b, 0x30, 0x09, 0x06,
+    0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x41, 0x41, 0x31, 0x12, 0x30, 0x10,
+    0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
+    0x68, 0x6f, 0x73, 0x74, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04,
+    0x07, 0x0c, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74,
+    0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x09, 0x6c,
+    0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x31, 0x12, 0x30, 0x10,
+    0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
+    0x68, 0x6f, 0x73, 0x74, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04,
+    0x03, 0x0c, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74,
+    0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+    0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
+    0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xca, 0x25, 0x51,
+    0xe7, 0xb4, 0x06, 0x2d, 0x11, 0xe1, 0x33, 0xfb, 0xde, 0xe1, 0x92, 0xbe,
+    0x63, 0x29, 0xf9, 0x1a, 0xea, 0xcd, 0x2d, 0x71, 0xf8, 0xc9, 0xae, 0x2b,
+    0x35, 0xae, 0x6f, 0xd1, 0x71, 0xae, 0xf5, 0x6f, 0xcc, 0xdd, 0xcf, 0x25,
+    0xd1, 0x59, 0x78, 0x30, 0x82, 0xec, 0x65, 0xe3, 0xd7, 0xa2, 0x6f, 0x7c,
+    0x84, 0x82, 0xec, 0x2f, 0xd4, 0x37, 0x7d, 0xbc, 0x6c, 0x36, 0x07, 0x82,
+    0x24, 0xed, 0x21, 0xe5, 0x81, 0xc3, 0x12, 0x2b, 0x54, 0x96, 0x44, 0xf7,
+    0xdd, 0x6a, 0x58, 0xe1, 0x2f, 0x3f, 0xcd, 0x67, 0x1e, 0x32, 0xc8, 0x99,
+    0x9b, 0xda, 0x33, 0x54, 0xdb, 0x52, 0x5d, 0xb2, 0xc5, 0x80, 0x50, 0x3b,
+    0x41, 0xe1, 0xd3, 0xa8, 0x0c, 0x0d, 0x23, 0x90, 0xe4, 0x62, 0xf4, 0x4f,
+    0xd6, 0x1b, 0xc5, 0x5a, 0xe0, 0x18, 0xa9, 0x3d, 0x74, 0xbb, 0x30, 0x20,
+    0xbd, 0x85, 0xbd, 0xc3, 0xbd, 0x69, 0x86, 0x62, 0x75, 0xa6, 0x9e, 0x1c,
+    0x74, 0x45, 0xa2, 0x5e, 0x67, 0x4c, 0xe0, 0xa2, 0x87, 0x13, 0x28, 0x8c,
+    0x76, 0xbf, 0x2e, 0xdf, 0xe7, 0xaf, 0x79, 0x12, 0xa9, 0x65, 0x42, 0x66,
+    0x1c, 0x25, 0xee, 0xca, 0x45, 0x29, 0x30, 0xff, 0x9c, 0x26, 0x14, 0x06,
+    0x63, 0x91, 0x08, 0xa4, 0x58, 0x8d, 0x22, 0x0b, 0xc8, 0x18, 0x8e, 0x42,
+    0xcd, 0x16, 0xb5, 0xdf, 0xa8, 0x7b, 0x7a, 0xb4, 0x71, 0x5f, 0xc2, 0xaa,
+    0x60, 0x99, 0xc5, 0x2f, 0xef, 0x13, 0xd1, 0x9b, 0x9c, 0x0a, 0x8c, 0x14,
+    0x7a, 0x8a, 0xb9, 0xdf, 0xdf, 0x9d, 0xaa, 0x76, 0x52, 0xff, 0x0c, 0x93,
+    0x68, 0x3f, 0x73, 0x0d, 0xb7, 0xc0, 0x21, 0x67, 0x48, 0xfd, 0xf2, 0xe2,
+    0xc3, 0x9c, 0xf5, 0xd4, 0x89, 0x86, 0xa9, 0x7b, 0x6a, 0x1b, 0x81, 0xa0,
+    0x89, 0x39, 0x71, 0xc9, 0x3a, 0x65, 0x54, 0xdb, 0x06, 0x22, 0x82, 0x7f,
+    0xc7, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xcf, 0x30, 0x81, 0xcc,
+    0x30, 0x81, 0x98, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0x90, 0x30,
+    0x81, 0x8d, 0xa1, 0x75, 0xa4, 0x73, 0x30, 0x71, 0x31, 0x0b, 0x30, 0x09,
+    0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x41, 0x41, 0x31, 0x12, 0x30,
+    0x10, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x09, 0x6c, 0x6f, 0x63, 0x61,
+    0x6c, 0x68, 0x6f, 0x73, 0x74, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55,
+    0x04, 0x07, 0x0c, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73,
+    0x74, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x09,
+    0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x31, 0x12, 0x30,
+    0x10, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x09, 0x6c, 0x6f, 0x63, 0x61,
+    0x6c, 0x68, 0x6f, 0x73, 0x74, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55,
+    0x04, 0x03, 0x0c, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73,
+    0x74, 0x82, 0x14, 0x60, 0x3a, 0x75, 0xa0, 0xfc, 0x63, 0x14, 0xe1, 0x70,
+    0x07, 0x16, 0x23, 0x7b, 0x84, 0x63, 0x3d, 0xe1, 0x1d, 0x07, 0x12, 0x30,
+    0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30,
+    0x00, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02,
+    0x04, 0xf0, 0x30, 0x14, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x0d, 0x30,
+    0x0b, 0x82, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74,
+    0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+    0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x13, 0x3c, 0x41, 0x03,
+    0xb2, 0x58, 0x00, 0x0a, 0x73, 0x1f, 0xb4, 0x15, 0xc7, 0x5f, 0xa5, 0x07,
+    0x14, 0xa6, 0xee, 0x7c, 0x83, 0x40, 0x85, 0x70, 0x40, 0x6c, 0x08, 0x94,
+    0x71, 0x77, 0x1c, 0xd8, 0x9a, 0xb3, 0x8d, 0x7c, 0xfa, 0x45, 0x96, 0x03,
+    0x51, 0x92, 0x46, 0xf8, 0x84, 0x6d, 0x09, 0x40, 0x3a, 0x53, 0x07, 0xc3,
+    0xd6, 0x1f, 0x72, 0xe6, 0x15, 0xeb, 0x49, 0x3a, 0x63, 0x2d, 0x11, 0x58,
+    0x73, 0xe3, 0x25, 0xde, 0xb0, 0x85, 0x5b, 0x45, 0xc6, 0xb9, 0x46, 0x2d,
+    0x86, 0x2d, 0xc9, 0x9b, 0x1f, 0x73, 0x6a, 0x18, 0xe7, 0x94, 0x49, 0x4c,
+    0x77, 0xea, 0x0b, 0xb6, 0xd1, 0xfb, 0x77, 0x6a, 0x1b, 0xfc, 0xb0, 0xcf,
+    0x5e, 0x29, 0xf1, 0xd5, 0xfc, 0x93, 0x8f, 0x48, 0xe3, 0xe0, 0xbd, 0x09,
+    0xa5, 0x7d, 0xa1, 0x50, 0x2c, 0x9f, 0xdc, 0x44, 0xe4, 0x85, 0xbb, 0xca,
+    0xab, 0xbd, 0x73, 0xa7, 0xd8, 0x3e, 0x31, 0x53, 0xfe, 0xd4, 0xc3, 0xd7,
+    0x05, 0x3c, 0x93, 0x72, 0xcc, 0x16, 0x2b, 0xe6, 0x33, 0xf0, 0xbd, 0xea,
+    0x9a, 0x5f, 0x24, 0x39, 0x7e, 0xfe, 0x82, 0x34, 0xa6, 0xb2, 0x23, 0xe4,
+    0x67, 0x8f, 0xf9, 0x60, 0x6b, 0xd7, 0x2e, 0xd9, 0xfb, 0x9a, 0x12, 0x3c,
+    0xb5, 0x3b, 0xf9, 0x9d, 0x0a, 0xbf, 0xff, 0x2d, 0x6b, 0x33, 0x82, 0xe6,
+    0x54, 0x75, 0x2b, 0xf2, 0x74, 0x3b, 0xc7, 0x96, 0xa5, 0x06, 0x70, 0xb6,
+    0x0a, 0x43, 0x7a, 0x7b, 0xa1, 0xc3, 0x8e, 0x2a, 0x58, 0xcc, 0xff, 0x49,
+    0xeb, 0x51, 0xb6, 0x7c, 0x7e, 0xa2, 0x68, 0x9f, 0x82, 0xe8, 0x2e, 0x4e,
+    0xf9, 0x14, 0xc7, 0xbe, 0x60, 0x42, 0x65, 0x9d, 0x5a, 0xce, 0xca, 0x09,
+    0xf7, 0xee, 0x48, 0x41, 0x93, 0x69, 0x7c, 0x3e, 0x47, 0xf0, 0x5e, 0x60,
+    0x5d, 0x1d, 0xa3, 0x67, 0x59, 0x3c, 0xf9, 0x3c, 0x49, 0x63, 0x74, 0x84};
+
+
+/* This array corresponds to the content of the file
+ * /resources/cert/server.key converted from BASE64 to HEX.
+ * When using this code, you need to REPLACE IT BY YOUR OWN PRIVATE KEY!
+ */
+const uint8_t SSL_KEY_ASN1[] = {
+    0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00,
+    0xca, 0x25, 0x51, 0xe7, 0xb4, 0x06, 0x2d, 0x11, 0xe1, 0x33, 0xfb, 0xde,
+    0xe1, 0x92, 0xbe, 0x63, 0x29, 0xf9, 0x1a, 0xea, 0xcd, 0x2d, 0x71, 0xf8,
+    0xc9, 0xae, 0x2b, 0x35, 0xae, 0x6f, 0xd1, 0x71, 0xae, 0xf5, 0x6f, 0xcc,
+    0xdd, 0xcf, 0x25, 0xd1, 0x59, 0x78, 0x30, 0x82, 0xec, 0x65, 0xe3, 0xd7,
+    0xa2, 0x6f, 0x7c, 0x84, 0x82, 0xec, 0x2f, 0xd4, 0x37, 0x7d, 0xbc, 0x6c,
+    0x36, 0x07, 0x82, 0x24, 0xed, 0x21, 0xe5, 0x81, 0xc3, 0x12, 0x2b, 0x54,
+    0x96, 0x44, 0xf7, 0xdd, 0x6a, 0x58, 0xe1, 0x2f, 0x3f, 0xcd, 0x67, 0x1e,
+    0x32, 0xc8, 0x99, 0x9b, 0xda, 0x33, 0x54, 0xdb, 0x52, 0x5d, 0xb2, 0xc5,
+    0x80, 0x50, 0x3b, 0x41, 0xe1, 0xd3, 0xa8, 0x0c, 0x0d, 0x23, 0x90, 0xe4,
+    0x62, 0xf4, 0x4f, 0xd6, 0x1b, 0xc5, 0x5a, 0xe0, 0x18, 0xa9, 0x3d, 0x74,
+    0xbb, 0x30, 0x20, 0xbd, 0x85, 0xbd, 0xc3, 0xbd, 0x69, 0x86, 0x62, 0x75,
+    0xa6, 0x9e, 0x1c, 0x74, 0x45, 0xa2, 0x5e, 0x67, 0x4c, 0xe0, 0xa2, 0x87,
+    0x13, 0x28, 0x8c, 0x76, 0xbf, 0x2e, 0xdf, 0xe7, 0xaf, 0x79, 0x12, 0xa9,
+    0x65, 0x42, 0x66, 0x1c, 0x25, 0xee, 0xca, 0x45, 0x29, 0x30, 0xff, 0x9c,
+    0x26, 0x14, 0x06, 0x63, 0x91, 0x08, 0xa4, 0x58, 0x8d, 0x22, 0x0b, 0xc8,
+    0x18, 0x8e, 0x42, 0xcd, 0x16, 0xb5, 0xdf, 0xa8, 0x7b, 0x7a, 0xb4, 0x71,
+    0x5f, 0xc2, 0xaa, 0x60, 0x99, 0xc5, 0x2f, 0xef, 0x13, 0xd1, 0x9b, 0x9c,
+    0x0a, 0x8c, 0x14, 0x7a, 0x8a, 0xb9, 0xdf, 0xdf, 0x9d, 0xaa, 0x76, 0x52,
+    0xff, 0x0c, 0x93, 0x68, 0x3f, 0x73, 0x0d, 0xb7, 0xc0, 0x21, 0x67, 0x48,
+    0xfd, 0xf2, 0xe2, 0xc3, 0x9c, 0xf5, 0xd4, 0x89, 0x86, 0xa9, 0x7b, 0x6a,
+    0x1b, 0x81, 0xa0, 0x89, 0x39, 0x71, 0xc9, 0x3a, 0x65, 0x54, 0xdb, 0x06,
+    0x22, 0x82, 0x7f, 0xc7, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x82, 0x01,
+    0x00, 0x73, 0xbb, 0x54, 0x1e, 0x34, 0xca, 0x48, 0x69, 0x71, 0x26, 0xc2,
+    0xf0, 0x02, 0xf3, 0x71, 0xbe, 0xf2, 0x5b, 0xe5, 0x16, 0x42, 0xeb, 0xde,
+    0xd1, 0x92, 0x1d, 0xfe, 0x2d, 0x18, 0xb6, 0x7a, 0x11, 0xfd, 0x1a, 0x15,
+    0xad, 0x13, 0xdc, 0xb2, 0x09, 0x1e, 0x91, 0x1a, 0x2d, 0x0a, 0xcc, 0xf6,
+    0xda, 0x10, 0xec, 0x85, 0x3c, 0x94, 0x7c, 0x46, 0x91, 0xd8, 0x47, 0x4b,
+    0x66, 0x24, 0xb4, 0xbd, 0xc5, 0x08, 0x62, 0x9c, 0xb4, 0x63, 0x0b, 0x76,
+    0xf5, 0x51, 0xa7, 0x20, 0xc5, 0x8a, 0x4a, 0x62, 0x7a, 0x1b, 0xac, 0x2c,
+    0x7a, 0x74, 0x96, 0xb6, 0xa3, 0x2d, 0x14, 0xb0, 0x63, 0x74, 0xcf, 0xa2,
+    0x37, 0x42, 0xd4, 0x2c, 0x68, 0xf6, 0xb2, 0xa8, 0x06, 0x66, 0x4b, 0x53,
+    0x7b, 0xfe, 0x4f, 0x63, 0x99, 0xf0, 0x82, 0x58, 0x19, 0xee, 0xe4, 0x8e,
+    0x03, 0xd3, 0xdb, 0xa5, 0x12, 0xfc, 0x8b, 0xfd, 0x90, 0xe2, 0x55, 0xd8,
+    0xb4, 0x59, 0xc8, 0x75, 0xab, 0x14, 0x02, 0x65, 0xee, 0x00, 0xf0, 0xdc,
+    0xa4, 0x5f, 0x1e, 0xbb, 0x10, 0x90, 0x57, 0xea, 0x45, 0x33, 0x75, 0xfd,
+    0x0c, 0x97, 0xe6, 0xd6, 0xe6, 0xaa, 0x08, 0x22, 0x26, 0x4a, 0x34, 0x0d,
+    0xfe, 0xb5, 0xde, 0xdb, 0xa8, 0xc0, 0x67, 0x83, 0x5f, 0x27, 0x62, 0x55,
+    0x5a, 0xa0, 0x83, 0x25, 0xe6, 0x19, 0xd9, 0x78, 0x33, 0x7b, 0x2b, 0xa0,
+    0x53, 0x1d, 0xed, 0x62, 0xd1, 0x10, 0x95, 0x9f, 0xb0, 0xa2, 0xe7, 0xe8,
+    0x58, 0x39, 0x31, 0x76, 0x65, 0x53, 0x28, 0x23, 0x98, 0xa9, 0xab, 0xc2,
+    0x9c, 0x57, 0x0e, 0x9c, 0x17, 0x10, 0x14, 0x35, 0x5b, 0x5d, 0xce, 0x94,
+    0x1b, 0xdf, 0x8b, 0x42, 0xc0, 0xc6, 0x0e, 0x5c, 0x48, 0x67, 0x4f, 0xaf,
+    0x27, 0x3d, 0xc4, 0xda, 0xfa, 0xb4, 0xbd, 0x8e, 0x55, 0xdd, 0xa8, 0x18,
+    0x34, 0x01, 0xb6, 0xd8, 0x09, 0x02, 0x81, 0x81, 0x00, 0xe9, 0x53, 0x5f,
+    0xf9, 0x0e, 0xdf, 0x1e, 0x18, 0x90, 0xcb, 0xb8, 0x66, 0xea, 0x70, 0x6a,
+    0x72, 0xc3, 0x6e, 0x87, 0x7a, 0x79, 0x2f, 0xc4, 0x50, 0x32, 0x32, 0xcf,
+    0x97, 0xa7, 0x3b, 0x2b, 0x90, 0xd1, 0x05, 0x39, 0x5f, 0x51, 0x47, 0x79,
+    0xc3, 0x1d, 0xd8, 0xaa, 0xca, 0xda, 0x1a, 0x38, 0x6b, 0xfa, 0x02, 0x29,
+    0xbd, 0x43, 0x45, 0xed, 0xe8, 0xae, 0xf5, 0xc0, 0xaa, 0xde, 0xe8, 0xac,
+    0x21, 0xf5, 0x62, 0x99, 0xea, 0xeb, 0xb2, 0x54, 0xfd, 0xf3, 0x6a, 0x9d,
+    0x13, 0xbe, 0x09, 0x51, 0xef, 0x0f, 0x12, 0xb2, 0x14, 0x20, 0xe9, 0x6d,
+    0xfe, 0x6c, 0x63, 0x02, 0x7c, 0xd7, 0x0e, 0xa9, 0x2f, 0x2b, 0x52, 0x68,
+    0x83, 0x50, 0xdd, 0xc2, 0xf1, 0x86, 0x7c, 0x33, 0xe8, 0x62, 0x6e, 0x8e,
+    0x48, 0x50, 0x5c, 0x84, 0x7e, 0x22, 0x36, 0x60, 0x74, 0x16, 0x27, 0xd5,
+    0x77, 0xb6, 0x94, 0x7e, 0x75, 0x02, 0x81, 0x81, 0x00, 0xdd, 0xca, 0x42,
+    0x1f, 0x3d, 0x3f, 0xc7, 0x4e, 0xce, 0x7d, 0x37, 0x09, 0xef, 0xf8, 0xee,
+    0x67, 0x97, 0xbb, 0xf8, 0x34, 0x49, 0x44, 0xa9, 0x9a, 0x07, 0x7f, 0x48,
+    0xaa, 0xb9, 0x77, 0xb6, 0x22, 0xfd, 0x88, 0x97, 0x77, 0x20, 0x6e, 0x0c,
+    0x67, 0x19, 0x2e, 0xc9, 0x58, 0x3c, 0xfd, 0xdb, 0x3b, 0xfb, 0x0b, 0xfb,
+    0x86, 0xa2, 0x74, 0x31, 0x60, 0xaa, 0x27, 0x41, 0x3d, 0xdf, 0x9a, 0xaa,
+    0xb3, 0xd8, 0x9a, 0x0a, 0x2d, 0xf9, 0xd7, 0xee, 0x67, 0xdc, 0x49, 0x40,
+    0x74, 0x30, 0x32, 0xb7, 0x94, 0xfd, 0x84, 0x13, 0xb8, 0x24, 0x89, 0xdf,
+    0xee, 0x7d, 0xe3, 0x1b, 0xe5, 0x76, 0xc4, 0x1b, 0x81, 0x32, 0xa6, 0x0f,
+    0x07, 0x26, 0x87, 0x3b, 0xff, 0xaf, 0xa9, 0x25, 0x71, 0xd0, 0x70, 0x2e,
+    0xa8, 0xbc, 0x7e, 0xe2, 0xe2, 0x6f, 0x71, 0x5e, 0xe2, 0xad, 0xc1, 0x22,
+    0x0c, 0x3f, 0xc4, 0x35, 0xcb, 0x02, 0x81, 0x80, 0x46, 0x40, 0x08, 0x21,
+    0x60, 0xcc, 0xe4, 0xae, 0xd8, 0xc9, 0xbd, 0x97, 0x9e, 0xf6, 0x81, 0xd6,
+    0x53, 0xe9, 0x2f, 0x79, 0x3c, 0x8b, 0x99, 0x3b, 0xdc, 0x21, 0x58, 0x47,
+    0x7c, 0xde, 0x5f, 0xdb, 0x96, 0x53, 0x50, 0x56, 0xd6, 0x8e, 0x02, 0xa7,
+    0x30, 0x91, 0x4f, 0xbb, 0x0b, 0xb7, 0xe1, 0x4d, 0x01, 0x55, 0x2d, 0x64,
+    0x02, 0xa1, 0x47, 0x64, 0x4b, 0x69, 0x4a, 0xbd, 0x27, 0xa8, 0x3e, 0x4b,
+    0x6b, 0x2a, 0x68, 0xd5, 0x46, 0x69, 0xc7, 0x15, 0x3e, 0xf8, 0xd6, 0x9a,
+    0x5f, 0x19, 0x47, 0x46, 0x06, 0xef, 0xc6, 0x16, 0x31, 0x62, 0x96, 0xef,
+    0x87, 0x8a, 0xb7, 0xf1, 0x06, 0x7f, 0x2f, 0x89, 0x38, 0x2d, 0xf3, 0xb1,
+    0xb5, 0xe3, 0x4f, 0x12, 0x91, 0x3f, 0x4c, 0x11, 0xa7, 0xb1, 0x49, 0xbd,
+    0x94, 0x14, 0x86, 0xff, 0xc3, 0x25, 0x44, 0x1d, 0x2f, 0x9e, 0x86, 0xb3,
+    0x28, 0x91, 0xc5, 0x11, 0x02, 0x81, 0x80, 0x57, 0x5c, 0xef, 0x54, 0xcc,
+    0xd4, 0x8d, 0x96, 0x9e, 0x41, 0xb6, 0x67, 0x64, 0xae, 0x62, 0x82, 0x4d,
+    0xc3, 0x8e, 0x0e, 0x52, 0x7a, 0x08, 0x70, 0x92, 0xd9, 0x71, 0x6f, 0x46,
+    0x65, 0x40, 0x4a, 0x62, 0x21, 0xe6, 0xbf, 0xd6, 0xf7, 0x62, 0x4d, 0x4e,
+    0x1f, 0x1e, 0xd2, 0x72, 0x1b, 0xf0, 0xba, 0x9c, 0xb5, 0xe8, 0x9a, 0xec,
+    0xec, 0xe5, 0xf2, 0x54, 0xb3, 0xe7, 0xc0, 0x0e, 0x8f, 0x27, 0x04, 0x76,
+    0xa2, 0x9e, 0xb5, 0xe3, 0x7f, 0x49, 0xfa, 0x81, 0x4c, 0x1d, 0x66, 0x67,
+    0x01, 0xe3, 0x4c, 0x7d, 0xdc, 0x03, 0xc4, 0x7a, 0x28, 0x11, 0x1c, 0x29,
+    0x5c, 0x47, 0x45, 0xc8, 0xd5, 0x90, 0x9c, 0x00, 0xae, 0x66, 0xa7, 0x03,
+    0x67, 0x2b, 0x9c, 0x18, 0xbe, 0x80, 0xf0, 0x67, 0x11, 0x79, 0x5f, 0x9f,
+    0xf8, 0x3f, 0x38, 0xc0, 0x7b, 0x20, 0xcc, 0x1b, 0x73, 0x43, 0x0d, 0x1e,
+    0x25, 0x14, 0xa7, 0x02, 0x81, 0x80, 0x09, 0xe0, 0xa5, 0xb9, 0x56, 0x96,
+    0x64, 0xf7, 0xd3, 0xc5, 0xbc, 0x19, 0x2b, 0x20, 0xcb, 0x08, 0x9e, 0x0b,
+    0x3e, 0x5e, 0xcc, 0x8e, 0xf2, 0x72, 0x83, 0x9b, 0x9f, 0x52, 0xf4, 0x0a,
+    0xda, 0xe0, 0x0b, 0x91, 0x14, 0x51, 0x8e, 0x19, 0x1b, 0x77, 0x7d, 0x2a,
+    0xec, 0x9a, 0xdc, 0xd1, 0x83, 0xff, 0x25, 0x75, 0xb6, 0xb7, 0xe4, 0x51,
+    0xb0, 0xa1, 0x22, 0x7b, 0x1f, 0xb6, 0xcd, 0x7d, 0xe0, 0x55, 0x2f, 0x3d,
+    0xe4, 0x1e, 0xe9, 0x4e, 0x77, 0x2e, 0xe5, 0x8c, 0xcb, 0x82, 0x5e, 0xee,
+    0xe7, 0x4e, 0x08, 0x09, 0x67, 0xb1, 0xcc, 0x67, 0x66, 0x68, 0xd1, 0x2c,
+    0x65, 0x08, 0xc7, 0x8a, 0x23, 0xc4, 0x5b, 0x7c, 0x9c, 0x54, 0x44, 0x2e,
+    0xe1, 0xad, 0x8f, 0x99, 0x2a, 0xd8, 0x8f, 0x31, 0x05, 0x7c, 0x9f, 0xbc,
+    0x79, 0x67, 0x40, 0x5e, 0xda, 0x2c, 0x38, 0x7d, 0x3f, 0x0b, 0x6c, 0x83,
+    0xcc, 0x75};
+
+
+/* This is an example for a custom SSL initialization function.
+ * It will setup the certificate and private key defined in the arrays above.
+ * Thus, they do not need to be located in files in the file system.
+ * To slightly improve security, you could use some encryption for the arrays
+ * above, and only decrypt it in memory before passing to the SSL_CTX_use_*
+ * functions. Then the data will not exist in the executable stored at the
+ * disk, but only in memory at runtime - making reverse engineering more
+ * complex. Here we use unencrypted versions, so you could test the conversion
+ * process from server.key/server.crt to these arrays on your own, and see if
+ * you come to the same result.
+ */
+static int
+init_ssl(void *ssl_ctx, void *user_data)
+{
+	SSL_CTX *ctx = (SSL_CTX *)ssl_ctx;
+
+	SSL_CTX_use_certificate_ASN1(ctx, sizeof(SSL_CERT_ASN1), SSL_CERT_ASN1);
+	SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA,
+	                            ctx,
+	                            SSL_KEY_ASN1,
+	                            sizeof(SSL_KEY_ASN1));
+
+	if (SSL_CTX_check_private_key(ctx) == 0) {
+		printf("SSL data inconsistency detected\n");
+		return -1;
+	}
+
+	return 0; /* let CivetWeb set up the rest of OpenSSL */
+}
+
+
+/* This handler function will serve all requests to the server.
+ * In a realistic example, you would have multiple handler functions for
+ * different URLs.
+ */
+static int
+request_handler(struct mg_connection *conn, void *cbdata)
+{
+	/* Get the URI from the request info. */
+	const struct mg_request_info *ri = mg_get_request_info(conn);
+	unsigned uri_len = (unsigned)strlen(ri->local_uri);
+
+	/* Example: get a cookie named "c" from the client. */
+	const char *cookie = mg_get_header(conn, "Cookie");
+	unsigned long cookie_number = 0;
+	if (cookie) {
+		char dst[32] = {0};
+		if (mg_get_cookie(cookie, "c", dst, sizeof(dst)) > 0) {
+			long val = atol(dst);
+			if (val > 0) {
+				cookie_number = val;
+			}
+		}
+	}
+	/* Calculate a value for a new cookie. Here just increment the previous
+	 * value. Thus, we count the number of times a client visited this a
+	 * page generated by this handler function. */
+	char new_cookie[32];
+	sprintf(new_cookie, "c=%lu", cookie_number + 1);
+
+	/* Generate a response text and status code. */
+	int status;
+	char response[1024];
+
+	if (uri_len <= 100) {
+		status = 200; /* 200 = OK */
+		sprintf(response,
+		        "Hello at %s\nYou have visited %lu sites here before!",
+		        ri->local_uri,
+		        cookie_number);
+	} else {
+		/* We don't like this URL */
+		status = 404; /* 404 = Not Found */
+		sprintf(response, "No such URL\n");
+	}
+
+	/* This sequence can be used to send a response including a cookie. */
+	unsigned long content_len = (unsigned long)strlen(response);
+	char content_len_text[32];
+	sprintf(content_len_text, "%lu", content_len);
+
+	mg_response_header_start(conn, status);
+	mg_response_header_add(conn, "Set-Cookie", new_cookie, -1);
+	mg_response_header_add(conn,
+	                       "Content-Type",
+	                       "text/plain; charset=utf-8",
+	                       -1);
+	mg_response_header_add(conn, "Content-Length", content_len_text, -1);
+	mg_response_header_send(conn);
+	mg_write(conn, response, content_len);
+
+	return status;
+}
+
+
+/* Main: Initialize and run the server */
+int
+main(int argc, char *argv[])
+{
+	int ret = mg_init_library(MG_FEATURES_TLS);
+	if (ret != MG_FEATURES_TLS) {
+		printf("Initializing SSL libraries failed\n");
+		return 1;
+	}
+
+	printf("Starting example server at https://localhost:443/\n");
+	struct mg_init_data init = {0};
+
+	struct mg_callbacks callbacks = {0};
+	callbacks.init_ssl = init_ssl;
+	init.callbacks = &callbacks;
+
+	init.user_data = NULL;
+
+	const char *options[] = {"listening_ports",
+	                         "80r,443s",
+	                         "authentication_domain",
+	                         "localhost",
+	                         "enable_auth_domain_check",
+	                         "no",
+	                         "ssl_protocol_version",
+	                         "4",
+	                         "ssl_cipher_list",
+	                         "ECDH+AESGCM+AES256:!aNULL:!MD5:!DSS",
+	                         "strict_transport_security_max_age",
+	                         "15552000",
+	                         NULL,
+	                         NULL};
+	init.configuration_options = options;
+
+	struct mg_error_data error = {0};
+	char error_text[256] = {0};
+	error.text = error_text;
+	error.text_buffer_size = sizeof(error_text);
+
+	struct mg_context *ctx = mg_start2(&init, &error);
+	if (ctx) {
+		mg_set_request_handler(ctx, "/", request_handler, NULL);
+		printf("Server running for 60 seconds\n");
+		sleep(60);
+		printf("Server exit\n");
+		mg_stop(ctx);
+	} else {
+		printf("Initialization failed: %u\n%s\n\n", error.code, error.text);
+	}
+
+	mg_exit_library();
+	return 0;
+}
diff --git a/examples/embedded_c/embedded_c.c b/examples/embedded_c/embedded_c.c
index 8cde083..ab8cb26 100644
--- a/examples/embedded_c/embedded_c.c
+++ b/examples/embedded_c/embedded_c.c
@@ -4,7 +4,7 @@
  * License http://opensource.org/licenses/mit-license.php MIT License
  */
 
-/* Note: This example ommits some error checking and input validation for a
+/* Note: This example omits some error checking and input validation for a
  * better clarity/readability of the code. Example codes undergo less quality
  * management than the main source files of this project. */
 
@@ -622,7 +622,6 @@ PostResponser(struct mg_connection *conn, void *cbdata)
 
 	if (0 != strcmp(ri->request_method, "POST")) {
 		/* Not a POST request */
-		char buf[1024];
 		int ret = mg_get_request_link(conn, buf, sizeof(buf));
 
 		mg_printf(conn,
@@ -655,7 +654,7 @@ PostResponser(struct mg_connection *conn, void *cbdata)
 	while (r > 0) {
 		r_total += r;
 		s = mg_send_chunk(conn, buf, r);
-		if (r != s) {
+		if (s <= 0) {
 			/* Send error */
 			break;
 		}
diff --git a/examples/linux_ws_server_cpp/CMakeLists.txt b/examples/linux_ws_server_cpp/CMakeLists.txt
new file mode 100644
index 0000000..6a71798
--- /dev/null
+++ b/examples/linux_ws_server_cpp/CMakeLists.txt
@@ -0,0 +1,24 @@
+cmake_minimum_required(VERSION 3.5.1)
+project(linux_ws_server)
+
+set(TARGET_NAME ${PROJECT_NAME})
+set(CMAKE_CXX_STANDARD 14)
+
+find_package(PkgConfig REQUIRED)
+find_package(civetweb)
+
+
+include_directories(
+    ${civetweb_INCLUDE_DIR}
+)
+
+link_directories (
+    /usr/local/lib
+)
+
+add_executable(${TARGET_NAME} main.cc)
+
+target_link_libraries(${TARGET_NAME}
+    libcivetweb-cpp.so
+    libcivetweb.so
+)
diff --git a/examples/linux_ws_server_cpp/README.md b/examples/linux_ws_server_cpp/README.md
new file mode 100644
index 0000000..662d784
--- /dev/null
+++ b/examples/linux_ws_server_cpp/README.md
@@ -0,0 +1,17 @@
+[TOC]
+
+# Civetweb websocket cpp
+
+## #1 Installation
+
+```shell
+git clone https://github.com/civetweb/civetweb.git
+mkdir buildx && cd buildx
+cmake -DBUILD_SHARED_LIBS=ON -DCIVETWEB_ENABLE_WEBSOCKETS=ON -DCIVETWEB_ENABLE_CXX=ON ..
+make
+sudo make install
+```
+
+
+
+
diff --git a/examples/linux_ws_server_cpp/main.cc b/examples/linux_ws_server_cpp/main.cc
new file mode 100644
index 0000000..faf477f
--- /dev/null
+++ b/examples/linux_ws_server_cpp/main.cc
@@ -0,0 +1,150 @@
+#include <iostream>
+#include <memory>
+#include <mutex>
+#include <sstream>
+#include <unistd.h>
+#include <unordered_map>
+
+#include "CivetServer.h"
+
+class WebSocketBase : public CivetWebSocketHandler
+{
+
+  public:
+	explicit WebSocketBase(std::string name) : name_(std::move(name))
+	{
+	}
+
+	bool
+	handleConnection(CivetServer *server, const mg_connection *conn) override
+	{
+		return true;
+	}
+
+	void
+	handleReadyState(CivetServer *server, mg_connection *conn) override
+	{
+		std::unique_lock<std::mutex> lock(mutex_);
+		pool_.emplace(conn, std::make_shared<std::mutex>());
+	}
+
+	bool
+	handleData(CivetServer *server,
+	           mg_connection *conn,
+	           int bits,
+	           char *data,
+	           size_t data_len) override
+	{
+
+		if ((bits & 0x0F) == MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE) {
+			return false;
+		}
+		data_.write(data, data_len);
+		if (current_opcode_ == 0x00) {
+			current_opcode_ = bits & 0x7f;
+		}
+		bool is_final_fragment = bits & 0x80;
+		if (is_final_fragment) {
+			switch (current_opcode_) {
+			case MG_WEBSOCKET_OPCODE_TEXT:
+				std::cout << data_.str() << std::endl; // print request data
+				break;
+			default:
+				break;
+			}
+			current_opcode_ = 0x00;
+			this->sendData(conn, data_.str()); // response
+			data_.clear();
+			data_.str(std::string());
+		}
+		return true;
+	}
+
+	void
+	handleClose(CivetServer *server, const mg_connection *conn) override
+	{
+		auto *connection = const_cast<mg_connection *>(conn);
+		std::shared_ptr<std::mutex> user_lock;
+		{
+			std::unique_lock<std::mutex> lock(mutex_);
+			user_lock = pool_[connection];
+		}
+		{
+			std::unique_lock<std::mutex> lock_connection(*user_lock);
+			std::unique_lock<std::mutex> lock(mutex_);
+			pool_.erase(connection);
+		}
+	}
+
+	bool
+	sendData(mg_connection *conn,
+	         const std::string &data,
+	         bool skippable = false,
+	         int op_code = MG_WEBSOCKET_OPCODE_TEXT)
+	{
+		std::shared_ptr<std::mutex> connection_lock;
+		{
+			std::unique_lock<std::mutex> lock(mutex_);
+			connection_lock = pool_[conn];
+		}
+
+		if (!connection_lock->try_lock()) {
+			if (skippable) {
+				return false;
+			}
+			connection_lock->lock();
+			std::unique_lock<std::mutex> lock(mutex_);
+		}
+
+		int ret = mg_websocket_write(conn, op_code, data.c_str(), data.size());
+		connection_lock->unlock();
+
+		if (ret != static_cast<int>(data.size())) {
+			if (data.empty() && ret == 2) {
+				return true;
+			}
+			return false;
+		}
+
+		return true;
+	}
+
+	thread_local static std::stringstream data_;
+	thread_local static unsigned char current_opcode_;
+
+  private:
+	const std::string name_;
+	mutable std::mutex mutex_;
+	std::unordered_map<mg_connection *, std::shared_ptr<std::mutex>> pool_;
+};
+
+thread_local unsigned char WebSocketBase::current_opcode_ = 0x00;
+thread_local std::stringstream WebSocketBase::data_;
+
+int
+main()
+{
+	std::cout << "Hello, Civetweb Websocket port:8080!" << std::endl;
+
+	mg_init_library(0);
+	std::vector<std::string> options = {
+	    "document_root",
+	    ".",
+	    "listening_ports",
+	    "8080",
+	    "access_control_allow_headers",
+	    "*",
+	    "access_control_allow_methods",
+	    "*",
+	    "access_control_allow_origin",
+	    "*",
+	};
+	auto server = std::make_shared<CivetServer>(options);
+	auto ws = std::make_shared<WebSocketBase>("simple");
+	server->addWebSocketHandler("/ws", *ws);
+	while (true) {
+		sleep(1);
+	}
+	server->close();
+	return 0;
+}
diff --git a/examples/rest/Makefile b/examples/rest/Makefile
index e4d44aa..864c7d3 100644
--- a/examples/rest/Makefile
+++ b/examples/rest/Makefile
@@ -1,5 +1,5 @@
-# 
-# Copyright (c) 2018 CivetWeb Developers
+#
+# Copyright (c) 2018, 2022 CivetWeb Developers
 # Copyright (c) 2013 No Face Press, LLC
 # License http://opensource.org/licenses/mit-license.php MIT License
 #
@@ -13,22 +13,22 @@ SRC = rest.c cJSON/cJSON.c cJSON/cJSON_Utils.c
 TOP = ../..
 CIVETWEB_LIB = libcivetweb.a
 
-CFLAGS = -I$(TOP)/include -IcJSON $(COPT) -DNO_FILES
+CFLAGS = -I$(TOP)/include -IcJSON $(COPT) -DNO_FILES -DMG_EXPERIMENTAL_INTERFACES
 LIBS = -lpthread
 
 include $(TOP)/resources/Makefile.in-os
 
-ifeq ($(TARGET_OS),LINUX) 
+ifeq ($(TARGET_OS),LINUX)
 	LIBS += -ldl
 endif
 
 all: $(PROG)
 
 $(PROG): $(CIVETWEB_LIB) $(SRC)
-	$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $(SRC) $(CIVETWEB_LIB) $(LIBS) -lcrypto -lssl -DUSE_SSL_DH=1
+	$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $(SRC) $(CIVETWEB_LIB) $(LIBS)
 
 $(CIVETWEB_LIB):
-	$(MAKE) -C $(TOP) WITH_IPV6=1 WITH_WEBSOCKET=1 COPT='-DNO_SSL_DL=1' clean lib
+	$(MAKE) -C $(TOP) WITH_IPV6=1 WITH_WEBSOCKET=1 COPT='-DNO_SSL -DMG_EXPERIMENTAL_INTERFACES' clean lib
 	cp $(TOP)/$(CIVETWEB_LIB) .
 
 clean:
diff --git a/examples/rest/cJSON/cJSON.c b/examples/rest/cJSON/cJSON.c
index 7e71ea9..9ecafbd 100644
--- a/examples/rest/cJSON/cJSON.c
+++ b/examples/rest/cJSON/cJSON.c
@@ -126,7 +126,7 @@ typedef struct internal_hooks
 } internal_hooks;
 
 #if defined(_MSC_VER)
-/* work around MSVC error C2322: '...' address of dillimport '...' is not static */
+/* work around MSVC error C2322: '...' address of dllimport '...' is not static */
 static void *internal_malloc(size_t size)
 {
     return malloc(size);
@@ -505,7 +505,7 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out
         }
     }
 
-    /* sprintf failed or buffer overrun occured */
+    /* sprintf failed or buffer overrun occurred */
     if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1)))
     {
         return false;
@@ -1556,7 +1556,7 @@ static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_bu
         buffer_skip_whitespace(input_buffer);
         if (!parse_string(current_item, input_buffer))
         {
-            goto fail; /* faile to parse name */
+            goto fail; /* failed to parse name */
         }
         buffer_skip_whitespace(input_buffer);
 
diff --git a/examples/rest/cJSON/cJSON.h b/examples/rest/cJSON/cJSON.h
index a9c68fa..49fd67b 100644
--- a/examples/rest/cJSON/cJSON.h
+++ b/examples/rest/cJSON/cJSON.h
@@ -195,7 +195,7 @@ CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
 /* Create a string where valuestring references a string so
  * it will not be freed by cJSON_Delete */
 CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
-/* Create an object/arrray that only references it's elements so
+/* Create an object/array that only references it's elements so
  * they will not be freed by cJSON_Delete */
 CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
 CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
@@ -217,7 +217,7 @@ CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJ
 CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
 CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
 
-/* Remove/Detatch items from Arrays/Objects. */
+/* Remove/Detach items from Arrays/Objects. */
 CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
 CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
 CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
diff --git a/examples/rest/rest.c b/examples/rest/rest.c
index 8b42583..664e804 100644
--- a/examples/rest/rest.c
+++ b/examples/rest/rest.c
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2018 the CivetWeb developers
+ * Revisited version: Copyright (c) 2022 the CivetWeb developers
  * MIT License
  */
 
@@ -10,6 +11,7 @@
 #include <unistd.h>
 #endif
 
+#include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
@@ -17,16 +19,10 @@
 #include "cJSON.h"
 #include "civetweb.h"
 
-
-#ifdef NO_SSL
 #define PORT "8089"
 #define HOST_INFO "http://localhost:8089"
-#else
-#define PORT "8089r,8843s"
-#define HOST_INFO "https://localhost:8843"
-#endif
 
-#define EXAMPLE_URI "/example"
+#define EXAMPLE_URI "/res/*/*"
 #define EXIT_URI "/exit"
 
 int exitNow = 0;
@@ -38,12 +34,16 @@ SendJSON(struct mg_connection *conn, cJSON *json_obj)
 	char *json_str = cJSON_PrintUnformatted(json_obj);
 	size_t json_str_len = strlen(json_str);
 
-	/* Send HTTP message header */
-	mg_send_http_ok(conn, "application/json; charset=utf-8", json_str_len);
+	/* Send HTTP message header (+1 for \n) */
+	mg_send_http_ok(conn, "application/json; charset=utf-8", json_str_len + 1);
 
 	/* Send HTTP message content */
 	mg_write(conn, json_str, json_str_len);
 
+	/* Add a newline. This is not required, but the result is more
+	 * human-readable in a debugger. */
+	mg_write(conn, "\n", 1);
+
 	/* Free string allocated by cJSON_Print* */
 	cJSON_free(json_str);
 
@@ -55,7 +55,7 @@ static unsigned request = 0; /* demo data: request counter */
 
 
 static int
-ExampleGET(struct mg_connection *conn)
+ExampleGET(struct mg_connection *conn, const char *p1, const char *p2)
 {
 	cJSON *obj = cJSON_CreateObject();
 
@@ -65,8 +65,10 @@ ExampleGET(struct mg_connection *conn)
 		return 500;
 	}
 
-
+	printf("GET %s/%s\n", p1, p2);
 	cJSON_AddStringToObject(obj, "version", CIVETWEB_VERSION);
+	cJSON_AddStringToObject(obj, "path1", p1);
+	cJSON_AddStringToObject(obj, "path2", p2);
 	cJSON_AddNumberToObject(obj, "request", ++request);
 	SendJSON(conn, obj);
 	cJSON_Delete(obj);
@@ -76,9 +78,9 @@ ExampleGET(struct mg_connection *conn)
 
 
 static int
-ExampleDELETE(struct mg_connection *conn)
+ExampleDELETE(struct mg_connection *conn, const char *p1, const char *p2)
 {
-	request = 0;
+	printf("DELETE %s/%s\n", p1, p2);
 	mg_send_http_error(conn,
 	                   204,
 	                   "%s",
@@ -89,13 +91,14 @@ ExampleDELETE(struct mg_connection *conn)
 
 
 static int
-ExamplePUT(struct mg_connection *conn)
+ExamplePUT(struct mg_connection *conn, const char *p1, const char *p2)
 {
 	char buffer[1024];
 	int dlen = mg_read(conn, buffer, sizeof(buffer) - 1);
 	cJSON *obj, *elem;
 	unsigned newvalue;
 
+	printf("PUT %s/%s\n", p1, p2);
 	if ((dlen < 1) || (dlen >= sizeof(buffer))) {
 		mg_send_http_error(conn, 400, "%s", "No request body data");
 		return 400;
@@ -139,43 +142,104 @@ ExamplePUT(struct mg_connection *conn)
 }
 
 
+#if 0 /* Old version: User code had to split the url. */
 static int
-ExamplePOST(struct mg_connection *conn)
+mg_vsplit(const char *url, const char *pattern, va_list va)
 {
-	/* In this example, do the same for PUT and POST */
-	return ExamplePUT(conn);
+	int ret = 0;
+	while (*url && *pattern) {
+		if (*url == *pattern) {
+			url++;
+			pattern++;
+		} else if (*pattern == '*') {
+			char *p = va_arg(va, char *);
+			size_t l = va_arg(va, size_t);
+			if (p == NULL || l == 0) {
+				return 0;
+			}
+			while ((*url != '/') && (*url != 0)) {
+				if (l == 0) {
+					return 0;
+				}
+				l--;
+				*p = *url;
+				p++;
+				url++;
+			}
+			*p = 0;
+			pattern++;
+			ret++;
+		} else {
+			return 0;
+		}
+	}
+	return ret;
 }
 
 
 static int
-ExamplePATCH(struct mg_connection *conn)
+mg_split(const char *url, const char *pattern, ...)
 {
-	/* In this example, do the same for PUT and PATCH */
-	return ExamplePUT(conn);
+	int ret;
+	va_list va;
+	va_start(va, pattern);
+	ret = mg_vsplit(url, pattern, va);
+	va_end(va);
+	return ret;
 }
+#endif
 
 
 static int
 ExampleHandler(struct mg_connection *conn, void *cbdata)
 {
-
+	char path1[1024], path2[1024];
 	const struct mg_request_info *ri = mg_get_request_info(conn);
+	const char *url = ri->local_uri;
+	size_t url_len = strlen(url);
+
+	/* Pattern matching */
+#if 0 /* Old version: User code had to split the url. */
+	if (2
+	    != mg_split(
+	           url, EXAMPLE_URI, path1, sizeof(path1), path2, sizeof(path2))) {
+		mg_send_http_error(conn, 404, "Invalid path: %s\n", url);
+		return 404;
+	}
+#else /* New version: User mg_match. */
+	struct mg_match_context mcx;
+	mcx.case_sensitive = 0;
+	ptrdiff_t ret = mg_match(EXAMPLE_URI, url, &mcx);
+	if ((ret != url_len) || (mcx.num_matches != 2)) {
+		/* Note: Could have done this with a $ at the end of the match
+		 * pattern as well. Then we would have to check for a return value
+		 * of -1 only. Here we use this version as minimum modification
+		 * of the existing code. */
+		printf("Match ret: %i\n", (int)ret);
+		mg_send_http_error(conn, 404, "Invalid path: %s\n", url);
+		return 404;
+	}
+	memcpy(path1, mcx.match[0].str, mcx.match[0].len);
+	path1[mcx.match[0].len] = 0;
+	memcpy(path2, mcx.match[1].str, mcx.match[1].len);
+	path2[mcx.match[1].len] = 0;
+#endif
+
+
 	(void)cbdata; /* currently unused */
 
+	/* According to method */
 	if (0 == strcmp(ri->request_method, "GET")) {
-		return ExampleGET(conn);
-	}
-	if (0 == strcmp(ri->request_method, "PUT")) {
-		return ExamplePUT(conn);
+		return ExampleGET(conn, path1, path2);
 	}
-	if (0 == strcmp(ri->request_method, "POST")) {
-		return ExamplePOST(conn);
+	if ((0 == strcmp(ri->request_method, "PUT"))
+	    || (0 == strcmp(ri->request_method, "POST"))
+	    || (0 == strcmp(ri->request_method, "PATCH"))) {
+		/* In this example, do the same for PUT, POST and PATCH */
+		return ExamplePUT(conn, path1, path2);
 	}
 	if (0 == strcmp(ri->request_method, "DELETE")) {
-		return ExampleDELETE(conn);
-	}
-	if (0 == strcmp(ri->request_method, "PATCH")) {
-		return ExamplePATCH(conn);
+		return ExampleDELETE(conn, path1, path2);
 	}
 
 	/* this is not a GET request */
@@ -185,7 +249,7 @@ ExampleHandler(struct mg_connection *conn, void *cbdata)
 }
 
 
-int
+static int
 ExitHandler(struct mg_connection *conn, void *cbdata)
 {
 	mg_printf(conn,
@@ -198,7 +262,7 @@ ExitHandler(struct mg_connection *conn, void *cbdata)
 }
 
 
-int
+static int
 log_message(const struct mg_connection *conn, const char *message)
 {
 	puts(message);
@@ -215,44 +279,15 @@ main(int argc, char *argv[])
 	                         "10000",
 	                         "error_log_file",
 	                         "error.log",
-#ifndef NO_SSL
-	                         "ssl_certificate",
-	                         "../../resources/cert/server.pem",
-	                         "ssl_protocol_version",
-	                         "3",
-	                         "ssl_cipher_list",
-	                         "DES-CBC3-SHA:AES128-SHA:AES128-GCM-SHA256",
-#endif
-	                         "enable_auth_domain_check",
-	                         "no",
 	                         0};
 
 	struct mg_callbacks callbacks;
 	struct mg_context *ctx;
 	int err = 0;
 
-/* Check if libcivetweb has been built with all required features. */
-#ifndef NO_SSL
-	if (!mg_check_feature(2)) {
-		fprintf(stderr,
-		        "Error: Embedded example built with SSL support, "
-		        "but civetweb library build without.\n");
-		err = 1;
-	}
-
-
-	mg_init_library(MG_FEATURES_SSL);
-
-#else
+	/* Init libcivetweb. */
 	mg_init_library(0);
 
-#endif
-	if (err) {
-		fprintf(stderr, "Cannot start CivetWeb - inconsistent build.\n");
-		return EXIT_FAILURE;
-	}
-
-
 	/* Callback will print error messages to console */
 	memset(&callbacks, 0, sizeof(callbacks));
 	callbacks.log_message = log_message;
@@ -270,11 +305,10 @@ main(int argc, char *argv[])
 	mg_set_request_handler(ctx, EXAMPLE_URI, ExampleHandler, 0);
 	mg_set_request_handler(ctx, EXIT_URI, ExitHandler, 0);
 
-	/* Show sone info */
+	/* Show some info */
 	printf("Start example: %s%s\n", HOST_INFO, EXAMPLE_URI);
 	printf("Exit example:  %s%s\n", HOST_INFO, EXIT_URI);
 
-
 	/* Wait until the server should be closed */
 	while (!exitNow) {
 #ifdef _WIN32
diff --git a/examples/ws_server/build.sh b/examples/ws_server/build.sh
new file mode 100644
index 0000000..705ae35
--- /dev/null
+++ b/examples/ws_server/build.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+[ -e ws_server ] && rm ws_server
+
+gcc ../../src/civetweb.c ws_server.c -I../../include/ -lpthread -o ws_server -DNO_SSL -DNO_CGI -DNO_FILESYSTEM -DUSE_WEBSOCKET -Wall
diff --git a/examples/ws_server/ws_server.c b/examples/ws_server/ws_server.c
new file mode 100644
index 0000000..eef362b
--- /dev/null
+++ b/examples/ws_server/ws_server.c
@@ -0,0 +1,192 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h> /* for sleep() */
+
+#include "civetweb.h"
+
+
+/* Global options for this example. */
+static const char WS_URL[] = "/wsURL";
+static const char *SERVER_OPTIONS[] =
+    {"listening_ports", "8081", "num_threads", "10", NULL, NULL};
+
+/* Define websocket sub-protocols. */
+/* This must be static data, available between mg_start and mg_stop. */
+static const char subprotocol_bin[] = "Company.ProtoName.bin";
+static const char subprotocol_json[] = "Company.ProtoName.json";
+static const char *subprotocols[] = {subprotocol_bin, subprotocol_json, NULL};
+static struct mg_websocket_subprotocols wsprot = {2, subprotocols};
+
+
+/* Exit flag for the server */
+volatile int g_exit = 0;
+
+
+/* User defined data structure for websocket client context. */
+struct tClientContext {
+	uint32_t connectionNumber;
+	uint32_t demo_var;
+};
+
+
+/* Handler for new websocket connections. */
+static int
+ws_connect_handler(const struct mg_connection *conn, void *user_data)
+{
+	(void)user_data; /* unused */
+
+	/* Allocate data for websocket client context, and initialize context. */
+	struct tClientContext *wsCliCtx =
+	    (struct tClientContext *)calloc(1, sizeof(struct tClientContext));
+	if (!wsCliCtx) {
+		/* reject client */
+		return 1;
+	}
+	static uint32_t connectionCounter = 0; /* Example data: client number */
+	wsCliCtx->connectionNumber = __sync_add_and_fetch(&connectionCounter, 1);
+	mg_set_user_connection_data(
+	    conn, wsCliCtx); /* client context assigned to connection */
+
+	/* DEBUG: New client connected (but not ready to receive data yet). */
+	const struct mg_request_info *ri = mg_get_request_info(conn);
+	printf("Client %u connected with subprotocol: %s\n",
+	       wsCliCtx->connectionNumber,
+	       ri->acceptedWebSocketSubprotocol);
+
+	return 0;
+}
+
+
+/* Handler indicating the client is ready to receive data. */
+static void
+ws_ready_handler(struct mg_connection *conn, void *user_data)
+{
+	(void)user_data; /* unused */
+
+	/* Get websocket client context information. */
+	struct tClientContext *wsCliCtx =
+	    (struct tClientContext *)mg_get_user_connection_data(conn);
+	const struct mg_request_info *ri = mg_get_request_info(conn);
+	(void)ri; /* in this example, we do not need the request_info */
+
+	/* Send "hello" message. */
+	const char *hello = "{}";
+	size_t hello_len = strlen(hello);
+	mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_TEXT, hello, hello_len);
+
+	/* DEBUG: New client ready to receive data. */
+	printf("Client %u ready to receive data\n", wsCliCtx->connectionNumber);
+}
+
+
+/* Handler indicating the client sent data to the server. */
+static int
+ws_data_handler(struct mg_connection *conn,
+                int opcode,
+                char *data,
+                size_t datasize,
+                void *user_data)
+{
+	(void)user_data; /* unused */
+
+	/* Get websocket client context information. */
+	struct tClientContext *wsCliCtx =
+	    (struct tClientContext *)mg_get_user_connection_data(conn);
+	const struct mg_request_info *ri = mg_get_request_info(conn);
+	(void)ri; /* in this example, we do not need the request_info */
+
+	/* DEBUG: Print data received from client. */
+	const char *messageType = "";
+	switch (opcode & 0xf) {
+	case MG_WEBSOCKET_OPCODE_TEXT:
+		messageType = "text";
+		break;
+	case MG_WEBSOCKET_OPCODE_BINARY:
+		messageType = "binary";
+		break;
+	case MG_WEBSOCKET_OPCODE_PING:
+		messageType = "ping";
+		break;
+	case MG_WEBSOCKET_OPCODE_PONG:
+		messageType = "pong";
+		break;
+	}
+	printf("Websocket received %lu bytes of %s data from client %u\n",
+	       (unsigned long)datasize,
+	       messageType,
+	       wsCliCtx->connectionNumber);
+
+	return 1;
+}
+
+
+/* Handler indicating the connection to the client is closing. */
+static void
+ws_close_handler(const struct mg_connection *conn, void *user_data)
+{
+	(void)user_data; /* unused */
+
+	/* Get websocket client context information. */
+	struct tClientContext *wsCliCtx =
+	    (struct tClientContext *)mg_get_user_connection_data(conn);
+
+	/* DEBUG: Client has left. */
+	printf("Client %u closing connection\n", wsCliCtx->connectionNumber);
+
+	/* Free memory allocated for client context in ws_connect_handler() call. */
+	free(wsCliCtx);
+}
+
+
+int
+main(int argc, char *argv[])
+{
+	/* Initialize CivetWeb library without OpenSSL/TLS support. */
+	mg_init_library(0);
+
+	/* Start the server using the advanced API. */
+	struct mg_callbacks callbacks = {0};
+	void *user_data = NULL;
+
+	struct mg_init_data mg_start_init_data = {0};
+	mg_start_init_data.callbacks = &callbacks;
+	mg_start_init_data.user_data = user_data;
+	mg_start_init_data.configuration_options = SERVER_OPTIONS;
+
+	struct mg_error_data mg_start_error_data = {0};
+	char errtxtbuf[256] = {0};
+	mg_start_error_data.text = errtxtbuf;
+	mg_start_error_data.text_buffer_size = sizeof(errtxtbuf);
+
+	struct mg_context *ctx =
+	    mg_start2(&mg_start_init_data, &mg_start_error_data);
+	if (!ctx) {
+		fprintf(stderr, "Cannot start server: %s\n", errtxtbuf);
+		mg_exit_library();
+		return 1;
+	}
+
+	/* Register the websocket callback functions. */
+	mg_set_websocket_handler_with_subprotocols(ctx,
+	                                           WS_URL,
+	                                           &wsprot,
+	                                           ws_connect_handler,
+	                                           ws_ready_handler,
+	                                           ws_data_handler,
+	                                           ws_close_handler,
+	                                           user_data);
+
+	/* Let the server run. */
+	printf("Websocket server running\n");
+	while (!g_exit) {
+		sleep(1);
+	}
+	printf("Websocket server stopping\n");
+
+	/* Stop server, disconnect all clients. Then deinitialize CivetWeb library.
+	 */
+	mg_stop(ctx);
+	mg_exit_library();
+}
diff --git a/include/CivetServer.h b/include/CivetServer.h
index b03b631..1cb73f8 100644
--- a/include/CivetServer.h
+++ b/include/CivetServer.h
@@ -72,7 +72,7 @@ class CIVETWEB_CXX_API CivetHandler
 	 *
 	 * @param server - the calling server
 	 * @param conn - the connection information
-	 * @param http - pointer to return status code
+	 * @param status_code - pointer to return status code
 	 * @returns true if implemented, false otherwise
 	 */
 	virtual bool handleGet(CivetServer *server,
@@ -93,7 +93,7 @@ class CIVETWEB_CXX_API CivetHandler
 	 *
 	 * @param server - the calling server
 	 * @param conn - the connection information
-	 * @param http - pointer to return status code
+	 * @param status_code - pointer to return status code
 	 * @returns true if implemented, false otherwise
 	 */
 	virtual bool handlePost(CivetServer *server,
@@ -114,7 +114,7 @@ class CIVETWEB_CXX_API CivetHandler
 	 *
 	 * @param server - the calling server
 	 * @param conn - the connection information
-	 * @param http - pointer to return status code
+	 * @param status_code - pointer to return status code
 	 * @returns true if implemented, false otherwise
 	 */
 	virtual bool handleHead(CivetServer *server,
@@ -135,7 +135,7 @@ class CIVETWEB_CXX_API CivetHandler
 	 *
 	 * @param server - the calling server
 	 * @param conn - the connection information
-	 * @param http - pointer to return status code
+	 * @param status_code - pointer to return status code
 	 * @returns true if implemented, false otherwise
 	 */
 	virtual bool handlePut(CivetServer *server,
@@ -156,7 +156,7 @@ class CIVETWEB_CXX_API CivetHandler
 	 *
 	 * @param server - the calling server
 	 * @param conn - the connection information
-	 * @param http - pointer to return status code
+	 * @param status_code - pointer to return status code
 	 * @returns true if implemented, false otherwise
 	 */
 	virtual bool handleDelete(CivetServer *server,
@@ -177,7 +177,7 @@ class CIVETWEB_CXX_API CivetHandler
 	 *
 	 * @param server - the calling server
 	 * @param conn - the connection information
-	 * @param http - pointer to return status code
+	 * @param status_code - pointer to return status code
 	 * @returns true if implemented, false otherwise
 	 */
 	virtual bool handleOptions(CivetServer *server,
@@ -198,7 +198,7 @@ class CIVETWEB_CXX_API CivetHandler
 	 *
 	 * @param server - the calling server
 	 * @param conn - the connection information
-	 * @param http - pointer to return status code
+	 * @param status_code - pointer to return status code
 	 * @returns true if implemented, false otherwise
 	 */
 	virtual bool handlePatch(CivetServer *server,
diff --git a/include/civetweb.h b/include/civetweb.h
index ab40c7c..167c6a7 100644
--- a/include/civetweb.h
+++ b/include/civetweb.h
@@ -23,9 +23,9 @@
 #ifndef CIVETWEB_HEADER_INCLUDED
 #define CIVETWEB_HEADER_INCLUDED
 
-#define CIVETWEB_VERSION "1.15"
+#define CIVETWEB_VERSION "1.16"
 #define CIVETWEB_VERSION_MAJOR (1)
-#define CIVETWEB_VERSION_MINOR (15)
+#define CIVETWEB_VERSION_MINOR (16)
 #define CIVETWEB_VERSION_PATCH (0)
 
 #ifndef CIVETWEB_API
@@ -654,7 +654,7 @@ CIVETWEB_API void *mg_get_thread_pointer(const struct mg_connection *conn);
    or write to the connection. */
 /* Note: An alternative is to use the init_connection callback
    instead to initialize the user connection data pointer. It is
-   reccomended to supply a pointer to some user defined data structure
+   recommended to supply a pointer to some user defined data structure
    as conn_data initializer in init_connection. In case it is required
    to change some data after the init_connection call, store another
    data pointer in the user defined data structure and modify that
@@ -1128,7 +1128,7 @@ CIVETWEB_API int mg_get_var2(const char *data,
    required to increase this value at compile time.
 
    Parameters:
-     data: form encoded iput string. Will be modified by this function.
+     data: form encoded input string. Will be modified by this function.
      form_fields: output list of name/value-pairs. A buffer with a size
                   specified by num_form_fields must be provided by the
                   caller.
@@ -1341,6 +1341,22 @@ CIVETWEB_API int mg_url_decode(const char *src,
 CIVETWEB_API int mg_url_encode(const char *src, char *dst, size_t dst_len);
 
 
+/* BASE64-encode input buffer into destination buffer.
+   returns -1 on OK. */
+CIVETWEB_API int mg_base64_encode(const unsigned char *src,
+                                  size_t src_len,
+                                  char *dst,
+                                  size_t *dst_len);
+
+
+/* BASE64-decode input buffer into destination buffer.
+   returns -1 on OK. */
+CIVETWEB_API int mg_base64_decode(const char *src,
+                                  size_t src_len,
+                                  unsigned char *dst,
+                                  size_t *dst_len);
+
+
 /* MD5 hash given strings.
    Buffer 'buf' must be 33 bytes long. Varargs is a NULL terminated list of
    ASCIIz strings. When function returns, buf will contain human-readable
@@ -1350,6 +1366,40 @@ CIVETWEB_API int mg_url_encode(const char *src, char *dst, size_t dst_len);
 CIVETWEB_API char *mg_md5(char buf[33], ...);
 
 
+#if !defined(MG_MATCH_CONTEXT_MAX_MATCHES)
+#define MG_MATCH_CONTEXT_MAX_MATCHES (32)
+#endif
+
+struct mg_match_element {
+	const char *str; /* First character matching wildcard */
+	size_t len;      /* Number of character matching wildcard */
+};
+
+struct mg_match_context {
+	int case_sensitive; /* Input: 1 (case sensitive) or 0 (insensitive) */
+	size_t num_matches; /* Output: Number of wildcard matches returned. */
+	struct mg_match_element match[MG_MATCH_CONTEXT_MAX_MATCHES]; /* Output */
+};
+
+
+#if defined(MG_EXPERIMENTAL_INTERFACES)
+/* Pattern matching and extraction function.
+   Parameters:
+     pat: Pattern string (see UserManual.md)
+     str: String to search for match patterns.
+     mcx: Match context (optional, can be NULL).
+
+   Return:
+     Number of characters matched.
+     -1 if no valid match was found.
+     Note: 0 characters might be a valid match for some patterns.
+*/
+CIVETWEB_API ptrdiff_t mg_match(const char *pat,
+                                const char *str,
+                                struct mg_match_context *mcx);
+#endif
+
+
 /* Print error message to the opened error log stream.
    This utilizes the provided logging configuration.
      conn: connection (not used for sending data, but to get perameters)
@@ -1505,6 +1555,7 @@ CIVETWEB_API int mg_get_response(struct mg_connection *conn,
  *  -1:    parameter error
  *  -2:    invalid connection type
  *  -3:    invalid connection status
+ *  -4:    network error (only if built with NO_RESPONSE_BUFFERING)
  */
 CIVETWEB_API int mg_response_header_start(struct mg_connection *conn,
                                           int status);
@@ -1557,6 +1608,7 @@ CIVETWEB_API int mg_response_header_add_lines(struct mg_connection *conn,
  *  -1:    parameter error
  *  -2:    invalid connection type
  *  -3:    invalid connection status
+ *  -4:    sending failed (network error)
  */
 CIVETWEB_API int mg_response_header_send(struct mg_connection *conn);
 
@@ -1600,7 +1652,7 @@ CIVETWEB_API unsigned mg_check_feature(unsigned feature);
      buffer: Store system information as string here.
      buflen: Length of buffer (including a byte required for a terminating 0).
    Return:
-     Available size of system information, exluding a terminating 0.
+     Available size of system information, excluding a terminating 0.
      The information is complete, if the return value is smaller than buflen.
      The result is a JSON formatted string, the exact content may vary.
    Note:
@@ -1617,7 +1669,7 @@ CIVETWEB_API int mg_get_system_info(char *buffer, int buflen);
      buffer: Store context information here.
      buflen: Length of buffer (including a byte required for a terminating 0).
    Return:
-     Available size of system information, exluding a terminating 0.
+     Available size of system information, excluding a terminating 0.
      The information is complete, if the return value is smaller than buflen.
      The result is a JSON formatted string, the exact content may vary.
      Note:
@@ -1646,7 +1698,7 @@ CIVETWEB_API void mg_disable_connection_keep_alive(struct mg_connection *conn);
      buffer: Store context information here.
      buflen: Length of buffer (including a byte required for a terminating 0).
    Return:
-     Available size of system information, exluding a terminating 0.
+     Available size of system information, excluding a terminating 0.
      The information is complete, if the return value is smaller than buflen.
      The result is a JSON formatted string, the exact content may vary.
    Note:
@@ -1669,11 +1721,80 @@ CIVETWEB_API int mg_get_connection_info(const struct mg_context *ctx,
    Note: Experimental interfaces may change
 */
 struct mg_error_data {
-	unsigned *code;          /* error code (number) */
+	unsigned code;           /* error code (number) */
+	unsigned code_sub;       /* error sub code (number) */
 	char *text;              /* buffer for error text */
 	size_t text_buffer_size; /* size of buffer of "text" */
 };
 
+
+/* Values for error "code" in mg_error_data */
+enum {
+	/* No error */
+	MG_ERROR_DATA_CODE_OK = 0u,
+
+	/* Caller provided invalid parameter */
+	MG_ERROR_DATA_CODE_INVALID_PARAM = 1u,
+
+	/* "configuration_option" contains invalid element */
+	MG_ERROR_DATA_CODE_INVALID_OPTION = 2u,
+
+	/* Initializen TLS / SSL library failed */
+	MG_ERROR_DATA_CODE_INIT_TLS_FAILED = 3u,
+
+	/* Mandatory "configuration_option" missing */
+	MG_ERROR_DATA_CODE_MISSING_OPTION = 4u,
+
+	/* Duplicate "authentication_domain" option */
+	MG_ERROR_DATA_CODE_DUPLICATE_DOMAIN = 5u,
+
+	/* Not enough memory */
+	MG_ERROR_DATA_CODE_OUT_OF_MEMORY = 6u,
+
+	/* Server already stopped */
+	MG_ERROR_DATA_CODE_SERVER_STOPPED = 7u,
+
+	/* mg_init_library must be called first */
+	MG_ERROR_DATA_CODE_INIT_LIBRARY_FAILED = 8u,
+
+	/* Operating system function failed */
+	MG_ERROR_DATA_CODE_OS_ERROR = 9u,
+
+	/* Failed to bind to server ports */
+	MG_ERROR_DATA_CODE_INIT_PORTS_FAILED = 10u,
+
+	/* Failed to switch user (option "run_as_user") */
+	MG_ERROR_DATA_CODE_INIT_USER_FAILED = 11u,
+
+	/* Access Control List error */
+	MG_ERROR_DATA_CODE_INIT_ACL_FAILED = 12u,
+
+	/* Global password file error */
+	MG_ERROR_DATA_CODE_INVALID_PASS_FILE = 13u,
+
+	/* Lua background script init error */
+	MG_ERROR_DATA_CODE_SCRIPT_ERROR = 14u,
+
+	/* Client: Host not found, invalid IP to connect */
+	MG_ERROR_DATA_CODE_HOST_NOT_FOUND = 15u,
+
+	/* Client: TCP connect timeout */
+	MG_ERROR_DATA_CODE_CONNECT_TIMEOUT = 16u,
+
+	/* Client: TCP connect failed */
+	MG_ERROR_DATA_CODE_CONNECT_FAILED = 17u,
+
+	/* Error using TLS client certificate */
+	MG_ERROR_DATA_CODE_TLS_CLIENT_CERT_ERROR = 18u,
+
+	/* Error setting trusted TLS server certificate for client connection */
+	MG_ERROR_DATA_CODE_TLS_SERVER_CERT_ERROR = 19u,
+
+	/* Error establishing TLS connection to HTTPS server */
+	MG_ERROR_DATA_CODE_TLS_CONNECT_ERROR = 20u
+};
+
+
 struct mg_init_data {
 	const struct mg_callbacks *callbacks; /* callback function pointer */
 	void *user_data;                      /* data */
diff --git a/resources/Makefile.in-duktape b/resources/Makefile.in-duktape
index 9556d40..eae05e7 100644
--- a/resources/Makefile.in-duktape
+++ b/resources/Makefile.in-duktape
@@ -46,7 +46,7 @@ ifeq ($(WITH_DUKTAPE_VERSION), 202)
 endif
 
 ifneq ($(DUKTAPE_VERSION_KNOWN), 1)
-  $(error Duktape: Unknwon version - $(WITH_DUKTAPE_VERSION))
+  $(error Duktape: Unknown version - $(WITH_DUKTAPE_VERSION))
 endif
 
 
diff --git a/resources/Makefile.in-lua b/resources/Makefile.in-lua
index ceb1f3b..d3038f6 100644
--- a/resources/Makefile.in-lua
+++ b/resources/Makefile.in-lua
@@ -1,6 +1,6 @@
 #
 # Copyright (c) 2013 No Face Press, LLC
-# Copyright (c) 2014-2017 the Civetweb developers
+# Copyright (c) 2014-2022 the Civetweb developers
 #
 # License http://opensource.org/licenses/mit-license.php MIT License
 #
@@ -44,7 +44,7 @@ ifeq ($(WITH_LUA_VERSION), 504)
 endif
 
 ifneq ($(LUA_VERSION_KNOWN), 1)
-  $(error Lua: Unknwon version - $(WITH_LUA_VERSION))
+  $(error Lua: Unknown version - $(WITH_LUA_VERSION))
 endif
 
 
@@ -150,6 +150,17 @@ CFLAGS += -DUSE_LUA_FILE_SYSTEM
 #SOURCE_DIRS = $(LFS_DIR)
 
 
+LXX_DIR = src/third_party
+LXX_SOURCE_FILES = lua_struct.c
+LXX_SOURCES = $(addprefix $(LXX_DIR)/, $(LXX_SOURCE_FILES))
+LXX_OBJECTS = $(LXX_SOURCES:.c=.o)
+LXX_CFLAGS = -I$(LXX_DIR)
+OBJECTS += $(LXX_OBJECTS)
+CFLAGS += $(LXX_CFLAGS)
+CFLAGS += -DUSE_LUA_STRUCT
+#SOURCE_DIRS = $(LXX_DIR)
+
+
 ifneq ($(WITH_LUA_VERSION), 501)
   LXML_DIR = src/third_party
   LXML_SOURCE_FILES = LuaXML_lib.c
diff --git a/resources/cert/make_certs.sh b/resources/cert/make_certs.sh
index fbeea48..d4a2d9b 100644
--- a/resources/cert/make_certs.sh
+++ b/resources/cert/make_certs.sh
@@ -1,4 +1,8 @@
 #!/bin/sh
+
+server_name="localhost"
+cert_subject="/C=XX/ST=ExampleState/L=ExampleCity/O=ExampleCorp/OU=ExampleDepartment/CN=$server_name"
+
 echo "Creating new certificates"
 rm server.* client.* rootCA.* server_bkup.*
 echo "Using 'pass' for every password"
@@ -7,7 +11,7 @@ echo "Using 'pass' for every password"
 echo "Generating a root CA ..."
 
 openssl genrsa -passout pass:pass -out rootCA.key 2048
-openssl req -passout pass:pass -new -key rootCA.key -out rootCA.csr -subj "/C=AA/ST=localhost/L=localhost/O=localhost/OU=localhost/CN=localhost"
+openssl req -passout pass:pass -new -key rootCA.key -out rootCA.csr -subj $cert_subject
 # For a test certificate, use "AA" as "user assigned" language code: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#AA
 
 cp rootCA.key rootCA.key.orig
@@ -20,7 +24,7 @@ cat rootCA.key >> rootCA.pem
 echo "Generating client certificate ..."
 
 openssl genrsa -passout pass:pass -out client.key 2048
-openssl req -passout pass:pass -new -key client.key -out client.csr -subj "/C=AA/ST=localhost/L=localhost/O=localhost/OU=localhost/CN=localhost"
+openssl req -passout pass:pass -new -key client.key -out client.csr -subj $cert_subject
 
 cp client.key client.key.orig
 
@@ -37,7 +41,7 @@ openssl pkcs12 -passout pass:pass -export -inkey client.key -in client.pem -name
 echo "Generating first server certificate ..."
 
 openssl genrsa -passout pass:pass -out server.key 2048
-openssl req -passout pass:pass -new -key server.key -out server.csr -subj "/C=AA/ST=localhost/L=localhost/O=localhost/OU=localhost/CN=localhost"
+openssl req -passout pass:pass -new -key server.key -out server.csr -subj $cert_subject
 
 cp server.key server.key.orig
 
@@ -46,7 +50,7 @@ openssl rsa -in server.key.orig -out server.key
 echo "authorityKeyIdentifier=keyid,issuer" > server.ext
 echo "basicConstraints=critical,CA:FALSE" >> server.ext
 echo "keyUsage=digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment" >> server.ext
-echo "subjectAltName=DNS:localhost" >> server.ext
+echo "subjectAltName=DNS:$server_name" >> server.ext
 
 openssl x509 -req -days 3650 -sha256 -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -extfile server.ext -in server.csr -out server.crt
 #openssl x509 -req -days 3650 -sha256 -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -extfile server.ext -in server.csr -signkey server.key -out server.crt
@@ -67,7 +71,7 @@ cat server.pin
 echo "Generating backup server certificate ..."
 
 openssl genrsa -passout pass:pass -out server_bkup.key 2048
-openssl req -passout pass:pass -new -key server_bkup.key -out server_bkup.csr -subj "/C=AA/ST=localhost/L=localhost/O=localhost/OU=localhost/CN=localhost"
+openssl req -passout pass:pass -new -key server_bkup.key -out server_bkup.csr -subj $cert_subject
 
 cp server_bkup.key server_bkup.key.orig
 
diff --git a/resources/check_defines.lua b/resources/check_defines.lua
index 0624c14..de6cd42 100644
--- a/resources/check_defines.lua
+++ b/resources/check_defines.lua
@@ -65,7 +65,7 @@ noifdef(path .. "src/civetweb_private_lua.h")
 noifdef(path .. "src/main.c")
 noifdef(path .. "src/md5.inl")
 noifdef(path .. "src/mod_duktape.inl")
-noifdef(path .. "src/mod_http2.inl")
+noifdef(path .. "src/http2.inl")
 noifdef(path .. "src/mod_lua.inl")
 noifdef(path .. "src/mod_lua_shared.inl")
 noifdef(path .. "src/mod_zlib.inl")
diff --git a/resources/coverity_check.sh b/resources/coverity_check.sh
index 034c264..f4224b6 100755
--- a/resources/coverity_check.sh
+++ b/resources/coverity_check.sh
@@ -26,6 +26,7 @@ cp src/md5.inl cov_build/src/
 cp src/sha1.inl cov_build/src/
 cp src/response.inl cov_build/src/
 cp src/timer.inl cov_build/src/
+cp src/http2.inl cov_build/src/
 cp src/handle_form.inl cov_build/src/
 cp src/openssl_dl.inl cov_build/src/
 cp include/civetweb.h cov_build/include/
@@ -34,7 +35,7 @@ cp resources/Makefile.in-os cov_build/resources/
 cd cov_build
 
 # new scan build
-~/cov-analysis-linux64-2019.03/bin/cov-build  --dir cov-int make WITH_IPV6=1 WITH_WEBSOCKET=1 WITH_SERVER_STATS=1 WITH_EXPERIMENTAL=1
+~/cov-analysis-linux64-2021.12.1/bin/cov-build  --dir cov-int make WITH_IPV6=1 WITH_WEBSOCKET=1 WITH_SERVER_STATS=1 WITH_HTTP2=1 WITH_EXPERIMENTAL=1
 
 
 # pack build results for upload
diff --git a/src/CivetServer.cpp b/src/CivetServer.cpp
index dc61b31..fafd6e1 100644
--- a/src/CivetServer.cpp
+++ b/src/CivetServer.cpp
@@ -396,7 +396,6 @@ CivetServer::CivetServer(const char **options,
     : context(0)
 {
 	struct CivetCallbacks callbacks;
-	memset(&callbacks, 0, sizeof(callbacks));
 
 	UserContext = UserContextIn;
 
@@ -407,10 +406,24 @@ CivetServer::CivetServer(const char **options,
 		userCloseHandler = NULL;
 	}
 	callbacks.connection_close = closeHandler;
-	context = mg_start(&callbacks, this, options);
+	struct mg_init_data mg_start_init_data = {0};
+	mg_start_init_data.callbacks = &callbacks;
+	mg_start_init_data.user_data = this;
+	mg_start_init_data.configuration_options = options;
+
+	struct mg_error_data mg_start_error_data = {0};
+	char errtxtbuf[256] = {0};
+	mg_start_error_data.text = errtxtbuf;
+	mg_start_error_data.text_buffer_size = sizeof(errtxtbuf);
+
+	context = mg_start2(&mg_start_init_data, &mg_start_error_data);
+
 	if (context == NULL) {
-		throw CivetException("null context when constructing CivetServer. "
-		                     "Possible problem binding to port.");
+		std::string exceptionMsg =
+		    "null context when constructing CivetServer. "
+		    "Possible problem binding to port. Error: ";
+		exceptionMsg += errtxtbuf;
+		throw CivetException(exceptionMsg);
 	}
 }
 
@@ -420,7 +433,6 @@ CivetServer::CivetServer(const std::vector<std::string> &options,
     : context(0)
 {
 	struct CivetCallbacks callbacks;
-	memset(&callbacks, 0, sizeof(callbacks));
 
 	UserContext = UserContextIn;
 
@@ -438,10 +450,25 @@ CivetServer::CivetServer(const std::vector<std::string> &options,
 	}
 	pointers.back() = NULL;
 
-	context = mg_start(&callbacks, this, &pointers[0]);
-	if (context == NULL)
-		throw CivetException("null context when constructing CivetServer. "
-		                     "Possible problem binding to port.");
+	struct mg_init_data mg_start_init_data = {0};
+	mg_start_init_data.callbacks = &callbacks;
+	mg_start_init_data.user_data = this;
+	mg_start_init_data.configuration_options = &pointers[0];
+
+	struct mg_error_data mg_start_error_data = {0};
+	char errtxtbuf[256] = {0};
+	mg_start_error_data.text = errtxtbuf;
+	mg_start_error_data.text_buffer_size = sizeof(errtxtbuf);
+
+	context = mg_start2(&mg_start_init_data, &mg_start_error_data);
+
+	if (context == NULL) {
+		std::string exceptionMsg =
+		    "null context when constructing CivetServer. "
+		    "Possible problem binding to port. Error: ";
+		exceptionMsg += errtxtbuf;
+		throw CivetException(exceptionMsg);
+	}
 }
 
 CivetServer::~CivetServer()
@@ -534,7 +561,9 @@ CivetServer::getCookie(struct mg_connection *conn,
 	                          _cookieValue,
 	                          sizeof(_cookieValue));
 	cookieValue.clear();
-	cookieValue.append(_cookieValue);
+	if (lRead >= 0) {
+		cookieValue.append(_cookieValue);
+	}
 	return lRead;
 }
 
diff --git a/src/civetweb.c b/src/civetweb.c
index fea9e6f..9e321ed 100644
--- a/src/civetweb.c
+++ b/src/civetweb.c
@@ -21,8 +21,10 @@
  */
 
 #if defined(__GNUC__) || defined(__MINGW32__)
+#ifndef GCC_VERSION
 #define GCC_VERSION                                                            \
 	(__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
+#endif
 #if GCC_VERSION >= 40500
 /* gcc diagnostic pragmas available */
 #define GCC_DIAGNOSTIC
@@ -182,20 +184,18 @@ mg_static_assert(sizeof(void *) >= sizeof(int), "data type size check");
 #endif /* __SYMBIAN32__ */
 
 #if defined(__ZEPHYR__)
-#include <time.h>
-
 #include <ctype.h>
-#include <net/socket.h>
-#include <posix/pthread.h>
-#include <posix/time.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <poll.h>
+#include <pthread.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <zephyr.h>
-
-#include <fcntl.h>
+#include <sys/socket.h>
+#include <time.h>
 
-#include <libc_extensions.h>
+#include <zephyr/kernel.h>
 
 /* Max worker threads is the max of pthreads minus the main application thread
  * and minus the main civetweb thread, thus -2
@@ -352,7 +352,7 @@ __cyg_profile_func_exit(void *this_fn, void *call_site)
 #endif
 
 
-#if defined(__MACH__) /* Apple OSX section */
+#if defined(__MACH__) && defined(__APPLE__) /* Apple OSX section */
 
 #if defined(__clang__)
 #if (__clang_major__ == 3) && ((__clang_minor__ == 7) || (__clang_minor__ == 8))
@@ -438,10 +438,12 @@ _civet_safe_clock_gettime(int clk_id, struct timespec *t)
 #endif
 
 
-#if !defined(_WIN32)
+#if defined(_WIN32)
+#define ERROR_TRY_AGAIN(err) ((err) == WSAEWOULDBLOCK)
+#else
 /* Unix might return different error codes indicating to try again.
  * For Linux EAGAIN==EWOULDBLOCK, maybe EAGAIN!=EWOULDBLOCK is history from
- * decades ago, but better check both and let the compile optimize it. */
+ * decades ago, but better check both and let the compiler optimize it. */
 #define ERROR_TRY_AGAIN(err)                                                   \
 	(((err) == EAGAIN) || ((err) == EWOULDBLOCK) || ((err) == EINTR))
 #endif
@@ -499,7 +501,7 @@ _civet_safe_clock_gettime(int clk_id, struct timespec *t)
 
 /********************************************************************/
 
-/* Helper makros */
+/* Helper macros */
 #if !defined(ARRAY_SIZE)
 #define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
 #endif
@@ -581,6 +583,7 @@ typedef const char *SOCK_OPT_TYPE;
 #endif
 #endif /* _MSC_VER */
 
+
 #define ERRNO ((int)(GetLastError()))
 #define NO_SOCKLEN_T
 
@@ -872,6 +875,7 @@ typedef unsigned short int in_port_t;
 #include <netdb.h>
 #include <netinet/in.h>
 #include <netinet/tcp.h>
+#include <poll.h>
 #include <pthread.h>
 #include <pwd.h>
 #include <stdarg.h>
@@ -879,7 +883,6 @@ typedef unsigned short int in_port_t;
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/poll.h>
 #include <sys/socket.h>
 #include <sys/time.h>
 #include <sys/utsname.h>
@@ -897,7 +900,7 @@ typedef unsigned short int in_port_t;
 #include <dlfcn.h>
 #endif
 
-#if defined(__MACH__)
+#if defined(__MACH__) && defined(__APPLE__)
 #define SSL_LIB "libssl.dylib"
 #define CRYPTO_LIB "libcrypto.dylib"
 #else
@@ -1555,10 +1558,15 @@ 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_1_0 or OPENSSL_API_1_1"
+#error "Please define OPENSSL_API_#_# or USE_MBEDTLS"
+#endif
+#if defined(OPENSSL_API_1_0) && defined(OPENSSL_API_1_1)
+#error "Multiple OPENSSL_API versions defined"
 #endif
-#if defined(OPENSSL_API_1_0) && defined(OPENSSL_API_1_1)                       \
-    && defined(OPENSSL_API_3_0)
+#if defined(OPENSSL_API_1_1) && defined(OPENSSL_API_3_0)
+#error "Multiple OPENSSL_API versions defined"
+#endif
+#if defined(OPENSSL_API_1_0) && defined(OPENSSL_API_3_0)
 #error "Multiple OPENSSL_API versions defined"
 #endif
 #if (defined(OPENSSL_API_1_0) || defined(OPENSSL_API_1_1)                      \
@@ -1955,6 +1963,7 @@ enum {
 #if defined(USE_TIMERS)
 	CGI_TIMEOUT,
 #endif
+	CGI_BUFFERING,
 
 	CGI2_EXTENSIONS,
 	CGI2_ENVIRONMENT,
@@ -1963,6 +1972,7 @@ enum {
 #if defined(USE_TIMERS)
 	CGI2_TIMEOUT,
 #endif
+	CGI2_BUFFERING,
 
 #if defined(USE_4_CGI)
 	CGI3_EXTENSIONS,
@@ -1972,6 +1982,7 @@ enum {
 #if defined(USE_TIMERS)
 	CGI3_TIMEOUT,
 #endif
+	CGI3_BUFFERING,
 
 	CGI4_EXTENSIONS,
 	CGI4_ENVIRONMENT,
@@ -1980,6 +1991,7 @@ enum {
 #if defined(USE_TIMERS)
 	CGI4_TIMEOUT,
 #endif
+	CGI4_BUFFERING,
 #endif
 
 	PUT_DELETE_PASSWORDS_FILE, /* must follow CGI_* */
@@ -1988,6 +2000,7 @@ enum {
 	ENABLE_AUTH_DOMAIN_CHECK,
 	SSI_EXTENSIONS,
 	ENABLE_DIRECTORY_LISTING,
+	ENABLE_WEBDAV,
 	GLOBAL_PASSWORDS_FILE,
 	INDEX_FILES,
 	ACCESS_CONTROL_LIST,
@@ -2094,6 +2107,7 @@ static const struct mg_option config_options[] = {
 #if defined(USE_TIMERS)
     {"cgi_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL},
 #endif
+    {"cgi_buffering", MG_CONFIG_TYPE_BOOLEAN, "yes"},
 
     {"cgi2_pattern", MG_CONFIG_TYPE_EXT_PATTERN, NULL},
     {"cgi2_environment", MG_CONFIG_TYPE_STRING_LIST, NULL},
@@ -2102,6 +2116,7 @@ static const struct mg_option config_options[] = {
 #if defined(USE_TIMERS)
     {"cgi2_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL},
 #endif
+    {"cgi2_buffering", MG_CONFIG_TYPE_BOOLEAN, "yes"},
 
 #if defined(USE_4_CGI)
     {"cgi3_pattern", MG_CONFIG_TYPE_EXT_PATTERN, NULL},
@@ -2111,14 +2126,17 @@ static const struct mg_option config_options[] = {
 #if defined(USE_TIMERS)
     {"cgi3_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL},
 #endif
+    {"cgi3_buffering", MG_CONFIG_TYPE_BOOLEAN, "yes"},
 
-    {"cgi2_pattern", MG_CONFIG_TYPE_EXT_PATTERN, NULL},
+    {"cgi4_pattern", MG_CONFIG_TYPE_EXT_PATTERN, NULL},
     {"cgi4_environment", MG_CONFIG_TYPE_STRING_LIST, NULL},
     {"cgi4_interpreter", MG_CONFIG_TYPE_FILE, NULL},
     {"cgi4_interpreter_args", MG_CONFIG_TYPE_STRING, NULL},
 #if defined(USE_TIMERS)
     {"cgi4_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL},
 #endif
+    {"cgi4_buffering", MG_CONFIG_TYPE_BOOLEAN, "yes"},
+
 #endif
 
     {"put_delete_auth_file", MG_CONFIG_TYPE_FILE, NULL},
@@ -2127,6 +2145,7 @@ static const struct mg_option config_options[] = {
     {"enable_auth_domain_check", MG_CONFIG_TYPE_BOOLEAN, "yes"},
     {"ssi_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.shtml$|**.shtm$"},
     {"enable_directory_listing", MG_CONFIG_TYPE_BOOLEAN, "yes"},
+    {"enable_webdav", MG_CONFIG_TYPE_BOOLEAN, "no"},
     {"global_auth_file", MG_CONFIG_TYPE_FILE, NULL},
     {"index_files",
      MG_CONFIG_TYPE_STRING_LIST,
@@ -2214,7 +2233,7 @@ struct mg_handler_info {
 	/* handler type */
 	int handler_type;
 
-	/* Handler for http/https or authorization requests. */
+	/* Handler for http/https or requests. */
 	mg_request_handler handler;
 	unsigned int refcount;
 	int removing;
@@ -2275,9 +2294,9 @@ struct mg_domain_context {
 typedef ptrdiff_t volatile stop_flag_t;
 
 static int
-STOP_FLAG_IS_ZERO(stop_flag_t *f)
+STOP_FLAG_IS_ZERO(const stop_flag_t *f)
 {
-	stop_flag_t sf = mg_atomic_add(f, 0);
+	stop_flag_t sf = mg_atomic_add((stop_flag_t *)f, 0);
 	return (sf == 0);
 }
 
@@ -2307,6 +2326,22 @@ typedef int volatile stop_flag_t;
 #endif /* STOP_FLAG_NEEDS_LOCK */
 
 
+#if !defined(NUM_WEBDAV_LOCKS)
+#define NUM_WEBDAV_LOCKS 10
+#endif
+#if !defined(LOCK_DURATION_S)
+#define LOCK_DURATION_S 60
+#endif
+
+
+struct twebdav_lock {
+	uint64_t locktime;
+	char token[33];
+	char path[UTF8_PATH_MAX * 2];
+	char user[UTF8_PATH_MAX * 2];
+};
+
+
 struct mg_context {
 
 	/* Part 1 - Physical context:
@@ -2369,6 +2404,9 @@ struct mg_context {
 	struct mg_memory_stat ctx_memory;
 #endif
 
+	/* WebDAV lock structures */
+	struct twebdav_lock webdav_lock[NUM_WEBDAV_LOCKS];
+
 	/* Operating system related */
 	char *systemName;  /* What operating system is running */
 	time_t start_time; /* Server start time, used for authentication
@@ -2472,7 +2510,7 @@ struct mg_connection {
 #if defined(USE_SERVER_STATS)
 	time_t conn_close_time; /* Time (wall clock) when connection was
 	                         * closed (or 0 if still open) */
-	double processing_time; /* Procesing time for one request. */
+	double processing_time; /* Processing time for one request. */
 #endif
 	struct timespec req_time; /* Time (since system start) when the request
 	                           * was received */
@@ -2537,7 +2575,6 @@ struct mg_connection {
 
 /* Directory entry */
 struct de {
-	struct mg_connection *conn;
 	char *file_name;
 	struct mg_file_stat file;
 };
@@ -2769,7 +2806,7 @@ mg_set_thread_name(const char *name)
 #elif defined(_GNU_SOURCE) && defined(__GLIBC__)                               \
     && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 12)))
 	/* pthread_setname_np first appeared in glibc in version 2.12 */
-#if defined(__MACH__)
+#if defined(__MACH__) && defined(__APPLE__)
 	/* OS X only current thread name can be changed */
 	(void)pthread_setname_np(threadName);
 #else
@@ -2785,14 +2822,14 @@ mg_set_thread_name(const char *name)
 #endif
 }
 #else /* !defined(NO_THREAD_NAME) */
-void
+static void
 mg_set_thread_name(const char *threadName)
 {
 }
 #endif
 
 
-const struct mg_option *
+CIVETWEB_API const struct mg_option *
 mg_get_valid_options(void)
 {
 	return config_options;
@@ -2978,7 +3015,7 @@ lowercase(const char *s)
 }
 
 
-int
+CIVETWEB_API int
 mg_strncasecmp(const char *s1, const char *s2, size_t len)
 {
 	int diff = 0;
@@ -2993,7 +3030,7 @@ mg_strncasecmp(const char *s1, const char *s2, size_t len)
 }
 
 
-int
+CIVETWEB_API int
 mg_strcasecmp(const char *s1, const char *s2)
 {
 	int diff;
@@ -3132,7 +3169,7 @@ get_option_index(const char *name)
 }
 
 
-const char *
+CIVETWEB_API const char *
 mg_get_option(const struct mg_context *ctx, const char *name)
 {
 	int i;
@@ -3147,28 +3184,28 @@ mg_get_option(const struct mg_context *ctx, const char *name)
 
 #define mg_get_option DO_NOT_USE_THIS_FUNCTION_INTERNALLY__access_directly
 
-struct mg_context *
+CIVETWEB_API struct mg_context *
 mg_get_context(const struct mg_connection *conn)
 {
 	return (conn == NULL) ? (struct mg_context *)NULL : (conn->phys_ctx);
 }
 
 
-void *
+CIVETWEB_API void *
 mg_get_user_data(const struct mg_context *ctx)
 {
 	return (ctx == NULL) ? NULL : ctx->user_data;
 }
 
 
-void *
+CIVETWEB_API void *
 mg_get_user_context_data(const struct mg_connection *conn)
 {
 	return mg_get_user_data(mg_get_context(conn));
 }
 
 
-void *
+CIVETWEB_API void *
 mg_get_thread_pointer(const struct mg_connection *conn)
 {
 	/* both methods should return the same pointer */
@@ -3184,7 +3221,7 @@ mg_get_thread_pointer(const struct mg_connection *conn)
 }
 
 
-void
+CIVETWEB_API void
 mg_set_user_connection_data(const struct mg_connection *const_conn, void *data)
 {
 	if (const_conn != NULL) {
@@ -3197,7 +3234,7 @@ mg_set_user_connection_data(const struct mg_connection *const_conn, void *data)
 }
 
 
-void *
+CIVETWEB_API void *
 mg_get_user_connection_data(const struct mg_connection *conn)
 {
 	if (conn != NULL) {
@@ -3207,7 +3244,7 @@ mg_get_user_connection_data(const struct mg_connection *conn)
 }
 
 
-int
+CIVETWEB_API int
 mg_get_server_ports(const struct mg_context *ctx,
                     int size,
                     struct mg_server_port *ports)
@@ -3292,8 +3329,7 @@ sockaddr_to_string(char *buf, size_t len, const union usa *usa)
 		0,
 		NI_NUMERICHOST);
 		*/
-		strncpy(buf, UNIX_DOMAIN_SOCKET_SERVER_NAME, len);
-		buf[len] = 0;
+		mg_strlcpy(buf, UNIX_DOMAIN_SOCKET_SERVER_NAME, len);
 	}
 #endif
 }
@@ -3319,7 +3355,6 @@ gmt_time_string(char *buf, size_t buf_len, time_t *t)
 		strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", tm);
 	} else {
 		mg_strlcpy(buf, "Thu, 01 Jan 1970 00:00:00 GMT", buf_len);
-		buf[buf_len - 1] = '\0';
 	}
 }
 
@@ -3462,7 +3497,7 @@ mg_cry_internal_wrap(const struct mg_connection *conn,
 }
 
 
-void
+CIVETWEB_API void
 mg_cry(const struct mg_connection *conn, const char *fmt, ...)
 {
 	va_list ap;
@@ -3475,14 +3510,14 @@ mg_cry(const struct mg_connection *conn, const char *fmt, ...)
 #define mg_cry DO_NOT_USE_THIS_FUNCTION__USE_mg_cry_internal
 
 
-const char *
+CIVETWEB_API const char *
 mg_version(void)
 {
 	return CIVETWEB_VERSION;
 }
 
 
-const struct mg_request_info *
+CIVETWEB_API const struct mg_request_info *
 mg_get_request_info(const struct mg_connection *conn)
 {
 	if (!conn) {
@@ -3522,7 +3557,7 @@ mg_get_request_info(const struct mg_connection *conn)
 }
 
 
-const struct mg_response_info *
+CIVETWEB_API const struct mg_response_info *
 mg_get_response_info(const struct mg_connection *conn)
 {
 	if (!conn) {
@@ -3573,6 +3608,7 @@ mg_construct_local_link(const struct mg_connection *conn,
 	if ((buflen < 1) || (buf == 0) || (conn == 0)) {
 		return -1;
 	} else {
+		int i, j;
 		int truncated = 0;
 		const struct mg_request_info *ri = &conn->request_info;
 
@@ -3584,11 +3620,31 @@ mg_construct_local_link(const struct mg_connection *conn,
 		        : ((ri->request_uri != NULL) ? ri->request_uri : ri->local_uri);
 		int port = (define_port > 0) ? define_port : ri->server_port;
 		int default_port = 80;
+		char *uri_encoded;
+		size_t uri_encoded_len;
 
 		if (uri == NULL) {
 			return -1;
 		}
 
+		uri_encoded_len = strlen(uri) * 3 + 1;
+		uri_encoded = (char *)mg_malloc_ctx(uri_encoded_len, conn->phys_ctx);
+		if (uri_encoded == NULL) {
+			return -1;
+		}
+		mg_url_encode(uri, uri_encoded, uri_encoded_len);
+
+		/* Directory separator should be preserved. */
+		for (i = j = 0; uri_encoded[i]; j++) {
+			if (!strncmp(uri_encoded + i, "%2f", 3)) {
+				uri_encoded[j] = '/';
+				i += 3;
+			} else {
+				uri_encoded[j] = uri_encoded[i++];
+			}
+		}
+		uri_encoded[j] = '\0';
+
 #if defined(USE_X_DOM_SOCKET)
 		if (conn->client.lsa.sa.sa_family == AF_UNIX) {
 			/* TODO: Define and document a link for UNIX domain sockets. */
@@ -3610,6 +3666,7 @@ mg_construct_local_link(const struct mg_connection *conn,
 			            server_name,
 			            ri->local_uri);
 			default_port = 0;
+			mg_free(uri_encoded);
 			return 0;
 		}
 #endif
@@ -3672,8 +3729,9 @@ mg_construct_local_link(const struct mg_connection *conn,
 			            server_domain,
 #endif
 			            portstr,
-			            ri->local_uri);
+			            uri_encoded);
 
+			mg_free(uri_encoded);
 			if (truncated) {
 				return -1;
 			}
@@ -3683,7 +3741,7 @@ mg_construct_local_link(const struct mg_connection *conn,
 }
 
 
-int
+CIVETWEB_API int
 mg_get_request_link(const struct mg_connection *conn, char *buf, size_t buflen)
 {
 	return mg_construct_local_link(conn, buf, buflen, NULL, -1, NULL);
@@ -3710,8 +3768,7 @@ skip_quoted(char **buf,
 	if (end_word > begin_word) {
 		p = end_word - 1;
 		while (*p == quotechar) {
-			/* While the delimiter is quoted, look for the next delimiter.
-			 */
+			/* While the delimiter is quoted, look for the next delimiter. */
 			/* This happens, e.g., in calls from parse_auth_header,
 			 * if the user name contains a " character. */
 
@@ -3773,7 +3830,6 @@ get_header(const struct mg_header *hdr, int num_hdr, const char *name)
 }
 
 
-#if defined(USE_WEBSOCKET)
 /* Retrieve requested HTTP header multiple values, and return the number of
  * found occurrences */
 static int
@@ -3793,10 +3849,9 @@ get_req_headers(const struct mg_request_info *ri,
 	}
 	return cnt;
 }
-#endif
 
 
-const char *
+CIVETWEB_API const char *
 mg_get_header(const struct mg_connection *conn, const char *name)
 {
 	if (!conn) {
@@ -3915,60 +3970,11 @@ header_has_option(const char *header, const char *option)
 }
 
 
-/* Perform case-insensitive match of string against pattern */
-static ptrdiff_t
-match_prefix(const char *pattern, size_t pattern_len, const char *str)
-{
-	const char *or_str;
-	ptrdiff_t i, j, len, res;
-
-	if ((or_str = (const char *)memchr(pattern, '|', pattern_len)) != NULL) {
-		res = match_prefix(pattern, (size_t)(or_str - pattern), str);
-		return (res > 0) ? res
-		                 : match_prefix(or_str + 1,
-		                                (size_t)((pattern + pattern_len)
-		                                         - (or_str + 1)),
-		                                str);
-	}
-
-	for (i = 0, j = 0; (i < (ptrdiff_t)pattern_len); i++, j++) {
-		if ((pattern[i] == '?') && (str[j] != '\0')) {
-			continue;
-		} else if (pattern[i] == '$') {
-			return (str[j] == '\0') ? j : -1;
-		} else if (pattern[i] == '*') {
-			i++;
-			if (pattern[i] == '*') {
-				i++;
-				len = (ptrdiff_t)strlen(str + j);
-			} else {
-				len = (ptrdiff_t)strcspn(str + j, "/");
-			}
-			if (i == (ptrdiff_t)pattern_len) {
-				return j + len;
-			}
-			do {
-				res = match_prefix(pattern + i,
-				                   (pattern_len - (size_t)i),
-				                   str + j + len);
-			} while (res == -1 && len-- > 0);
-			return (res == -1) ? -1 : j + res + len;
-		} else if (lowercase(&pattern[i]) != lowercase(&str[j])) {
-			return -1;
-		}
-	}
-	return (ptrdiff_t)j;
-}
-
+/* Sorting function implemented in a separate file */
+#include "sort.inl"
 
-static ptrdiff_t
-match_prefix_strlen(const char *pattern, const char *str)
-{
-	if (pattern == NULL) {
-		return -1;
-	}
-	return match_prefix(pattern, strlen(pattern), str);
-}
+/* Pattern matching has been reimplemented in a new file */
+#include "match.inl"
 
 
 /* HTTP 1.1 assumes keep alive if "Connection:" header is not set
@@ -4139,6 +4145,26 @@ send_additional_header(struct mg_connection *conn)
 }
 
 
+static void
+send_cors_header(struct mg_connection *conn)
+{
+	const char *origin_hdr = mg_get_header(conn, "Origin");
+	const char *cors_orig_cfg =
+	    conn->dom_ctx->config[ACCESS_CONTROL_ALLOW_ORIGIN];
+
+	if (cors_orig_cfg && *cors_orig_cfg && origin_hdr && *origin_hdr) {
+		/* Cross-origin resource sharing (CORS), see
+		 * http://www.html5rocks.com/en/tutorials/cors/,
+		 * http://www.html5rocks.com/static/images/cors_server_flowchart.png
+		 * CORS preflight is not supported for files. */
+		mg_response_header_add(conn,
+		                       "Access-Control-Allow-Origin",
+		                       cors_orig_cfg,
+		                       -1);
+	}
+}
+
+
 #if !defined(NO_FILESYSTEMS)
 static void handle_file_based_request(struct mg_connection *conn,
                                       const char *path,
@@ -4146,7 +4172,7 @@ static void handle_file_based_request(struct mg_connection *conn,
 #endif /* NO_FILESYSTEMS */
 
 
-const char *
+CIVETWEB_API const char *
 mg_get_response_code_text(const struct mg_connection *conn, int response_code)
 {
 	/* See IANA HTTP status code assignment:
@@ -4460,7 +4486,7 @@ mg_send_http_error_impl(struct mg_connection *conn,
 						len = (int)sizeof(path_buf) - 32;
 					}
 
-					/* Start with the file extenstion from the configuration. */
+					/* Start with the file extension from the configuration. */
 					tstr = strchr(error_page_file_ext, '.');
 
 					while (tstr) {
@@ -4486,7 +4512,7 @@ mg_send_http_error_impl(struct mg_connection *conn,
 						DEBUG_TRACE("Check error page %s - not found",
 						            path_buf);
 
-						/* Continue with the next file extenstion from the
+						/* Continue with the next file extension from the
 						 * configuration (if there is a next one). */
 						tstr = strchr(tstr + i, '.');
 					}
@@ -4507,6 +4533,7 @@ mg_send_http_error_impl(struct mg_connection *conn,
 		mg_response_header_start(conn, status);
 		send_no_cache_header(conn);
 		send_additional_header(conn);
+		send_cors_header(conn);
 		if (has_body) {
 			mg_response_header_add(conn,
 			                       "Content-Type",
@@ -4531,7 +4558,7 @@ mg_send_http_error_impl(struct mg_connection *conn,
 }
 
 
-int
+CIVETWEB_API int
 mg_send_http_error(struct mg_connection *conn, int status, const char *fmt, ...)
 {
 	va_list ap;
@@ -4545,7 +4572,7 @@ mg_send_http_error(struct mg_connection *conn, int status, const char *fmt, ...)
 }
 
 
-int
+CIVETWEB_API int
 mg_send_http_ok(struct mg_connection *conn,
                 const char *mime_type,
                 long long content_length)
@@ -4558,6 +4585,7 @@ mg_send_http_ok(struct mg_connection *conn,
 	mg_response_header_start(conn, 200);
 	send_no_cache_header(conn);
 	send_additional_header(conn);
+	send_cors_header(conn);
 	mg_response_header_add(conn, "Content-Type", mime_type, -1);
 	if (content_length < 0) {
 		/* Size not known. Use chunked encoding (HTTP/1.x) */
@@ -4586,7 +4614,7 @@ mg_send_http_ok(struct mg_connection *conn,
 }
 
 
-int
+CIVETWEB_API int
 mg_send_http_redirect(struct mg_connection *conn,
                       const char *target_url,
                       int redirect_code)
@@ -4602,11 +4630,11 @@ mg_send_http_redirect(struct mg_connection *conn,
 	 *   307  | temporary | always keep method  | HTTP/1.1
 	 *   308  | permanent | always keep method  | HTTP/1.1
 	 */
-	const char *redirect_text;
-	int ret;
-	size_t content_len = 0;
+
 #if defined(MG_SEND_REDIRECT_BODY)
-	char reply[MG_BUF_LEN];
+	char redirect_body[MG_BUF_LEN];
+	size_t content_len = 0;
+	char content_len_text[32];
 #endif
 
 	/* In case redirect_code=0, use 307. */
@@ -4622,9 +4650,6 @@ mg_send_http_redirect(struct mg_connection *conn,
 		return -2;
 	}
 
-	/* Get proper text for response code */
-	redirect_text = mg_get_response_code_text(conn, redirect_code);
-
 	/* If target_url is not defined, redirect to "/". */
 	if ((target_url == NULL) || (*target_url == 0)) {
 		target_url = "/";
@@ -4656,39 +4681,45 @@ mg_send_http_redirect(struct mg_connection *conn,
 	mg_snprintf(
 	    conn,
 	    NULL /* ignore truncation */,
-	    reply,
-	    sizeof(reply),
+	    redirect_body,
+	    sizeof(redirect_body),
 	    "<html><head>%s</head><body><a href=\"%s\">%s</a></body></html>",
 	    redirect_text,
 	    target_url,
 	    target_url);
 	content_len = strlen(reply);
+	sprintf(content_len_text, "%lu", (unsigned long)content_len);
 #endif
 
-	/* Do not send any additional header. For all other options,
-	 * including caching, there are suitable defaults. */
-	ret = mg_printf(conn,
-	                "HTTP/1.1 %i %s\r\n"
-	                "Location: %s\r\n"
-	                "Content-Length: %u\r\n"
-	                "Connection: %s\r\n\r\n",
-	                redirect_code,
-	                redirect_text,
-	                target_url,
-	                (unsigned int)content_len,
-	                suggest_connection_header(conn));
+	/* Send all required headers */
+	mg_response_header_start(conn, redirect_code);
+	mg_response_header_add(conn, "Location", target_url, -1);
+	if ((redirect_code == 301) || (redirect_code == 308)) {
+		/* Permanent redirect */
+		send_static_cache_header(conn);
+	} else {
+		/* Temporary redirect */
+		send_no_cache_header(conn);
+	}
+	send_additional_header(conn);
+	send_cors_header(conn);
+#if defined(MG_SEND_REDIRECT_BODY)
+	mg_response_header_add(conn, "Content-Type", "text/html", -1);
+	mg_response_header_add(conn, "Content-Length", content_len_text, -1);
+#else
+	mg_response_header_add(conn, "Content-Length", "0", 1);
+#endif
+	mg_response_header_send(conn);
 
 #if defined(MG_SEND_REDIRECT_BODY)
 	/* Send response body */
-	if (ret > 0) {
-		/* ... unless it is a HEAD request */
-		if (0 != strcmp(conn->request_info.request_method, "HEAD")) {
-			ret = mg_write(conn, reply, content_len);
-		}
+	/* ... unless it is a HEAD request */
+	if (0 != strcmp(conn->request_info.request_method, "HEAD")) {
+		ret = mg_write(conn, redirect_body, content_len);
 	}
 #endif
 
-	return (ret > 0) ? ret : -1;
+	return 1;
 }
 
 
@@ -5262,7 +5293,7 @@ set_close_on_exec(SOCKET sock,
 }
 
 
-int
+CIVETWEB_API int
 mg_start_thread(mg_thread_func_t f, void *p)
 {
 #if defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1)
@@ -5430,7 +5461,7 @@ spawn_process(struct mg_connection *conn,
               int fdout[2],
               int fderr[2],
               const char *dir,
-              unsigned char cgi_config_idx)
+              int cgi_config_idx)
 {
 	HANDLE me;
 	char *interp;
@@ -5665,7 +5696,7 @@ set_close_on_exec(int fd,
 }
 
 
-int
+CIVETWEB_API int
 mg_start_thread(mg_thread_func_t func, void *param)
 {
 	pthread_t thread_id;
@@ -5742,7 +5773,7 @@ spawn_process(struct mg_connection *conn,
               int fdout[2],
               int fderr[2],
               const char *dir,
-              unsigned char cgi_config_idx)
+              int cgi_config_idx)
 {
 	pid_t pid;
 	const char *interp;
@@ -5807,7 +5838,7 @@ spawn_process(struct mg_connection *conn,
 
 			interp = conn->dom_ctx->config[CGI_INTERPRETER + cgi_config_idx];
 			if (interp == NULL) {
-				/* no interpreter configured, call the programm directly */
+				/* no interpreter configured, call the program directly */
 				(void)execle(prog, prog, NULL, envp);
 				mg_cry_internal(conn,
 				                "%s: execle(%s): %s",
@@ -5935,15 +5966,18 @@ mg_poll(struct mg_pollfd *pfd,
 
 		result = poll(pfd, n, ms_now);
 		if (result != 0) {
-			/* Poll returned either success (1) or error (-1).
-			 * Forward both to the caller. */
-			if ((check_pollerr)
-			    && ((pfd[0].revents & (POLLIN | POLLOUT | POLLERR))
-			        == POLLERR)) {
-				/* One and only file descriptor returned error */
-				return -1;
+			int err = ERRNO;
+			if ((result == 1) || (!ERROR_TRY_AGAIN(err))) {
+				/* Poll returned either success (1) or error (-1).
+				 * Forward both to the caller. */
+				if ((check_pollerr)
+				    && ((pfd[0].revents & (POLLIN | POLLOUT | POLLERR))
+				        == POLLERR)) {
+					/* One and only file descriptor returned error */
+					return -1;
+				}
+				return result;
 			}
-			return result;
 		}
 
 		/* Poll returned timeout (0). */
@@ -6054,17 +6088,10 @@ push_inner(struct mg_context *ctx,
 		} else {
 			n = (int)send(sock, buf, (len_t)len, MSG_NOSIGNAL);
 			err = (n < 0) ? ERRNO : 0;
-#if defined(_WIN32)
-			if (err == WSAEWOULDBLOCK) {
-				err = 0;
-				n = 0;
-			}
-#else
 			if (ERROR_TRY_AGAIN(err)) {
 				err = 0;
 				n = 0;
 			}
-#endif
 			if (n < 0) {
 				/* shutdown of the socket at client side */
 				return -2;
@@ -6343,7 +6370,7 @@ pull_inner(FILE *fp,
 				return -2;
 			}
 		} else if (pollres < 0) {
-			/* error callint poll */
+			/* error calling poll */
 			return -2;
 		} else {
 			/* pollres = 0 means timeout */
@@ -6565,15 +6592,16 @@ handle_request_stat_log(struct mg_connection *conn)
 
 #if defined(USE_HTTP2)
 #if defined(NO_SSL)
-#error "HTTP2 requires ALPN, APLN requires SSL/TLS"
+#error "HTTP2 requires ALPN, ALPN requires SSL/TLS"
 #endif
 #define USE_ALPN
-#include "mod_http2.inl"
+#include "http2.inl"
 /* Not supported with HTTP/2 */
 #define HTTP1_only                                                             \
 	{                                                                          \
 		if (conn->protocol_type == PROTOCOL_TYPE_HTTP2) {                      \
 			http2_must_use_http1(conn);                                        \
+			DEBUG_TRACE("%s", "must use HTTP/1.x");                            \
 			return;                                                            \
 		}                                                                      \
 	}
@@ -6582,7 +6610,7 @@ handle_request_stat_log(struct mg_connection *conn)
 #endif
 
 
-int
+CIVETWEB_API int
 mg_read(struct mg_connection *conn, void *buf, size_t len)
 {
 	if (len > INT_MAX) {
@@ -6644,6 +6672,22 @@ mg_read(struct mg_connection *conn, void *buf, size_t len)
 					if (mg_read_inner(conn, lenbuf + i, 1) != 1) {
 						lenbuf[i] = 0;
 					}
+					if ((i > 0) && (lenbuf[i] == ';')) {
+						// chunk extension --> skip chars until next CR
+						//
+						// RFC 2616, 3.6.1 Chunked Transfer Coding
+						// (https://www.rfc-editor.org/rfc/rfc2616#page-25)
+						//
+						// chunk          = chunk-size [ chunk-extension ] CRLF
+						//                  chunk-data CRLF
+						// ...
+						// chunk-extension= *( ";" chunk-ext-name [ "="
+						// chunk-ext-val ] )
+						do
+							++conn->content_len;
+						while (mg_read_inner(conn, lenbuf + i, 1) == 1
+						       && lenbuf[i] != '\r');
+					}
 					if ((i > 0) && (lenbuf[i] == '\r')
 					    && (lenbuf[i - 1] != '\r')) {
 						continue;
@@ -6669,13 +6713,54 @@ mg_read(struct mg_connection *conn, void *buf, size_t len)
 					conn->is_chunked = 2;
 					return -1;
 				}
-				if (chunkSize == 0) {
+				if (conn->is_chunked == 3) {
 					/* try discarding trailer for keep-alive */
-					conn->content_len += 2;
-					if ((mg_read_inner(conn, lenbuf, 2) == 2)
-					    && (lenbuf[0] == '\r') && (lenbuf[1] == '\n')) {
-						conn->is_chunked = 4;
+
+					// We found the last chunk (length 0) including the
+					// CRLF that terminates that chunk. Now follows a possibly
+					// empty trailer and a final CRLF.
+					//
+					// see RFC 2616, 3.6.1 Chunked Transfer Coding
+					// (https://www.rfc-editor.org/rfc/rfc2616#page-25)
+					//
+					// Chunked-Body   = *chunk
+					// 	                last-chunk
+					// 	                trailer
+					// 	                CRLF
+					// ...
+					// last-chunk     = 1*("0") [ chunk-extension ] CRLF
+					// ...
+					// trailer        = *(entity-header CRLF)
+
+					int crlf_count = 2; // one CRLF already determined
+
+					while (crlf_count < 4 && conn->is_chunked == 3) {
+						++conn->content_len;
+						if (mg_read_inner(conn, lenbuf, 1) == 1) {
+							if ((crlf_count == 0 || crlf_count == 2)) {
+								if (lenbuf[0] == '\r')
+									++crlf_count;
+								else
+									crlf_count = 0;
+							} else {
+								// previous character was a CR
+								// --> next character must be LF
+
+								if (lenbuf[0] == '\n')
+									++crlf_count;
+								else
+									conn->is_chunked = 2;
+							}
+						} else
+							// premature end of trailer
+							conn->is_chunked = 2;
 					}
+
+					if (conn->is_chunked == 2)
+						return -1;
+					else
+						conn->is_chunked = 4;
+
 					break;
 				}
 
@@ -6690,7 +6775,7 @@ mg_read(struct mg_connection *conn, void *buf, size_t len)
 }
 
 
-int
+CIVETWEB_API int
 mg_write(struct mg_connection *conn, const void *buf, size_t len)
 {
 	time_t now;
@@ -6771,7 +6856,7 @@ mg_write(struct mg_connection *conn, const void *buf, size_t len)
 
 
 /* Send a chunk, if "Transfer-Encoding: chunked" is used */
-int
+CIVETWEB_API int
 mg_send_chunk(struct mg_connection *conn,
               const char *chunk,
               unsigned int chunk_len)
@@ -6907,6 +6992,20 @@ alloc_vprintf(char **out_buf,
 }
 
 
+static int
+alloc_printf(char **out_buf, const char *fmt, ...)
+{
+	va_list ap;
+	int result;
+
+	va_start(ap, fmt);
+	result = alloc_vprintf(out_buf, NULL, 0, fmt, ap);
+	va_end(ap);
+
+	return result;
+}
+
+
 #if defined(GCC_DIAGNOSTIC)
 /* Enable format-nonliteral warning again. */
 #pragma GCC diagnostic pop
@@ -6931,7 +7030,7 @@ mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap)
 }
 
 
-int
+CIVETWEB_API int
 mg_printf(struct mg_connection *conn, const char *fmt, ...)
 {
 	va_list ap;
@@ -6945,7 +7044,7 @@ mg_printf(struct mg_connection *conn, const char *fmt, ...)
 }
 
 
-int
+CIVETWEB_API int
 mg_url_decode(const char *src,
               int src_len,
               char *dst,
@@ -6985,7 +7084,7 @@ url_decode_in_place(char *buf)
 }
 
 
-int
+CIVETWEB_API int
 mg_get_var(const char *data,
            size_t data_len,
            const char *name,
@@ -6996,7 +7095,7 @@ mg_get_var(const char *data,
 }
 
 
-int
+CIVETWEB_API int
 mg_get_var2(const char *data,
             size_t data_len,
             const char *name,
@@ -7054,7 +7153,7 @@ mg_get_var2(const char *data,
 
 
 /* split a string "key1=val1&key2=val2" into key/value pairs */
-int
+CIVETWEB_API int
 mg_split_form_urlencoded(char *data,
                          struct mg_header *form_fields,
                          unsigned num_form_fields)
@@ -7152,8 +7251,8 @@ mg_split_form_urlencoded(char *data,
 }
 
 
-/* HCP24: some changes to compare hole var_name */
-int
+/* HCP24: some changes to compare whole var_name */
+CIVETWEB_API int
 mg_get_cookie(const char *cookie_header,
               const char *var_name,
               char *dst,
@@ -7202,13 +7301,30 @@ mg_get_cookie(const char *cookie_header,
 }
 
 
-#if defined(USE_WEBSOCKET) || defined(USE_LUA)
-static void
-base64_encode(const unsigned char *src, int src_len, char *dst)
+CIVETWEB_API int
+mg_base64_encode(const unsigned char *src,
+                 size_t src_len,
+                 char *dst,
+                 size_t *dst_len)
 {
 	static const char *b64 =
 	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-	int i, j, a, b, c;
+	size_t i, j;
+	int a, b, c;
+
+	if (dst_len != NULL) {
+		/* Expected length including 0 termination: */
+		/* IN 1 -> OUT 5, IN 2 -> OUT 5, IN 3 -> OUT 5, IN 4 -> OUT 9,
+		 * IN 5 -> OUT 9, IN 6 -> OUT 9, IN 7 -> OUT 13, etc. */
+		size_t expected_len = ((src_len + 2) / 3) * 4 + 1;
+		if (*dst_len < expected_len) {
+			if (*dst_len > 0) {
+				dst[0] = '\0';
+			}
+			*dst_len = expected_len;
+			return 0;
+		}
+	}
 
 	for (i = j = 0; i < src_len; i += 3) {
 		a = src[i];
@@ -7228,22 +7344,27 @@ base64_encode(const unsigned char *src, int src_len, char *dst)
 		dst[j++] = '=';
 	}
 	dst[j++] = '\0';
+
+	if (dst_len != NULL) {
+		*dst_len = (size_t)j;
+	}
+
+	/* Return -1 for "OK" */
+	return -1;
 }
-#endif
 
 
-#if defined(USE_LUA)
 static unsigned char
 b64reverse(char letter)
 {
 	if ((letter >= 'A') && (letter <= 'Z')) {
-		return letter - 'A';
+		return (unsigned char)(letter - 'A');
 	}
 	if ((letter >= 'a') && (letter <= 'z')) {
-		return letter - 'a' + 26;
+		return (unsigned char)(letter - 'a' + 26);
 	}
 	if ((letter >= '0') && (letter <= '9')) {
-		return letter - '0' + 52;
+		return (unsigned char)(letter - '0' + 52);
 	}
 	if (letter == '+') {
 		return 62;
@@ -7258,46 +7379,85 @@ b64reverse(char letter)
 }
 
 
-static int
-base64_decode(const unsigned char *src, int src_len, char *dst, size_t *dst_len)
+CIVETWEB_API int
+mg_base64_decode(const char *src,
+                 size_t src_len,
+                 unsigned char *dst,
+                 size_t *dst_len)
 {
-	int i;
+	size_t i;
 	unsigned char a, b, c, d;
+	size_t dst_len_limit = (size_t)-1;
+	size_t dst_len_used = 0;
 
-	*dst_len = 0;
+	if (dst_len != NULL) {
+		dst_len_limit = *dst_len;
+		*dst_len = 0;
+	}
 
 	for (i = 0; i < src_len; i += 4) {
+		/* Read 4 characters from BASE64 string */
 		a = b64reverse(src[i]);
 		if (a >= 254) {
-			return i;
+			return (int)i;
 		}
 
 		b = b64reverse(((i + 1) >= src_len) ? 0 : src[i + 1]);
 		if (b >= 254) {
-			return i + 1;
+			return (int)i + 1;
 		}
 
 		c = b64reverse(((i + 2) >= src_len) ? 0 : src[i + 2]);
 		if (c == 254) {
-			return i + 2;
+			return (int)i + 2;
 		}
 
 		d = b64reverse(((i + 3) >= src_len) ? 0 : src[i + 3]);
 		if (d == 254) {
-			return i + 3;
+			return (int)i + 3;
 		}
 
-		dst[(*dst_len)++] = (a << 2) + (b >> 4);
+		/* Add first (of 3) decoded character */
+		if (dst_len_used < dst_len_limit) {
+			dst[dst_len_used] = (unsigned char)((unsigned char)(a << 2)
+			                                    + (unsigned char)(b >> 4));
+		}
+		dst_len_used++;
+
 		if (c != 255) {
-			dst[(*dst_len)++] = (b << 4) + (c >> 2);
+			if (dst_len_used < dst_len_limit) {
+
+				dst[dst_len_used] = (unsigned char)((unsigned char)(b << 4)
+				                                    + (unsigned char)(c >> 2));
+			}
+			dst_len_used++;
 			if (d != 255) {
-				dst[(*dst_len)++] = (c << 6) + d;
+				if (dst_len_used < dst_len_limit) {
+					dst[dst_len_used] =
+					    (unsigned char)((unsigned char)(c << 6) + d);
+				}
+				dst_len_used++;
 			}
 		}
 	}
+
+	/* Add terminating zero */
+	if (dst_len_used < dst_len_limit) {
+		dst[dst_len_used] = '\0';
+	}
+	dst_len_used++;
+	if (dst_len != NULL) {
+		*dst_len = dst_len_used;
+	}
+
+	if (dst_len_used > dst_len_limit) {
+		/* Out of memory */
+		return 0;
+	}
+
+	/* Return -1 for "OK" */
 	return -1;
 }
-#endif
 
 
 static int
@@ -7305,9 +7465,35 @@ is_put_or_delete_method(const struct mg_connection *conn)
 {
 	if (conn) {
 		const char *s = conn->request_info.request_method;
-		return (s != NULL)
-		       && (!strcmp(s, "PUT") || !strcmp(s, "DELETE")
-		           || !strcmp(s, "MKCOL") || !strcmp(s, "PATCH"));
+		if (s != NULL) {
+			/* PUT, DELETE, MKCOL, PATCH, LOCK, UNLOCK, PROPPATCH, MOVE, COPY */
+			return (!strcmp(s, "PUT") || !strcmp(s, "DELETE")
+			        || !strcmp(s, "MKCOL") || !strcmp(s, "PATCH")
+			        || !strcmp(s, "LOCK") || !strcmp(s, "UNLOCK")
+			        || !strcmp(s, "PROPPATCH") || !strcmp(s, "MOVE")
+			        || !strcmp(s, "COPY"));
+		}
+	}
+	return 0;
+}
+
+
+static int
+is_civetweb_webdav_method(const struct mg_connection *conn)
+{
+	/* Note: Here we only have to identify the WebDav methods that need special
+	 * handling in the CivetWeb code - not all methods used in WebDav. In
+	 * particular, methods used on directories (when using Windows Explorer as
+	 * WebDav client).
+	 */
+	if (conn) {
+		const char *s = conn->request_info.request_method;
+		if (s != NULL) {
+			/* These are the civetweb builtin DAV methods */
+			return (!strcmp(s, "PROPFIND") || !strcmp(s, "PROPPATCH")
+			        || !strcmp(s, "LOCK") || !strcmp(s, "UNLOCK")
+			        || !strcmp(s, "MOVE") || !strcmp(s, "COPY"));
+		}
 	}
 	return 0;
 }
@@ -7321,7 +7507,7 @@ extention_matches_script(
 )
 {
 #if !defined(NO_CGI)
-	unsigned char cgi_config_idx, inc, max;
+	int cgi_config_idx, inc, max;
 #endif
 
 #if defined(USE_LUA)
@@ -7439,8 +7625,9 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */
               struct mg_file_stat *filestat, /* out: file status structure */
               int *is_found,                 /* out: file found (directly) */
               int *is_script_resource,       /* out: handled by a script? */
-              int *is_websocket_request,     /* out: websocket connetion? */
+              int *is_websocket_request,     /* out: websocket connection? */
               int *is_put_or_delete_request, /* out: put/delete a file? */
+              int *is_webdav_request,        /* out: webdav request? */
               int *is_template_text          /* out: SSI file or LSP file? */
 )
 {
@@ -7470,15 +7657,19 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */
 	*is_script_resource = 0;
 	*is_template_text = 0;
 
-	/* Step 2: Check if the request attempts to modify the file system */
+	/* Step 2: Classify the request method */
+	/* Step 2a: Check if the request attempts to modify the file system */
 	*is_put_or_delete_request = is_put_or_delete_method(conn);
+	/* Step 2b: Check if the request uses WebDav method that requires special
+	 * handling */
+	*is_webdav_request = is_civetweb_webdav_method(conn);
 
 	/* Step 3: Check if it is a websocket request, and modify the document
 	 * root if required */
 #if defined(USE_WEBSOCKET)
 	*is_websocket_request = (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET);
 #if !defined(NO_FILES)
-	if (*is_websocket_request && conn->dom_ctx->config[WEBSOCKET_ROOT]) {
+	if ((*is_websocket_request) && conn->dom_ctx->config[WEBSOCKET_ROOT]) {
 		root = conn->dom_ctx->config[WEBSOCKET_ROOT];
 	}
 #endif /* !NO_FILES */
@@ -7575,7 +7766,9 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */
 
 		/* 8.4: If the request target is a directory, there could be
 		 * a substitute file (index.html, index.cgi, ...). */
-		if (filestat->is_directory && is_uri_end_slash) {
+		/* But do not substitute a directory for a WebDav request */
+		if (filestat->is_directory && is_uri_end_slash
+		    && (!*is_webdav_request)) {
 			/* Use a local copy here, since substitute_index_file will
 			 * change the content of the file status */
 			struct mg_file_stat tmp_filestat;
@@ -7647,6 +7840,11 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */
 	allow_substitute_script_subresources =
 	    !mg_strcasecmp(conn->dom_ctx->config[ALLOW_INDEX_SCRIPT_SUB_RES],
 	                   "yes");
+	if (*is_webdav_request) {
+		/* TO BE DEFINED: Should scripts handle special WebDAV methods lile
+		 * PROPFIND for their subresources? */
+		/* allow_substitute_script_subresources = 0; */
+	}
 
 	sep_pos = tmp_str_len;
 	while (sep_pos > 0) {
@@ -8130,7 +8328,7 @@ static const struct {
     {NULL, 0, NULL}};
 
 
-const char *
+CIVETWEB_API const char *
 mg_get_builtin_mime_type(const char *path)
 {
 	const char *ext;
@@ -8202,7 +8400,7 @@ bin2str(char *to, const unsigned char *p, size_t len)
 
 /* Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
  */
-char *
+CIVETWEB_API char *
 mg_md5(char buf[33], ...)
 {
 	md5_byte_t hash[16];
@@ -8226,14 +8424,14 @@ mg_md5(char buf[33], ...)
 
 /* Check the user's password, return 1 if OK */
 static int
-check_password(const char *method,
-               const char *ha1,
-               const char *uri,
-               const char *nonce,
-               const char *nc,
-               const char *cnonce,
-               const char *qop,
-               const char *response)
+check_password_digest(const char *method,
+                      const char *ha1,
+                      const char *uri,
+                      const char *nonce,
+                      const char *nc,
+                      const char *cnonce,
+                      const char *qop,
+                      const char *response)
 {
 	char ha2[32 + 1], expected_response[32 + 1];
 
@@ -8345,7 +8543,10 @@ open_auth_file(struct mg_connection *conn,
 
 /* Parsed Authorization header */
 struct ah {
-	char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
+	char *user;
+	int type;             /* 1 = basic, 2 = digest */
+	char *plain_password; /* Basic only */
+	char *uri, *cnonce, *response, *qop, *nc, *nonce; /* Digest only */
 };
 
 
@@ -8365,8 +8566,44 @@ parse_auth_header(struct mg_connection *conn,
 	}
 
 	(void)memset(ah, 0, sizeof(*ah));
-	if (((auth_header = mg_get_header(conn, "Authorization")) == NULL)
-	    || mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
+	auth_header = mg_get_header(conn, "Authorization");
+
+	if (auth_header == NULL) {
+		/* No Authorization header at all */
+		return 0;
+	}
+	if (0 == mg_strncasecmp(auth_header, "Basic ", 6)) {
+		/* Basic Auth (we never asked for this, but some client may send it) */
+		char *split;
+		const char *userpw_b64 = auth_header + 6;
+		size_t userpw_b64_len = strlen(userpw_b64);
+		size_t buf_len_r = buf_size;
+		if (mg_base64_decode(
+		        userpw_b64, userpw_b64_len, (unsigned char *)buf, &buf_len_r)
+		    != -1) {
+			return 0; /* decode error */
+		}
+		split = strchr(buf, ':');
+		if (!split) {
+			return 0; /* Format error */
+		}
+
+		/* Separate string at ':' */
+		*split = 0;
+
+		/* User name is before ':', Password is after ':'  */
+		ah->user = buf;
+		ah->type = 1;
+		ah->plain_password = split + 1;
+
+		return 1;
+
+	} else if (0 == mg_strncasecmp(auth_header, "Digest ", 7)) {
+		/* Digest Auth ... implemented below */
+		ah->type = 2;
+
+	} else {
+		/* Unknown or invalid Auth method */
 		return 0;
 	}
 
@@ -8451,15 +8688,7 @@ parse_auth_header(struct mg_connection *conn,
 	(void)nonce;
 #endif
 
-	/* CGI needs it as REMOTE_USER */
-	if (ah->user != NULL) {
-		conn->request_info.remote_user =
-		    mg_strdup_ctx(ah->user, conn->phys_ctx);
-	} else {
-		return 0;
-	}
-
-	return 1;
+	return (ah->user != NULL);
 }
 
 
@@ -8594,14 +8823,32 @@ read_auth_file(struct mg_file *filep,
 
 		if (!strcmp(workdata->ah.user, workdata->f_user)
 		    && !strcmp(workdata->domain, workdata->f_domain)) {
-			return check_password(workdata->conn->request_info.request_method,
-			                      workdata->f_ha1,
-			                      workdata->ah.uri,
-			                      workdata->ah.nonce,
-			                      workdata->ah.nc,
-			                      workdata->ah.cnonce,
-			                      workdata->ah.qop,
-			                      workdata->ah.response);
+			switch (workdata->ah.type) {
+			case 1: /* Basic */
+			{
+				char md5[33];
+				mg_md5(md5,
+				       workdata->f_user,
+				       ":",
+				       workdata->domain,
+				       ":",
+				       workdata->ah.plain_password,
+				       NULL);
+				return 0 == memcmp(workdata->f_ha1, md5, 33);
+			}
+			case 2: /* Digest */
+				return check_password_digest(
+				    workdata->conn->request_info.request_method,
+				    workdata->f_ha1,
+				    workdata->ah.uri,
+				    workdata->ah.nonce,
+				    workdata->ah.nc,
+				    workdata->ah.cnonce,
+				    workdata->ah.qop,
+				    workdata->ah.response);
+			default: /* None/Other/Unknown */
+				return 0;
+			}
 		}
 	}
 
@@ -8627,6 +8874,10 @@ authorize(struct mg_connection *conn, struct mg_file *filep, const char *realm)
 		return 0;
 	}
 
+	/* CGI needs it as REMOTE_USER */
+	conn->request_info.remote_user =
+	    mg_strdup_ctx(workdata.ah.user, conn->phys_ctx);
+
 	if (realm) {
 		workdata.domain = realm;
 	} else {
@@ -8638,7 +8889,7 @@ authorize(struct mg_connection *conn, struct mg_file *filep, const char *realm)
 
 
 /* Public function to check http digest authentication header */
-int
+CIVETWEB_API int
 mg_check_digest_access_authentication(struct mg_connection *conn,
                                       const char *realm,
                                       const char *filename)
@@ -8767,7 +9018,7 @@ send_authorization_request(struct mg_connection *conn, const char *realm)
 /* Interface function. Parameters are provided by the user, so do
  * at least some basic checks.
  */
-int
+CIVETWEB_API int
 mg_send_digest_access_authentication_request(struct mg_connection *conn,
                                              const char *realm)
 {
@@ -8783,42 +9034,41 @@ mg_send_digest_access_authentication_request(struct mg_connection *conn,
 static int
 is_authorized_for_put(struct mg_connection *conn)
 {
+	int ret = 0;
+
 	if (conn) {
 		struct mg_file file = STRUCT_FILE_INITIALIZER;
 		const char *passfile = conn->dom_ctx->config[PUT_DELETE_PASSWORDS_FILE];
-		int ret = 0;
 
 		if (passfile != NULL
 		    && mg_fopen(conn, passfile, MG_FOPEN_MODE_READ, &file)) {
 			ret = authorize(conn, &file, NULL);
 			(void)mg_fclose(&file.access); /* ignore error on read only file */
 		}
-
-		return ret;
 	}
-	return 0;
+
+	DEBUG_TRACE("file write authorization: %i", ret);
+	return ret;
 }
 #endif
 
 
-static int
-modify_passwords_file(const char *fname,
-                      const char *domain,
-                      const char *user,
-                      const char *pass,
-                      const char *ha1)
+CIVETWEB_API int
+mg_modify_passwords_file_ha1(const char *fname,
+                             const char *domain,
+                             const char *user,
+                             const char *ha1)
 {
-	int found, i;
-	char line[512], u[512] = "", d[512] = "", ha1buf[33],
-	                tmp[UTF8_PATH_MAX + 8];
-	FILE *fp, *fp2;
-
-	found = 0;
-	fp = fp2 = NULL;
+	int found = 0, i, result = 1;
+	char line[512], u[256], d[256], h[256];
+	struct stat st = {0};
+	FILE *fp = NULL;
+	char *temp_file = NULL;
+	int temp_file_offs = 0;
 
 	/* Regard empty password as no password - remove user record. */
-	if ((pass != NULL) && (pass[0] == '\0')) {
-		pass = NULL;
+	if ((ha1 != NULL) && (ha1[0] == '\0')) {
+		ha1 = NULL;
 	}
 
 	/* Other arguments must not be empty */
@@ -8827,8 +9077,7 @@ modify_passwords_file(const char *fname,
 	}
 
 	/* Using the given file format, user name and domain must not contain
-	 * ':'
-	 */
+	 * the ':' character */
 	if (strchr(user, ':') != NULL) {
 		return 0;
 	}
@@ -8844,7 +9093,7 @@ modify_passwords_file(const char *fname,
 		}
 	}
 	if (user[i]) {
-		return 0;
+		return 0; /* user name too long */
 	}
 	for (i = 0; ((i < 255) && (domain[i] != 0)); i++) {
 		if (iscntrl((unsigned char)domain[i])) {
@@ -8852,92 +9101,137 @@ modify_passwords_file(const char *fname,
 		}
 	}
 	if (domain[i]) {
-		return 0;
+		return 0; /* domain name too long */
 	}
 
 	/* The maximum length of the path to the password file is limited */
-	if ((strlen(fname) + 4) >= UTF8_PATH_MAX) {
+	if (strlen(fname) >= UTF8_PATH_MAX) {
 		return 0;
 	}
 
-	/* Create a temporary file name. Length has been checked before. */
-	strcpy(tmp, fname);
-	strcat(tmp, ".tmp");
+	/* Check if the file exists, and get file size */
+	if (0 == stat(fname, &st)) {
+		int temp_buf_len;
+		if (st.st_size > 10485760) {
+			/* Some funster provided a >10 MB text file */
+			return 0;
+		}
 
-	/* Create the file if does not exist */
-	/* Use of fopen here is OK, since fname is only ASCII */
-	if ((fp = fopen(fname, "a+")) != NULL) {
-		(void)fclose(fp);
-	}
+		/* Add enough space for one more line */
+		temp_buf_len = (int)st.st_size + 1024;
 
-	/* Open the given file and temporary file */
-	if ((fp = fopen(fname, "r")) == NULL) {
-		return 0;
-	} else if ((fp2 = fopen(tmp, "w+")) == NULL) {
+		/* Allocate memory (instead of using a temporary file) */
+		temp_file = (char *)mg_calloc((size_t)temp_buf_len, 1);
+		if (!temp_file) {
+			/* Out of memory */
+			return 0;
+		}
+
+		/* File exists. Read it into a memory buffer. */
+		fp = fopen(fname, "r");
+		if (fp == NULL) {
+			/* Cannot read file. No permission? */
+			mg_free(temp_file);
+			return 0;
+		}
+
+		/* Read content and store in memory */
+		while ((fgets(line, sizeof(line), fp) != NULL)
+		       && ((temp_file_offs + 600) < temp_buf_len)) {
+			/* file format is "user:domain:hash\n" */
+			if (sscanf(line, "%255[^:]:%255[^:]:%255s", u, d, h) != 3) {
+				continue;
+			}
+			u[255] = 0;
+			d[255] = 0;
+			h[255] = 0;
+
+			if (!strcmp(u, user) && !strcmp(d, domain)) {
+				/* Found the user: change the password hash or drop the user
+				 */
+				if ((ha1 != NULL) && (!found)) {
+					i = sprintf(temp_file + temp_file_offs,
+					            "%s:%s:%s\n",
+					            user,
+					            domain,
+					            ha1);
+					if (i < 1) {
+						fclose(fp);
+						mg_free(temp_file);
+						return 0;
+					}
+					temp_file_offs += i;
+				}
+				found = 1;
+			} else {
+				/* Copy existing user, including password hash */
+				i = sprintf(temp_file + temp_file_offs, "%s:%s:%s\n", u, d, h);
+				if (i < 1) {
+					fclose(fp);
+					mg_free(temp_file);
+					return 0;
+				}
+				temp_file_offs += i;
+			}
+		}
 		fclose(fp);
+	}
+
+	/* Create new file */
+	fp = fopen(fname, "w");
+	if (!fp) {
+		mg_free(temp_file);
 		return 0;
 	}
 
-	/* Copy the stuff to temporary file */
-	while (fgets(line, sizeof(line), fp) != NULL) {
-		if (sscanf(line, "%255[^:]:%255[^:]:%*s", u, d) != 2) {
-			continue;
-		}
-		u[255] = 0;
-		d[255] = 0;
+#if !defined(_WIN32)
+	/* On Linux & co., restrict file read/write permissions to the owner */
+	if (fchmod(fileno(fp), S_IRUSR | S_IWUSR) != 0) {
+		result = 0;
+	}
+#endif
 
-		if (!strcmp(u, user) && !strcmp(d, domain)) {
-			found++;
-			if (pass != NULL) {
-				mg_md5(ha1buf, user, ":", domain, ":", pass, NULL);
-				fprintf(fp2, "%s:%s:%s\n", user, domain, ha1buf);
-			} else if (ha1 != NULL) {
-				fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
-			}
-		} else {
-			fprintf(fp2, "%s", line);
+	if ((temp_file != NULL) && (temp_file_offs > 0)) {
+		/* Store buffered content of old file */
+		if (fwrite(temp_file, 1, (size_t)temp_file_offs, fp)
+		    != (size_t)temp_file_offs) {
+			result = 0;
 		}
 	}
 
 	/* If new user, just add it */
-	if (!found) {
-		if (pass != NULL) {
-			mg_md5(ha1buf, user, ":", domain, ":", pass, NULL);
-			fprintf(fp2, "%s:%s:%s\n", user, domain, ha1buf);
-		} else if (ha1 != NULL) {
-			fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
+	if ((ha1 != NULL) && (!found)) {
+		if (fprintf(fp, "%s:%s:%s\n", user, domain, ha1) < 6) {
+			result = 0;
 		}
 	}
 
-	/* Close files */
-	fclose(fp);
-	fclose(fp2);
-
-	/* Put the temp file in place of real file */
-	IGNORE_UNUSED_RESULT(remove(fname));
-	IGNORE_UNUSED_RESULT(rename(tmp, fname));
+	/* All data written */
+	if (fclose(fp) != 0) {
+		result = 0;
+	}
 
-	return 1;
+	mg_free(temp_file);
+	return result;
 }
 
 
-int
+CIVETWEB_API int
 mg_modify_passwords_file(const char *fname,
                          const char *domain,
                          const char *user,
                          const char *pass)
 {
-	return modify_passwords_file(fname, domain, user, pass, NULL);
-}
-
+	char ha1buf[33];
+	if ((fname == NULL) || (domain == NULL) || (user == NULL)) {
+		return 0;
+	}
+	if ((pass == NULL) || (pass[0] == 0)) {
+		return mg_modify_passwords_file_ha1(fname, domain, user, NULL);
+	}
 
-int
-mg_modify_passwords_file_ha1(const char *fname,
-                             const char *domain,
-                             const char *user,
-                             const char *ha1)
-{
-	return modify_passwords_file(fname, domain, user, NULL, ha1);
+	mg_md5(ha1buf, user, ":", domain, ":", pass, NULL);
+	return mg_modify_passwords_file_ha1(fname, domain, user, ha1buf);
 }
 
 
@@ -8995,8 +9289,7 @@ connect_socket(
     const char *host,
     int port,    /* 1..65535, or -99 for domain sockets (may be changed) */
     int use_ssl, /* 0 or 1 */
-    char *ebuf,
-    size_t ebuf_len,
+    struct mg_error_data *error,
     SOCKET *sock /* output: socket, must not be NULL */,
     union usa *sa /* output: socket address, must not be NULL  */
 )
@@ -9007,17 +9300,16 @@ connect_socket(
 	*sock = INVALID_SOCKET;
 	memset(sa, 0, sizeof(*sa));
 
-	if (ebuf_len > 0) {
-		*ebuf = 0;
-	}
-
 	if (host == NULL) {
-		mg_snprintf(NULL,
-		            NULL, /* No truncation check for ebuf */
-		            ebuf,
-		            ebuf_len,
-		            "%s",
-		            "NULL host");
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INVALID_PARAM;
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for ebuf */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            "NULL host");
+		}
 		return 0;
 	}
 
@@ -9026,45 +9318,57 @@ connect_socket(
 		/* Unix domain socket */
 		size_t hostlen = strlen(host);
 		if (hostlen >= sizeof(sa->sun.sun_path)) {
-			mg_snprintf(NULL,
-			            NULL, /* No truncation check for ebuf */
-			            ebuf,
-			            ebuf_len,
-			            "%s",
-			            "host length exceeds limit");
+			if (error != NULL) {
+				error->code = MG_ERROR_DATA_CODE_INVALID_PARAM;
+				mg_snprintf(NULL,
+				            NULL, /* No truncation check for ebuf */
+				            error->text,
+				            error->text_buffer_size,
+				            "%s",
+				            "host length exceeds limit");
+			}
 			return 0;
 		}
 	} else
 #endif
 	    if ((port <= 0) || !is_valid_port((unsigned)port)) {
-		mg_snprintf(NULL,
-		            NULL, /* No truncation check for ebuf */
-		            ebuf,
-		            ebuf_len,
-		            "%s",
-		            "invalid port");
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INVALID_PARAM;
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for ebuf */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            "invalid port");
+		}
 		return 0;
 	}
 
 #if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(NO_SSL_DL)
 #if defined(OPENSSL_API_1_1) || defined(OPENSSL_API_3_0)
 	if (use_ssl && (TLS_client_method == NULL)) {
-		mg_snprintf(NULL,
-		            NULL, /* No truncation check for ebuf */
-		            ebuf,
-		            ebuf_len,
-		            "%s",
-		            "SSL is not initialized");
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INIT_LIBRARY_FAILED;
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for ebuf */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            "SSL is not initialized");
+		}
 		return 0;
 	}
 #else
 	if (use_ssl && (SSLv23_client_method == NULL)) {
-		mg_snprintf(NULL,
-		            NULL, /* No truncation check for ebuf */
-		            ebuf,
-		            ebuf_len,
-		            "%s",
-		            "SSL is not initialized");
+		if (error != 0) {
+			error->code = MG_ERROR_DATA_CODE_INIT_LIBRARY_FAILED;
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for ebuf */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            "SSL is not initialized");
+		}
 		return 0;
 	}
 #endif /* OPENSSL_API_1_1 || OPENSSL_API_3_0*/
@@ -9072,7 +9376,6 @@ connect_socket(
 	(void)use_ssl;
 #endif /* NO SSL */
 
-
 #if defined(USE_X_DOM_SOCKET)
 	if (port == -99) {
 		size_t hostlen = strlen(host);
@@ -9107,12 +9410,15 @@ connect_socket(
 	}
 
 	if (ip_ver == 0) {
-		mg_snprintf(NULL,
-		            NULL, /* No truncation check for ebuf */
-		            ebuf,
-		            ebuf_len,
-		            "%s",
-		            "host not found");
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_HOST_NOT_FOUND;
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for ebuf */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            "host not found");
+		}
 		return 0;
 	}
 
@@ -9131,22 +9437,30 @@ connect_socket(
 #endif
 
 	if (*sock == INVALID_SOCKET) {
-		mg_snprintf(NULL,
-		            NULL, /* No truncation check for ebuf */
-		            ebuf,
-		            ebuf_len,
-		            "socket(): %s",
-		            strerror(ERRNO));
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_OS_ERROR;
+			error->code_sub = (unsigned)ERRNO;
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for ebuf */
+			            error->text,
+			            error->text_buffer_size,
+			            "socket(): %s",
+			            strerror(ERRNO));
+		}
 		return 0;
 	}
 
 	if (0 != set_non_blocking_mode(*sock)) {
-		mg_snprintf(NULL,
-		            NULL, /* No truncation check for ebuf */
-		            ebuf,
-		            ebuf_len,
-		            "Cannot set socket to non-blocking: %s",
-		            strerror(ERRNO));
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_OS_ERROR;
+			error->code_sub = (unsigned)ERRNO;
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for ebuf */
+			            error->text,
+			            error->text_buffer_size,
+			            "Cannot set socket to non-blocking: %s",
+			            strerror(ERRNO));
+		}
 		closesocket(*sock);
 		*sock = INVALID_SOCKET;
 		return 0;
@@ -9214,13 +9528,16 @@ connect_socket(
 
 		if (pollres != 1) {
 			/* Not connected */
-			mg_snprintf(NULL,
-			            NULL, /* No truncation check for ebuf */
-			            ebuf,
-			            ebuf_len,
-			            "connect(%s:%d): timeout",
-			            host,
-			            port);
+			if (error != NULL) {
+				error->code = MG_ERROR_DATA_CODE_CONNECT_TIMEOUT;
+				mg_snprintf(NULL,
+				            NULL, /* No truncation check for ebuf */
+				            error->text,
+				            error->text_buffer_size,
+				            "connect(%s:%d): timeout",
+				            host,
+				            port);
+			}
 			closesocket(*sock);
 			*sock = INVALID_SOCKET;
 			return 0;
@@ -9239,14 +9556,18 @@ connect_socket(
 
 	if (conn_ret != 0) {
 		/* Not connected */
-		mg_snprintf(NULL,
-		            NULL, /* No truncation check for ebuf */
-		            ebuf,
-		            ebuf_len,
-		            "connect(%s:%d): error %s",
-		            host,
-		            port,
-		            strerror(sockerr));
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_CONNECT_FAILED;
+			error->code_sub = (unsigned)ERRNO;
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for ebuf */
+			            error->text,
+			            error->text_buffer_size,
+			            "connect(%s:%d): error %s",
+			            host,
+			            port,
+			            strerror(sockerr));
+		}
 		closesocket(*sock);
 		*sock = INVALID_SOCKET;
 		return 0;
@@ -9256,7 +9577,7 @@ connect_socket(
 }
 
 
-int
+CIVETWEB_API int
 mg_url_encode(const char *src, char *dst, size_t dst_len)
 {
 	static const char *dont_escape = "._-$,;~()";
@@ -9285,7 +9606,7 @@ mg_url_encode(const char *src, char *dst, size_t dst_len)
 /* Return 0 on success, non-zero if an error occurs. */
 
 static int
-print_dir_entry(struct de *de)
+print_dir_entry(struct mg_connection *conn, struct de *de)
 {
 	size_t namesize, escsize, i;
 	char *href, *esc, *p;
@@ -9322,7 +9643,7 @@ print_dir_entry(struct de *de)
 	}
 
 	if (de->file.is_directory) {
-		mg_snprintf(de->conn,
+		mg_snprintf(conn,
 		            NULL, /* Buffer is big enough */
 		            size,
 		            sizeof(size),
@@ -9332,28 +9653,28 @@ print_dir_entry(struct de *de)
 		/* We use (signed) cast below because MSVC 6 compiler cannot
 		 * convert unsigned __int64 to double. Sigh. */
 		if (de->file.size < 1024) {
-			mg_snprintf(de->conn,
+			mg_snprintf(conn,
 			            NULL, /* Buffer is big enough */
 			            size,
 			            sizeof(size),
 			            "%d",
 			            (int)de->file.size);
 		} else if (de->file.size < 0x100000) {
-			mg_snprintf(de->conn,
+			mg_snprintf(conn,
 			            NULL, /* Buffer is big enough */
 			            size,
 			            sizeof(size),
 			            "%.1fk",
 			            (double)de->file.size / 1024.0);
 		} else if (de->file.size < 0x40000000) {
-			mg_snprintf(de->conn,
+			mg_snprintf(conn,
 			            NULL, /* Buffer is big enough */
 			            size,
 			            sizeof(size),
 			            "%.1fM",
 			            (double)de->file.size / 1048576);
 		} else {
-			mg_snprintf(de->conn,
+			mg_snprintf(conn,
 			            NULL, /* Buffer is big enough */
 			            size,
 			            sizeof(size),
@@ -9374,9 +9695,8 @@ print_dir_entry(struct de *de)
 		strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", tm);
 	} else {
 		mg_strlcpy(mod, "01-Jan-1970 00:00", sizeof(mod));
-		mod[sizeof(mod) - 1] = '\0';
 	}
-	mg_printf(de->conn,
+	mg_printf(conn,
 	          "<tr><td><a href=\"%s%s\">%s%s</a></td>"
 	          "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
 	          href,
@@ -9391,28 +9711,28 @@ print_dir_entry(struct de *de)
 
 
 /* This function is called from send_directory() and used for
- * sorting directory entries by size, or name, or modification time.
- * On windows, __cdecl specification is needed in case if project is built
- * with __stdcall convention. qsort always requires __cdels callback. */
-static int WINCDECL
-compare_dir_entries(const void *p1, const void *p2)
+ * sorting directory entries by size, name, or modification time. */
+static int
+compare_dir_entries(const void *p1, const void *p2, void *arg)
 {
+	const char *query_string = (const char *)(arg != NULL ? arg : "");
 	if (p1 && p2) {
 		const struct de *a = (const struct de *)p1, *b = (const struct de *)p2;
-		const char *query_string = a->conn->request_info.query_string;
 		int cmp_result = 0;
 
 		if ((query_string == NULL) || (query_string[0] == '\0')) {
 			query_string = "n";
 		}
 
+		/* Sort Directories vs Files */
 		if (a->file.is_directory && !b->file.is_directory) {
 			return -1; /* Always put directories on top */
 		} else if (!a->file.is_directory && b->file.is_directory) {
 			return 1; /* Always put directories on top */
-		} else if (*query_string == 'n') {
-			cmp_result = strcmp(a->file_name, b->file_name);
-		} else if (*query_string == 's') {
+		}
+
+		/* Sort by size or date */
+		if (*query_string == 's') {
 			cmp_result = (a->file.size == b->file.size)
 			                 ? 0
 			                 : ((a->file.size > b->file.size) ? 1 : -1);
@@ -9424,6 +9744,15 @@ compare_dir_entries(const void *p1, const void *p2)
 			                                                           : -1);
 		}
 
+		/* Sort by name:
+		 * if (*query_string == 'n')  ...
+		 * but also sort files of same size/date by name as secondary criterion.
+		 */
+		if (cmp_result == 0) {
+			cmp_result = strcmp(a->file_name, b->file_name);
+		}
+
+		/* For descending order, invert result */
 		return (query_string[1] == 'd') ? -cmp_result : cmp_result;
 	}
 	return 0;
@@ -9459,7 +9788,6 @@ scan_directory(struct mg_connection *conn,
 	if ((dirp = mg_opendir(conn, dir)) == NULL) {
 		return 0;
 	} else {
-		de.conn = conn;
 
 		while ((dp = mg_readdir(dirp)) != NULL) {
 			/* Do not show current dir and hidden files */
@@ -9517,7 +9845,6 @@ remove_directory(struct mg_connection *conn, const char *dir)
 	if ((dirp = mg_opendir(conn, dir)) == NULL) {
 		return 0;
 	} else {
-		de.conn = conn;
 
 		while ((dp = mg_readdir(dirp)) != NULL) {
 			/* Do not show current dir (but show hidden files as they will
@@ -9605,7 +9932,6 @@ dir_scan_callback(struct de *de, void *data)
 		return 1;
 	}
 	entries[dsd->num_entries].file = de->file;
-	entries[dsd->num_entries].conn = de->conn;
 	dsd->num_entries++;
 
 	return 0;
@@ -9680,6 +10006,7 @@ handle_directory_request(struct mg_connection *conn, const char *dir)
 
 	/* Body */
 	mg_printf(conn,
+	          "<!DOCTYPE html>"
 	          "<html><head><title>Index of %s</title>"
 	          "<style>th {text-align: left;}</style></head>"
 	          "<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
@@ -9705,12 +10032,13 @@ handle_directory_request(struct mg_connection *conn, const char *dir)
 
 	/* Sort and print directory entries */
 	if (data.entries != NULL) {
-		qsort(data.entries,
-		      data.num_entries,
-		      sizeof(data.entries[0]),
-		      compare_dir_entries);
+		mg_sort(data.entries,
+		        data.num_entries,
+		        sizeof(data.entries[0]),
+		        compare_dir_entries,
+		        (void *)conn->request_info.query_string);
 		for (i = 0; i < data.num_entries; i++) {
-			print_dir_entry(&data.entries[i]);
+			print_dir_entry(conn, &data.entries[i]);
 			mg_free(data.entries[i].file_name);
 		}
 		mg_free(data.entries);
@@ -9727,7 +10055,8 @@ static void
 send_file_data(struct mg_connection *conn,
                struct mg_file *filep,
                int64_t offset,
-               int64_t len)
+               int64_t len,
+               int no_buffering)
 {
 	char buf[MG_BUF_LEN];
 	int to_read, num_read, num_written;
@@ -9800,8 +10129,17 @@ send_file_data(struct mg_connection *conn,
 			    "Error: Unable to access file at requested position.");
 		} else {
 			while (len > 0) {
-				/* Calculate how much to read from the file in the buffer */
-				to_read = sizeof(buf);
+				/* Calculate how much to read from the file into the buffer. */
+				/* If no_buffering is set, we should not wait until the
+				 * CGI->Server buffer is filled, but send everything
+				 * immediately. In theory buffering could be turned off using
+				 * setbuf(filep->access.fp, NULL);
+				 * setvbuf(filep->access.fp, NULL, _IONBF, 0);
+				 * but in practice this does not work. A "Linux only" solution
+				 * may be to use select(). The only portable way is to read byte
+				 * by byte, but this is quite inefficient from a performance
+				 * point of view. */
+				to_read = no_buffering ? 1 : sizeof(buf);
 				if ((int64_t)to_read > len) {
 					to_read = (int)len;
 				}
@@ -9893,9 +10231,6 @@ handle_static_file_request(struct mg_connection *conn,
 	int n, truncated;
 	char gz_path[UTF8_PATH_MAX];
 	const char *encoding = 0;
-	const char *origin_hdr;
-	const char *cors_orig_cfg;
-	const char *cors1, *cors2;
 	int is_head_request;
 
 #if defined(USE_ZLIB)
@@ -10036,21 +10371,6 @@ handle_static_file_request(struct mg_connection *conn,
 	}
 #endif
 
-	/* Standard CORS header */
-	cors_orig_cfg = conn->dom_ctx->config[ACCESS_CONTROL_ALLOW_ORIGIN];
-	origin_hdr = mg_get_header(conn, "Origin");
-	if (cors_orig_cfg && *cors_orig_cfg && origin_hdr) {
-		/* Cross-origin resource sharing (CORS), see
-		 * http://www.html5rocks.com/en/tutorials/cors/,
-		 * http://www.html5rocks.com/static/images/cors_server_flowchart.png
-		 * -
-		 * preflight is not supported for files. */
-		cors1 = "Access-Control-Allow-Origin";
-		cors2 = cors_orig_cfg;
-	} else {
-		cors1 = cors2 = "";
-	}
-
 	/* Prepare Etag, and Last-Modified headers. */
 	gmt_time_string(lm, sizeof(lm), &filep->stat.last_modified);
 	construct_etag(etag, sizeof(etag), &filep->stat);
@@ -10059,13 +10379,11 @@ handle_static_file_request(struct mg_connection *conn,
 	mg_response_header_start(conn, conn->status_code);
 	send_static_cache_header(conn);
 	send_additional_header(conn);
+	send_cors_header(conn);
 	mg_response_header_add(conn,
 	                       "Content-Type",
 	                       mime_vec.ptr,
 	                       (int)mime_vec.len);
-	if (cors1[0] != 0) {
-		mg_response_header_add(conn, cors1, cors2, -1);
-	}
 	mg_response_header_add(conn, "Last-Modified", lm, -1);
 	mg_response_header_add(conn, "Etag", etag, -1);
 
@@ -10122,14 +10440,14 @@ handle_static_file_request(struct mg_connection *conn,
 #endif
 		{
 			/* Send file directly */
-			send_file_data(conn, filep, r1, cl);
+			send_file_data(conn, filep, r1, cl, 0); /* send static file */
 		}
 	}
 	(void)mg_fclose(&filep->access); /* ignore error on read only file */
 }
 
 
-int
+CIVETWEB_API int
 mg_send_file_body(struct mg_connection *conn, const char *path)
 {
 	struct mg_file file = STRUCT_FILE_INITIALIZER;
@@ -10137,7 +10455,7 @@ mg_send_file_body(struct mg_connection *conn, const char *path)
 		return -1;
 	}
 	fclose_on_exec(&file.access, conn);
-	send_file_data(conn, &file, 0, INT64_MAX);
+	send_file_data(conn, &file, 0, INT64_MAX, 0); /* send static file */
 	(void)mg_fclose(&file.access); /* Ignore errors for readonly files */
 	return 0;                      /* >= 0 for OK */
 }
@@ -10188,14 +10506,14 @@ handle_not_modified_static_file_request(struct mg_connection *conn,
 
 
 #if !defined(NO_FILESYSTEMS)
-void
+CIVETWEB_API void
 mg_send_file(struct mg_connection *conn, const char *path)
 {
 	mg_send_mime_file2(conn, path, NULL, NULL);
 }
 
 
-void
+CIVETWEB_API void
 mg_send_mime_file(struct mg_connection *conn,
                   const char *path,
                   const char *mime_type)
@@ -10204,7 +10522,7 @@ mg_send_mime_file(struct mg_connection *conn,
 }
 
 
-void
+CIVETWEB_API void
 mg_send_mime_file2(struct mg_connection *conn,
                    const char *path,
                    const char *mime_type,
@@ -10272,7 +10590,7 @@ put_dir(struct mg_connection *conn, const char *path)
 		/* Try to create intermediate directory */
 		DEBUG_TRACE("mkdir(%s)", buf);
 		if (!mg_stat(conn, buf, &file.stat) && mg_mkdir(conn, buf, 0755) != 0) {
-			/* path does not exixt and can not be created */
+			/* path does not exist and can not be created */
 			res = -2;
 			break;
 		}
@@ -10300,7 +10618,7 @@ remove_bad_file(const struct mg_connection *conn, const char *path)
 }
 
 
-long long
+CIVETWEB_API long long
 mg_store_body(struct mg_connection *conn, const char *path)
 {
 	char buf[MG_BUF_LEN];
@@ -10530,9 +10848,10 @@ static const struct mg_http_method_info http_methods[] = {
     {"LOCK", 1, 1, 0, 0, 0},
     {"UNLOCK", 1, 0, 0, 0, 0},
     {"PROPPATCH", 1, 1, 0, 0, 0},
+    {"COPY", 1, 0, 0, 0, 0},
+    {"MOVE", 1, 1, 0, 0, 0},
 
     /* Unsupported WEBDAV Methods: */
-    /* COPY, MOVE (RFC 2518) */
     /* + 11 methods from RFC 3253 */
     /* ORDERPATCH (RFC 3648) */
     /* ACL (RFC 3744) */
@@ -10553,6 +10872,10 @@ static const struct mg_http_method_info http_methods[] = {
 };
 
 
+/* All method names */
+static char *all_methods = NULL; /* Built by mg_init_library */
+
+
 static const struct mg_http_method_info *
 get_http_method_info(const char *method)
 {
@@ -10686,7 +11009,7 @@ parse_http_response(char *buf, int len, struct mg_response_info *ri)
 	ri->http_version = ri->status_text = NULL;
 	ri->num_headers = ri->status_code = 0;
 
-	/* RFC says that all initial whitespaces should be ingored */
+	/* RFC says that all initial whitespaces should be ignored */
 	/* This included all leading \r and \n (isspace) */
 	/* See table: http://www.cplusplus.com/reference/cctype/ */
 	while ((len > 0) && isspace((unsigned char)*buf)) {
@@ -10764,7 +11087,6 @@ parse_http_response(char *buf, int len, struct mg_response_info *ri)
 		buf++;
 	} while (isspace((unsigned char)*buf));
 
-
 	/* Parse all HTTP headers */
 	ri->num_headers = parse_http_headers(&buf, ri->http_headers);
 	if (ri->num_headers < 0) {
@@ -11032,7 +11354,7 @@ static int
 prepare_cgi_environment(struct mg_connection *conn,
                         const char *prog,
                         struct cgi_environment *env,
-                        unsigned char cgi_config_idx)
+                        int cgi_config_idx)
 {
 	const char *s;
 	struct vec var_vec;
@@ -11264,13 +11586,13 @@ abort_cgi_process(void *data)
 static void
 handle_cgi_request(struct mg_connection *conn,
                    const char *prog,
-                   unsigned char cgi_config_idx)
+                   int cgi_config_idx)
 {
 	char *buf;
 	size_t buflen;
 	int headers_len, data_len, i, truncated;
 	int fdin[2] = {-1, -1}, fdout[2] = {-1, -1}, fderr[2] = {-1, -1};
-	const char *status, *status_text, *connection_state;
+	const char *status, *status_text;
 	char *pbuf, dir[UTF8_PATH_MAX], *p;
 	struct mg_request_info ri;
 	struct cgi_environment blk;
@@ -11278,6 +11600,8 @@ handle_cgi_request(struct mg_connection *conn,
 	struct mg_file fout = STRUCT_FILE_INITIALIZER;
 	pid_t pid = (pid_t)-1;
 	struct process_control_data *proc = NULL;
+	char *cfg_buffering = conn->dom_ctx->config[CGI_BUFFERING + cgi_config_idx];
+	int no_buffering = 0;
 
 #if defined(USE_TIMERS)
 	double cgi_timeout;
@@ -11289,8 +11613,12 @@ handle_cgi_request(struct mg_connection *conn,
 		cgi_timeout =
 		    atof(config_options[REQUEST_TIMEOUT].default_value) * 0.001;
 	}
-
 #endif
+	if (cfg_buffering != NULL) {
+		if (!mg_strcasecmp(cfg_buffering, "no")) {
+			no_buffering = 1;
+		}
+	}
 
 	buf = NULL;
 	buflen = conn->phys_ctx->max_request_size;
@@ -11512,9 +11840,8 @@ handle_cgi_request(struct mg_connection *conn,
 	} else {
 		conn->status_code = 200;
 	}
-	connection_state =
-	    get_header(ri.http_headers, ri.num_headers, "Connection");
-	if (!header_has_option(connection_state, "keep-alive")) {
+
+	if (!should_keep_alive(conn)) {
 		conn->must_close = 1;
 	}
 
@@ -11539,7 +11866,7 @@ handle_cgi_request(struct mg_connection *conn,
 
 	/* Read the rest of CGI output and send to the client */
 	DEBUG_TRACE("CGI: %s", "forward all data");
-	send_file_data(conn, &fout, 0, INT64_MAX);
+	send_file_data(conn, &fout, 0, INT64_MAX, no_buffering); /* send CGI data */
 	DEBUG_TRACE("CGI: %s", "all data sent");
 
 done:
@@ -11585,7 +11912,7 @@ done:
 
 #if !defined(NO_FILES)
 static void
-mkcol(struct mg_connection *conn, const char *path)
+dav_mkcol(struct mg_connection *conn, const char *path)
 {
 	int rc, body_len;
 	struct de de;
@@ -11623,9 +11950,8 @@ mkcol(struct mg_connection *conn, const char *path)
 	}
 
 	rc = mg_mkdir(conn, path, 0755);
-
+	DEBUG_TRACE("mkdir %s: %i", path, rc);
 	if (rc == 0) {
-
 		/* Create 201 "Created" response */
 		mg_response_header_start(conn, 201);
 		send_static_cache_header(conn);
@@ -11634,22 +11960,197 @@ mkcol(struct mg_connection *conn, const char *path)
 
 		/* Send all headers - there is no body */
 		mg_response_header_send(conn);
-
 	} else {
-		if (errno == EEXIST) {
-			mg_send_http_error(
-			    conn, 405, "Error: mkcol(%s): %s", path, strerror(ERRNO));
-		} else if (errno == EACCES) {
-			mg_send_http_error(
-			    conn, 403, "Error: mkcol(%s): %s", path, strerror(ERRNO));
-		} else if (errno == ENOENT) {
-			mg_send_http_error(
-			    conn, 409, "Error: mkcol(%s): %s", path, strerror(ERRNO));
+		int http_status = 500;
+		switch (errno) {
+		case EEXIST:
+			http_status = 405;
+			break;
+		case EACCES:
+			http_status = 403;
+			break;
+		case ENOENT:
+			http_status = 409;
+			break;
+		}
+
+		mg_send_http_error(conn,
+		                   http_status,
+		                   "Error processing %s: %s",
+		                   path,
+		                   strerror(ERRNO));
+	}
+}
+
+
+/* Forward decrlaration */
+static int get_uri_type(const char *uri);
+static const char *
+get_rel_url_at_current_server(const char *uri,
+                              const struct mg_connection *conn);
+
+
+static void
+dav_move_file(struct mg_connection *conn, const char *path, int do_copy)
+{
+	const char *overwrite_hdr;
+	const char *destination_hdr;
+	const char *root;
+	int rc, dest_uri_type;
+	int http_status = 400;
+	int do_overwrite = 0;
+	int destination_ok = 0;
+	char dest_path[UTF8_PATH_MAX];
+	struct mg_file_stat ignored;
+
+	if (conn == NULL) {
+		return;
+	}
+
+	root = conn->dom_ctx->config[DOCUMENT_ROOT];
+	overwrite_hdr = mg_get_header(conn, "Overwrite");
+	destination_hdr = mg_get_header(conn, "Destination");
+	if ((overwrite_hdr != NULL) && (toupper(overwrite_hdr[0]) == 'T')) {
+		do_overwrite = 1;
+	}
+
+	if ((destination_hdr == NULL) || (destination_hdr[0] == 0)) {
+		mg_send_http_error(conn, 400, "%s", "Missing destination");
+		return;
+	}
+
+	if (root != NULL) {
+		char *local_dest = NULL;
+		dest_uri_type = get_uri_type(destination_hdr);
+		if (dest_uri_type == 2) {
+			local_dest = mg_strdup_ctx(destination_hdr, conn->phys_ctx);
+		} else if ((dest_uri_type == 3) || (dest_uri_type == 4)) {
+			const char *h =
+			    get_rel_url_at_current_server(destination_hdr, conn);
+			if (h) {
+				size_t len = strlen(h);
+				local_dest = mg_malloc_ctx(len + 1, conn->phys_ctx);
+				mg_url_decode(h, (int)len, local_dest, (int)len + 1, 0);
+			}
+		}
+		if (local_dest != NULL) {
+			remove_dot_segments(local_dest);
+			if (local_dest[0] == '/') {
+				int trunc_check = 0;
+				mg_snprintf(conn,
+				            &trunc_check,
+				            dest_path,
+				            sizeof(dest_path),
+				            "%s/%s",
+				            root,
+				            local_dest);
+				if (trunc_check == 0) {
+					destination_ok = 1;
+				}
+			}
+			mg_free(local_dest);
+		}
+	}
+
+	if (!destination_ok) {
+		mg_send_http_error(conn, 502, "%s", "Illegal destination");
+		return;
+	}
+
+	/* Check now if this file exists */
+	if (mg_stat(conn, dest_path, &ignored)) {
+		/* File exists */
+		if (do_overwrite) {
+			/* Overwrite allowed: delete the file first */
+			if (0 != remove(dest_path)) {
+				/* No overwrite: return error */
+				mg_send_http_error(conn,
+				                   403,
+				                   "Cannot overwrite file: %s",
+				                   dest_path);
+				return;
+			}
 		} else {
-			mg_send_http_error(
-			    conn, 500, "fopen(%s): %s", path, strerror(ERRNO));
+			/* No overwrite: return error */
+			mg_send_http_error(conn,
+			                   412,
+			                   "Destination already exists: %s",
+			                   dest_path);
+			return;
+		}
+	}
+
+	/* Copy / Move / Rename operation. */
+	DEBUG_TRACE("%s %s to %s", (do_copy ? "copy" : "move"), path, dest_path);
+#if defined(_WIN32)
+	{
+		/* For Windows, we need to convert from UTF-8 to UTF-16 first. */
+		wchar_t wSource[UTF16_PATH_MAX];
+		wchar_t wDest[UTF16_PATH_MAX];
+		BOOL ok;
+
+		path_to_unicode(conn, path, wSource, ARRAY_SIZE(wSource));
+		path_to_unicode(conn, dest_path, wDest, ARRAY_SIZE(wDest));
+		if (do_copy) {
+			ok = CopyFileW(wSource, wDest, do_overwrite ? FALSE : TRUE);
+		} else {
+			ok = MoveFileExW(wSource,
+			                 wDest,
+			                 do_overwrite ? MOVEFILE_REPLACE_EXISTING : 0);
+		}
+		if (ok) {
+			rc = 0;
+		} else {
+			DWORD lastErr = GetLastError();
+			if (lastErr == ERROR_ALREADY_EXISTS) {
+				mg_send_http_error(conn,
+				                   412,
+				                   "Destination already exists: %s",
+				                   dest_path);
+				return;
+			}
+			rc = -1;
+			http_status = 400;
+		}
+	}
+
+#else
+	{
+		/* Linux uses already UTF-8, we don't need to convert file names. */
+
+		if (do_copy) {
+			/* TODO: COPY for Linux. */
+			mg_send_http_error(conn, 403, "%s", "COPY forbidden");
+			return;
+		}
+
+		rc = rename(path, dest_path);
+		if (rc) {
+			switch (errno) {
+			case EEXIST:
+				http_status = 412;
+				break;
+			case EACCES:
+				http_status = 403;
+				break;
+			case ENOENT:
+				http_status = 409;
+				break;
+			}
 		}
 	}
+#endif
+
+	if (rc == 0) {
+		/* Create 204 "No Content" response */
+		mg_response_header_start(conn, 204);
+		mg_response_header_add(conn, "Content-Length", "0", -1);
+
+		/* Send all headers - there is no body */
+		mg_response_header_send(conn);
+	} else {
+		mg_send_http_error(conn, http_status, "Operation failed");
+	}
 }
 
 
@@ -11665,6 +12166,8 @@ put_file(struct mg_connection *conn, const char *path)
 		return;
 	}
 
+	DEBUG_TRACE("store %s", path);
+
 	if (mg_stat(conn, path, &file.stat)) {
 		/* File already exists */
 		conn->status_code = 200;
@@ -11735,7 +12238,7 @@ put_file(struct mg_connection *conn, const char *path)
 	}
 
 	/* A file should be created or overwritten. */
-	/* Currently CivetWeb does not nead read+write access. */
+	/* Currently CivetWeb does not need read+write access. */
 	if (!mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &file)
 	    || file.access.fp == NULL) {
 		(void)mg_fclose(&file.access);
@@ -11752,7 +12255,13 @@ put_file(struct mg_connection *conn, const char *path)
 	r1 = r2 = 0;
 	if ((range != NULL) && parse_range_header(range, &r1, &r2) > 0) {
 		conn->status_code = 206; /* Partial content */
-		fseeko(file.access.fp, r1, SEEK_SET);
+		if (0 != fseeko(file.access.fp, r1, SEEK_SET)) {
+			mg_send_http_error(conn,
+			                   500,
+			                   "Error: Internal error processing file %s",
+			                   path);
+			return;
+		}
 	}
 
 	if (!forward_body_data(conn, file.access.fp, INVALID_SOCKET, NULL)) {
@@ -11794,6 +12303,8 @@ delete_file(struct mg_connection *conn, const char *path)
 		return;
 	}
 
+	DEBUG_TRACE("delete %s", path);
+
 	if (de.file.is_directory) {
 		if (remove_directory(conn, path)) {
 			/* Delete is successful: Return 204 without content. */
@@ -11881,7 +12392,7 @@ do_ssi_include(struct mg_connection *conn,
 
 	} else if ((sscanf(tag, " file=\"%511[^\"]\"", file_name) == 1)
 	           || (sscanf(tag, " \"%511[^\"]\"", file_name) == 1)) {
-		/* File name is relative to the currect document */
+		/* File name is relative to the current document */
 		file_name[511] = 0;
 		(void)mg_snprintf(conn, &truncated, path, sizeof(path), "%s", ssi);
 
@@ -11920,7 +12431,7 @@ do_ssi_include(struct mg_connection *conn,
 		    > 0) {
 			send_ssi_file(conn, path, &file, include_level + 1);
 		} else {
-			send_file_data(conn, &file, 0, INT64_MAX);
+			send_file_data(conn, &file, 0, INT64_MAX, 0); /* send static file */
 		}
 		(void)mg_fclose(&file.access); /* Ignore errors for readonly files */
 	}
@@ -11944,7 +12455,7 @@ do_ssi_exec(struct mg_connection *conn, char *tag)
 			                cmd,
 			                strerror(ERRNO));
 		} else {
-			send_file_data(conn, &file, 0, INT64_MAX);
+			send_file_data(conn, &file, 0, INT64_MAX, 0); /* send static file */
 			pclose(file.access.fp);
 		}
 	}
@@ -12080,22 +12591,11 @@ handle_ssi_file_request(struct mg_connection *conn,
 {
 	char date[64];
 	time_t curtime = time(NULL);
-	const char *cors_orig_cfg;
-	const char *cors1, *cors2;
 
 	if ((conn == NULL) || (path == NULL) || (filep == NULL)) {
 		return;
 	}
 
-	cors_orig_cfg = conn->dom_ctx->config[ACCESS_CONTROL_ALLOW_ORIGIN];
-	if (cors_orig_cfg && *cors_orig_cfg && mg_get_header(conn, "Origin")) {
-		/* Cross-origin resource sharing (CORS). */
-		cors1 = "Access-Control-Allow-Origin";
-		cors2 = cors_orig_cfg;
-	} else {
-		cors1 = cors2 = "";
-	}
-
 	if (!mg_fopen(conn, path, MG_FOPEN_MODE_READ, filep)) {
 		/* File exists (precondition for calling this function),
 		 * but can not be opened by the server. */
@@ -12115,10 +12615,8 @@ handle_ssi_file_request(struct mg_connection *conn,
 		mg_response_header_start(conn, 200);
 		send_no_cache_header(conn);
 		send_additional_header(conn);
+		send_cors_header(conn);
 		mg_response_header_add(conn, "Content-Type", "text/html", -1);
-		if (cors1[0]) {
-			mg_response_header_add(conn, cors1, cors2, -1);
-		}
 		mg_response_header_send(conn);
 
 		/* Header sent, now send body */
@@ -12133,7 +12631,7 @@ handle_ssi_file_request(struct mg_connection *conn,
 static void
 send_options(struct mg_connection *conn)
 {
-	if (!conn) {
+	if (!conn || !all_methods) {
 		return;
 	}
 
@@ -12143,13 +12641,10 @@ send_options(struct mg_connection *conn)
 
 	mg_response_header_start(conn, 200);
 	mg_response_header_add(conn, "Content-Type", "text/html", -1);
+
 	if (conn->protocol_type == PROTOCOL_TYPE_HTTP1) {
 		/* Use the same as before */
-		mg_response_header_add(
-		    conn,
-		    "Allow",
-		    "GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS, PROPFIND, MKCOL",
-		    -1);
+		mg_response_header_add(conn, "Allow", all_methods, -1);
 		mg_response_header_add(conn, "DAV", "1", -1);
 	} else {
 		/* TODO: Check this later for HTTP/2 */
@@ -12167,34 +12662,37 @@ print_props(struct mg_connection *conn,
             const char *name,
             struct mg_file_stat *filep)
 {
-	size_t href_size, i, j;
-	int len;
-	char *href, mtime[64];
+	size_t i;
+	char mtime[64];
+	char link_buf[UTF8_PATH_MAX * 2]; /* Path + server root */
+	char *link_concat;
+	size_t link_concat_len;
 
 	if ((conn == NULL) || (uri == NULL) || (name == NULL) || (filep == NULL)) {
 		return 0;
 	}
-	/* Estimate worst case size for encoding */
-	href_size = (strlen(uri) + strlen(name)) * 3 + 1;
-	href = (char *)mg_malloc(href_size);
-	if (href == NULL) {
+
+	link_concat_len = strlen(uri) + strlen(name) + 1;
+	link_concat = mg_malloc_ctx(link_concat_len, conn->phys_ctx);
+	if (!link_concat) {
 		return 0;
 	}
-	len = mg_url_encode(uri, href, href_size);
-	if (len >= 0) {
-		/* Append an extra string */
-		mg_url_encode(name, href + len, href_size - (size_t)len);
-	}
-	/* Directory separator should be preserved. */
-	for (i = j = 0; href[i]; j++) {
-		if (!strncmp(href + i, "%2f", 3)) {
-			href[j] = '/';
-			i += 3;
-		} else {
-			href[j] = href[i++];
-		}
-	}
-	href[j] = '\0';
+	strcpy(link_concat, uri);
+	strcat(link_concat, name);
+
+	/* Get full link used in request */
+	mg_construct_local_link(
+	    conn, link_buf, sizeof(link_buf), NULL, 0, link_concat);
+
+	/*
+	OutputDebugStringA("print_props:\n  uri: ");
+	OutputDebugStringA(uri);
+	OutputDebugStringA("\n  name: ");
+	OutputDebugStringA(name);
+	OutputDebugStringA("\n  link: ");
+	OutputDebugStringA(link_buf);
+	OutputDebugStringA("\n");
+	*/
 
 	gmt_time_string(mtime, sizeof(mtime), &filep->last_modified);
 	mg_printf(conn,
@@ -12205,15 +12703,40 @@ print_props(struct mg_connection *conn,
 	          "<d:resourcetype>%s</d:resourcetype>"
 	          "<d:getcontentlength>%" INT64_FMT "</d:getcontentlength>"
 	          "<d:getlastmodified>%s</d:getlastmodified>"
-	          "</d:prop>"
-	          "<d:status>HTTP/1.1 200 OK</d:status>"
-	          "</d:propstat>"
-	          "</d:response>\n",
-	          href,
+	          "<d:lockdiscovery>",
+	          link_buf,
 	          filep->is_directory ? "<d:collection/>" : "",
 	          filep->size,
 	          mtime);
-	mg_free(href);
+
+	for (i = 0; i < NUM_WEBDAV_LOCKS; i++) {
+		struct twebdav_lock *dav_lock = conn->phys_ctx->webdav_lock;
+		if (!strcmp(dav_lock[i].path, link_buf)) {
+			mg_printf(conn,
+			          "<d:activelock>"
+			          "<d:locktype><d:write/></d:locktype>"
+			          "<d:lockscope><d:exclusive/></d:lockscope>"
+			          "<d:depth>0</d:depth>"
+			          "<d:owner>%s</d:owner>"
+			          "<d:timeout>Second-%u</d:timeout>"
+			          "<d:locktoken>"
+			          "<d:href>%s</d:href>"
+			          "</d:locktoken>"
+			          "</d:activelock>\n",
+			          dav_lock[i].user,
+			          (unsigned)LOCK_DURATION_S,
+			          dav_lock[i].token);
+		}
+	}
+
+	mg_printf(conn,
+	          "</d:lockdiscovery>"
+	          "</d:prop>"
+	          "<d:status>HTTP/1.1 200 OK</d:status>"
+	          "</d:propstat>"
+	          "</d:response>\n");
+
+	mg_free(link_concat);
 	return 1;
 }
 
@@ -12238,46 +12761,242 @@ handle_propfind(struct mg_connection *conn,
                 struct mg_file_stat *filep)
 {
 	const char *depth = mg_get_header(conn, "Depth");
-	char date[64];
-	time_t curtime = time(NULL);
-
-	gmt_time_string(date, sizeof(date), &curtime);
 
 	if (!conn || !path || !filep || !conn->dom_ctx) {
 		return;
 	}
 
+	/* return 207 "Multi-Status" */
 	conn->must_close = 1;
+	mg_response_header_start(conn, 207);
+	send_static_cache_header(conn);
+	send_additional_header(conn);
+	mg_response_header_add(conn,
+	                       "Content-Type",
+	                       "application/xml; charset=utf-8",
+	                       -1);
+	mg_response_header_send(conn);
+
+	/* Content */
+	mg_printf(conn,
+	          "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+	          "<d:multistatus xmlns:d='DAV:'>\n");
+
+	/* Print properties for the requested resource itself */
+	print_props(conn, conn->request_info.local_uri, "", filep);
+
+	/* If it is a directory, print directory entries too if Depth is not 0
+	 */
+	if (filep->is_directory
+	    && !mg_strcasecmp(conn->dom_ctx->config[ENABLE_DIRECTORY_LISTING],
+	                      "yes")
+	    && ((depth == NULL) || (strcmp(depth, "0") != 0))) {
+		scan_directory(conn, path, conn, &print_dav_dir_entry);
+	}
+
+	mg_printf(conn, "%s\n", "</d:multistatus>");
+}
+
+
+static void
+dav_lock_file(struct mg_connection *conn, const char *path)
+{
+	/* internal function - therefore conn is assumed to be valid */
+	char link_buf[UTF8_PATH_MAX * 2]; /* Path + server root */
+	uint64_t new_locktime;
+	int lock_index = -1;
+	int i;
+	uint64_t LOCK_DURATION_NS =
+	    (uint64_t)(LOCK_DURATION_S) * (uint64_t)1000000000;
+	struct twebdav_lock *dav_lock = conn->phys_ctx->webdav_lock;
+
+	if (!path || !conn->dom_ctx || !conn->request_info.remote_user) {
+		return;
+	}
+	mg_get_request_link(conn, link_buf, sizeof(link_buf));
+
+	/* const char *refresh = mg_get_header(conn, "If"); */
+	/* Link refresh should have an "If" header:
+	 * http://www.webdav.org/specs/rfc2518.html#n-example---refreshing-a-write-lock
+	 * But it seems Windows Explorer does not send them.
+	 */
+
+	mg_lock_context(conn->phys_ctx);
+	new_locktime = mg_get_current_time_ns();
+
+	/* Find a slot for a lock */
+	while (lock_index < 0) {
+		/* find existing lock */
+		for (i = 0; i < NUM_WEBDAV_LOCKS; i++) {
+			if (!strcmp(dav_lock[i].path, link_buf)) {
+				if (!strcmp(conn->request_info.remote_user, dav_lock[i].user)) {
+					/* locked by the same user */
+					dav_lock[i].locktime = new_locktime;
+					lock_index = i;
+					break;
+				} else {
+					/* already locked by someone else */
+					if (new_locktime
+					    > (dav_lock[i].locktime + LOCK_DURATION_NS)) {
+						/* Lock expired */
+						dav_lock[i].path[0] = 0;
+					} else {
+						/* Lock still valid */
+						mg_unlock_context(conn->phys_ctx);
+						mg_send_http_error(conn, 423, "%s", "Already locked");
+						return;
+					}
+				}
+			}
+		}
+
+		/* create new lock token */
+		for (i = 0; i < NUM_WEBDAV_LOCKS; i++) {
+			if (dav_lock[i].path[0] == 0) {
+				char s[32];
+				dav_lock[i].locktime = mg_get_current_time_ns();
+				sprintf(s, "%" UINT64_FMT, (uint64_t)dav_lock[i].locktime);
+				mg_md5(dav_lock[i].token,
+				       link_buf,
+				       "\x01",
+				       s,
+				       "\x01",
+				       conn->request_info.remote_user,
+				       NULL);
+				mg_strlcpy(dav_lock[i].path,
+				           link_buf,
+				           sizeof(dav_lock[i].path));
+				mg_strlcpy(dav_lock[i].user,
+				           conn->request_info.remote_user,
+				           sizeof(dav_lock[i].user));
+				lock_index = i;
+				break;
+			}
+		}
+		if (lock_index < 0) {
+			/* too many locks. Find oldest lock */
+			uint64_t oldest_locktime = dav_lock[0].locktime;
+			lock_index = 0;
+			for (i = 1; i < NUM_WEBDAV_LOCKS; i++) {
+				if (dav_lock[i].locktime < oldest_locktime) {
+					oldest_locktime = dav_lock[i].locktime;
+					lock_index = i;
+				}
+			}
+			/* invalidate oldest lock */
+			dav_lock[lock_index].path[0] = 0;
+		}
+	}
+	mg_unlock_context(conn->phys_ctx);
+
+	/* return 200 "OK" */
+	conn->must_close = 1;
+	mg_response_header_start(conn, 200);
+	send_static_cache_header(conn);
+	send_additional_header(conn);
+	mg_response_header_add(conn,
+	                       "Content-Type",
+	                       "application/xml; charset=utf-8",
+	                       -1);
+	mg_response_header_add(conn, "Lock-Token", dav_lock[lock_index].token, -1);
+	mg_response_header_send(conn);
+
+	/* Content */
+	mg_printf(conn,
+	          "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+	          "<d:prop xmlns:d=\"DAV:\">\n"
+	          "     <d:lockdiscovery>\n"
+	          "       <d:activelock>\n"
+	          "         <d:lockscope><d:exclusive/></d:lockscope>\n"
+	          "         <d:locktype><d:write/></d:locktype>\n"
+	          "         <d:owner>\n"
+	          "           <d:href>%s</d:href>\n"
+	          "         </d:owner>\n"
+	          "         <d:timeout>Second-%u</d:timeout>\n"
+	          "         <d:locktoken><d:href>%s</d:href></d:locktoken>\n"
+	          "         <d:lockroot>\n"
+	          "           <d:href>%s</d:href>\n"
+	          "         </d:lockroot>\n"
+	          "       </d:activelock>\n"
+	          "     </d:lockdiscovery>\n"
+	          "   </d:prop>\n",
+	          dav_lock[lock_index].user,
+	          (LOCK_DURATION_S),
+	          dav_lock[lock_index].token,
+	          dav_lock[lock_index].path);
+}
+
+
+static void
+dav_unlock_file(struct mg_connection *conn, const char *path)
+{
+	/* internal function - therefore conn is assumed to be valid */
+	char link_buf[UTF8_PATH_MAX * 2]; /* Path + server root */
+	struct twebdav_lock *dav_lock = conn->phys_ctx->webdav_lock;
+	int lock_index;
+
+	if (!path || !conn->dom_ctx || !conn->request_info.remote_user) {
+		return;
+	}
+
+	mg_get_request_link(conn, link_buf, sizeof(link_buf));
+
+	mg_lock_context(conn->phys_ctx);
+	/* find existing lock */
+	for (lock_index = 0; lock_index < NUM_WEBDAV_LOCKS; lock_index++) {
+		if (!strcmp(dav_lock[lock_index].path, link_buf)) {
+			/* Success: return 204 "No Content" */
+			mg_unlock_context(conn->phys_ctx);
+			conn->must_close = 1;
+			mg_response_header_start(conn, 204);
+			mg_response_header_send(conn);
+			return;
+		}
+	}
+	mg_unlock_context(conn->phys_ctx);
+
+	/* Error: Cannot unlock a resource that is not locked */
+	mg_send_http_error(conn, 423, "%s", "Lock not found");
+}
+
+
+static void
+dav_proppatch(struct mg_connection *conn, const char *path)
+{
+	char link_buf[UTF8_PATH_MAX * 2]; /* Path + server root */
+
+	if (!conn || !path || !conn->dom_ctx) {
+		return;
+	}
 
 	/* return 207 "Multi-Status" */
+	conn->must_close = 1;
 	mg_response_header_start(conn, 207);
 	send_static_cache_header(conn);
 	send_additional_header(conn);
-	mg_response_header_add(conn, "Content-Type", "text/xml; charset=utf-8", -1);
+	mg_response_header_add(conn,
+	                       "Content-Type",
+	                       "application/xml; charset=utf-8",
+	                       -1);
 	mg_response_header_send(conn);
 
+	mg_get_request_link(conn, link_buf, sizeof(link_buf));
+
 	/* Content */
 	mg_printf(conn,
 	          "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
-	          "<d:multistatus xmlns:d='DAV:'>\n");
-
-	/* Print properties for the requested resource itself */
-	print_props(conn, conn->request_info.local_uri, "", filep);
-
-	/* If it is a directory, print directory entries too if Depth is not 0
-	 */
-	if (filep->is_directory
-	    && !mg_strcasecmp(conn->dom_ctx->config[ENABLE_DIRECTORY_LISTING],
-	                      "yes")
-	    && ((depth == NULL) || (strcmp(depth, "0") != 0))) {
-		scan_directory(conn, path, conn, &print_dav_dir_entry);
-	}
-
-	mg_printf(conn, "%s\n", "</d:multistatus>");
+	          "<d:multistatus xmlns:d='DAV:'>\n"
+	          "<d:response>\n<d:href>%s</d:href>\n",
+	          link_buf);
+	mg_printf(conn,
+	          "<d:propstat><d:status>HTTP/1.1 403 "
+	          "Forbidden</d:status></d:propstat>\n");
+	mg_printf(conn, "%s\n", "</d:response></d:multistatus>");
 }
 #endif
 
-void
+
+CIVETWEB_API void
 mg_lock_connection(struct mg_connection *conn)
 {
 	if (conn) {
@@ -12285,7 +13004,8 @@ mg_lock_connection(struct mg_connection *conn)
 	}
 }
 
-void
+
+CIVETWEB_API void
 mg_unlock_connection(struct mg_connection *conn)
 {
 	if (conn) {
@@ -12293,7 +13013,8 @@ mg_unlock_connection(struct mg_connection *conn)
 	}
 }
 
-void
+
+CIVETWEB_API void
 mg_lock_context(struct mg_context *ctx)
 {
 	if (ctx && (ctx->context_type == CONTEXT_SERVER)) {
@@ -12301,7 +13022,8 @@ mg_lock_context(struct mg_context *ctx)
 	}
 }
 
-void
+
+CIVETWEB_API void
 mg_unlock_context(struct mg_context *ctx)
 {
 	if (ctx && (ctx->context_type == CONTEXT_SERVER)) {
@@ -12321,16 +13043,21 @@ mg_unlock_context(struct mg_context *ctx)
 #if defined(USE_WEBSOCKET)
 
 #if !defined(NO_SSL_DL)
+#if !defined(OPENSSL_API_3_0)
 #define SHA_API static
 #include "sha1.inl"
 #endif
+#endif
 
 static int
 send_websocket_handshake(struct mg_connection *conn, const char *websock_key)
 {
 	static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
 	char buf[100], sha[20], b64_sha[sizeof(sha) * 2];
+	size_t dst_len = sizeof(b64_sha);
+#if !defined(OPENSSL_API_3_0)
 	SHA_CTX sha_ctx;
+#endif
 	int truncated;
 
 	/* Calculate Sec-WebSocket-Accept reply from Sec-WebSocket-Key. */
@@ -12342,10 +13069,19 @@ send_websocket_handshake(struct mg_connection *conn, const char *websock_key)
 
 	DEBUG_TRACE("%s", "Send websocket handshake");
 
+#if defined(OPENSSL_API_3_0)
+	EVP_Digest((unsigned char *)buf,
+	           (uint32_t)strlen(buf),
+	           (unsigned char *)sha,
+	           NULL,
+	           EVP_get_digestbyname("sha1"),
+	           NULL);
+#else
 	SHA1_Init(&sha_ctx);
 	SHA1_Update(&sha_ctx, (unsigned char *)buf, (uint32_t)strlen(buf));
 	SHA1_Final((unsigned char *)sha, &sha_ctx);
-	base64_encode((unsigned char *)sha, sizeof(sha), b64_sha);
+#endif
+	mg_base64_encode((unsigned char *)sha, sizeof(sha), b64_sha, &dst_len);
 	mg_printf(conn,
 	          "HTTP/1.1 101 Switching Protocols\r\n"
 	          "Upgrade: websocket\r\n"
@@ -12411,7 +13147,6 @@ read_websocket(struct mg_connection *conn,
 	unsigned char mem[4096];
 	unsigned char mop; /* mask flag and opcode */
 
-
 	/* Variables used for connection monitoring */
 	double timeout = -1.0;
 	int enable_ping_pong = 0;
@@ -12592,7 +13327,6 @@ read_websocket(struct mg_connection *conn,
 					break;
 				}
 
-
 			} else {
 				/* Exit the loop if callback signals to exit (server side),
 				 * or "connection close" opcode received (client side). */
@@ -12817,7 +13551,7 @@ mg_websocket_write_exec(struct mg_connection *conn,
 		header[0] = 0xC0u | (unsigned char)((unsigned)opcode & 0xf);
 		conn->websocket_deflate_state.avail_in = (uInt)dataLen;
 		conn->websocket_deflate_state.next_in = (unsigned char *)data;
-		deflated_size = (Bytef *)compressBound((uLong)dataLen);
+		deflated_size = (size_t)compressBound((uLong)dataLen);
 		deflated = mg_calloc(deflated_size, sizeof(Bytef));
 		if (deflated == NULL) {
 			mg_cry_internal(
@@ -12891,7 +13625,8 @@ mg_websocket_write_exec(struct mg_connection *conn,
 	return retval;
 }
 
-int
+
+CIVETWEB_API int
 mg_websocket_write(struct mg_connection *conn,
                    int opcode,
                    const char *data,
@@ -12927,7 +13662,7 @@ mask_data(const char *in, size_t in_len, uint32_t masking_key, char *out)
 }
 
 
-int
+CIVETWEB_API int
 mg_websocket_client_write(struct mg_connection *conn,
                           int opcode,
                           const char *data,
@@ -13019,7 +13754,7 @@ handle_websocket_request(struct mg_connection *conn,
 		return;
 	}
 
-	/* Step 1.3: Could check for "Host", but we do not really nead this
+	/* Step 1.3: Could check for "Host", but we do not really need this
 	 * value for anything, so just ignore it. */
 
 	/* Step 2: If a callback is responsible, call it. */
@@ -13030,28 +13765,35 @@ handle_websocket_request(struct mg_connection *conn,
 		                                          "Sec-WebSocket-Protocol",
 		                                          protocols,
 		                                          64);
+
 		if ((nbSubprotocolHeader > 0) && subprotocols) {
-			int cnt = 0;
-			int idx;
-			unsigned long len;
+
+			int headerNo, idx;
+			size_t len;
 			const char *sep, *curSubProtocol,
 			    *acceptedWebSocketSubprotocol = NULL;
 
-
 			/* look for matching subprotocol */
-			do {
-				const char *protocol = protocols[cnt];
-
-				do {
-					sep = strchr(protocol, ',');
-					curSubProtocol = protocol;
-					len = sep ? (unsigned long)(sep - protocol)
-					          : (unsigned long)strlen(protocol);
-					while (sep && isspace((unsigned char)*++sep))
-						; // ignore leading whitespaces
-					protocol = sep;
+			for (headerNo = 0; headerNo < nbSubprotocolHeader; headerNo++) {
+				/* There might be multiple headers ... */
+				const char *protocol = protocols[headerNo];
+				curSubProtocol = protocol;
+
+				/* ... and in every header there might be a , separated list */
+				while (!acceptedWebSocketSubprotocol && (*curSubProtocol)) {
+
+					while ((*curSubProtocol == ' ') || (*curSubProtocol == ','))
+						curSubProtocol++;
+					sep = strchr(curSubProtocol, ',');
+					if (sep) {
+						len = (size_t)(sep - curSubProtocol);
+					} else {
+						len = strlen(curSubProtocol);
+					}
 
 					for (idx = 0; idx < subprotocols->nb_subprotocols; idx++) {
+						// COMPARE: curSubProtocol ==
+						// subprotocols->subprotocols[idx]
 						if ((strlen(subprotocols->subprotocols[idx]) == len)
 						    && (strncmp(curSubProtocol,
 						                subprotocols->subprotocols[idx],
@@ -13062,38 +13804,12 @@ handle_websocket_request(struct mg_connection *conn,
 							break;
 						}
 					}
-				} while (sep && !acceptedWebSocketSubprotocol);
-			} while (++cnt < nbSubprotocolHeader
-			         && !acceptedWebSocketSubprotocol);
+					curSubProtocol += len;
+				}
+			}
 
 			conn->request_info.acceptedWebSocketSubprotocol =
 			    acceptedWebSocketSubprotocol;
-
-		} else if (nbSubprotocolHeader > 0) {
-			/* keep legacy behavior */
-			const char *protocol = protocols[0];
-
-			/* The protocol is a comma separated list of names. */
-			/* The server must only return one value from this list. */
-			/* First check if it is a list or just a single value. */
-			const char *sep = strrchr(protocol, ',');
-			if (sep == NULL) {
-				/* Just a single protocol -> accept it. */
-				conn->request_info.acceptedWebSocketSubprotocol = protocol;
-			} else {
-				/* Multiple protocols -> accept the last one. */
-				/* This is just a quick fix if the client offers multiple
-				 * protocols. The handler should have a list of accepted
-				 * protocols on his own
-				 * and use it to select one protocol among those the client
-				 * has
-				 * offered.
-				 */
-				while (isspace((unsigned char)*++sep)) {
-					; /* ignore leading whitespaces */
-				}
-				conn->request_info.acceptedWebSocketSubprotocol = sep;
-			}
 		}
 
 #if defined(USE_ZLIB) && defined(MG_EXPERIMENTAL_INTERFACES)
@@ -13120,7 +13836,7 @@ handle_websocket_request(struct mg_connection *conn,
 			    conn->dom_ctx->config[LUA_WEBSOCKET_EXTENSIONS], path);
 		}
 
-		if (lua_websock) {
+		if (lua_websock > 0) {
 			/* Step 3.2: Lua is responsible: call it. */
 			conn->lua_websocket_state = lua_websocket_new(path, conn);
 			if (!conn->lua_websocket_state) {
@@ -13195,30 +13911,40 @@ handle_websocket_request(struct mg_connection *conn,
 static int
 should_switch_to_protocol(const struct mg_connection *conn)
 {
-	const char *upgrade, *connection;
+	const char *connection_headers[8];
+	const char *upgrade_to;
+	int connection_header_count, i, should_upgrade;
 
-	/* A websocket protocoll has the following HTTP headers:
+	/* A websocket protocol has the following HTTP headers:
 	 *
 	 * Connection: Upgrade
 	 * Upgrade: Websocket
+	 *
+	 * It seems some clients use multiple headers:
+	 * see https://github.com/civetweb/civetweb/issues/1083
 	 */
-
-	connection = mg_get_header(conn, "Connection");
-	if (connection == NULL) {
-		return PROTOCOL_TYPE_HTTP1;
+	connection_header_count = get_req_headers(&conn->request_info,
+	                                          "Connection",
+	                                          connection_headers,
+	                                          8);
+	should_upgrade = 0;
+	for (i = 0; i < connection_header_count; i++) {
+		if (0 != mg_strcasestr(connection_headers[i], "upgrade")) {
+			should_upgrade = 1;
+		}
 	}
-	if (!mg_strcasestr(connection, "upgrade")) {
+	if (!should_upgrade) {
 		return PROTOCOL_TYPE_HTTP1;
 	}
 
-	upgrade = mg_get_header(conn, "Upgrade");
-	if (upgrade == NULL) {
+	upgrade_to = mg_get_header(conn, "Upgrade");
+	if (upgrade_to == NULL) {
 		/* "Connection: Upgrade" without "Upgrade" Header --> Error */
 		return -1;
 	}
 
 	/* Upgrade to ... */
-	if (0 != mg_strcasestr(upgrade, "websocket")) {
+	if (0 != mg_strcasestr(upgrade_to, "websocket")) {
 		/* The headers "Host", "Sec-WebSocket-Key", "Sec-WebSocket-Protocol" and
 		 * "Sec-WebSocket-Version" are also required.
 		 * Don't check them here, since even an unsupported websocket protocol
@@ -13227,7 +13953,7 @@ should_switch_to_protocol(const struct mg_connection *conn)
 		 */
 		return PROTOCOL_TYPE_WEBSOCKET; /* Websocket */
 	}
-	if (0 != mg_strcasestr(upgrade, "h2")) {
+	if (0 != mg_strcasestr(upgrade_to, "h2")) {
 		return PROTOCOL_TYPE_HTTP2; /* Websocket */
 	}
 
@@ -13387,7 +14113,7 @@ set_throttle(const char *spec, const union usa *rsa, const char *uri)
 }
 
 
-/* The mg_upload function is superseeded by mg_handle_form_request. */
+/* The mg_upload function is superseded by mg_handle_form_request. */
 #include "handle_form.inl"
 
 
@@ -13736,7 +14462,7 @@ mg_set_handler_type(struct mg_context *phys_ctx,
 }
 
 
-void
+CIVETWEB_API void
 mg_set_request_handler(struct mg_context *ctx,
                        const char *uri,
                        mg_request_handler handler,
@@ -13758,7 +14484,7 @@ mg_set_request_handler(struct mg_context *ctx,
 }
 
 
-void
+CIVETWEB_API void
 mg_set_websocket_handler(struct mg_context *ctx,
                          const char *uri,
                          mg_websocket_connect_handler connect_handler,
@@ -13778,7 +14504,7 @@ mg_set_websocket_handler(struct mg_context *ctx,
 }
 
 
-void
+CIVETWEB_API void
 mg_set_websocket_handler_with_subprotocols(
     struct mg_context *ctx,
     const char *uri,
@@ -13808,7 +14534,7 @@ mg_set_websocket_handler_with_subprotocols(
 }
 
 
-void
+CIVETWEB_API void
 mg_set_auth_handler(struct mg_context *ctx,
                     const char *uri,
                     mg_authorization_handler handler,
@@ -13982,7 +14708,7 @@ handle_request(struct mg_connection *conn)
 	int uri_len, ssl_index;
 	int is_found = 0, is_script_resource = 0, is_websocket_request = 0,
 	    is_put_or_delete_request = 0, is_callback_resource = 0,
-	    is_template_text_file = 0;
+	    is_template_text_file = 0, is_webdav_request = 0;
 	int i;
 	struct mg_file file = STRUCT_FILE_INITIALIZER;
 	mg_request_handler callback_handler = NULL;
@@ -14036,11 +14762,11 @@ handle_request(struct mg_connection *conn)
 
 	/* 1.3. decode url (if config says so) */
 	if (should_decode_url(conn)) {
-		mg_url_decode(
-		    ri->local_uri, uri_len, (char *)ri->local_uri, uri_len + 1, 0);
+		url_decode_in_place((char *)ri->local_uri);
 	}
 
-	/* URL decode the query-string only if explicity set in the configuration */
+	/* URL decode the query-string only if explicitly set in the configuration
+	 */
 	if (conn->request_info.query_string) {
 		if (should_decode_query_string(conn)) {
 			url_decode_in_place((char *)conn->request_info.query_string);
@@ -14062,7 +14788,7 @@ handle_request(struct mg_connection *conn)
 	ri->local_uri = tmp;
 
 	/* step 1. completed, the url is known now */
-	DEBUG_TRACE("URL: %s", ri->local_uri);
+	DEBUG_TRACE("REQUEST: %s %s", ri->request_method, ri->local_uri);
 
 	/* 2. if this ip has limited speed, set it for this connection */
 	conn->throttle = set_throttle(conn->dom_ctx->config[THROTTLE],
@@ -14082,11 +14808,13 @@ handle_request(struct mg_connection *conn)
 			if (!conn->must_close) {
 				discard_unread_request_data(conn);
 			}
+			DEBUG_TRACE("%s", "begin_request handled request");
 			return;
 		} else if (i == 0) {
 			/* civetweb should process the request */
 		} else {
 			/* unspecified - may change with the next version */
+			DEBUG_TRACE("%s", "done (undocumented behavior)");
 			return;
 		}
 	}
@@ -14155,8 +14883,8 @@ handle_request(struct mg_connection *conn)
 				}
 			}
 			mg_printf(conn, "Access-Control-Max-Age: 60\r\n");
-
 			mg_printf(conn, "\r\n");
+			DEBUG_TRACE("%s", "OPTIONS done");
 			return;
 		}
 	}
@@ -14195,6 +14923,9 @@ handle_request(struct mg_connection *conn)
 		is_callback_resource = 1;
 		is_script_resource = 1;
 		is_put_or_delete_request = is_put_or_delete_method(conn);
+		/* Never handle a C callback according to File WebDav rules,
+		 * even if it is a webdav method */
+		is_webdav_request = 0; /* is_civetweb_webdav_method(conn); */
 	} else {
 	no_callback_resource:
 
@@ -14210,9 +14941,24 @@ handle_request(struct mg_connection *conn)
 		              &is_script_resource,
 		              &is_websocket_request,
 		              &is_put_or_delete_request,
+		              &is_webdav_request,
 		              &is_template_text_file);
 	}
 
+	/* 5.3. A webdav request (PROPFIND/PROPPATCH/LOCK/UNLOCK) */
+	if (is_webdav_request) {
+		/* TODO: Do we need a config option? */
+		const char *webdav_enable = conn->dom_ctx->config[ENABLE_WEBDAV];
+		if (webdav_enable[0] != 'y') {
+			mg_send_http_error(conn,
+			                   405,
+			                   "%s method not allowed",
+			                   conn->request_info.request_method);
+			DEBUG_TRACE("%s", "webdav rejected");
+			return;
+		}
+	}
+
 	/* 6. authorization check */
 	/* 6.1. a custom authorization handler is installed */
 	if (get_request_handler(conn,
@@ -14230,7 +14976,7 @@ handle_request(struct mg_connection *conn)
 
 			/* Callback handler will not be used anymore. Release it */
 			release_handler_ref(conn, handler_info);
-
+			DEBUG_TRACE("%s", "auth handler rejected request");
 			return;
 		}
 	} else if (is_put_or_delete_request && !is_script_resource
@@ -14241,7 +14987,8 @@ handle_request(struct mg_connection *conn)
 #if defined(NO_FILES)
 		if (1) {
 #else
-		if (conn->dom_ctx->config[DOCUMENT_ROOT] == NULL) {
+		if (conn->dom_ctx->config[DOCUMENT_ROOT] == NULL
+		    || conn->dom_ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL) {
 #endif
 			/* This code path will not be called for request handlers */
 			DEBUG_ASSERT(handler_info == NULL);
@@ -14252,6 +14999,7 @@ handle_request(struct mg_connection *conn)
 			                   405,
 			                   "%s method not allowed",
 			                   conn->request_info.request_method);
+			DEBUG_TRACE("%s", "all file based put/delete requests rejected");
 			return;
 		}
 
@@ -14261,6 +15009,7 @@ handle_request(struct mg_connection *conn)
 		 */
 		if (!is_authorized_for_put(conn)) {
 			send_authorization_request(conn, NULL);
+			DEBUG_TRACE("%s", "file write needs authorization");
 			return;
 		}
 #endif
@@ -14274,7 +15023,7 @@ handle_request(struct mg_connection *conn)
 
 			/* Callback handler will not be used anymore. Release it */
 			release_handler_ref(conn, handler_info);
-
+			DEBUG_TRACE("%s", "access authorization required");
 			return;
 		}
 	}
@@ -14316,15 +15065,6 @@ handle_request(struct mg_connection *conn)
 				/* For the moment, use option c: We look for a proper file,
 				 * but since a file request is not always a script resource,
 				 * the authorization check might be different. */
-				interpret_uri(conn,
-				              path,
-				              sizeof(path),
-				              &file.stat,
-				              &is_found,
-				              &is_script_resource,
-				              &is_websocket_request,
-				              &is_put_or_delete_request,
-				              &is_template_text_file);
 				callback_handler = NULL;
 
 				/* Here we are at a dead end:
@@ -14351,6 +15091,7 @@ handle_request(struct mg_connection *conn)
 			                         callback_data);
 #endif
 		}
+		DEBUG_TRACE("%s", "websocket handling done");
 		return;
 	}
 
@@ -14378,6 +15119,7 @@ handle_request(struct mg_connection *conn)
 		} else {
 			mg_send_http_error(conn, 404, "%s", "Not found");
 		}
+		DEBUG_TRACE("%s", "websocket script done");
 		return;
 	} else
 #endif
@@ -14393,6 +15135,7 @@ handle_request(struct mg_connection *conn)
 	 * by a script file. Thus, a DOCUMENT_ROOT must exist. */
 	if (conn->dom_ctx->config[DOCUMENT_ROOT] == NULL) {
 		mg_send_http_error(conn, 404, "%s", "Not Found");
+		DEBUG_TRACE("%s", "no document root available");
 		return;
 	}
 
@@ -14400,34 +15143,89 @@ handle_request(struct mg_connection *conn)
 	if (is_script_resource) {
 		HTTP1_only;
 		handle_file_based_request(conn, path, &file);
+		DEBUG_TRACE("%s", "script handling done");
 		return;
 	}
 
+	/* Request was not handled by a callback or script. It will be
+	 * handled by a server internal method. */
+
 	/* 11. Handle put/delete/mkcol requests */
 	if (is_put_or_delete_request) {
 		HTTP1_only;
 		/* 11.1. PUT method */
 		if (!strcmp(ri->request_method, "PUT")) {
 			put_file(conn, path);
+			DEBUG_TRACE("handling %s request to %s done",
+			            ri->request_method,
+			            path);
 			return;
 		}
 		/* 11.2. DELETE method */
 		if (!strcmp(ri->request_method, "DELETE")) {
 			delete_file(conn, path);
+			DEBUG_TRACE("handling %s request to %s done",
+			            ri->request_method,
+			            path);
 			return;
 		}
 		/* 11.3. MKCOL method */
 		if (!strcmp(ri->request_method, "MKCOL")) {
-			mkcol(conn, path);
+			dav_mkcol(conn, path);
+			DEBUG_TRACE("handling %s request to %s done",
+			            ri->request_method,
+			            path);
+			return;
+		}
+		/* 11.4. MOVE method */
+		if (!strcmp(ri->request_method, "MOVE")) {
+			dav_move_file(conn, path, 0);
+			DEBUG_TRACE("handling %s request to %s done",
+			            ri->request_method,
+			            path);
+			return;
+		}
+		if (!strcmp(ri->request_method, "COPY")) {
+			dav_move_file(conn, path, 1);
+			DEBUG_TRACE("handling %s request to %s done",
+			            ri->request_method,
+			            path);
+			return;
+		}
+		/* 11.5. LOCK method */
+		if (!strcmp(ri->request_method, "LOCK")) {
+			dav_lock_file(conn, path);
+			DEBUG_TRACE("handling %s request to %s done",
+			            ri->request_method,
+			            path);
+			return;
+		}
+		/* 11.6. UNLOCK method */
+		if (!strcmp(ri->request_method, "UNLOCK")) {
+			dav_unlock_file(conn, path);
+			DEBUG_TRACE("handling %s request to %s done",
+			            ri->request_method,
+			            path);
 			return;
 		}
-		/* 11.4. PATCH method
+		/* 11.7. PROPPATCH method */
+		if (!strcmp(ri->request_method, "PROPPATCH")) {
+			dav_proppatch(conn, path);
+			DEBUG_TRACE("handling %s request to %s done",
+			            ri->request_method,
+			            path);
+			return;
+		}
+		/* 11.8. Other methods, e.g.: PATCH
 		 * This method is not supported for static resources,
 		 * only for scripts (Lua, CGI) and callbacks. */
 		mg_send_http_error(conn,
 		                   405,
 		                   "%s method not allowed",
 		                   conn->request_info.request_method);
+		DEBUG_TRACE("method %s on %s is not supported",
+		            ri->request_method,
+		            path);
 		return;
 	}
 
@@ -14435,30 +15233,40 @@ handle_request(struct mg_connection *conn)
 	 * hidden */
 	if (!is_found || (must_hide_file(conn, path))) {
 		mg_send_http_error(conn, 404, "%s", "Not found");
+		DEBUG_TRACE("handling %s request to %s: file not found",
+		            ri->request_method,
+		            path);
 		return;
 	}
 
 	/* 12. Directory uris should end with a slash */
-	if (file.stat.is_directory && (uri_len > 0)
+	if (file.stat.is_directory && ((uri_len = (int)strlen(ri->local_uri)) > 0)
 	    && (ri->local_uri[uri_len - 1] != '/')) {
 
-		size_t len = strlen(ri->request_uri);
-		size_t lenQS = ri->query_string ? strlen(ri->query_string) + 1 : 0;
-		char *new_path = (char *)mg_malloc_ctx(len + lenQS + 2, conn->phys_ctx);
+		/* Path + server root */
+		size_t buflen = UTF8_PATH_MAX * 2 + 2;
+		char *new_path;
+
+		if (ri->query_string) {
+			buflen += strlen(ri->query_string);
+		}
+		new_path = (char *)mg_malloc_ctx(buflen, conn->phys_ctx);
 		if (!new_path) {
 			mg_send_http_error(conn, 500, "out or memory");
 		} else {
-			memcpy(new_path, ri->request_uri, len);
-			new_path[len] = '/';
-			new_path[len + 1] = 0;
+			mg_get_request_link(conn, new_path, buflen - 1);
+			strcat(new_path, "/");
 			if (ri->query_string) {
-				new_path[len + 1] = '?';
-				/* Copy query string including terminating zero */
-				memcpy(new_path + len + 2, ri->query_string, lenQS);
+				/* Append ? and query string */
+				strcat(new_path, "?");
+				strcat(new_path, ri->query_string);
 			}
 			mg_send_http_redirect(conn, new_path, 301);
 			mg_free(new_path);
 		}
+		DEBUG_TRACE("%s request to %s: directory redirection sent",
+		            ri->request_method,
+		            path);
 		return;
 	}
 
@@ -14466,6 +15274,7 @@ handle_request(struct mg_connection *conn)
 	/* 13.1. Handle PROPFIND */
 	if (!strcmp(ri->request_method, "PROPFIND")) {
 		handle_propfind(conn, path, &file.stat);
+		DEBUG_TRACE("handling %s request to %s done", ri->request_method, path);
 		return;
 	}
 	/* 13.2. Handle OPTIONS for files */
@@ -14476,6 +15285,7 @@ handle_request(struct mg_connection *conn)
 		 * Lua and CGI scripts may fully support CORS this way (including
 		 * preflights). */
 		send_options(conn);
+		DEBUG_TRACE("handling %s request to %s done", ri->request_method, path);
 		return;
 	}
 	/* 13.3. everything but GET and HEAD (e.g. POST) */
@@ -14485,6 +15295,7 @@ handle_request(struct mg_connection *conn)
 		                   405,
 		                   "%s method not allowed",
 		                   conn->request_info.request_method);
+		DEBUG_TRACE("handling %s request to %s done", ri->request_method, path);
 		return;
 	}
 
@@ -14502,6 +15313,7 @@ handle_request(struct mg_connection *conn)
 			                   "%s",
 			                   "Error: Directory listing denied");
 		}
+		DEBUG_TRACE("handling %s request to %s done", ri->request_method, path);
 		return;
 	}
 
@@ -14509,6 +15321,9 @@ handle_request(struct mg_connection *conn)
 	if (is_template_text_file) {
 		HTTP1_only;
 		handle_file_based_request(conn, path, &file);
+		DEBUG_TRACE("handling %s request to %s done (template)",
+		            ri->request_method,
+		            path);
 		return;
 	}
 
@@ -14517,12 +15332,18 @@ handle_request(struct mg_connection *conn)
 	if ((!conn->in_error_handler) && is_not_modified(conn, &file.stat)) {
 		/* Send 304 "Not Modified" - this must not send any body data */
 		handle_not_modified_static_file_request(conn, &file);
+		DEBUG_TRACE("handling %s request to %s done (not modified)",
+		            ri->request_method,
+		            path);
 		return;
 	}
 #endif /* !NO_CACHING */
 
 	/* 17. Static file - not cached */
 	handle_static_file_request(conn, path, &file, NULL, NULL);
+	DEBUG_TRACE("handling %s request to %s done (static)",
+	            ri->request_method,
+	            path);
 
 #endif /* !defined(NO_FILES) */
 }
@@ -14535,7 +15356,7 @@ handle_file_based_request(struct mg_connection *conn,
                           struct mg_file *file)
 {
 #if !defined(NO_CGI)
-	unsigned char cgi_config_idx, inc, max;
+	int cgi_config_idx, inc, max;
 #endif
 
 	if (!conn || !conn->dom_ctx) {
@@ -14597,7 +15418,7 @@ handle_file_based_request(struct mg_connection *conn,
 			    > 0) {
 				if (is_in_script_path(conn, path)) {
 					/* CGI scripts may support all HTTP methods */
-					handle_cgi_request(conn, path, 0);
+					handle_cgi_request(conn, path, cgi_config_idx);
 				} else {
 					/* Script was in an illegal path */
 					mg_send_http_error(conn, 403, "%s", "Forbidden");
@@ -14761,7 +15582,7 @@ parse_port_string(const struct vec *vec, struct socket *so, int *ip_version)
 		*ip_version = 4;
 
 	} else if ((cb = strchr(vec->ptr, ':')) != NULL) {
-		/* String could be a hostname. This check algotithm
+		/* String could be a hostname. This check algorithm
 		 * will only work for RFC 952 compliant hostnames,
 		 * starting with a letter, containing only letters,
 		 * digits and hyphen ('-'). Newer specs may allow
@@ -15244,7 +16065,12 @@ log_access(const struct mg_connection *conn)
 	const struct mg_request_info *ri;
 	struct mg_file fi;
 	char date[64], src_addr[IP_ADDR_STR_LEN];
+#if defined(REENTRANT_TIME)
+	struct tm _tm;
+	struct tm *tm = &_tm;
+#else
 	struct tm *tm;
+#endif
 
 	const char *referer;
 	const char *user_agent;
@@ -15322,12 +16148,15 @@ log_access(const struct mg_connection *conn)
 
 	/* If we did not get a log message from Lua, create it here. */
 	if (!log_buf[0]) {
+#if defined(REENTRANT_TIME)
+		localtime_r(&conn->conn_birth_time, tm);
+#else
 		tm = localtime(&conn->conn_birth_time);
+#endif
 		if (tm != NULL) {
 			strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z", tm);
 		} else {
 			mg_strlcpy(date, "01/Jan/1970:00:00:00 +0000", sizeof(date));
-			date[sizeof(date) - 1] = '\0';
 		}
 
 		ri = &conn->request_info;
@@ -15359,7 +16188,7 @@ log_access(const struct mg_connection *conn)
 	/* Here we have a log message in log_buf. Call the callback */
 	if (conn->phys_ctx->callbacks.log_access) {
 		if (conn->phys_ctx->callbacks.log_access(conn, log_buf)) {
-			/* do not log if callack returns non-zero */
+			/* do not log if callback returns non-zero */
 			if (fi.access.fp) {
 				mg_fclose(&fi.access);
 			}
@@ -15716,7 +16545,7 @@ sslize(struct mg_connection *conn,
 					pollres =
 					    mg_poll(&pfd, 1, 50, &(conn->phys_ctx->stop_flag));
 					if (pollres < 0) {
-						/* Break if error occured (-1)
+						/* Break if error occurred (-1)
 						 * or server shutdown (-2) */
 						break;
 					}
@@ -16215,7 +17044,7 @@ ssl_get_protocol(int version_id)
  * examples, however some (maybe most, but not all) headers of OpenSSL
  * versions / OpenSSL compatibility layers have it. Having a different
  * definition will cause a warning in C and an error in C++. Use "const SSL
- * *", while automatical conversion from "SSL *" works for all compilers,
+ * *", while automatic conversion from "SSL *" works for all compilers,
  * but not other way around */
 static void
 ssl_info_callback(const SSL *ssl, int what, int ret)
@@ -16494,7 +17323,7 @@ init_ssl_ctx_impl(struct mg_context *phys_ctx,
 	                                                   phys_ctx->user_data));
 
 	/* If callback returns 0, civetweb sets up the SSL certificate.
-	 * If it returns 1, civetweb assumes the calback already did this.
+	 * If it returns 1, civetweb assumes the callback already did this.
 	 * If it returns -1, initializing ssl fails. */
 	if (callback_ret < 0) {
 		mg_cry_ctx_internal(phys_ctx,
@@ -16516,7 +17345,7 @@ init_ssl_ctx_impl(struct mg_context *phys_ctx,
 	                         phys_ctx->user_data));
 
 	/* If domain callback returns 0, civetweb sets up the SSL certificate.
-	 * If it returns 1, civetweb assumes the calback already did this.
+	 * If it returns 1, civetweb assumes the callback already did this.
 	 * If it returns -1, initializing ssl fails. */
 	if (callback_ret < 0) {
 		mg_cry_ctx_internal(phys_ctx,
@@ -16931,7 +17760,6 @@ close_socket_gracefully(struct mg_connection *conn)
 	/* Send FIN to the client */
 	shutdown(conn->client.sock, SHUTDOWN_WR);
 
-
 #if defined(_WIN32)
 	/* Read and discard pending incoming data. If we do not do that and
 	 * close
@@ -17063,7 +17891,6 @@ close_connection(struct mg_connection *conn)
 	 * it must be done in the connection_close callback. */
 	mg_set_user_connection_data(conn, NULL);
 
-
 #if defined(USE_SERVER_STATS)
 	conn->conn_state = 7; /* closing */
 #endif
@@ -17107,7 +17934,7 @@ close_connection(struct mg_connection *conn)
 }
 
 
-void
+CIVETWEB_API void
 mg_close_connection(struct mg_connection *conn)
 {
 	if ((conn == NULL) || (conn->phys_ctx == NULL)) {
@@ -17173,8 +18000,8 @@ mg_close_connection(struct mg_connection *conn)
 static struct mg_connection *
 mg_connect_client_impl(const struct mg_client_options *client_options,
                        int use_ssl,
-                       char *ebuf,
-                       size_t ebuf_len)
+                       struct mg_init_data *init,
+                       struct mg_error_data *error)
 {
 	struct mg_connection *conn = NULL;
 	SOCKET sock;
@@ -17188,18 +18015,31 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 	/* Size of structures, aligned to 8 bytes */
 	size_t conn_size = ((sizeof(struct mg_connection) + 7) >> 3) << 3;
 	size_t ctx_size = ((sizeof(struct mg_context) + 7) >> 3) << 3;
+	size_t alloc_size = conn_size + ctx_size + max_req_size;
 
-	conn =
-	    (struct mg_connection *)mg_calloc(1,
-	                                      conn_size + ctx_size + max_req_size);
+	(void)init; /* TODO: Implement required options */
+
+	conn = (struct mg_connection *)mg_calloc(1, alloc_size);
+
+	if (error != NULL) {
+		error->code = MG_ERROR_DATA_CODE_OK;
+		error->code_sub = 0;
+		if (error->text_buffer_size > 0) {
+			error->text[0] = 0;
+		}
+	}
 
 	if (conn == NULL) {
-		mg_snprintf(NULL,
-		            NULL, /* No truncation check for ebuf */
-		            ebuf,
-		            ebuf_len,
-		            "calloc(): %s",
-		            strerror(ERRNO));
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY;
+			error->code_sub = (unsigned)alloc_size;
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for ebuf */
+			            error->text,
+			            error->text_buffer_size,
+			            "calloc(): %s",
+			            strerror(ERRNO));
+		}
 		return NULL;
 	}
 
@@ -17224,12 +18064,11 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 	                    client_options->host,
 	                    client_options->port,
 	                    use_ssl,
-	                    ebuf,
-	                    ebuf_len,
+	                    error,
 	                    &sock,
 	                    &sa)) {
-		/* ebuf is set by connect_socket,
-		 * free all memory and return NULL; */
+		/* "error" will be set by connect_socket. */
+		/* free all memory and return NULL; */
 		mg_free(conn);
 		return NULL;
 	}
@@ -17237,37 +18076,48 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 #if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client
 #if (defined(OPENSSL_API_1_1) || defined(OPENSSL_API_3_0))                     \
     && !defined(NO_SSL_DL)
+
 	if (use_ssl
 	    && (conn->dom_ctx->ssl_ctx = SSL_CTX_new(TLS_client_method()))
 	           == NULL) {
-		mg_snprintf(NULL,
-		            NULL, /* No truncation check for ebuf */
-		            ebuf,
-		            ebuf_len,
-		            "SSL_CTX_new error: %s",
-		            ssl_error());
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INIT_TLS_FAILED;
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for ebuf */
+			            error->text,
+			            error->text_buffer_size,
+			            "SSL_CTX_new error: %s",
+			            ssl_error());
+		}
+
 		closesocket(sock);
 		mg_free(conn);
 		return NULL;
 	}
+
 #else
+
 	if (use_ssl
 	    && (conn->dom_ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method()))
 	           == NULL) {
-		mg_snprintf(NULL,
-		            NULL, /* No truncation check for ebuf */
-		            ebuf,
-		            ebuf_len,
-		            "SSL_CTX_new error: %s",
-		            ssl_error());
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INIT_TLS_FAILED;
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for ebuf */
+			            error->text,
+			            error->text_buffer_size,
+			            "SSL_CTX_new error: %s",
+			            ssl_error());
+		}
+
 		closesocket(sock);
 		mg_free(conn);
 		return NULL;
 	}
+
 #endif /* OPENSSL_API_1_1 || OPENSSL_API_3_0 */
 #endif /* NO_SSL */
 
-
 #if defined(USE_IPV6)
 	len = (sa.sa.sa_family == AF_INET) ? sizeof(conn->client.rsa.sin)
 	                                   : sizeof(conn->client.rsa.sin6);
@@ -17291,11 +18141,15 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 
 	conn->client.is_ssl = use_ssl ? 1 : 0;
 	if (0 != pthread_mutex_init(&conn->mutex, &pthread_mutex_attr)) {
-		mg_snprintf(NULL,
-		            NULL, /* No truncation check for ebuf */
-		            ebuf,
-		            ebuf_len,
-		            "Can not create mutex");
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_OS_ERROR;
+			error->code_sub = (unsigned)ERRNO;
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for ebuf */
+			            error->text,
+			            error->text_buffer_size,
+			            "Can not create mutex");
+		}
 #if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client
 		SSL_CTX_free(conn->dom_ctx->ssl_ctx);
 #endif
@@ -17304,7 +18158,6 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 		return NULL;
 	}
 
-
 #if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client
 	if (use_ssl) {
 		/* TODO: Check ssl_verify_peer and ssl_ca_path here.
@@ -17319,11 +18172,15 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 			                      conn->dom_ctx,
 			                      client_options->client_cert,
 			                      NULL)) {
-				mg_snprintf(NULL,
-				            NULL, /* No truncation check for ebuf */
-				            ebuf,
-				            ebuf_len,
-				            "Can not use SSL client certificate");
+				if (error != NULL) {
+					error->code = MG_ERROR_DATA_CODE_TLS_CLIENT_CERT_ERROR;
+					mg_snprintf(NULL,
+					            NULL, /* No truncation check for ebuf */
+					            error->text,
+					            error->text_buffer_size,
+					            "Can not use SSL client certificate");
+				}
+
 				SSL_CTX_free(conn->dom_ctx->ssl_ctx);
 				closesocket(sock);
 				mg_free(conn);
@@ -17336,9 +18193,15 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 			                                  client_options->server_cert,
 			                                  NULL)
 			    != 1) {
-				mg_cry_internal(conn,
-				                "SSL_CTX_load_verify_locations error: %s ",
-				                ssl_error());
+				if (error != NULL) {
+					error->code = MG_ERROR_DATA_CODE_TLS_SERVER_CERT_ERROR;
+					mg_snprintf(NULL,
+					            NULL, /* No truncation check for ebuf */
+					            error->text,
+					            error->text_buffer_size,
+					            "SSL_CTX_load_verify_locations error: %s",
+					            ssl_error());
+				}
 				SSL_CTX_free(conn->dom_ctx->ssl_ctx);
 				closesocket(sock);
 				mg_free(conn);
@@ -17350,11 +18213,14 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 		}
 
 		if (!sslize(conn, SSL_connect, client_options)) {
-			mg_snprintf(NULL,
-			            NULL, /* No truncation check for ebuf */
-			            ebuf,
-			            ebuf_len,
-			            "SSL connection error");
+			if (error != NULL) {
+				error->code = MG_ERROR_DATA_CODE_TLS_CONNECT_ERROR;
+				mg_snprintf(NULL,
+				            NULL, /* No truncation check for ebuf */
+				            error->text,
+				            error->text_buffer_size,
+				            "SSL connection error");
+			}
 			SSL_CTX_free(conn->dom_ctx->ssl_ctx);
 			closesocket(sock);
 			mg_free(conn);
@@ -17372,14 +18238,18 @@ mg_connect_client_secure(const struct mg_client_options *client_options,
                          char *error_buffer,
                          size_t error_buffer_size)
 {
-	return mg_connect_client_impl(client_options,
-	                              1,
-	                              error_buffer,
-	                              error_buffer_size);
+	struct mg_init_data init;
+	struct mg_error_data error;
+
+	memset(&init, 0, sizeof(init));
+	memset(&error, 0, sizeof(error));
+	error.text_buffer_size = error_buffer_size;
+	error.text = error_buffer;
+	return mg_connect_client_impl(client_options, 1, &init, &error);
 }
 
 
-struct mg_connection *
+CIVETWEB_API struct mg_connection *
 mg_connect_client(const char *host,
                   int port,
                   int use_ssl,
@@ -17387,18 +18257,28 @@ mg_connect_client(const char *host,
                   size_t error_buffer_size)
 {
 	struct mg_client_options opts;
+	struct mg_init_data init;
+	struct mg_error_data error;
+
+	memset(&init, 0, sizeof(init));
+
+	memset(&error, 0, sizeof(error));
+	error.text_buffer_size = error_buffer_size;
+	error.text = error_buffer;
+
 	memset(&opts, 0, sizeof(opts));
 	opts.host = host;
 	opts.port = port;
-	return mg_connect_client_impl(&opts,
-	                              use_ssl,
-	                              error_buffer,
-	                              error_buffer_size);
+	if (use_ssl) {
+		opts.host_name = host;
+	}
+
+	return mg_connect_client_impl(&opts, use_ssl, &init, &error);
 }
 
 
 #if defined(MG_EXPERIMENTAL_INTERFACES)
-struct mg_connection *
+CIVETWEB_API struct mg_connection *
 mg_connect_client2(const char *host,
                    const char *protocol,
                    int port,
@@ -17406,18 +18286,22 @@ mg_connect_client2(const char *host,
                    struct mg_init_data *init,
                    struct mg_error_data *error)
 {
+	(void)path;
+
 	int is_ssl, is_ws;
 	/* void *user_data = (init != NULL) ? init->user_data : NULL; -- TODO */
 
 	if (error != NULL) {
-		error->code = 0;
+		error->code = MG_ERROR_DATA_CODE_OK;
+		error->code_sub = 0;
 		if (error->text_buffer_size > 0) {
 			*error->text = 0;
 		}
 	}
 
 	if ((host == NULL) || (protocol == NULL)) {
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INVALID_PARAM;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -17428,7 +18312,7 @@ mg_connect_client2(const char *host,
 		return NULL;
 	}
 
-	/* check all known protocolls */
+	/* check all known protocols */
 	if (!mg_strcasecmp(protocol, "http")) {
 		is_ssl = 0;
 		is_ws = 0;
@@ -17444,7 +18328,8 @@ mg_connect_client2(const char *host,
 		is_ws = 1;
 #endif
 	} else {
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INVALID_PARAM;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -17473,18 +18358,18 @@ mg_connect_client2(const char *host,
 		    experimental_websocket_client_close_wrapper,
 		    (void *)init->callbacks);
 	}
+#else
+	(void)is_ws;
 #endif
 
 	/* TODO: all additional options */
 	struct mg_client_options opts;
+
 	memset(&opts, 0, sizeof(opts));
 	opts.host = host;
 	opts.port = port;
-	return mg_connect_client_impl(&opts,
-	                              is_ssl,
-	                              ((error != NULL) ? error->text : NULL),
-	                              ((error != NULL) ? error->text_buffer_size
-	                                               : 0));
+
+	return mg_connect_client_impl(&opts, is_ssl, init, error);
 }
 #endif
 
@@ -17933,7 +18818,7 @@ get_response(struct mg_connection *conn, char *ebuf, size_t ebuf_len, int *err)
 }
 
 
-int
+CIVETWEB_API int
 mg_get_response(struct mg_connection *conn,
                 char *ebuf,
                 size_t ebuf_len,
@@ -17985,7 +18870,7 @@ mg_get_response(struct mg_connection *conn,
 }
 
 
-struct mg_connection *
+CIVETWEB_API struct mg_connection *
 mg_download(const char *host,
             int port,
             int use_ssl,
@@ -18129,16 +19014,21 @@ mg_connect_websocket_client_impl(const struct mg_client_options *client_options,
 	const char *host = client_options->host;
 	int i;
 
+	struct mg_init_data init;
+	struct mg_error_data error;
+
+	memset(&init, 0, sizeof(init));
+	memset(&error, 0, sizeof(error));
+	error.text_buffer_size = error_buffer_size;
+	error.text = error_buffer;
+
 #if defined(__clang__)
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wformat-nonliteral"
 #endif
 
 	/* Establish the client connection and request upgrade */
-	conn = mg_connect_client_impl(client_options,
-	                              use_ssl,
-	                              error_buffer,
-	                              error_buffer_size);
+	conn = mg_connect_client_impl(client_options, use_ssl, &init, &error);
 
 	/* Connection object will be null if something goes wrong */
 	if (conn == NULL) {
@@ -18318,7 +19208,7 @@ mg_connect_websocket_client_impl(const struct mg_client_options *client_options,
 }
 
 
-struct mg_connection *
+CIVETWEB_API struct mg_connection *
 mg_connect_websocket_client(const char *host,
                             int port,
                             int use_ssl,
@@ -18348,7 +19238,7 @@ mg_connect_websocket_client(const char *host,
 }
 
 
-struct mg_connection *
+CIVETWEB_API struct mg_connection *
 mg_connect_websocket_client_secure(
     const struct mg_client_options *client_options,
     char *error_buffer,
@@ -18374,7 +19264,8 @@ mg_connect_websocket_client_secure(
 	                                        user_data);
 }
 
-struct mg_connection *
+
+CIVETWEB_API struct mg_connection *
 mg_connect_websocket_client_extensions(const char *host,
                                        int port,
                                        int use_ssl,
@@ -18404,7 +19295,8 @@ mg_connect_websocket_client_extensions(const char *host,
 	                                        user_data);
 }
 
-struct mg_connection *
+
+CIVETWEB_API struct mg_connection *
 mg_connect_websocket_client_secure_extensions(
     const struct mg_client_options *client_options,
     char *error_buffer,
@@ -18431,6 +19323,7 @@ mg_connect_websocket_client_secure_extensions(
 	                                        user_data);
 }
 
+
 /* Prepare connection data structure */
 static void
 init_connection(struct mg_connection *conn)
@@ -18448,6 +19341,7 @@ init_connection(struct mg_connection *conn)
 	conn->data_len = 0;
 	conn->handled_requests = 0;
 	conn->connection_type = CONNECTION_TYPE_INVALID;
+	conn->request_info.acceptedWebSocketSubprotocol = NULL;
 	mg_set_user_connection_data(conn, NULL);
 
 #if defined(USE_SERVER_STATS)
@@ -18902,7 +19796,7 @@ worker_thread_run(struct mg_connection *conn)
 		                   sizeof(conn->request_info.remote_addr),
 		                   &conn->client.rsa);
 
-		DEBUG_TRACE("Incomming %sconnection from %s",
+		DEBUG_TRACE("Incoming %sconnection from %s",
 		            (conn->client.is_ssl ? "SSL " : ""),
 		            conn->request_info.remote_addr);
 
@@ -19297,6 +20191,7 @@ master_thread_run(struct mg_context *ctx)
 				        "stop");
 			}
 		}
+		DEBUG_TRACE("Close Lua background state %p", lstate);
 		lua_close(lstate);
 
 		ctx->lua_background_state = 0;
@@ -19428,7 +20323,7 @@ free_context(struct mg_context *ctx)
 		if (callback_ret == 0) {
 			SSL_CTX_free(ctx->dd.ssl_ctx);
 		}
-		/* else: ignore error and ommit SSL_CTX_free in case
+		/* else: ignore error and omit SSL_CTX_free in case
 		 * callback_ret is 1 */
 	}
 #endif /* !NO_SSL */
@@ -19447,7 +20342,7 @@ free_context(struct mg_context *ctx)
 }
 
 
-void
+CIVETWEB_API void
 mg_stop(struct mg_context *ctx)
 {
 	pthread_t mt;
@@ -19526,7 +20421,6 @@ get_system_name(char **sysName)
 
 	*sysName = mg_strdup(name);
 
-
 #elif defined(__ZEPHYR__)
 	*sysName = mg_strdup("Zephyr OS");
 #else
@@ -19566,7 +20460,7 @@ legacy_init(const char **options)
 }
 
 
-struct mg_context *
+CIVETWEB_API struct mg_context *
 mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 {
 	struct mg_context *ctx;
@@ -19581,7 +20475,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 	struct mg_workerTLS tls;
 
 	if (error != NULL) {
-		error->code = 0;
+		error->code = MG_ERROR_DATA_CODE_OK;
+		error->code_sub = 0;
 		if (error->text_buffer_size > 0) {
 			*error->text = 0;
 		}
@@ -19594,7 +20489,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		legacy_init(options);
 	}
 	if (mg_init_library_called == 0) {
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INIT_LIBRARY_FAILED;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -19606,8 +20502,11 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 	}
 
 	/* Allocate context and initialize reasonable general case defaults. */
-	if ((ctx = (struct mg_context *)mg_calloc(1, sizeof(*ctx))) == NULL) {
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+	ctx = (struct mg_context *)mg_calloc(1, sizeof(*ctx));
+	if (ctx == NULL) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY;
+			error->code_sub = (unsigned)sizeof(*ctx);
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -19644,13 +20543,16 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 	ok &= (0 == pthread_mutex_init(&ctx->lua_bg_mutex, &pthread_mutex_attr));
 #endif
 	if (!ok) {
+		unsigned error_id = (unsigned)ERRNO;
 		const char *err_msg =
 		    "Cannot initialize thread synchronization objects";
 		/* Fatal error - abort start. However, this situation should never
 		 * occur in practice. */
 
 		mg_cry_ctx_internal(ctx, "%s", err_msg);
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_OS_ERROR;
+			error->code_sub = error_id;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -19683,9 +20585,12 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 
 	/* Store options */
 	while (options && (name = *options++) != NULL) {
-		if ((idx = get_option_index(name)) == -1) {
+		idx = get_option_index(name);
+		if (idx == -1) {
 			mg_cry_ctx_internal(ctx, "Invalid option: %s", name);
-			if ((error != NULL) && (error->text_buffer_size > 0)) {
+			if (error != NULL) {
+				error->code = MG_ERROR_DATA_CODE_INVALID_OPTION;
+				error->code_sub = (unsigned)-1;
 				mg_snprintf(NULL,
 				            NULL, /* No truncation check for error buffers */
 				            error->text,
@@ -19693,12 +20598,16 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 				            "Invalid configuration option: %s",
 				            name);
 			}
+
 			free_context(ctx);
 			pthread_setspecific(sTlsKey, NULL);
 			return NULL;
+
 		} else if ((value = *options++) == NULL) {
 			mg_cry_ctx_internal(ctx, "%s: option value cannot be NULL", name);
-			if ((error != NULL) && (error->text_buffer_size > 0)) {
+			if (error != NULL) {
+				error->code = MG_ERROR_DATA_CODE_INVALID_OPTION;
+				error->code_sub = (unsigned)idx;
 				mg_snprintf(NULL,
 				            NULL, /* No truncation check for error buffers */
 				            error->text,
@@ -19706,6 +20615,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 				            "Invalid configuration option value: %s",
 				            name);
 			}
+
 			free_context(ctx);
 			pthread_setspecific(sTlsKey, NULL);
 			return NULL;
@@ -19734,7 +20644,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		mg_cry_ctx_internal(ctx,
 		                    "%s too small",
 		                    config_options[MAX_REQUEST_SIZE].name);
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INVALID_OPTION;
+			error->code_sub = (unsigned)MAX_REQUEST_SIZE;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -19742,6 +20654,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "Invalid configuration option value: %s",
 			            config_options[MAX_REQUEST_SIZE].name);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -19755,7 +20668,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		mg_cry_ctx_internal(ctx,
 		                    "%s too small",
 		                    config_options[CONNECTION_QUEUE_SIZE].name);
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INVALID_OPTION;
+			error->code_sub = CONNECTION_QUEUE_SIZE;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -19763,6 +20678,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "Invalid configuration option value: %s",
 			            config_options[CONNECTION_QUEUE_SIZE].name);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -19773,7 +20689,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		mg_cry_ctx_internal(ctx,
 		                    "Out of memory: Cannot allocate %s",
 		                    config_options[CONNECTION_QUEUE_SIZE].name);
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY;
+			error->code_sub = (unsigned)itmp * (unsigned)sizeof(struct socket);
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -19781,6 +20699,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "Out of memory: Cannot allocate %s",
 			            config_options[CONNECTION_QUEUE_SIZE].name);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -19797,7 +20716,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		} else {
 			mg_cry_ctx_internal(ctx, "%s", "Too many worker threads");
 		}
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INVALID_OPTION;
+			error->code_sub = NUM_THREADS;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -19805,6 +20726,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "Invalid configuration option value: %s",
 			            config_options[NUM_THREADS].name);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -19814,7 +20736,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 #if defined(NO_FILES)
 	if (ctx->dd.config[DOCUMENT_ROOT] != NULL) {
 		mg_cry_ctx_internal(ctx, "%s", "Document root must not be set");
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INVALID_OPTION;
+			error->code_sub = (unsigned)DOCUMENT_ROOT;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -19822,6 +20746,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "Invalid configuration option value: %s",
 			            config_options[DOCUMENT_ROOT].name);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -19849,7 +20774,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			mg_cry_ctx_internal(ctx,
 			                    "lua_background_script load error: %s",
 			                    ebuf);
-			if ((error != NULL) && (error->text_buffer_size > 0)) {
+			if (error != NULL) {
+				error->code = MG_ERROR_DATA_CODE_SCRIPT_ERROR;
 				mg_snprintf(NULL,
 				            NULL, /* No truncation check for error buffers */
 				            error->text,
@@ -19858,6 +20784,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 				            config_options[LUA_BACKGROUND_SCRIPT].name,
 				            ebuf);
 			}
+
 			pthread_mutex_unlock(&ctx->lua_bg_mutex);
 
 			free_context(ctx);
@@ -19893,7 +20820,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			mg_cry_ctx_internal(ctx,
 			                    "lua_background_script start error: %s",
 			                    ebuf);
-			if ((error != NULL) && (error->text_buffer_size > 0)) {
+			if (error != NULL) {
+				error->code = MG_ERROR_DATA_CODE_SCRIPT_ERROR;
 				mg_snprintf(NULL,
 				            NULL, /* No truncation check for error buffers */
 				            error->text,
@@ -19925,7 +20853,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		/* Fatal error - abort start. */
 		mg_cry_ctx_internal(ctx, "%s", err_msg);
 
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INVALID_PASS_FILE;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -19945,7 +20874,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		/* Fatal error - abort start. */
 		mg_cry_ctx_internal(ctx, "%s", err_msg);
 
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INIT_TLS_FAILED;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -19953,6 +20883,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "%s",
 			            err_msg);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -19964,7 +20895,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		/* Fatal error - abort start. */
 		mg_cry_ctx_internal(ctx, "%s", err_msg);
 
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INIT_TLS_FAILED;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -19972,6 +20904,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "%s",
 			            err_msg);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -19983,7 +20916,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		/* Fatal error - abort start. */
 		mg_cry_ctx_internal(ctx, "%s", err_msg);
 
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INIT_PORTS_FAILED;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -19991,19 +20925,20 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "%s",
 			            err_msg);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
 	}
 
-
 #if !defined(_WIN32) && !defined(__ZEPHYR__)
 	if (!set_uid_option(ctx)) {
 		const char *err_msg = "Failed to run as configured user";
 		/* Fatal error - abort start. */
 		mg_cry_ctx_internal(ctx, "%s", err_msg);
 
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INIT_USER_FAILED;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -20011,6 +20946,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "%s",
 			            err_msg);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -20022,7 +20958,8 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		/* Fatal error - abort start. */
 		mg_cry_ctx_internal(ctx, "%s", err_msg);
 
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INIT_ACL_FAILED;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -20030,6 +20967,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "%s",
 			            err_msg);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -20044,7 +20982,10 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		const char *err_msg = "Not enough memory for worker thread ID array";
 		mg_cry_ctx_internal(ctx, "%s", err_msg);
 
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY;
+			error->code_sub =
+			    (unsigned)ctx->cfg_worker_threads * (unsigned)sizeof(pthread_t);
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -20052,6 +20993,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "%s",
 			            err_msg);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -20065,7 +21007,10 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		    "Not enough memory for worker thread connection array";
 		mg_cry_ctx_internal(ctx, "%s", err_msg);
 
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY;
+			error->code_sub = (unsigned)ctx->cfg_worker_threads
+			                  * (unsigned)sizeof(struct mg_connection);
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -20073,6 +21018,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "%s",
 			            err_msg);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -20088,7 +21034,10 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		mg_cry_ctx_internal(ctx, "%s", err_msg);
 		mg_free(ctx->worker_threadids);
 
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY;
+			error->code_sub = (unsigned)ctx->cfg_worker_threads
+			                  * (unsigned)sizeof(ctx->client_wait_events[0]);
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -20096,6 +21045,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "%s",
 			            err_msg);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -20111,7 +21061,10 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		mg_free(ctx->client_wait_events);
 		mg_free(ctx->worker_threadids);
 
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY;
+			error->code_sub = (unsigned)ctx->cfg_worker_threads
+			                  * (unsigned)sizeof(ctx->client_socks[0]);
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -20119,6 +21072,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "%s",
 			            err_msg);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -20137,7 +21091,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			mg_free(ctx->client_wait_events);
 			mg_free(ctx->worker_threadids);
 
-			if ((error != NULL) && (error->text_buffer_size > 0)) {
+			if (error != NULL) {
+				error->code = MG_ERROR_DATA_CODE_OS_ERROR;
+				error->code_sub = (unsigned)ERRNO;
 				mg_snprintf(NULL,
 				            NULL, /* No truncation check for error buffers */
 				            error->text,
@@ -20145,6 +21101,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 				            err_msg,
 				            i);
 			}
+
 			free_context(ctx);
 			pthread_setspecific(sTlsKey, NULL);
 			return NULL;
@@ -20157,7 +21114,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 		const char *err_msg = "Error creating timers";
 		mg_cry_ctx_internal(ctx, "%s", err_msg);
 
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_OS_ERROR;
+			error->code_sub = (unsigned)ERRNO;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -20165,6 +21124,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 			            "%s",
 			            err_msg);
 		}
+
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -20213,7 +21173,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 				                    "Cannot create threads: error %ld",
 				                    error_no);
 
-				if ((error != NULL) && (error->text_buffer_size > 0)) {
+				if (error != NULL) {
+					error->code = MG_ERROR_DATA_CODE_OS_ERROR;
+					error->code_sub = (unsigned)error_no;
 					mg_snprintf(
 					    NULL,
 					    NULL, /* No truncation check for error buffers */
@@ -20222,6 +21184,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 					    "Cannot create first worker thread: error %ld",
 					    error_no);
 				}
+
 				free_context(ctx);
 				pthread_setspecific(sTlsKey, NULL);
 				return NULL;
@@ -20238,7 +21201,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 }
 
 
-struct mg_context *
+CIVETWEB_API struct mg_context *
 mg_start(const struct mg_callbacks *callbacks,
          void *user_data,
          const char **options)
@@ -20253,7 +21216,7 @@ mg_start(const struct mg_callbacks *callbacks,
 
 
 /* Add an additional domain to an already running web server. */
-int
+CIVETWEB_API int
 mg_start_domain2(struct mg_context *ctx,
                  const char **options,
                  struct mg_error_data *error)
@@ -20266,14 +21229,16 @@ mg_start_domain2(struct mg_context *ctx,
 	int idx, i;
 
 	if (error != NULL) {
-		error->code = 0;
+		error->code = MG_ERROR_DATA_CODE_OK;
+		error->code_sub = 0;
 		if (error->text_buffer_size > 0) {
 			*error->text = 0;
 		}
 	}
 
 	if ((ctx == NULL) || (options == NULL)) {
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INVALID_PARAM;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -20285,7 +21250,8 @@ mg_start_domain2(struct mg_context *ctx,
 	}
 
 	if (!STOP_FLAG_IS_ZERO(&ctx->stop_flag)) {
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_SERVER_STOPPED;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -20293,7 +21259,7 @@ mg_start_domain2(struct mg_context *ctx,
 			            "%s",
 			            "Server already stopped");
 		}
-		return -1;
+		return -7;
 	}
 
 	new_dom = (struct mg_domain_context *)
@@ -20301,7 +21267,9 @@ mg_start_domain2(struct mg_context *ctx,
 
 	if (!new_dom) {
 		/* Out of memory */
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_OUT_OF_MEMORY;
+			error->code_sub = (unsigned)sizeof(struct mg_domain_context);
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -20314,9 +21282,12 @@ mg_start_domain2(struct mg_context *ctx,
 
 	/* Store options - TODO: unite duplicate code */
 	while (options && (name = *options++) != NULL) {
-		if ((idx = get_option_index(name)) == -1) {
+		idx = get_option_index(name);
+		if (idx == -1) {
 			mg_cry_ctx_internal(ctx, "Invalid option: %s", name);
-			if ((error != NULL) && (error->text_buffer_size > 0)) {
+			if (error != NULL) {
+				error->code = MG_ERROR_DATA_CODE_INVALID_OPTION;
+				error->code_sub = (unsigned)-1;
 				mg_snprintf(NULL,
 				            NULL, /* No truncation check for error buffers */
 				            error->text,
@@ -20328,7 +21299,9 @@ mg_start_domain2(struct mg_context *ctx,
 			return -2;
 		} else if ((value = *options++) == NULL) {
 			mg_cry_ctx_internal(ctx, "%s: option value cannot be NULL", name);
-			if ((error != NULL) && (error->text_buffer_size > 0)) {
+			if (error != NULL) {
+				error->code = MG_ERROR_DATA_CODE_INVALID_OPTION;
+				error->code_sub = (unsigned)idx;
 				mg_snprintf(NULL,
 				            NULL, /* No truncation check for error buffers */
 				            error->text,
@@ -20352,7 +21325,9 @@ mg_start_domain2(struct mg_context *ctx,
 	/* TODO: Maybe use a new option hostname? */
 	if (!new_dom->config[AUTHENTICATION_DOMAIN]) {
 		mg_cry_ctx_internal(ctx, "%s", "authentication domain required");
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_MISSING_OPTION;
+			error->code_sub = AUTHENTICATION_DOMAIN;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -20376,8 +21351,7 @@ mg_start_domain2(struct mg_context *ctx,
 	new_dom->handlers = NULL;
 	new_dom->next = NULL;
 	new_dom->nonce_count = 0;
-	new_dom->auth_nonce_mask =
-	    (uint64_t)get_random() ^ ((uint64_t)get_random() << 31);
+	new_dom->auth_nonce_mask = get_random() ^ (get_random() << 31);
 
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
 	new_dom->shared_lua_websockets = NULL;
@@ -20386,7 +21360,8 @@ mg_start_domain2(struct mg_context *ctx,
 #if !defined(NO_SSL) && !defined(USE_MBEDTLS)
 	if (!init_ssl_ctx(ctx, new_dom)) {
 		/* Init SSL failed */
-		if ((error != NULL) && (error->text_buffer_size > 0)) {
+		if (error != NULL) {
+			error->code = MG_ERROR_DATA_CODE_INIT_TLS_FAILED;
 			mg_snprintf(NULL,
 			            NULL, /* No truncation check for error buffers */
 			            error->text,
@@ -20411,7 +21386,8 @@ mg_start_domain2(struct mg_context *ctx,
 			mg_cry_ctx_internal(ctx,
 			                    "domain %s already in use",
 			                    new_dom->config[AUTHENTICATION_DOMAIN]);
-			if ((error != NULL) && (error->text_buffer_size > 0)) {
+			if (error != NULL) {
+				error->code = MG_ERROR_DATA_CODE_DUPLICATE_DOMAIN;
 				mg_snprintf(NULL,
 				            NULL, /* No truncation check for error buffers */
 				            error->text,
@@ -20442,7 +21418,7 @@ mg_start_domain2(struct mg_context *ctx,
 }
 
 
-int
+CIVETWEB_API int
 mg_start_domain(struct mg_context *ctx, const char **options)
 {
 	return mg_start_domain2(ctx, options, NULL);
@@ -20450,7 +21426,7 @@ mg_start_domain(struct mg_context *ctx, const char **options)
 
 
 /* Feature check API function */
-unsigned
+CIVETWEB_API unsigned
 mg_check_feature(unsigned feature)
 {
 	static const unsigned feature_set = 0
@@ -20532,7 +21508,7 @@ mg_str_append(char **dst, char *end, const char *src)
 
 /* Get system information. It can be printed or stored by the caller.
  * Return the size of available information. */
-int
+CIVETWEB_API int
 mg_get_system_info(char *buffer, int buflen)
 {
 	char *end, *append_eoobj = NULL, block[256];
@@ -20722,7 +21698,6 @@ mg_get_system_info(char *buffer, int buflen)
 		system_info_length += mg_str_append(&buffer, end, block);
 	}
 
-
 	/* Compiler information */
 	/* http://sourceforge.net/p/predef/wiki/Compilers/ */
 	{
@@ -20864,7 +21839,7 @@ mg_get_system_info(char *buffer, int buflen)
 
 /* Get context information. It can be printed or stored by the caller.
  * Return the size of available information. */
-int
+CIVETWEB_API int
 mg_get_context_info(const struct mg_context *ctx, char *buffer, int buflen)
 {
 #if defined(USE_SERVER_STATS)
@@ -21063,7 +22038,7 @@ mg_get_context_info(const struct mg_context *ctx, char *buffer, int buflen)
 }
 
 
-void
+CIVETWEB_API void
 mg_disable_connection_keep_alive(struct mg_connection *conn)
 {
 	/* https://github.com/civetweb/civetweb/issues/727 */
@@ -21076,7 +22051,7 @@ mg_disable_connection_keep_alive(struct mg_connection *conn)
 #if defined(MG_EXPERIMENTAL_INTERFACES)
 /* Get connection information. It can be printed or stored by the caller.
  * Return the size of available information. */
-int
+CIVETWEB_API int
 mg_get_connection_info(const struct mg_context *ctx,
                        int idx,
                        char *buffer,
@@ -21322,12 +22297,53 @@ mg_get_connection_info(const struct mg_context *ctx,
 
 	return (int)connection_info_length;
 }
+
+#if 0
+/* Get handler information. It can be printed or stored by the caller.
+ * Return the size of available information. */
+CIVETWEB_API int
+mg_get_handler_info(struct mg_context *ctx,
+                       char *buffer,
+                       int buflen)
+{
+    int handler_info_len = 0;
+    struct mg_handler_info *tmp_rh;
+    mg_lock_context(ctx);
+
+    for (tmp_rh = ctx->dd.handlers; tmp_rh != NULL; tmp_rh = tmp_rh->next) {
+
+        if (buflen > handler_info_len+ tmp_rh->uri_len) {
+        memcpy(buffer+handler_info_len, tmp_rh->uri, tmp_rh->uri_len);
+        }
+        handler_info_len += tmp_rh->uri_len;
+
+        switch (tmp_rh->handler_type) {
+            case REQUEST_HANDLER:
+                (void)tmp_rh->handler;
+            break;
+            case WEBSOCKET_HANDLER:
+        (void)tmp_rh->connect_handler;
+       (void) tmp_rh->ready_handler;
+       (void) tmp_rh->data_handler;
+       (void) tmp_rh->close_handler;
+            break;
+            case AUTH_HANDLER:
+             (void) tmp_rh->auth_handler;
+            break;
+        }
+        (void)cbdata;
+    }
+
+    mg_unlock_context(ctx);
+    return handler_info_len;
+}
+#endif
 #endif
 
 
 /* Initialize this library. This function does not need to be thread safe.
  */
-unsigned
+CIVETWEB_API unsigned
 mg_init_library(unsigned features)
 {
 	unsigned features_to_init = mg_check_feature(features & 0xFFu);
@@ -21343,6 +22359,9 @@ mg_init_library(unsigned features)
 	mg_global_lock();
 
 	if (mg_init_library_called <= 0) {
+		int i;
+		size_t len;
+
 #if defined(_WIN32)
 		int file_mutex_init = 1;
 		int wsa = 1;
@@ -21370,7 +22389,6 @@ mg_init_library(unsigned features)
 #endif
 		}
 
-
 		if (failed) {
 #if defined(_WIN32)
 			if (wsa == 0) {
@@ -21392,16 +22410,40 @@ mg_init_library(unsigned features)
 			return 0;
 		}
 
-#if defined(USE_LUA)
-		lua_init_optional_libraries();
-#endif
+		len = 1;
+		for (i = 0; http_methods[i].name != NULL; i++) {
+			size_t sl = strlen(http_methods[i].name);
+			len += sl;
+			if (i > 0) {
+				len += 2;
+			}
+		}
+		all_methods = (char *)mg_malloc(len);
+		if (!all_methods) {
+			/* Must never happen */
+			mg_global_unlock();
+			(void)pthread_mutex_destroy(&global_lock_mutex);
+			return 0;
+		}
+		all_methods[0] = 0;
+		for (i = 0; http_methods[i].name != NULL; i++) {
+			if (i > 0) {
+				strcat(all_methods, ", ");
+				strcat(all_methods, http_methods[i].name);
+			} else {
+				strcpy(all_methods, http_methods[i].name);
+			}
+		}
 	}
 
-	mg_global_unlock();
+#if defined(USE_LUA)
+	lua_init_optional_libraries();
+#endif
 
 #if (defined(OPENSSL_API_1_0) || defined(OPENSSL_API_1_1)                      \
      || defined(OPENSSL_API_3_0))                                              \
     && !defined(NO_SSL)
+
 	if (features_to_init & MG_FEATURES_SSL) {
 		if (!mg_openssl_initialized) {
 			char ebuf[128];
@@ -21416,9 +22458,9 @@ mg_init_library(unsigned features)
 			/* ssl already initialized */
 		}
 	}
+
 #endif
 
-	mg_global_lock();
 	if (mg_init_library_called <= 0) {
 		mg_init_library_called = 1;
 	} else {
@@ -21431,7 +22473,7 @@ mg_init_library(unsigned features)
 
 
 /* Un-initialize this library. */
-unsigned
+CIVETWEB_API unsigned
 mg_exit_library(void)
 {
 	if (mg_init_library_called <= 0) {
@@ -21461,6 +22503,8 @@ mg_exit_library(void)
 #if defined(USE_LUA)
 		lua_exit_optional_libraries();
 #endif
+		mg_free(all_methods);
+		all_methods = NULL;
 
 		mg_global_unlock();
 		(void)pthread_mutex_destroy(&global_lock_mutex);
diff --git a/src/handle_form.inl b/src/handle_form.inl
index eaad88d..be477a0 100644
--- a/src/handle_form.inl
+++ b/src/handle_form.inl
@@ -220,7 +220,7 @@ mg_handle_form_request(struct mg_connection *conn,
 		}
 
 		/* GET request: form data is in the query string. */
-		/* The entire data has already been loaded, so there is no nead to
+		/* The entire data has already been loaded, so there is no need to
 		 * call mg_read. We just need to split the query string into key-value
 		 * pairs. */
 		data = conn->request_info.query_string;
diff --git a/src/mod_http2.inl b/src/http2.inl
similarity index 97%
rename from src/mod_http2.inl
rename to src/http2.inl
index daa3e99..cea695e 100644
--- a/src/mod_http2.inl
+++ b/src/http2.inl
@@ -654,7 +654,7 @@ hpack_getnum(const uint8_t *buf,
  * the encoded string.
  */
 static char *
-hpack_decode(const uint8_t *buf, int *i, struct mg_context *ctx)
+hpack_decode(const uint8_t *buf, int *i, int max_i, struct mg_context *ctx)
 {
 	uint64_t byte_len64;
 	int byte_len;
@@ -670,6 +670,11 @@ hpack_decode(const uint8_t *buf, int *i, struct mg_context *ctx)
 	byte_len = (int)byte_len64;
 	bit_len = byte_len * 8;
 
+	/* check size */
+	if ((*i) + byte_len > max_i) {
+		return NULL;
+	}
+
 	/* Now read the string */
 	if (!is_huff) {
 		/* Not huffman encoded: Copy directly */
@@ -718,6 +723,10 @@ hpack_decode(const uint8_t *buf, int *i, struct mg_context *ctx)
 					break;
 				}
 			}
+			if (bytesStored == sizeof(str)) {
+				/* too long */
+				return 0;
+			}
 		}
 	}
 }
@@ -820,7 +829,7 @@ hpack_encode(uint8_t *store, const char *load, int lower)
 
 
 static const char http2_pri[] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
-static unsigned char http2_pri_len = 24; /* = strlen(http2_pri) */
+static const unsigned char http2_pri_len = 24; /* = strlen(http2_pri) */
 
 
 /* Read and check the HTTP/2 primer/preface:
@@ -829,17 +838,19 @@ static int
 is_valid_http2_primer(struct mg_connection *conn)
 {
 	size_t pri_len = http2_pri_len;
-	char buf[32];
+	char buf[32]; /* Buffer must hold 24 bytes primer */
 
-	if (pri_len > sizeof(buf)) {
-		/* Should never be reached - the RFC primer has 24 bytes */
+	int read_pri_len = mg_read(conn, buf, pri_len);
+	if (read_pri_len != (int)pri_len) {
+		/* Size does not match.
+		 * This includes cases where mg_read returns error codes */
 		return 0;
 	}
-	int read_pri_len = mg_read(conn, buf, pri_len);
-	if ((read_pri_len != (int)pri_len)
-	    || (0 != memcmp(buf, http2_pri, pri_len))) {
+	if (0 != memcmp(buf, http2_pri, pri_len)) {
+		/* Primer does not match */
 		return 0;
 	}
+	/* Primer does match */
 	return 1;
 }
 
@@ -850,7 +861,7 @@ is_valid_http2_primer(struct mg_connection *conn)
 	         (conn)->client.sock,                                              \
 	         (conn)->ssl,                                                      \
 	         (const char *)(data),                                             \
-	         (int)(len));
+	         (int)(len))
 
 
 static void
@@ -949,7 +960,7 @@ http2_send_response_headers(struct mg_connection *conn)
 	uint16_t header_len = 0;
 	int has_date = 0;
 	int has_connection_header = 0;
-	int i;
+	int i, ok;
 
 	if ((conn->status_code < 100) || (conn->status_code > 999)) {
 		/* Invalid status: Set status to "Internal Server Error" */
@@ -1058,16 +1069,23 @@ http2_send_response_headers(struct mg_connection *conn)
 	http2_header_frame[8] = (conn->http2.stream_id & 0xFFu);
 
 	/* Send header frame */
-	mg_xwrite(conn, http2_header_frame, 9);
-	mg_xwrite(conn, header_bin, header_len);
-
-	DEBUG_TRACE("HTTP2 response header sent: stream %u", conn->http2.stream_id);
-
+	ok = 1;
+	if (mg_xwrite(conn, http2_header_frame, 9) != 9) {
+		ok = 0;
+	} else if (mg_xwrite(conn, header_bin, header_len) != header_len) {
+		ok = 0;
+	}
+	if (ok) {
+		DEBUG_TRACE("HTTP2 response header sent: stream %u",
+		            conn->http2.stream_id);
+	} else {
+		DEBUG_TRACE("HTTP2 response header sending error: stream %u",
+		            conn->http2.stream_id);
+	}
 
 	(void)has_connection_header; /* ignore for the moment */
 
-
-	return 42; /* TODO */
+	return ok;
 }
 
 
@@ -1420,8 +1438,13 @@ handle_http2(struct mg_connection *conn)
 				/* Get Header name "key" */
 				if (idx == 0) {
 					/* Index 0: Header name encoded in following bytes */
-					key = hpack_decode(buf, &i, conn->phys_ctx);
+					key =
+					    hpack_decode(buf, &i, (int)bytes_read, conn->phys_ctx);
 					CHECK_LEAK_HDR_ALLOC(key);
+					if (!key) {
+						DEBUG_TRACE("HTTP2 key decoding error");
+						goto clean_http2;
+					}
 				} else if (/*(idx >= 15) &&*/ (idx <= 61)) {
 					/* Take key name from predefined header table */
 					key = mg_strdup_ctx(hpack_predefined[idx].name,
@@ -1480,8 +1503,16 @@ handle_http2(struct mg_connection *conn)
 
 				} else {
 					/* Read value from HTTP2 stream */
-					val = hpack_decode(buf, &i, conn->phys_ctx); /* leak? */
+					val = hpack_decode(buf,
+					                   &i,
+					                   (int)bytes_read,
+					                   conn->phys_ctx); /* leak? */
 					CHECK_LEAK_HDR_ALLOC(val);
+					if (!val) {
+						DEBUG_TRACE("HTTP2 value decoding error");
+						mg_free((void *)key);
+						goto clean_http2;
+					}
 
 					if (indexing) {
 						/* Add to index */
@@ -1546,6 +1577,7 @@ handle_http2(struct mg_connection *conn)
 					} else if (!strcmp(":path", key)) {
 						conn->request_info.local_uri = val;
 						conn->request_info.request_uri = val;
+						conn->request_info.local_uri_raw = val;
 					} else if (!strcmp(":status", key)) {
 						conn->status_code = atoi(val);
 					}
@@ -1803,7 +1835,7 @@ HPACK_TABLE_TEST()
 
 	for (i = 0; i < 256; i++) {
 		if (reverse_map[i] == -1) {
-			ck_abort_msg("reverse map at %i mising", i);
+			ck_abort_msg("reverse map at %i missing", i);
 		}
 	}
 
diff --git a/src/main.c b/src/main.c
index 3e98283..66510d0 100644
--- a/src/main.c
+++ b/src/main.c
@@ -173,19 +173,17 @@ extern char *_getcwd(char *buf, size_t size);
 #define PATH_MAX (1024)
 #endif
 
-#define MAX_OPTIONS (50)
+#define MAX_OPTIONS (100) /* TODO: Read from civetweb.c ? */
 #define MAX_CONF_FILE_LINE_SIZE (8 * 1024)
 
 struct tuser_data {
-	char *first_message;
+	int _unused;
 };
 
-
-/* Exit flag for the main loop (read and writen by different threads, thus
+/* Exit flag for the main loop (read and written by different threads, thus
  * volatile). */
 volatile int g_exit_flag = 0; /* 0 = continue running main loop */
 
-
 static char g_server_base_name[40]; /* Set by init_server_name() */
 
 static const char *g_server_name; /* Default from init_server_name,
@@ -260,7 +258,7 @@ static NO_RETURN void
 die(const char *fmt, ...)
 {
 	va_list ap;
-	char msg[512] = "";
+	char msg[1024] = "";
 
 	va_start(ap, fmt);
 	(void)vsnprintf(msg, sizeof(msg) - 1, fmt, ap);
@@ -268,7 +266,7 @@ die(const char *fmt, ...)
 	va_end(ap);
 
 #if defined(_WIN32)
-	MessageBox(NULL, msg, "Error", MB_OK);
+	MessageBox(NULL, msg, "Error", MB_ICONERROR | MB_OK);
 #else
 	fprintf(stderr, "%s\n", msg);
 #endif
@@ -469,137 +467,8 @@ sdup(const char *str)
 }
 
 
-#if 0 /* Unused code from "string duplicate with escape" */
-static unsigned
-hex2dec(char x)
-{
-    if ((x >= '0') && (x <= '9')) {
-        return (unsigned)x - (unsigned)'0';
-    }
-    if ((x >= 'A') && (x <= 'F')) {
-        return (unsigned)x - (unsigned)'A' + 10u;
-    }
-    if ((x >= 'a') && (x <= 'f')) {
-        return (unsigned)x - (unsigned)'a' + 10u;
-    }
-    return 0;
-}
-
-
-static char *
-sdupesc(const char *str)
-{
-	char *p = sdup(str);
-
-	if (p) {
-		char *d = p;
-		while ((d = strchr(d, '\\')) != NULL) {
-			switch (d[1]) {
-			case 'a':
-				d[0] = '\a';
-				memmove(d + 1, d + 2, strlen(d + 1));
-				break;
-			case 'b':
-				d[0] = '\b';
-				memmove(d + 1, d + 2, strlen(d + 1));
-				break;
-			case 'e':
-				d[0] = 27;
-				memmove(d + 1, d + 2, strlen(d + 1));
-				break;
-			case 'f':
-				d[0] = '\f';
-				memmove(d + 1, d + 2, strlen(d + 1));
-				break;
-			case 'n':
-				d[0] = '\n';
-				memmove(d + 1, d + 2, strlen(d + 1));
-				break;
-			case 'r':
-				d[0] = '\r';
-				memmove(d + 1, d + 2, strlen(d + 1));
-				break;
-			case 't':
-				d[0] = '\t';
-				memmove(d + 1, d + 2, strlen(d + 1));
-				break;
-			case 'u':
-				if (isxdigit(d[2]) && isxdigit(d[3]) && isxdigit(d[4])
-				    && isxdigit(d[5])) {
-					unsigned short u = (unsigned short)(hex2dec(d[2]) * 4096
-					                                    + hex2dec(d[3]) * 256
-					                                    + hex2dec(d[4]) * 16
-					                                    + hex2dec(d[5]));
-					char mbc[16];
-					int mbl = wctomb(mbc, (wchar_t)u);
-					if ((mbl > 0) && (mbl < 6)) {
-						memcpy(d, mbc, (unsigned)mbl);
-						memmove(d + mbl, d + 6, strlen(d + 5));
-						/* Advance mbl characters (+1 is below) */
-						d += (mbl - 1);
-					} else {
-						/* Invalid multi byte character */
-						/* TODO: define what to do */
-					}
-				} else {
-					/* Invalid esc sequence */
-					/* TODO: define what to do */
-				}
-				break;
-			case 'v':
-				d[0] = '\v';
-				memmove(d + 1, d + 2, strlen(d + 1));
-				break;
-			case 'x':
-				if (isxdigit(d[2]) && isxdigit(d[3])) {
-					d[0] = (char)((unsigned char)(hex2dec(d[2]) * 16
-					                              + hex2dec(d[3])));
-					memmove(d + 1, d + 4, strlen(d + 3));
-				} else {
-					/* Invalid esc sequence */
-					/* TODO: define what to do */
-				}
-				break;
-			case 'z':
-				d[0] = 0;
-				memmove(d + 1, d + 2, strlen(d + 1));
-				break;
-			case '\\':
-				d[0] = '\\';
-				memmove(d + 1, d + 2, strlen(d + 1));
-				break;
-			case '\'':
-				d[0] = '\'';
-				memmove(d + 1, d + 2, strlen(d + 1));
-				break;
-			case '\"':
-				d[0] = '\"';
-				memmove(d + 1, d + 2, strlen(d + 1));
-				break;
-			case 0:
-				if (d == p) {
-					/* Line is only \ */
-					free(p);
-					return NULL;
-				}
-			/* no break */
-			default:
-				/* invalid ESC sequence */
-				/* TODO: define what to do */
-				break;
-			}
-
-			/* Advance to next character */
-			d++;
-		}
-	}
-	return p;
-}
-#endif
-
-
 static const char *
-get_option(char **options, const char *option_name)
+get_option(const char **options, const char *option_name)
 {
 	int i = 0;
 	const char *opt_value = NULL;
@@ -623,7 +492,7 @@ get_option(char **options, const char *option_name)
 
 
 static int
-set_option(char **options, const char *name, const char *value)
+set_option(const char **options, const char *name, const char *value)
 {
 	int i, type;
 	const struct mg_option *default_options = mg_get_valid_options();
@@ -766,11 +635,11 @@ set_option(char **options, const char *name, const char *value)
 					die("Out of memory");
 				}
 				sprintf(s, "%s%s%s", options[2 * i + 1], multi_sep, value);
-				free(options[2 * i + 1]);
+				free((char *)options[2 * i + 1]);
 				options[2 * i + 1] = s;
 			} else {
 				/* Option already set. Overwrite */
-				free(options[2 * i + 1]);
+				free((char *)options[2 * i + 1]);
 				options[2 * i + 1] = sdup(value);
 			}
 			break;
@@ -794,7 +663,7 @@ set_option(char **options, const char *name, const char *value)
 
 
 static int
-read_config_file(const char *config_file, char **options)
+read_config_file(const char *config_file, const char **options)
 {
 	char line[MAX_CONF_FILE_LINE_SIZE], *p;
 	FILE *fp = NULL;
@@ -866,7 +735,7 @@ read_config_file(const char *config_file, char **options)
 
 
 static void
-process_command_line_arguments(int argc, char *argv[], char **options)
+process_command_line_arguments(int argc, char *argv[], const char **options)
 {
 	char *p;
 	size_t i, cmd_line_opts_start = 1;
@@ -990,22 +859,6 @@ free_system_info(void)
 }
 
 
-static int
-log_message(const struct mg_connection *conn, const char *message)
-{
-	const struct mg_context *ctx = mg_get_context(conn);
-	struct tuser_data *ud = (struct tuser_data *)mg_get_user_data(ctx);
-
-	fprintf(stderr, "%s\n", message);
-
-	if (ud->first_message == NULL) {
-		ud->first_message = sdup(message);
-	}
-
-	return 0;
-}
-
-
 static int
 is_path_absolute(const char *path)
 {
@@ -1022,7 +875,7 @@ is_path_absolute(const char *path)
 
 
 static int
-verify_existence(char **options, const char *option_name, int must_be_dir)
+verify_existence(const char **options, const char *option_name, int must_be_dir)
 {
 	struct stat st;
 	const char *path = get_option(options, option_name);
@@ -1058,7 +911,7 @@ verify_existence(char **options, const char *option_name, int must_be_dir)
 
 
 static void
-set_absolute_path(char *options[],
+set_absolute_path(const char *options[],
                   const char *option_name,
                   const char *path_to_civetweb_exe)
 {
@@ -1283,7 +1136,7 @@ run_client(const char *url_arg)
 
 
 static int
-sanitize_options(char *options[] /* server options */,
+sanitize_options(const char *options[] /* server options */,
                  const char *arg0 /* argv[0] */)
 {
 	int ok = 1;
@@ -1318,11 +1171,20 @@ sanitize_options(char *options[] /* server options */,
 }
 
 
+#ifdef _WIN32
+/* Forward declaration for Windows only */
+static void show_settings_dialog(void);
+#endif
+
+
 static void
 start_civetweb(int argc, char *argv[])
 {
 	struct mg_callbacks callbacks;
-	char *options[2 * MAX_OPTIONS + 1];
+	const char *options[2 * MAX_OPTIONS + 1];
+	struct mg_init_data init;
+	struct mg_error_data error;
+	char error_text[256];
 	int i;
 
 	/* Start option -I:
@@ -1417,7 +1279,7 @@ start_civetweb(int argc, char *argv[])
 	}
 
 	/* Initialize options structure */
-	memset(options, 0, sizeof(options));
+	memset((void *)options, 0, sizeof(options));
 	set_option(options, "document_root", ".");
 
 	/* Update config based on command line arguments */
@@ -1440,7 +1302,7 @@ start_civetweb(int argc, char *argv[])
 				if (mg_strcasecmp(options[i + 1], "yes") == 0) {
 					fprintf(stdout, "daemonize.\n");
 					if (daemon(0, 0) != 0) {
-						fprintf(stdout, "Faild to daemonize main process.\n");
+						fprintf(stdout, "Failed to daemonize main process.\n");
 						exit(EXIT_FAILURE);
 					}
 					FILE *fp;
@@ -1460,24 +1322,54 @@ start_civetweb(int argc, char *argv[])
 	/* Initialize user data */
 	memset(&g_user_data, 0, sizeof(g_user_data));
 
-	/* Start Civetweb */
 	memset(&callbacks, 0, sizeof(callbacks));
-	callbacks.log_message = &log_message;
-	g_ctx = mg_start(&callbacks, &g_user_data, (const char **)options);
+
+	memset(&init, 0, sizeof(init));
+	init.callbacks = &callbacks;
+	init.configuration_options = options;
+	init.user_data = &g_user_data;
+
+	memset(&error, 0, sizeof(error));
+	error.text = error_text;
+	error.text_buffer_size = sizeof(error_text);
+
+	/* Start Civetweb */
+	g_ctx = mg_start2(&init, &error);
 
 	/* mg_start copies all options to an internal buffer.
 	 * The options data field here is not required anymore. */
 	for (i = 0; options[i] != NULL; i++) {
-		free(options[i]);
+		free((char *)options[i]);
 	}
 
 	/* If mg_start fails, it returns NULL */
 	if (g_ctx == NULL) {
-		die("Failed to start %s:\n%s",
+#ifdef _WIN32
+		/* On Windows: provide option to edit configuration file. */
+		char msgboxtxt[1024];
+		int ret;
+		sprintf(msgboxtxt,
+		        "Failed to start %s with code %u:\n%s\n\nEdit settings?",
+		        g_server_name,
+		        error.code,
+		        error_text);
+		ret =
+		    MessageBox(NULL, msgboxtxt, "Error", MB_ICONERROR | MB_YESNOCANCEL);
+		if (ret == IDYES) {
+			show_settings_dialog();
+
+			/* Hitting "save" will also restart the server. */
+			if (g_ctx != NULL) {
+				return;
+			}
+		}
+		exit(EXIT_FAILURE);
+#else
+		die("Failed to start %s with code %u:\n%s",
 		    g_server_name,
-		    ((g_user_data.first_message == NULL) ? "unknown reason"
-		                                         : g_user_data.first_message));
-		/* TODO: Edit file g_config_file_name */
+		    error.code,
+		    error_text);
+#endif
 	}
 
 #if defined(MG_EXPERIMENTAL_INTERFACES)
@@ -1506,7 +1398,7 @@ start_civetweb(int argc, char *argv[])
 		}
 
 		for (j = 0; options[j] != NULL; j++) {
-			free(options[j]);
+			free((void *)options[j]);
 		}
 	}
 #endif
@@ -1517,8 +1409,6 @@ static void
 stop_civetweb(void)
 {
 	mg_stop(g_ctx);
-	free(g_user_data.first_message);
-	g_user_data.first_message = NULL;
 }
 
 
@@ -1709,7 +1599,7 @@ SettingsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
 	int i, j;
 	const char *name, *value;
 	const struct mg_option *default_options = mg_get_valid_options();
-	char *file_options[MAX_OPTIONS * 2 + 1] = {0};
+	const char *file_options[MAX_OPTIONS * 2 + 1] = {0};
 	char *title;
 	struct dlg_proc_param *pdlg_proc_param;
 
@@ -1773,8 +1663,8 @@ SettingsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
 				}
 			}
 			for (i = 0; i < MAX_OPTIONS; i++) {
-				free(file_options[2 * i]);
-				free(file_options[2 * i + 1]);
+				free((void *)file_options[2 * i]);
+				free((void *)file_options[2 * i + 1]);
 			}
 			break;
 
@@ -1836,7 +1726,7 @@ SettingsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
 				}
 
 				if (path[0] != '\0') {
-					/* Something has been choosen */
+					/* Something has been chosen */
 					SetWindowText(GetDlgItem(hDlg, ID_CONTROLS + i), path);
 				}
 			}
@@ -2311,7 +2201,7 @@ optioncmp(const char *o1, const char *o2)
 
 
 static void
-show_settings_dialog()
+show_settings_dialog(void)
 {
 	/* Parameter for size/format tuning of the dialog */
 	short HEIGHT = 15;
diff --git a/src/match.inl b/src/match.inl
new file mode 100644
index 0000000..a5011f5
--- /dev/null
+++ b/src/match.inl
@@ -0,0 +1,262 @@
+/* Reimplementation of pattern matching */
+/* This file is part of the CivetWeb web server.
+ * See https://github.com/civetweb/civetweb/
+ */
+
+
+/* Initialize structure with 0 matches */
+static void
+match_context_reset(struct mg_match_context *mcx)
+{
+	mcx->num_matches = 0;
+	memset(mcx->match, 0, sizeof(mcx->match));
+}
+
+
+/* Add a new match to the list of matches */
+static void
+match_context_push(const char *str, size_t len, struct mg_match_context *mcx)
+{
+	if (mcx->num_matches < MG_MATCH_CONTEXT_MAX_MATCHES) {
+		mcx->match[mcx->num_matches].str = str;
+		mcx->match[mcx->num_matches].len = len;
+		mcx->num_matches++;
+	}
+}
+
+
+static ptrdiff_t
+mg_match_impl(const char *pat,
+              size_t pat_len,
+              const char *str,
+              struct mg_match_context *mcx)
+{
+	/* Parse string */
+	size_t i_pat = 0; /* Pattern index */
+	size_t i_str = 0; /* Pattern index */
+
+	int case_sensitive = ((mcx != NULL) ? mcx->case_sensitive : 0); /* 0 or 1 */
+
+	while (i_pat < pat_len) {
+
+		/* Pattern ? matches one character, except / and NULL character */
+		if ((pat[i_pat] == '?') && (str[i_str] != '\0')
+		    && (str[i_str] != '/')) {
+			size_t i_str_start = i_str;
+			do {
+				/* Advance as long as there are ? */
+				i_pat++;
+				i_str++;
+			} while ((pat[i_pat] == '?') && (str[i_str] != '\0')
+			         && (str[i_str] != '/') && (i_pat < pat_len));
+
+			/* If we have a match context, add the substring we just found */
+			if (mcx) {
+				match_context_push(str + i_str_start, i_str - i_str_start, mcx);
+			}
+
+			/* Reached end of pattern ? */
+			if (i_pat == pat_len) {
+				return (ptrdiff_t)i_str;
+			}
+		}
+
+		/* Pattern $ matches end of string */
+		if (pat[i_pat] == '$') {
+			return (str[i_str] == '\0') ? (ptrdiff_t)i_str : -1;
+		}
+
+		/* Pattern * or ** matches multiple characters */
+		if (pat[i_pat] == '*') {
+			size_t len; /* length matched by "*" or "**" */
+			ptrdiff_t ret;
+
+			i_pat++;
+			if ((pat[i_pat] == '*') && (i_pat < pat_len)) {
+				/* Pattern ** matches all */
+				i_pat++;
+				len = strlen(str + i_str);
+			} else {
+				/* Pattern * matches all except / character */
+				len = strcspn(str + i_str, "/");
+			}
+
+			if (i_pat == pat_len) {
+				/* End of pattern reached. Add all to match context. */
+				if (mcx) {
+					match_context_push(str + i_str, len, mcx);
+				}
+				return ((ptrdiff_t)(i_str + len));
+			}
+
+			/* This loop searches for the longest possible match */
+			do {
+				ret = mg_match_impl(pat + i_pat,
+				                    (pat_len - (size_t)i_pat),
+				                    str + i_str + len,
+				                    mcx);
+			} while ((ret == -1) && (len-- > 0));
+
+			/* If we have a match context, add the substring we just found */
+			if (ret >= 0) {
+				if (mcx) {
+					match_context_push(str + i_str, len, mcx);
+				}
+				return ((ptrdiff_t)i_str + ret + (ptrdiff_t)len);
+			}
+
+			return -1;
+		}
+
+
+		/* Single character compare */
+		if (case_sensitive) {
+			if (pat[i_pat] != str[i_str]) {
+				/* case sensitive compare: mismatch */
+				return -1;
+			}
+		} else if (lowercase(&pat[i_pat]) != lowercase(&str[i_str])) {
+			/* case insensitive compare: mismatch */
+			return -1;
+		}
+
+		i_pat++;
+		i_str++;
+	}
+	return (ptrdiff_t)i_str;
+}
+
+
+static ptrdiff_t
+mg_match_alternatives(const char *pat,
+                      size_t pat_len,
+                      const char *str,
+                      struct mg_match_context *mcx)
+{
+	const char *match_alternative = (const char *)memchr(pat, '|', pat_len);
+
+	if (mcx != NULL) {
+		match_context_reset(mcx);
+	}
+
+	while (match_alternative != NULL) {
+		/* Split at | for alternative match */
+		size_t left_size = (size_t)(match_alternative - pat);
+
+		/* Try left string first */
+		ptrdiff_t ret = mg_match_impl(pat, left_size, str, mcx);
+		if (ret >= 0) {
+			/* A 0-byte match is also valid */
+			return ret;
+		}
+
+		/* Reset possible incomplete match data */
+		if (mcx != NULL) {
+			match_context_reset(mcx);
+		}
+
+		/* If no match: try right side */
+		pat += left_size + 1;
+		pat_len -= left_size + 1;
+		match_alternative = (const char *)memchr(pat, '|', pat_len);
+	}
+
+	/* Handled all | operators. This is the final string. */
+	return mg_match_impl(pat, pat_len, str, mcx);
+}
+
+
+static int
+match_compare(const void *p1, const void *p2, void *user)
+{
+	const struct mg_match_element *e1 = (const struct mg_match_element *)p1;
+	const struct mg_match_element *e2 = (const struct mg_match_element *)p2;
+
+	/* unused */
+	(void)user;
+
+	if (e1->str > e2->str) {
+		return +1;
+	}
+	if (e1->str < e2->str) {
+		return -1;
+	}
+	return 0;
+}
+
+
+#if defined(MG_EXPERIMENTAL_INTERFACES)
+CIVETWEB_API
+#else
+static
+#endif
+ptrdiff_t
+mg_match(const char *pat, const char *str, struct mg_match_context *mcx)
+{
+	size_t pat_len = strlen(pat);
+	ptrdiff_t ret = mg_match_alternatives(pat, pat_len, str, mcx);
+	if (mcx != NULL) {
+		if (ret < 0) {
+			/* Remove possible incomplete data */
+			match_context_reset(mcx);
+		} else {
+			/* Join "?*" to one pattern. */
+			size_t i, j;
+
+			/* Use difference of two array elements instead of sizeof, since
+			 * there may be some additional padding bytes. */
+			size_t elmsize =
+			    (size_t)(&mcx->match[1]) - (size_t)(&mcx->match[0]);
+
+			/* First sort the matches by address ("str" begin to end) */
+			mg_sort(mcx->match, mcx->num_matches, elmsize, match_compare, NULL);
+
+			/* Join consecutive matches */
+			i = 1;
+			while (i < mcx->num_matches) {
+				if ((mcx->match[i - 1].str + mcx->match[i - 1].len)
+				    == mcx->match[i].str) {
+					/* Two matches are consecutive. Join length. */
+					mcx->match[i - 1].len += mcx->match[i].len;
+
+					/* Shift all list elements. */
+					for (j = i + 1; j < mcx->num_matches; j++) {
+						mcx->match[j - 1].len = mcx->match[j].len;
+						mcx->match[j - 1].str = mcx->match[j].str;
+					}
+
+					/* Remove/blank last list element. */
+					mcx->num_matches--;
+					mcx->match[mcx->num_matches].str = NULL;
+					mcx->match[mcx->num_matches].len = 0;
+
+				} else {
+					i++;
+				}
+			}
+		}
+	}
+	return ret;
+}
+
+
+static ptrdiff_t
+match_prefix(const char *pattern, size_t pattern_len, const char *str)
+{
+	if (pattern == NULL) {
+		return -1;
+	}
+	return mg_match_alternatives(pattern, pattern_len, str, NULL);
+}
+
+
+static ptrdiff_t
+match_prefix_strlen(const char *pattern, const char *str)
+{
+	if (pattern == NULL) {
+		return -1;
+	}
+	return mg_match_alternatives(pattern, strlen(pattern), str, NULL);
+}
+
+/* End of match.inl */
diff --git a/src/md5.inl b/src/md5.inl
index b2f5f97..5dd3a60 100644
--- a/src/md5.inl
+++ b/src/md5.inl
@@ -131,6 +131,7 @@ MD5_STATIC void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
  */
 
 #if !defined(MD5_STATIC)
+#include <stdint.h>
 #include <string.h>
 #endif
 
@@ -239,7 +240,7 @@ md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
 			 * On little-endian machines, we can process properly aligned
 			 * data without copying it.
 			 */
-			if (!((data - (const md5_byte_t *)0) & 3)) {
+			if (!(((uintptr_t)data) & 3)) {
 				/* data are properly aligned, a direct assignment is possible */
 				/* cast through a (void *) should avoid a compiler warning,
 				   see
diff --git a/src/mod_lua.inl b/src/mod_lua.inl
index 5cc9431..f1073e5 100644
--- a/src/mod_lua.inl
+++ b/src/mod_lua.inl
@@ -359,22 +359,26 @@ static int
 lsp_connect(lua_State *L)
 {
 	int num_args = lua_gettop(L);
-	char ebuf[100];
+	struct mg_error_data error;
+	char ebuf[128];
 	SOCKET sock;
 	union usa sa;
 	int ok;
 
+	memset(&error, 0, sizeof(error));
+	error.text = ebuf;
+	error.text_buffer_size = sizeof(ebuf);
+
 	if ((num_args == 3) && lua_isstring(L, 1) && lua_isnumber(L, 2)
 	    && lua_isnumber(L, 3)) {
 
 		const char *host = lua_tostring(L, 1);
-		const int port = lua_tointeger(L, 2);
-		const int is_ssl = lua_tointeger(L, 3);
+		const int port = (int)lua_tointeger(L, 2);
+		const int is_ssl = (int)lua_tointeger(L, 3);
 
-		ok = connect_socket(
-		    NULL, host, port, is_ssl, ebuf, sizeof(ebuf), &sock, &sa);
+		ok = connect_socket(NULL, host, port, is_ssl, &error, &sock, &sa);
 		if (!ok) {
-			return luaL_error(L, ebuf);
+			return luaL_error(L, error.text);
 		} else {
 			set_blocking_mode(sock);
 			lua_newtable(L);
@@ -942,7 +946,7 @@ lsp_include(lua_State *L)
 
 			} else if ((*path_type == 'r') || (*path_type == 'f')) {
 				/* "relative" = file name is relative to the
-				 * currect document */
+				 * current document */
 				(void)mg_snprintf(
 				    conn,
 				    &truncated,
@@ -1266,7 +1270,7 @@ lsp_split_form_urlencoded(lua_State *L)
 	/* Get input (const string) */
 	in = lua_tolstring(L, 1, &len);
 
-	/* Create a modifyable copy */
+	/* Create a modifiable copy */
 	buf = (char *)mg_malloc_ctx(len + 1, ctx);
 	if (buf == NULL) {
 		return luaL_error(L, "out of memory in invalid split_form_data() call");
@@ -1506,7 +1510,6 @@ lsp_base64_encode(lua_State *L)
 	int num_args = lua_gettop(L);
 	const char *text;
 	size_t text_len;
-	char *dst;
 	struct mg_context *ctx;
 
 	lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
@@ -1516,9 +1519,14 @@ lsp_base64_encode(lua_State *L)
 	if (num_args == 1) {
 		text = lua_tolstring(L, 1, &text_len);
 		if (text) {
-			dst = (char *)mg_malloc_ctx(text_len * 8 / 6 + 4, ctx);
+			/* Base 64 encodes 8 bits into 6 */
+			size_t dst_len = text_len * 8 / 6 + 4;
+			char *dst = (char *)mg_malloc_ctx(dst_len, ctx);
 			if (dst) {
-				base64_encode((const unsigned char *)text, (int)text_len, dst);
+				mg_base64_encode((const unsigned char *)text,
+				                 (int)text_len,
+				                 dst,
+				                 &dst_len);
 				lua_pushstring(L, dst);
 				mg_free(dst);
 			} else {
@@ -1543,7 +1551,7 @@ lsp_base64_decode(lua_State *L)
 	const char *text;
 	size_t text_len, dst_len;
 	int ret;
-	char *dst;
+	unsigned char *dst;
 	struct mg_context *ctx;
 
 	lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
@@ -1553,18 +1561,15 @@ lsp_base64_decode(lua_State *L)
 	if (num_args == 1) {
 		text = lua_tolstring(L, 1, &text_len);
 		if (text) {
-			dst = (char *)mg_malloc_ctx(text_len, ctx);
+			dst = (unsigned char *)mg_malloc_ctx(text_len, ctx);
 			if (dst) {
-				ret = base64_decode((const unsigned char *)text,
-				                    (int)text_len,
-				                    dst,
-				                    &dst_len);
+				ret = mg_base64_decode(text, (int)text_len, dst, &dst_len);
 				if (ret != -1) {
 					mg_free(dst);
 					return luaL_error(
 					    L, "illegal character in lsp_base64_decode() call");
 				} else {
-					lua_pushlstring(L, dst, dst_len);
+					lua_pushlstring(L, (char *)dst, dst_len);
 					mg_free(dst);
 				}
 			} else {
@@ -1619,10 +1624,10 @@ lsp_random(lua_State *L)
 		/* The civetweb internal random number generator will generate
 		 * a 64 bit random number. */
 		uint64_t r = get_random();
-		/* Lua "number" is a IEEE 754 double precission float:
+		/* Lua "number" is a IEEE 754 double precision float:
 		 * https://en.wikipedia.org/wiki/Double-precision_floating-point_format
 		 * Thus, mask with 2^53-1 to get an integer with the maximum
-		 * precission available. */
+		 * precision available. */
 		r &= ((((uint64_t)1) << 53) - 1);
 		lua_pushnumber(L, (double)r);
 		return 1;
@@ -2480,7 +2485,7 @@ enum {
 	LUA_ENV_TYPE_LUA_SERVER_PAGE = 0, /* page.lp */
 	LUA_ENV_TYPE_PLAIN_LUA_PAGE = 1,  /* script.lua */
 	LUA_ENV_TYPE_LUA_WEBSOCKET = 2,   /* websock.lua */
-	LUA_ENV_TYPE_BACKGROUND = 9 /* Lua backgrond script or exec from cmdline */
+	LUA_ENV_TYPE_BACKGROUND = 9 /* Lua background script or exec from cmdline */
 };
 
 
@@ -2643,13 +2648,12 @@ static void *
 lua_allocator(void *ud, void *ptr, size_t osize, size_t nsize)
 {
 	(void)osize; /* not used */
-
+	struct mg_context *ctx = (struct mg_context *)ud;
 	if (nsize == 0) {
 		mg_free(ptr);
 		return NULL;
 	}
-
-	return mg_realloc_ctx(ptr, nsize, (struct mg_context *)ud);
+	return mg_realloc_ctx(ptr, nsize, ctx);
 }
 
 
@@ -2665,7 +2669,6 @@ civetweb_open_lua_libs(lua_State *L)
 		extern void luaL_openlibs(lua_State *);
 		luaL_openlibs(L);
 	}
-
 #if defined(USE_LUA_SQLITE3)
 	{
 		extern int luaopen_lsqlite3(lua_State *);
@@ -2676,7 +2679,6 @@ civetweb_open_lua_libs(lua_State *L)
 	{
 		extern int luaopen_LuaXML_lib(lua_State *);
 		luaopen_LuaXML_lib(L);
-		// lua_pushvalue(L, -1); to copy value
 		lua_setglobal(L, "xml");
 	}
 #endif
@@ -2686,6 +2688,18 @@ civetweb_open_lua_libs(lua_State *L)
 		luaopen_lfs(L);
 	}
 #endif
+#if defined(USE_LUA_STRUCT)
+	{
+		int luaopen_struct(lua_State * L);
+		luaopen_struct(L);
+	}
+#endif
+#if defined(USE_LUA_SHARED_MEMORY)
+	{
+		extern int luaopen_lsh(lua_State *);
+		luaopen_lsh(L);
+	}
+#endif
 }
 
 
@@ -2706,7 +2720,7 @@ lsp_mg_gc(lua_State *L)
 
 	lua_pushlightuserdata(L, (void *)&lua_regkey_environment_type);
 	lua_gettable(L, LUA_REGISTRYINDEX);
-	context_flags = lua_tointeger(L, -1);
+	context_flags = (int)lua_tointeger(L, -1);
 
 	if (ctx != NULL) {
 		if (ctx->callbacks.exit_lua != NULL) {
@@ -2785,6 +2799,12 @@ prepare_lua_environment(struct mg_context *ctx,
 
 	int lua_context_flags = lua_env_type;
 
+	DEBUG_TRACE("Lua environment type %i: %p, connection %p, script %s",
+	            lua_env_type,
+	            L,
+	            conn,
+	            script_name);
+
 	civetweb_open_lua_libs(L);
 
 #if defined(MG_EXPERIMENTAL_INTERFACES)
@@ -3042,6 +3062,7 @@ mg_exec_lua_script(struct mg_connection *conn,
 		} else {
 			lua_pcall(L, 0, 0, -2);
 		}
+		DEBUG_TRACE("Close Lua environment %p", L);
 		lua_close(L);
 	}
 }
@@ -3201,10 +3222,13 @@ handle_lsp_request(struct mg_connection *conn,
 
 cleanup_handle_lsp_request:
 
-	if (L != NULL && ls == NULL)
+	if (L != NULL && ls == NULL) {
+		DEBUG_TRACE("Close Lua environment %p", L);
 		lua_close(L);
-	if (p != NULL)
+	}
+	if (p != NULL) {
 		munmap(p, filep->stat.size);
+	}
 	(void)mg_fclose(&filep->access);
 
 	return error;
@@ -3500,6 +3524,7 @@ mg_lua_context_script_prepare(const char *file_name,
 		            "Error loading file %s: %s\n",
 		            file_name,
 		            lua_err_txt);
+		DEBUG_TRACE("Close Lua environment %p", L);
 		lua_close(L);
 		return 0;
 	}
@@ -3536,6 +3561,7 @@ mg_lua_context_script_run(lua_State *L,
 		            "Error running file %s: %s\n",
 		            file_name,
 		            lua_err_txt);
+		DEBUG_TRACE("Close Lua environment %p", L);
 		lua_close(L);
 		return 0;
 	}
@@ -3552,6 +3578,7 @@ mg_lua_context_script_run(lua_State *L,
 			            ebuf_len,
 			            "Script %s returned false\n",
 			            file_name);
+			DEBUG_TRACE("Close Lua environment %p", L);
 			lua_close(L);
 			return 0;
 		}
@@ -3581,6 +3608,8 @@ lua_ctx_exit(struct mg_context *ctx)
 
 	mg_lock_context(ctx);
 	while (*shared_websock_list) {
+		DEBUG_TRACE("Close Lua environment %p",
+		            (*shared_websock_list)->ws.state);
 		lua_close((*shared_websock_list)->ws.state);
 		mg_free((*shared_websock_list)->ws.script);
 
@@ -3614,6 +3643,7 @@ run_lua(const char *file_name)
 		} else {
 			func_ret = EXIT_SUCCESS;
 		}
+		DEBUG_TRACE("Close Lua environment %p", L);
 		lua_close(L);
 	} else {
 		fprintf(stderr, "%s\n", ebuf);
diff --git a/src/mod_mbedtls.inl b/src/mod_mbedtls.inl
index 4fefbfa..822fc2e 100644
--- a/src/mod_mbedtls.inl
+++ b/src/mod_mbedtls.inl
@@ -1,11 +1,19 @@
 #if defined(USE_MBEDTLS) // USE_MBEDTLS used with NO_SSL
 
-#include "mbedtls/certs.h"
 #include "mbedtls/ctr_drbg.h"
 #include "mbedtls/debug.h"
 #include "mbedtls/entropy.h"
 #include "mbedtls/error.h"
+
+#if MBEDTLS_VERSION_NUMBER >= 0x03000000
+// The file include/mbedtls/net.h was removed in v3.0.0 because its only
+// function was to include mbedtls/net_sockets.h which now should be included
+// directly.
+#include "mbedtls/net_sockets.h"
+#else
 #include "mbedtls/net.h"
+#endif
+
 #include "mbedtls/pk.h"
 #include "mbedtls/platform.h"
 #include "mbedtls/ssl.h"
@@ -90,7 +98,17 @@ mbed_sslctx_init(SSL_CTX *ctx, const char *crt)
 		return -1;
 	}
 
+#if MBEDTLS_VERSION_NUMBER >= 0x03000000
+	// mbedtls_pk_parse_keyfile() has changed in mbedTLS 3.0. You now need
+	// to pass a properly seeded, cryptographically secure RNG when calling
+	// these functions. It is used for blinding, a countermeasure against
+	// side-channel attacks.
+	// https://github.com/Mbed-TLS/mbedtls/blob/development/docs/3.0-migration-guide.md#some-functions-gained-an-rng-parameter
+	rc = mbedtls_pk_parse_keyfile(
+	    &ctx->pkey, crt, NULL, mbedtls_ctr_drbg_random, &ctx->ctr);
+#else
 	rc = mbedtls_pk_parse_keyfile(&ctx->pkey, crt, NULL);
+#endif
 	if (rc != 0) {
 		DEBUG_TRACE("TLS parse key file failed (%i)", rc);
 		return -1;
diff --git a/src/mod_zlib.inl b/src/mod_zlib.inl
index 2c42094..068ec77 100644
--- a/src/mod_zlib.inl
+++ b/src/mod_zlib.inl
@@ -12,7 +12,7 @@ zalloc(void *opaque, uInt items, uInt size)
 {
 	struct mg_connection *conn = (struct mg_connection *)opaque;
 	void *ret = mg_calloc_ctx(items, size, conn->phys_ctx);
-	(void)conn; /* mg_calloc_ctx makro might not need it */
+	(void)conn; /* mg_calloc_ctx macro might not need it */
 
 	return ret;
 }
diff --git a/src/response.inl b/src/response.inl
index 0c7c00c..35e6ba8 100644
--- a/src/response.inl
+++ b/src/response.inl
@@ -37,7 +37,7 @@ free_buffered_response_header_list(struct mg_connection *conn)
 
 
 /* Send first line of HTTP/1.x response */
-static void
+static int
 send_http1_response_status_line(struct mg_connection *conn)
 {
 	const char *status_txt;
@@ -55,7 +55,13 @@ send_http1_response_status_line(struct mg_connection *conn)
 	/* mg_get_response_code_text will never return NULL */
 	status_txt = mg_get_response_code_text(conn, conn->status_code);
 
-	mg_printf(conn, "HTTP/%s %i %s\r\n", http_version, status_code, status_txt);
+	if (mg_printf(
+	        conn, "HTTP/%s %i %s\r\n", http_version, status_code, status_txt)
+	    < 10) {
+		/* Network sending failed */
+		return 0;
+	}
+	return 1;
 }
 
 
@@ -68,10 +74,12 @@ send_http1_response_status_line(struct mg_connection *conn)
  *  -1:    parameter error
  *  -2:    invalid connection type
  *  -3:    invalid connection status
+ *  -4:    network error (only if built with NO_RESPONSE_BUFFERING)
  */
 int
 mg_response_header_start(struct mg_connection *conn, int status)
 {
+	int ret = 0;
 	if ((conn == NULL) || (status < 100) || (status > 999)) {
 		/* Parameter error */
 		return -1;
@@ -93,11 +101,13 @@ mg_response_header_start(struct mg_connection *conn, int status)
 #if !defined(NO_RESPONSE_BUFFERING)
 	free_buffered_response_header_list(conn);
 #else
-	send_http1_response_status_line(conn);
+	if (!send_http1_response_status_line(conn)) {
+		ret = -4;
+	};
 	conn->request_state = 1; /* Reset from 10 to 1 */
 #endif
 
-	return 0;
+	return ret;
 }
 
 
@@ -254,6 +264,7 @@ static int http2_send_response_headers(struct mg_connection *conn);
  *  -1:    parameter error
  *  -2:    invalid connection type
  *  -3:    invalid connection status
+ *  -4:    network send failed
  */
 int
 mg_response_header_send(struct mg_connection *conn)
@@ -285,12 +296,16 @@ mg_response_header_send(struct mg_connection *conn)
 #if defined(USE_HTTP2)
 	if (conn->protocol_type == PROTOCOL_TYPE_HTTP2) {
 		int ret = http2_send_response_headers(conn);
-		return ret ? 0 : 0; /* todo */
+		free_buffered_response_header_list(conn);
+		return (ret ? 0 : -4);
 	}
 #endif
 
 	/* Send */
-	send_http1_response_status_line(conn);
+	if (!send_http1_response_status_line(conn)) {
+		free_buffered_response_header_list(conn);
+		return -4;
+	};
 	for (i = 0; i < conn->response_info.num_headers; i++) {
 		mg_printf(conn,
 		          "%s: %s\r\n",
@@ -322,5 +337,6 @@ mg_response_header_send(struct mg_connection *conn)
 	conn->request_state = 3;
 
 	/* ok */
+	free_buffered_response_header_list(conn);
 	return 0;
 }
diff --git a/src/sort.inl b/src/sort.inl
new file mode 100644
index 0000000..87a5eb7
--- /dev/null
+++ b/src/sort.inl
@@ -0,0 +1,48 @@
+/* Sort function. */
+/* from https://github.com/bel2125/sort_r */
+
+static void
+mg_sort(void *data,
+        size_t elemcount,
+        size_t elemsize,
+        int (*compfunc)(const void *data1, const void *data2, void *userarg),
+        void *userarg)
+{
+	/* We cannot use qsort_r here. For a detailed reason, see
+	 * https://github.com/civetweb/civetweb/issues/1048#issuecomment-1047093014
+	 * https://stackoverflow.com/questions/39560773/different-declarations-of-qsort-r-on-mac-and-linux
+	 */
+
+	/* We use ShellSort here with this gap sequence: https://oeis.org/A102549 */
+	size_t A102549[9] = {1, 4, 10, 23, 57, 132, 301, 701, 1750};
+	size_t gap, i, j, k;
+	int Aidx;
+	void *tmp = alloca(elemsize);
+
+	for (Aidx = 8; Aidx >= 0; Aidx--) {
+		gap = A102549[Aidx];
+		if (gap > (elemcount / 2)) {
+			continue;
+		}
+		for (i = 0; i < gap; i++) {
+			for (j = i; j < elemcount; j += gap) {
+				memcpy(tmp, (void *)((size_t)data + elemsize * j), elemsize);
+
+				for (k = j; k >= gap; k -= gap) {
+					void *cmp = (void *)((size_t)data + elemsize * (k - gap));
+					int cmpres = compfunc(cmp, tmp, userarg);
+					if (cmpres > 0) {
+						memcpy((void *)((size_t)data + elemsize * k),
+						       cmp,
+						       elemsize);
+					} else {
+						break;
+					}
+				}
+				memcpy((void *)((size_t)data + elemsize * k), tmp, elemsize);
+			}
+		}
+	}
+}
+
+/* end if sort.inl */
diff --git a/test/access/.htpasswd b/test/access/.htpasswd
new file mode 100644
index 0000000..c2ee126
--- /dev/null
+++ b/test/access/.htpasswd
@@ -0,0 +1 @@
+user:localhost:1bfdc2f06b8793144b20e067b29ac26a
diff --git a/test/flush.php b/test/flush.php
new file mode 100644
index 0000000..64f1b18
--- /dev/null
+++ b/test/flush.php
@@ -0,0 +1,16 @@
+<?php
+// Server test options:
+//  ./civetweb -document_root test -cgi_interpreter /usr/bin/php-cgi -allow_sendfile_call no -num_threads 2 -cgi_buffering no &
+
+set_time_limit(20);
+header('Content-Type: text/plain; charset=utf-8');
+
+echo "CivetWeb Flush Test:\nPrint one line every second.\n\n";
+for($i = 1; $i <= 10; $i++) {
+	@printf("Line: %2d / 10\n", $i);
+	@flush();
+	@ob_flush();
+	sleep(1);
+}
+echo "\nEnd of test.\n";
+?>
\ No newline at end of file
diff --git a/test/lua_backbround_script_logging.lua b/test/lua_backbround_script_logging.lua
index 2683698..10e3dec 100644
--- a/test/lua_backbround_script_logging.lua
+++ b/test/lua_backbround_script_logging.lua
@@ -44,7 +44,7 @@ function log(req, resp)
 	  logfile:flush();
 	end
 
-	-- Loging already done here
+	-- Logging already done here
 	return false;
 end
 
diff --git a/test/page.lp b/test/page.lp
index 8f11f79..4165bfb 100644
--- a/test/page.lp
+++ b/test/page.lp
@@ -48,7 +48,7 @@ The following features are available:
     );
   ]])
 
-  -- Add colums to table created with older version
+  -- Add columns to table created with older version
   db:exec("ALTER TABLE requests ADD COLUMN civetwebversion;")
   db:exec("ALTER TABLE requests ADD COLUMN luaversion;")
   db:exec("ALTER TABLE requests ADD COLUMN aux;")
diff --git a/test/page.lua b/test/page.lua
index de42648..f815be3 100644
--- a/test/page.lua
+++ b/test/page.lua
@@ -64,7 +64,7 @@ if db then
     );
   ]])
 
-  -- Add colums to table created with older version
+  -- Add columns to table created with older version
   db:exec("ALTER TABLE requests ADD COLUMN civetwebversion;")
   db:exec("ALTER TABLE requests ADD COLUMN luaversion;")
   db:exec("ALTER TABLE requests ADD COLUMN aux;")
diff --git a/test/page2.lua b/test/page2.lua
index caae1bc..be6be61 100644
--- a/test/page2.lua
+++ b/test/page2.lua
@@ -75,6 +75,8 @@ print_if_available(lfs, "LuaFileSystem (lfs)")
 print_if_available(json, "JSON binding (json)")
 print_if_available(xml, "LuaXML (xml)")
 print_if_available(shared, "Lua shared data (shared)")
+print_if_available(lsh, "LSH")
+print_if_available(struct, "struct")
 
 
 --recurse(_G)
diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt
index a354b12..7aa3497 100644
--- a/unittest/CMakeLists.txt
+++ b/unittest/CMakeLists.txt
@@ -5,41 +5,27 @@ else()
   set(THIRD_PARTY_LOGGING 1)
 endif()
 
+message(STATUS "CMAKE UNIT TEST LINE 8")
+
 # We use the check unit testing framework for our C unit tests
 include(ExternalProject)
-if(NOT WIN32)
-  # Apply the patch to check to fix CMake building on OS X
-  set(CHECK_PATCH_COMMAND patch
-     ${CIVETWEB_THIRD_PARTY_DIR}/src/check-unit-test-framework/src/check_run.c
-     ${CMAKE_SOURCE_DIR}/cmake/check/check_run.patch
-   )
-else()
-  set(CHECK_PATCH_COMMAND "")
-endif()
-
 IF (DEFINED ENV{CHECK_URL})
 SET (CHECK_URL $ENV{CHECK_URL})
 ELSE()
 SET (CHECK_URL "https://github.com/civetweb/check/archive/master.zip")
 ENDIF()
 
+## Print what version of the CHECK unit test framework we are using
+message(STATUS "Using check unit test framework from ${CHECK_URL}")
 
 ExternalProject_Add(check-unit-test-framework
   DEPENDS civetweb-c-library
 
-## Use an official, released check version:
-#  URL "https://codeload.github.com/libcheck/check/zip/${CIVETWEB_CHECK_VERSION}"
-#  DOWNLOAD_NAME "${CIVETWEB_CHECK_VERSION}.zip"
-#  URL_MD5 ${CIVETWEB_CHECK_MD5_HASH}
-
-## Use a civetweb specific patched version
-
 URL ${CHECK_URL}
 DOWNLOAD_NAME "master.zip"
 
   PREFIX "${CIVETWEB_THIRD_PARTY_DIR}"
   BUILD_IN_SOURCE 1
-  PATCH_COMMAND ${CHECK_PATCH_COMMAND}
   CMAKE_ARGS
     "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
     "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}"
@@ -50,7 +36,8 @@ DOWNLOAD_NAME "master.zip"
   LOG_BUILD ${THIRD_PARTY_LOGGING}
   LOG_TEST ${THIRD_PARTY_LOGGING}
   LOG_INSTALL ${THIRD_PARTY_LOGGING})
-
+  
+  
 ExternalProject_Get_Property(check-unit-test-framework INSTALL_DIR)
 set(CHECK_INSTALL_DIR ${INSTALL_DIR})
 unset(INSTALL_DIR)
@@ -181,6 +168,7 @@ civetweb_add_test(PublicFunc "Options")
 civetweb_add_test(PublicFunc "MIME types")
 civetweb_add_test(PublicFunc "strcasecmp")
 civetweb_add_test(PublicFunc "URL encoding decoding")
+civetweb_add_test(PublicFunc "BASE64 encoding decoding")
 civetweb_add_test(PublicFunc "Cookies and variables")
 civetweb_add_test(PublicFunc "MD5")
 civetweb_add_test(PublicFunc "Aux functions")
@@ -206,7 +194,6 @@ civetweb_add_test(PublicServer "Error handling")
 civetweb_add_test(PublicServer "Error logging")
 civetweb_add_test(PublicServer "Limit speed")
 civetweb_add_test(PublicServer "Large file")
-civetweb_add_test(PublicServer "File in memory")
 
 # Timer tests
 civetweb_add_test(Timer "Timer Single Shot")
diff --git a/unittest/civetweb_check.h b/unittest/civetweb_check.h
index 08193f1..e1fadd8 100644
--- a/unittest/civetweb_check.h
+++ b/unittest/civetweb_check.h
@@ -50,9 +50,7 @@
 #endif
 #include <stdint.h>
 
-/* All unit tests use the "check" framework.
- * Download from https://libcheck.github.io/check/
- */
+/* All unit tests use the "check" unit test framework. */
 #include "check.h"
 
 #if (CHECK_MINOR_VERSION < 11)
diff --git a/unittest/main.c b/unittest/main.c
index d7a7051..26790a8 100644
--- a/unittest/main.c
+++ b/unittest/main.c
@@ -41,9 +41,6 @@
  *
  * Note: CivetWeb is tested using it's own fork of check:
  * https://github.com/civetweb/check
- * Required fixes from this fork are already available
- * in the main repository:
- * https://github.com/libcheck/check
  */
 
 #define FILENAME_LEN (128)
diff --git a/unittest/private.c b/unittest/private.c
index 584f3f0..8e85d71 100644
--- a/unittest/private.c
+++ b/unittest/private.c
@@ -324,6 +324,394 @@ START_TEST(test_match_prefix)
 END_TEST
 
 
+START_TEST(test_match_prefix_strlen)
+{
+	/* Copyright (c) 2022 the CivetWeb developers */
+	ck_assert_int_eq(5, match_prefix_strlen("/Test", "/test"));
+	ck_assert_int_eq(-1, match_prefix_strlen("/Test", "/my/test"));
+	ck_assert_int_eq(3, match_prefix_strlen("/my", "/my/test"));
+	ck_assert_int_eq(-1, match_prefix_strlen("/my$", "/my/test"));
+	ck_assert_int_eq(8, match_prefix_strlen("/*/Test", "/my/test"));
+
+	ck_assert_int_eq(17,
+	                 match_prefix_strlen("/api/*/*.cgi", "/api/obj/prop.cgi"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/abc/*/*.cgi", "/api/obj/prop.cgi"));
+	ck_assert_int_eq(18,
+	                 match_prefix_strlen("/api/*/*.cgi", "/api/obj/other.cgi"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/api/*/*.cgi",
+	                                     "/api/obj/too/deep.cgi"));
+	ck_assert_int_eq(17,
+	                 match_prefix_strlen("/api/*/*.cgi$", "/api/obj/prop.cgi"));
+	ck_assert_int_eq(18,
+	                 match_prefix_strlen("/api/*/*.cgi$",
+	                                     "/api/obj/other.cgi"));
+	ck_assert_int_eq(17,
+	                 match_prefix_strlen("/api/*/*.cgi",
+	                                     "/api/obj/prop.cgiZZZ"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/api/*/*.cgi$",
+	                                     "/api/obj/prop.cgiZZZ"));
+	ck_assert_int_eq(-1, match_prefix_strlen("/*/*.cgi", "/api/obj/prop.cgi"));
+
+	ck_assert_int_eq(7, match_prefix_strlen("I????IT", "ItestIT"));
+	ck_assert_int_eq(-1, match_prefix_strlen("I????IT", "IseeIT"));
+	ck_assert_int_eq(23, match_prefix_strlen("**$", "EveryThing/matches this"));
+	ck_assert_int_eq(23,
+	                 match_prefix_strlen("?**$", "EveryThing/matches this"));
+	ck_assert_int_eq(23,
+	                 match_prefix_strlen("**?$", "EveryThing/matches this"));
+	ck_assert_int_eq(0, match_prefix_strlen("**$", ""));
+	ck_assert_int_eq(-1, match_prefix_strlen("?**$", ""));
+	ck_assert_int_eq(-1, match_prefix_strlen("**?$", ""));
+	ck_assert_int_eq(-1, match_prefix_strlen("/**?$", "/"));
+	ck_assert_int_eq(1, match_prefix_strlen("/**$", "/"));
+	ck_assert_int_eq(-1, match_prefix_strlen("//", "/"));
+	ck_assert_int_eq(1, match_prefix_strlen("/", "//"));
+	ck_assert_int_eq(-1, match_prefix_strlen("/$", "//"));
+
+	/* ? pattern should not match / character */
+	ck_assert_int_eq(-1, match_prefix_strlen("/?", "//"));
+	ck_assert_int_eq(3, match_prefix_strlen("/?/$", "/a/"));
+	ck_assert_int_eq(-1, match_prefix_strlen("/?/$", "///"));
+
+	/* Pattern From UserManual.md */
+	ck_assert_int_eq(20,
+	                 match_prefix_strlen("**.cgi$", "anywhere/anyname.cgi"));
+	ck_assert_int_eq(-1, match_prefix_strlen("**.cgi$", "name.cgi.not.at.end"));
+	ck_assert_int_eq(4, match_prefix_strlen("/foo", "/foo"));
+	ck_assert_int_eq(4, match_prefix_strlen("/foo", "/foobar"));
+	ck_assert_int_eq(-1, match_prefix_strlen("/foo", "not.at.start./foo"));
+	ck_assert_int_eq(1, match_prefix_strlen("**a$|**b$", "a"));
+	ck_assert_int_eq(2, match_prefix_strlen("**a$|**b$", "xb"));
+	ck_assert_int_eq(-1, match_prefix_strlen("**a$|**b$", "abc"));
+
+	ck_assert_int_eq(14,
+	                 match_prefix_strlen("/data/????.css$", "/data/12.4.css"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/data/????.css$", "/data/12/4.css"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/data/????.css$", "/data/../4.css"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/data/????.css$", "/else/12.4.css"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/data/????.css$", "/data/1234.cssx"));
+	ck_assert_int_eq(13, match_prefix_strlen("/data/*.js$", "/data/1234.js"));
+	ck_assert_int_eq(17,
+	                 match_prefix_strlen("/data/*.js$", "/data/12345678.js"));
+	ck_assert_int_eq(-1, match_prefix_strlen("/data/*.js$", "/else/1234.js"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/data/*.js$", "/data/../some.js"));
+	ck_assert_int_eq(-1, match_prefix_strlen("/data/*.js$", "/data//x.js"));
+	ck_assert_int_eq(-1, match_prefix_strlen("/data/*.js$", "/data/./x.js"));
+	ck_assert_int_eq(34,
+	                 match_prefix_strlen("/api/*/*.cgi$",
+	                                     "/api/resourcetype/resourcename.cgi"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/api/*/*.cgi$",
+	                                     "/api/resourcename.cgi"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/*.jpg$|/*.jpeg$",
+	                                     "/somewhere/something.txt"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/*.jpg$|/*.jpeg$", "/something.txt"));
+	ck_assert_int_eq(10, match_prefix_strlen("/*.jpg$|/*.jpeg$", "/image.jpg"));
+	ck_assert_int_eq(11,
+	                 match_prefix_strlen("/*.jpg$|/*.jpeg$", "/image.jpeg"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/*.jpg$|/*.jpeg$",
+	                                     "/image.jpeg.exe"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/*.jpg$|/*.jpeg$", "/sub/image.jpg"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("/*.jpg$|/*.jpeg$",
+	                                     "/sub/image.jpeg"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("**.jpg$|**.jpeg$",
+	                                     "/somewhere/something.txt"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("**.jpg$|**.jpeg$", "/something.txt"));
+	ck_assert_int_eq(10, match_prefix_strlen("**.jpg$|**.jpeg$", "/image.jpg"));
+	ck_assert_int_eq(11,
+	                 match_prefix_strlen("**.jpg$|**.jpeg$", "/image.jpeg"));
+	ck_assert_int_eq(-1,
+	                 match_prefix_strlen("**.jpg$|**.jpeg$",
+	                                     "/image.jpeg.exe"));
+	ck_assert_int_eq(14,
+	                 match_prefix_strlen("**.jpg$|**.jpeg$", "/sub/image.jpg"));
+	ck_assert_int_eq(15,
+	                 match_prefix_strlen("**.jpg$|**.jpeg$",
+	                                     "/sub/image.jpeg"));
+	ck_assert_int_eq(14,
+	                 match_prefix_strlen("/*/*.jpg$|/*/*.jpeg$",
+	                                     "/sub/image.jpg"));
+	ck_assert_int_eq(15,
+	                 match_prefix_strlen("/*/*.jpg$|/*/*.jpeg$",
+	                                     "/sub/image.jpeg"));
+}
+END_TEST
+
+
+START_TEST(test_match_prefix_fuzz)
+{
+	/* Copyright (c) 2022 the CivetWeb developers */
+	{
+		/* From fuzz test */
+		const char *pat = "**cacc//d/?dad?";
+		const char *str =
+		    "dbbddb/cb/ddcdbcbbab/dcdcbbbcaaacdbcac/dbdcadaa/bcaca/d/a/adcad";
+		ck_assert_int_eq(6, match_prefix(pat, 1, str));
+		ck_assert_int_eq(63, match_prefix(pat, 2, str));
+		ck_assert_int_eq(61, match_prefix(pat, 3, str));
+		ck_assert_int_eq(62, match_prefix(pat, 4, str));
+		ck_assert_int_eq(52, match_prefix(pat, 5, str));
+		ck_assert_int_eq(-1, match_prefix(pat, 6, str));
+	}
+	{
+		/* From fuzz test */
+		const char *pat = "a/a*d**/?*/*cdd";
+		const char *str =
+		    "a/aaddba/ddadbaacac//bcaadbc/badaccbdadadcbb//ccd/dcbacdcddc//c";
+		ck_assert_int_eq(0, match_prefix(pat, 0, str));
+		ck_assert_int_eq(1, match_prefix(pat, 1, str));
+		ck_assert_int_eq(2, match_prefix(pat, 2, str));
+		ck_assert_int_eq(3, match_prefix(pat, 3, str));
+		ck_assert_int_eq(8, match_prefix(pat, 4, str));
+		ck_assert_int_eq(6, match_prefix(pat, 5, str));
+		ck_assert_int_eq(8, match_prefix(pat, 6, str));
+		ck_assert_int_eq(63, match_prefix(pat, 7, str));
+		ck_assert_int_eq(62, match_prefix(pat, 8, str));
+		ck_assert_int_eq(63, match_prefix(pat, 9, str));
+		ck_assert_int_eq(63, match_prefix(pat, 10, str));
+		ck_assert_int_eq(61, match_prefix(pat, 11, str));
+		ck_assert_int_eq(61, match_prefix(pat, 12, str));
+		ck_assert_int_eq(60, match_prefix(pat, 13, str));
+		ck_assert_int_eq(58, match_prefix(pat, 14, str));
+		ck_assert_int_eq(59, match_prefix(pat, 15, str));
+		ck_assert_int_eq(59, match_prefix_strlen(pat, str));
+	}
+	{
+		/* From fuzz test */
+		const char *pat = "cc/?**ba????**b";
+		const char *str =
+		    "cc/babdb/cbb/baa/da/cd///ccabbcdcdaa/dbacbdbadaccb/dbdcc/cdbbac";
+		ck_assert_int_eq(0, match_prefix(pat, 0, str));
+		ck_assert_int_eq(1, match_prefix(pat, 1, str));
+		ck_assert_int_eq(2, match_prefix(pat, 2, str));
+		ck_assert_int_eq(3, match_prefix(pat, 3, str));
+		ck_assert_int_eq(4, match_prefix(pat, 4, str));
+		ck_assert_int_eq(8, match_prefix(pat, 5, str));
+		ck_assert_int_eq(63, match_prefix(pat, 6, str));
+		ck_assert_int_eq(61, match_prefix(pat, 7, str));
+		ck_assert_int_eq(62, match_prefix(pat, 8, str));
+		ck_assert_int_eq(63, match_prefix(pat, 9, str));
+		ck_assert_int_eq(47, match_prefix(pat, 10, str));
+		ck_assert_int_eq(48, match_prefix(pat, 11, str));
+		ck_assert_int_eq(49, match_prefix(pat, 12, str));
+		ck_assert_int_eq(50, match_prefix(pat, 13, str));
+		ck_assert_int_eq(63, match_prefix(pat, 14, str));
+		ck_assert_int_eq(61, match_prefix(pat, 15, str));
+		ck_assert_int_eq(61, match_prefix_strlen(pat, str));
+	}
+	{
+		/* From fuzz test */
+		const char *pat = "?**ba*db*b?*/a?";
+		const char *str =
+		    "bd/bcdddabbd//bcb//acbcaaac/dcbbcdadabadba/bd/baadbabcc/a/bcb//";
+		ck_assert_int_eq(0, match_prefix(pat, 0, str));
+		ck_assert_int_eq(1, match_prefix(pat, 1, str));
+		ck_assert_int_eq(2, match_prefix(pat, 2, str));
+		ck_assert_int_eq(63, match_prefix(pat, 3, str));
+		ck_assert_int_eq(61, match_prefix(pat, 4, str));
+		ck_assert_int_eq(52, match_prefix(pat, 5, str));
+		ck_assert_int_eq(55, match_prefix(pat, 6, str));
+		ck_assert_int_eq(50, match_prefix(pat, 7, str));
+		ck_assert_int_eq(51, match_prefix(pat, 8, str));
+		ck_assert_int_eq(55, match_prefix(pat, 9, str));
+		ck_assert_int_eq(53, match_prefix(pat, 10, str));
+		ck_assert_int_eq(54, match_prefix(pat, 11, str));
+		ck_assert_int_eq(55, match_prefix(pat, 12, str));
+		ck_assert_int_eq(56, match_prefix(pat, 13, str));
+		ck_assert_int_eq(57, match_prefix(pat, 14, str));
+		ck_assert_int_eq(-1, match_prefix(pat, 15, str));
+		ck_assert_int_eq(-1, match_prefix_strlen(pat, str));
+	}
+	{
+		/* From fuzz test */
+		const char *pat = "?b*da*bc?c**//*";
+		const char *str =
+		    "dbadabcbcbbba/a///d//dcdd////daccbcaaa/a/bacddab/bdcbbdd/bbaa/a";
+		ck_assert_int_eq(0, match_prefix(pat, 0, str));
+		ck_assert_int_eq(1, match_prefix(pat, 1, str));
+		ck_assert_int_eq(2, match_prefix(pat, 2, str));
+		ck_assert_int_eq(13, match_prefix(pat, 3, str));
+		ck_assert_int_eq(4, match_prefix(pat, 4, str));
+		ck_assert_int_eq(5, match_prefix(pat, 5, str));
+		ck_assert_int_eq(13, match_prefix(pat, 6, str));
+		ck_assert_int_eq(12, match_prefix(pat, 7, str));
+		ck_assert_int_eq(9, match_prefix(pat, 8, str));
+		ck_assert_int_eq(10, match_prefix(pat, 9, str));
+		ck_assert_int_eq(9, match_prefix(pat, 10, str));
+		ck_assert_int_eq(13, match_prefix(pat, 11, str));
+		ck_assert_int_eq(63, match_prefix(pat, 12, str));
+		ck_assert_int_eq(62, match_prefix(pat, 13, str));
+		ck_assert_int_eq(29, match_prefix(pat, 14, str));
+		ck_assert_int_eq(38, match_prefix(pat, 15, str));
+		ck_assert_int_eq(38, match_prefix_strlen(pat, str));
+	}
+	{
+		/* From fuzz test */
+		const char *pat = "?**c**dc**b*c*a";
+		const char *str = "cba/a/bbcdcccc/bcacacdadab/dad/";
+		ck_assert_int_eq(0, match_prefix(pat, 0, str));
+		ck_assert_int_eq(1, match_prefix(pat, 1, str));
+		ck_assert_int_eq(3, match_prefix(pat, 2, str));
+		ck_assert_int_eq(31, match_prefix(pat, 3, str));
+		ck_assert_int_eq(21, match_prefix(pat, 4, str));
+		ck_assert_int_eq(26, match_prefix(pat, 5, str));
+		ck_assert_int_eq(31, match_prefix(pat, 6, str));
+		ck_assert_int_eq(30, match_prefix(pat, 7, str));
+		ck_assert_int_eq(11, match_prefix(pat, 8, str));
+		ck_assert_int_eq(14, match_prefix(pat, 9, str));
+		ck_assert_int_eq(31, match_prefix(pat, 10, str));
+		ck_assert_int_eq(26, match_prefix(pat, 11, str));
+		ck_assert_int_eq(26, match_prefix(pat, 12, str));
+		ck_assert_int_eq(21, match_prefix(pat, 13, str));
+		ck_assert_int_eq(26, match_prefix(pat, 14, str));
+		ck_assert_int_eq(25, match_prefix(pat, 15, str));
+		ck_assert_int_eq(25, match_prefix_strlen(pat, str));
+	}
+	{
+		/* From fuzz test */
+		const char *pat = "$|*ca|**c/c*b|a";
+		const char *str = "bcdaa/a//acdc/bac/caacacdcbcdc/";
+		ck_assert_int_eq(0, match_prefix(pat, 0, str));
+		ck_assert_int_eq(-1, match_prefix(pat, 1, str));
+		ck_assert_int_eq(0, match_prefix(pat, 2, str));
+		ck_assert_int_eq(5, match_prefix(pat, 3, str));
+		ck_assert_int_eq(2, match_prefix(pat, 4, str));
+		ck_assert_int_eq(-1, match_prefix(pat, 5, str));
+		ck_assert_int_eq(0, match_prefix(pat, 6, str));
+		ck_assert_int_eq(5, match_prefix(pat, 7, str));
+		ck_assert_int_eq(31, match_prefix(pat, 8, str));
+		ck_assert_int_eq(30, match_prefix(pat, 9, str));
+		ck_assert_int_eq(31, match_prefix(pat, 10, str));
+		ck_assert_int_eq(19, match_prefix(pat, 11, str));
+		ck_assert_int_eq(30, match_prefix(pat, 12, str));
+		ck_assert_int_eq(27, match_prefix(pat, 13, str));
+		ck_assert_int_eq(27, match_prefix(pat, 14, str));
+		ck_assert_int_eq(27, match_prefix(pat, 15, str));
+		ck_assert_int_eq(27, match_prefix_strlen(pat, str));
+	}
+	{
+		/* From fuzz test */
+		const char *pat = "*b?c|?$|*b?b**?";
+		const char *str = "cbbbddddda/dbcdbbbccdcb/a//ddab";
+		ck_assert_int_eq(0, match_prefix(pat, 0, str));
+		ck_assert_int_eq(10, match_prefix(pat, 1, str));
+		ck_assert_int_eq(4, match_prefix(pat, 2, str));
+		ck_assert_int_eq(5, match_prefix(pat, 3, str));
+		ck_assert_int_eq(-1, match_prefix(pat, 4, str));
+		ck_assert_int_eq(0, match_prefix(pat, 5, str));
+		ck_assert_int_eq(1, match_prefix(pat, 6, str));
+		ck_assert_int_eq(-1, match_prefix(pat, 7, str));
+		ck_assert_int_eq(0, match_prefix(pat, 8, str));
+		ck_assert_int_eq(10, match_prefix(pat, 9, str));
+		ck_assert_int_eq(4, match_prefix(pat, 10, str));
+		ck_assert_int_eq(5, match_prefix(pat, 11, str));
+		ck_assert_int_eq(4, match_prefix(pat, 12, str));
+		ck_assert_int_eq(10, match_prefix(pat, 13, str));
+		ck_assert_int_eq(31, match_prefix(pat, 14, str));
+		ck_assert_int_eq(31, match_prefix(pat, 15, str));
+		ck_assert_int_eq(31, match_prefix_strlen(pat, str));
+	}
+	{
+		/* From fuzz test */
+		const char *pat = "c|b?*$|*ca*/*ba";
+		const char *str = "bdccacb/aaadbadd/ccaca/c/cdcb/d";
+		ck_assert_int_eq(0, match_prefix(pat, 0, str));
+		ck_assert_int_eq(-1, match_prefix(pat, 1, str));
+		ck_assert_int_eq(0, match_prefix(pat, 2, str));
+		ck_assert_int_eq(1, match_prefix(pat, 3, str));
+		ck_assert_int_eq(2, match_prefix(pat, 4, str));
+		ck_assert_int_eq(7, match_prefix(pat, 5, str));
+		ck_assert_int_eq(-1, match_prefix(pat, 6, str));
+		ck_assert_int_eq(0, match_prefix(pat, 7, str));
+		ck_assert_int_eq(7, match_prefix(pat, 8, str));
+		ck_assert_int_eq(6, match_prefix(pat, 9, str));
+		ck_assert_int_eq(5, match_prefix(pat, 10, str));
+		ck_assert_int_eq(7, match_prefix(pat, 11, str));
+		ck_assert_int_eq(8, match_prefix(pat, 12, str));
+		ck_assert_int_eq(16, match_prefix(pat, 13, str));
+		ck_assert_int_eq(13, match_prefix(pat, 14, str));
+		ck_assert_int_eq(14, match_prefix(pat, 15, str));
+		ck_assert_int_eq(14, match_prefix_strlen(pat, str));
+	}
+}
+END_TEST
+
+
+START_TEST(test_mg_match)
+{
+	/* Copyright (c) 2022 the CivetWeb developers */
+	struct mg_match_context mcx;
+
+	ck_assert_int_eq(4, mg_match_alternatives("a*D", 3, "abcde", NULL));
+
+	memset(&mcx, 0, sizeof(mcx));
+	mcx.case_sensitive = 0;
+	ck_assert_int_eq(4, mg_match_alternatives("a*D", 3, "abcde", &mcx));
+	ck_assert_int_eq(1, (int)mcx.num_matches);
+	ck_assert_int_eq(2, (int)mcx.match[0].len);
+	ck_assert(!memcmp(mcx.match[0].str, "bc", 2));
+
+	memset(&mcx, 0, sizeof(mcx));
+	mcx.case_sensitive = 1;
+	ck_assert_int_eq(-1, mg_match_alternatives("a*D", 3, "abcde", &mcx));
+	ck_assert_int_eq(0, (int)mcx.num_matches);
+
+	memset(&mcx, 0, sizeof(mcx));
+	mcx.case_sensitive = 1;
+	ck_assert_int_eq(4, mg_match_alternatives("a??d", 4, "abcde", &mcx));
+	ck_assert_int_eq(1, (int)mcx.num_matches);
+	ck_assert_int_eq(2, (int)mcx.match[0].len);
+	ck_assert(!memcmp(mcx.match[0].str, "bc", 2));
+
+	memset(&mcx, 0, sizeof(mcx));
+	mcx.case_sensitive = 1;
+	ck_assert_int_eq(5, mg_match_alternatives("a??d*", 5, "abcde", &mcx));
+	ck_assert_int_eq(2, (int)mcx.num_matches);
+	ck_assert_int_eq(2, (int)mcx.match[0].len);
+	ck_assert(!memcmp(mcx.match[0].str, "bc", 2));
+	ck_assert_int_eq(1, (int)mcx.match[1].len);
+	ck_assert(!memcmp(mcx.match[1].str, "e", 1));
+
+	memset(&mcx, 0, sizeof(mcx));
+	mcx.case_sensitive = 1;
+	ck_assert_int_eq(4, mg_match_alternatives("a??d*", 5, "abcd", &mcx));
+	ck_assert_int_eq(2, (int)mcx.num_matches);
+	ck_assert_int_eq(2, (int)mcx.match[0].len);
+	ck_assert(!memcmp(mcx.match[0].str, "bc", 2));
+	ck_assert_int_eq(0, (int)mcx.match[1].len);
+
+	memset(&mcx, 0, sizeof(mcx));
+	mcx.case_sensitive = 0;
+	ck_assert_int_eq(2, mg_match_alternatives("a?|?B", 5, "ABC", &mcx));
+	ck_assert_int_eq(1, (int)mcx.num_matches);
+	ck_assert_int_eq(1, (int)mcx.match[0].len);
+	ck_assert(!memcmp(mcx.match[0].str, "B", 1));
+
+	memset(&mcx, 0, sizeof(mcx));
+	mcx.case_sensitive = 1;
+	ck_assert_int_eq(2, mg_match_alternatives("a?|?B", 5, "ABC", &mcx));
+	ck_assert_int_eq(1, (int)mcx.num_matches);
+	ck_assert_int_eq(1, (int)mcx.match[0].len);
+	ck_assert(!memcmp(mcx.match[0].str, "A", 1));
+}
+END_TEST
+
+
 START_TEST(test_remove_dot_segments)
 {
 	int i;
@@ -399,7 +787,7 @@ END_TEST
 
 START_TEST(test_is_valid_uri)
 {
-	/* is_valid_uri is superseeded by get_uri_type */
+	/* is_valid_uri is superseded by get_uri_type */
 	ck_assert_int_eq(2, get_uri_type("/api"));
 	ck_assert_int_eq(2, get_uri_type("/api/"));
 	ck_assert_int_eq(2,
@@ -464,7 +852,7 @@ END_TEST
 
 
 static int
-alloc_printf(char **buf, size_t size, const char *fmt, ...)
+alloc_vprintf_wrapper(char **buf, size_t size, const char *fmt, ...)
 {
 	/* Test helper function - adapted from unit_test.c */
 	/* Copyright (c) 2013-2015 the Civetweb developers */
@@ -482,23 +870,6 @@ alloc_printf(char **buf, size_t size, const char *fmt, ...)
 }
 
 
-static int
-alloc_printf2(char **buf, const char *fmt, ...)
-{
-	/* Test alternative implementation */
-	va_list ap;
-	int ret = 0;
-
-	mark_point();
-
-	va_start(ap, fmt);
-	ret = alloc_vprintf2(buf, fmt, ap);
-	va_end(ap);
-
-	return ret;
-}
-
-
 START_TEST(test_alloc_vprintf)
 {
 	/* Adapted from unit_test.c */
@@ -507,23 +878,23 @@ START_TEST(test_alloc_vprintf)
 	char buf[MG_BUF_LEN], *p = buf;
 	mark_point();
 
-	ck_assert(alloc_printf(&p, sizeof(buf), "%s", "hi") == 2);
+	ck_assert(alloc_vprintf_wrapper(&p, sizeof(buf), "%s", "hi") == 2);
 	ck_assert(p == buf);
 
-	ck_assert(alloc_printf(&p, sizeof(buf), "%s", "") == 0);
+	ck_assert(alloc_vprintf_wrapper(&p, sizeof(buf), "%s", "") == 0);
 	ck_assert(p == buf);
 
-	ck_assert(alloc_printf(&p, sizeof(buf), "") == 0);
+	ck_assert(alloc_vprintf_wrapper(&p, sizeof(buf), "") == 0);
 	ck_assert(p == buf);
 
 	/* Pass small buffer, make sure alloc_printf allocates */
-	ck_assert(alloc_printf(&p, 1, "%s", "hello") == 5);
+	ck_assert(alloc_vprintf_wrapper(&p, 1, "%s", "hello") == 5);
 	ck_assert(p != buf);
 	mg_free(p);
 	p = buf;
 
-	/* Test alternative implementation */
-	ck_assert(alloc_printf2(&p, "%s", "hello") == 5);
+	/* Test new wrapper implementation */
+	ck_assert(alloc_printf(&p, "%s", "hello") == 5);
 	ck_assert(p != buf);
 	mg_free(p);
 	p = buf;
@@ -981,67 +1352,8 @@ START_TEST(test_encode_decode)
 	int ret;
 	size_t len;
 
-#if defined(USE_WEBSOCKET) || defined(USE_LUA)
-	const char *alpha_b64_enc = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=";
-	const char *nonalpha_b64_enc =
-	    "ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9A";
-
 	mark_point();
 
-	memset(buf, 77, sizeof(buf));
-	base64_encode((unsigned char *)"a", 1, buf);
-	ck_assert_str_eq(buf, "YQ==");
-
-	memset(buf, 77, sizeof(buf));
-	base64_encode((unsigned char *)"ab", 1, buf);
-	ck_assert_str_eq(buf, "YQ==");
-
-	memset(buf, 77, sizeof(buf));
-	base64_encode((unsigned char *)"ab", 2, buf);
-	ck_assert_str_eq(buf, "YWI=");
-
-	memset(buf, 77, sizeof(buf));
-	base64_encode((unsigned char *)alpha, 3, buf);
-	ck_assert_str_eq(buf, "YWJj");
-
-	memset(buf, 77, sizeof(buf));
-	base64_encode((unsigned char *)alpha, 4, buf);
-	ck_assert_str_eq(buf, "YWJjZA==");
-
-	memset(buf, 77, sizeof(buf));
-	base64_encode((unsigned char *)alpha, 5, buf);
-	ck_assert_str_eq(buf, "YWJjZGU=");
-
-	memset(buf, 77, sizeof(buf));
-	base64_encode((unsigned char *)alpha, 6, buf);
-	ck_assert_str_eq(buf, "YWJjZGVm");
-
-	memset(buf, 77, sizeof(buf));
-	base64_encode((unsigned char *)alpha, (int)strlen(alpha), buf);
-	ck_assert_str_eq(buf, alpha_b64_enc);
-
-	memset(buf, 77, sizeof(buf));
-	base64_encode((unsigned char *)nonalpha, (int)strlen(nonalpha), buf);
-	ck_assert_str_eq(buf, nonalpha_b64_enc);
-#endif
-
-#if defined(USE_LUA)
-	memset(buf, 77, sizeof(buf));
-	len = 9999;
-	ret = base64_decode((unsigned char *)alpha_b64_enc,
-	                    (int)strlen(alpha_b64_enc),
-	                    buf,
-	                    &len);
-	ck_assert_int_eq(ret, -1);
-	ck_assert_uint_eq((unsigned int)len, (unsigned int)strlen(alpha));
-	ck_assert_str_eq(buf, alpha);
-
-	memset(buf, 77, sizeof(buf));
-	len = 9999;
-	ret = base64_decode((unsigned char *)"AAA*AAA", 7, buf, &len);
-	ck_assert_int_eq(ret, 3);
-#endif
-
 	memset(buf, 77, sizeof(buf));
 	ret = mg_url_encode(alpha, buf, sizeof(buf));
 	ck_assert_int_eq(ret, (int)strlen(buf));
@@ -1441,6 +1753,9 @@ make_private_suite(void)
 	suite_add_tcase(suite, tcase_http_keep_alive);
 
 	tcase_add_test(tcase_url_parsing_1, test_match_prefix);
+	tcase_add_test(tcase_url_parsing_1, test_match_prefix_strlen);
+	tcase_add_test(tcase_url_parsing_1, test_match_prefix_fuzz);
+	tcase_add_test(tcase_url_parsing_1, test_mg_match);
 	tcase_set_timeout(tcase_url_parsing_1, civetweb_min_test_timeout);
 	suite_add_tcase(suite, tcase_url_parsing_1);
 
diff --git a/unittest/public_func.c b/unittest/public_func.c
index 0d448d9..3726953 100644
--- a/unittest/public_func.c
+++ b/unittest/public_func.c
@@ -391,7 +391,7 @@ START_TEST(test_mg_get_var)
 	ck_assert_int_eq(ret, 10);
 	ck_assert_str_eq("this is it", buf);
 
-	/* longer value in the middle of a longer string - seccond occurrence of key
+	/* longer value in the middle of a longer string - second occurrence of key
 	 */
 	memset(buf, 77, sizeof(buf));
 	ret =
@@ -533,6 +533,93 @@ START_TEST(test_mg_url_decode)
 END_TEST
 
 
+START_TEST(test_mg_base64)
+{
+	char buf[128];
+	const char *alpha = "abcdefghijklmnopqrstuvwxyz";
+	const char *nonalpha = " !\"#$%&'()*+,-./0123456789:;<=>?@";
+	int ret;
+	size_t len;
+
+	const char *alpha_b64_enc = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=";
+	const char *nonalpha_b64_enc =
+	    "ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9A";
+
+	mark_point();
+
+	memset(buf, 77, sizeof(buf));
+	mg_base64_encode((unsigned char *)"a", 1, buf, NULL);
+	ck_assert_str_eq(buf, "YQ==");
+
+	memset(buf, 77, sizeof(buf));
+	len = 9999;
+	mg_base64_encode((unsigned char *)"ab", 1, buf, &len);
+	ck_assert_str_eq(buf, "YQ==");
+	ck_assert_int_eq((int)len, 5);
+
+	memset(buf, 77, sizeof(buf));
+	len = 9999;
+	mg_base64_encode((unsigned char *)"ab", 2, buf, &len);
+	ck_assert_str_eq(buf, "YWI=");
+	ck_assert_int_eq((int)len, 5);
+
+	memset(buf, 77, sizeof(buf));
+	len = 9999;
+	mg_base64_encode((unsigned char *)alpha, 3, buf, &len);
+	ck_assert_str_eq(buf, "YWJj");
+	ck_assert_int_eq((int)len, 5);
+
+	memset(buf, 77, sizeof(buf));
+	len = 9999;
+	mg_base64_encode((unsigned char *)alpha, 4, buf, &len);
+	ck_assert_str_eq(buf, "YWJjZA==");
+	ck_assert_int_eq((int)len, 9);
+
+	memset(buf, 77, sizeof(buf));
+	len = 9999;
+	mg_base64_encode((unsigned char *)alpha, 5, buf, &len);
+	ck_assert_str_eq(buf, "YWJjZGU=");
+	ck_assert_int_eq((int)len, 9);
+
+	memset(buf, 77, sizeof(buf));
+	len = 9999;
+	mg_base64_encode((unsigned char *)alpha, 6, buf, &len);
+	ck_assert_str_eq(buf, "YWJjZGVm");
+	ck_assert_int_eq((int)len, 9);
+
+	memset(buf, 77, sizeof(buf));
+	len = 9999;
+	mg_base64_encode((unsigned char *)alpha, strlen(alpha), buf, &len);
+	ck_assert_str_eq(buf, alpha_b64_enc);
+	ck_assert_int_eq((int)len, (int)strlen(alpha_b64_enc) + 1);
+
+	memset(buf, 77, sizeof(buf));
+	len = 9999;
+	mg_base64_encode((unsigned char *)nonalpha,
+	                 strlen(nonalpha),
+	                 (char *)buf,
+	                 &len);
+	ck_assert_str_eq(buf, nonalpha_b64_enc);
+	ck_assert_int_eq((int)len, (int)strlen(nonalpha_b64_enc) + 1);
+
+	memset(buf, 77, sizeof(buf));
+	len = 9999;
+	ret = mg_base64_decode((char *)alpha_b64_enc,
+	                       strlen(alpha_b64_enc),
+	                       (unsigned char *)buf,
+	                       &len);
+	ck_assert_int_eq(ret, -1);
+	ck_assert_int_eq((int)len, (int)strlen(alpha) + 1);
+	ck_assert_str_eq(buf, alpha);
+
+	memset(buf, 77, sizeof(buf));
+	len = 9999;
+	ret = mg_base64_decode((char *)"AAA*AAA", 7, (unsigned char *)buf, &len);
+	ck_assert_int_eq(ret, 3);
+}
+END_TEST
+
+
 #define MG_MAX_FORM_FIELDS (64)
 
 START_TEST(test_mg_split_form_urlencoded)
@@ -638,6 +725,8 @@ make_public_func_suite(void)
 	TCase *const tcase_strncasecmp = tcase_create("strcasecmp");
 	TCase *const tcase_urlencodingdecoding =
 	    tcase_create("URL encoding decoding");
+	TCase *const tcase_base64encodingdecoding =
+	    tcase_create("BASE64 encoding decoding");
 	TCase *const tcase_cookies = tcase_create("Cookies and variables");
 	TCase *const tcase_md5 = tcase_create("MD5");
 	TCase *const tcase_aux = tcase_create("Aux functions");
@@ -664,6 +753,11 @@ make_public_func_suite(void)
 	tcase_set_timeout(tcase_urlencodingdecoding, civetweb_min_test_timeout);
 	suite_add_tcase(suite, tcase_urlencodingdecoding);
 
+	tcase_add_test(tcase_base64encodingdecoding, test_mg_base64);
+	tcase_set_timeout(tcase_base64encodingdecoding, civetweb_min_test_timeout);
+	suite_add_tcase(suite, tcase_base64encodingdecoding);
+	suite_add_tcase(suite, tcase_base64encodingdecoding);
+
 	tcase_add_test(tcase_cookies, test_mg_get_cookie);
 	tcase_add_test(tcase_cookies, test_mg_get_var);
 	tcase_set_timeout(tcase_cookies, civetweb_min_test_timeout);
diff --git a/unittest/public_server.c b/unittest/public_server.c
index c3bd092..02bf525 100644
--- a/unittest/public_server.c
+++ b/unittest/public_server.c
@@ -361,7 +361,7 @@ test_mg_start(const struct mg_callbacks *callbacks,
 		/* Give the server some time to start in the test VM. */
 		/* Don't need to do this if mg_start failed. */
 		test_sleep(SLEEP_AFTER_MG_START);
-		ck_assert_int_eq(error.code, 0);
+		ck_assert_uint_eq(error.code, 0);
 		ck_assert_str_eq(error.text, "");
 	} else if (line > 0) {
 		/* mg_start is not supposed to fail anywhere, except for
@@ -383,7 +383,7 @@ static void
 test_mg_stop(struct mg_context *ctx, unsigned line)
 {
 	(void)line;
-#ifdef __MACH__
+#if defined(__MACH__) && defined(__APPLE__)
 	/* For unknown reasons, there are sporadic hangs
 	 * for OSX if mark_point is called here */
 	test_sleep(SLEEP_BEFORE_MG_STOP);
@@ -1141,14 +1141,14 @@ websock_server_data(struct mg_connection *conn,
 	}
 	mark_point();
 
-	return 1; /* return 1 to keep the connetion open */
+	return 1; /* return 1 to keep the connection open */
 }
 
 
 static void
 websock_server_close(const struct mg_connection *conn, void *udata)
 {
-#ifndef __MACH__
+#if !defined(__MACH__) && !defined(__APPLE__)
 	ck_assert_ptr_eq((void *)udata, (void *)(ptrdiff_t)7531);
 	WS_TEST_TRACE("Server: Close connection\n");
 
@@ -1225,7 +1225,7 @@ websocket_client_close_handler(const struct mg_connection *conn,
 	struct tclient_data *pclient_data =
 	    (struct tclient_data *)mg_get_user_data(ctx);
 
-#ifndef __MACH__
+#if !defined(__MACH__) && !defined(__APPLE__)
 	ck_assert_ptr_eq(user_data, (void *)pclient_data);
 
 	ck_assert(pclient_data != NULL);
@@ -1239,7 +1239,7 @@ websocket_client_close_handler(const struct mg_connection *conn,
 	(void)user_data;
 	pclient_data->closed++;
 
-#endif /* __MACH__ */
+#endif /* __MACH__ && __APPLE__ */
 }
 
 #endif /* USE_WEBSOCKET */
@@ -1801,9 +1801,9 @@ START_TEST(test_request_handlers)
 #else
 	ck_assert_int_eq(client_ri->status_code, 200);
 	i = mg_read(client_conn, buf, sizeof(buf));
-	ck_assert(i > 6);
-	buf[6] = 0;
-	ck_assert_str_eq(buf, "<html>");
+	ck_assert(i > 21);
+	buf[21] = 0;
+	ck_assert_str_eq(buf, "<!DOCTYPE html><html>");
 #endif
 	mg_close_connection(client_conn);
 
@@ -1842,10 +1842,21 @@ START_TEST(test_request_handlers)
 	client_ri = mg_get_response_info(client_conn);
 
 	ck_assert(client_ri != NULL);
+	/* Result must be an error code*/
+	ck_assert_int_gt(client_ri->status_code, 400);
+	ck_assert_int_lt(client_ri->status_code, 500);
+
 #if defined(NO_FILES)
+	/* In case there is no filesystem, PUT is not a valid method */
 	ck_assert_int_eq(client_ri->status_code, 405); /* method not allowed */
 #else
-	ck_assert_int_eq(client_ri->status_code, 401); /* not authorized */
+	/* In case there is a filesystem but no auth file is provided,
+	 * PUT is not a valid method */
+	if (client_ri->status_code != 405) {
+		/* 401: It would be possible in principle, but there client needs
+		 * to send authentication data */
+		ck_assert_int_eq(client_ri->status_code, 401); /* not authorized */
+	}
 #endif
 	mg_close_connection(client_conn);
 
@@ -3789,6 +3800,28 @@ START_TEST(test_error_handling)
 	mg_close_connection(client_conn);
 	test_sleep(1);
 
+
+	/* Try DELETE when put_delete_auth_file is not configured */
+	memset(client_err, 0, sizeof(client_err));
+	client_conn =
+	    mg_connect_client("127.0.0.1", 8080, 0, client_err, sizeof(client_err));
+
+	ck_assert_str_eq(client_err, "");
+	ck_assert(client_conn != NULL);
+
+	mg_printf(client_conn, "DELETE /something/not/existing HTTP/1.0\r\n\r\n");
+	client_res =
+	    mg_get_response(client_conn, client_err, sizeof(client_err), 10000);
+	ck_assert_int_ge(client_res, 0);
+	ck_assert_str_eq(client_err, "");
+	client_ri = mg_get_response_info(client_conn);
+	ck_assert(client_ri != NULL);
+
+	ck_assert_int_eq(client_ri->status_code, 405);
+	mg_close_connection(client_conn);
+	test_sleep(1);
+
+
 	/* Create an error.htm file */
 	f = fopen("error.htm", "wt");
 	ck_assert(f != NULL);
@@ -4329,7 +4362,7 @@ START_TEST(test_large_file)
 	OPTIONS[opt_cnt++] = "8443s";
 	OPTIONS[opt_cnt++] = "ssl_certificate";
 	OPTIONS[opt_cnt++] = ssl_cert;
-#ifdef __MACH__
+#if defined(__MACH__) && defined(__APPLE__)
 	/* The Apple builds on Travis CI seem to have problems with TLS1.x
 	 * Allow SSLv3 and TLS */
 	OPTIONS[opt_cnt++] = "ssl_protocol_version";
@@ -4602,143 +4635,6 @@ START_TEST(test_mg_store_body)
 END_TEST
 
 
-#if defined(MG_USE_OPEN_FILE) && !defined(NO_FILES)
-
-#define FILE_IN_MEM_SIZE (1024 * 100)
-static char *file_in_mem_data;
-
-static const char *
-test_file_in_memory_open_file(const struct mg_connection *conn,
-                              const char *file_path,
-                              size_t *file_size)
-{
-	(void)conn;
-
-	if (strcmp(file_path, "./file_in_mem") == 0) {
-		/* File is in memory */
-		*file_size = FILE_IN_MEM_SIZE;
-		return file_in_mem_data;
-	} else {
-		/* File is not in memory */
-		return NULL;
-	}
-}
-
-
-START_TEST(test_file_in_memory)
-{
-	/* Server var */
-	struct mg_context *ctx;
-	struct mg_callbacks callbacks;
-	const char *OPTIONS[32];
-	int opt_cnt = 0;
-#if !defined(NO_SSL)
-	const char *ssl_cert = locate_ssl_cert();
-#endif
-
-	/* Client var */
-	struct mg_connection *client;
-	char client_err_buf[256];
-	char client_data_buf[256];
-	const struct mg_request_info *client_ri;
-	int64_t data_read;
-	int r, i;
-
-	/* Prepare test data */
-	file_in_mem_data = (char *)malloc(FILE_IN_MEM_SIZE);
-	ck_assert_ptr_ne(file_in_mem_data, NULL);
-	for (r = 0; r < FILE_IN_MEM_SIZE; r++) {
-		file_in_mem_data[r] = (char)(r);
-	}
-
-	/* Set options and start server */
-	OPTIONS[opt_cnt++] = "document_root";
-	OPTIONS[opt_cnt++] = ".";
-#if defined(NO_SSL)
-	OPTIONS[opt_cnt++] = "listening_ports";
-	OPTIONS[opt_cnt++] = "8080";
-#else
-	OPTIONS[opt_cnt++] = "listening_ports";
-	OPTIONS[opt_cnt++] = "8443s";
-	OPTIONS[opt_cnt++] = "ssl_certificate";
-	OPTIONS[opt_cnt++] = ssl_cert;
-	ck_assert(ssl_cert != NULL);
-#endif
-	OPTIONS[opt_cnt] = NULL;
-
-
-	memset(&callbacks, 0, sizeof(callbacks));
-	callbacks.open_file = test_file_in_memory_open_file;
-
-	ctx = test_mg_start(&callbacks, 0, OPTIONS, __LINE__);
-	ck_assert(ctx != NULL);
-
-	/* connect client */
-	memset(client_err_buf, 0, sizeof(client_err_buf));
-	memset(client_data_buf, 0, sizeof(client_data_buf));
-
-	client =
-	    mg_download("127.0.0.1",
-#if defined(NO_SSL)
-	                8080,
-	                0,
-#else
-	                8443,
-	                1,
-#endif
-	                client_err_buf,
-	                sizeof(client_err_buf),
-	                "GET /file_in_mem HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n");
-
-	ck_assert(client != NULL);
-	ck_assert_str_eq(client_err_buf, "");
-
-	client_ri = mg_get_response_info(client);
-
-	ck_assert(client_ri != NULL);
-	ck_assert_int_eq(client_ri->status_code, 200);
-
-	ck_assert_int_eq(client_ri->content_length, FILE_IN_MEM_SIZE);
-
-	data_read = 0;
-	while (data_read < client_ri->content_length) {
-		r = mg_read(client, client_data_buf, sizeof(client_data_buf));
-		if (r > 0) {
-			for (i = 0; i < r; i++) {
-				ck_assert_int_eq((int)client_data_buf[i],
-				                 (int)file_in_mem_data[data_read + i]);
-			}
-			data_read += r;
-		}
-	}
-
-	/* Nothing left to read */
-	r = mg_read(client, client_data_buf, sizeof(client_data_buf));
-	ck_assert_int_eq(r, 0);
-
-	/* Close the client connection */
-	mg_close_connection(client);
-
-	/* Stop the server */
-	test_mg_stop(ctx, __LINE__);
-
-	/* Free test data */
-	free(file_in_mem_data);
-	file_in_mem_data = NULL;
-}
-END_TEST
-
-#else /* defined(MG_USE_OPEN_FILE) */
-
-START_TEST(test_file_in_memory)
-{
-	mark_point();
-}
-END_TEST
-
-#endif
-
-
 static void
 minimal_http_https_client_impl(const char *server,
                                uint16_t port,
@@ -4878,6 +4774,7 @@ START_TEST(test_minimal_tls_client)
 	const char *external_server_ip;
 	mark_point();
 	external_server_ip = get_external_server_ip();
+	(void)external_server_ip; /* unused in some cases */
 	mark_point();
 
 #if !defined(NO_SSL) /* dont run https test if SSL is not enabled */
@@ -4995,8 +4892,8 @@ START_TEST(test_minimal_http_server_callback)
 	/* Call a test client */
 	minimal_http_client_check("127.0.0.1",
 	                          8080,
-	                          "/8?Altenative=Response",
-	                          "Altenative=Response");
+	                          "/8?Alternative=Response",
+	                          "Alternative=Response");
 
 	/* Run the server for 5 seconds */
 	test_sleep(5);
@@ -5103,8 +5000,8 @@ START_TEST(test_minimal_https_server_callback)
 	/* Call a test client */
 	minimal_https_client_check("127.0.0.1",
 	                           8443,
-	                           "/8?Altenative=Response",
-	                           "Altenative=Response");
+	                           "/8?Alternative=Response",
+	                           "Alternative=Response");
 
 	/* Run the server for 5 seconds */
 	test_sleep(5);
@@ -5236,10 +5133,6 @@ make_public_server_suite(void)
 	tcase_set_timeout(tcase_large_file, civetweb_mid_server_test_timeout);
 	suite_add_tcase(suite, tcase_large_file);
 
-	tcase_add_test(tcase_file_in_mem, test_file_in_memory);
-	tcase_set_timeout(tcase_file_in_mem, civetweb_mid_server_test_timeout);
-	suite_add_tcase(suite, tcase_file_in_mem);
-
 	return suite;
 }
 #endif
@@ -5282,7 +5175,6 @@ MAIN_PUBLIC_SERVER(void)
 	test_error_log_file(0);
 	test_throttle(0);
 	test_large_file(0);
-	test_file_in_memory(0);
 
 	mg_exit_library();
 

More details

Full run details

Historical runs