From c90d7a66db174d16d045efe23db1c513d23216a8 Mon Sep 17 00:00:00 2001 From: Howard Wu <40033067+Howard20181@users.noreply.github.com> Date: Thu, 12 Jan 2023 21:25:50 +0800 Subject: [PATCH 001/120] Fix build (#563) ref ToruNiina/toml11/commit/22db720ad55c3470c4d036ae74be35e68c761845 --- .github/workflows/build.yml | 70 +++++++++++++++++--------------- .github/workflows/docker.yml | 24 ++++++----- scripts/build.alpine.release.sh | 9 +--- scripts/build.macos.release.sh | 4 +- scripts/build.windows.release.sh | 11 ++--- scripts/config.termux.sh | 11 ++--- 6 files changed, 60 insertions(+), 69 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2a3e38a4a..2ed3bfdca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,9 @@ name: GitHub CI -on: [push] +on: + push: + branches: [ master ] + workflow_dispatch: + pull_request: concurrency: group: ${{ github.ref }}-${{ github.workflow }} @@ -10,23 +14,23 @@ jobs: name: Linux x86 Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h - name: Build run: docker run --rm -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:x86-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" - name: Upload - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: subconverter_linux32 path: subconverter/ - name: Package Release - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} run: tar czf subconverter_linux32.tar.gz subconverter - name: Draft Release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} with: files: subconverter_linux32.tar.gz draft: true @@ -35,23 +39,23 @@ jobs: name: Linux x86_64 Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h - name: Build run: docker run -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:amd64-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" - name: Upload - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: subconverter_linux64 path: subconverter/ - name: Package Release - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} run: tar czf subconverter_linux64.tar.gz subconverter - name: Draft Release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} with: files: subconverter_linux64.tar.gz draft: true @@ -60,23 +64,23 @@ jobs: name: Linux armv7 Build runs-on: [self-hosted, linux, ARM64] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h - name: Build run: docker run -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:armv7-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" - name: Upload - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: subconverter_armv7 path: subconverter/ - name: Package Release - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} run: tar czf subconverter_armv7.tar.gz subconverter - name: Draft Release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} with: files: subconverter_armv7.tar.gz draft: true @@ -85,23 +89,23 @@ jobs: name: Linux aarch64 Build runs-on: [self-hosted, linux, ARM64] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h - name: Build run: docker run -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:aarch64-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" - name: Upload - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: subconverter_aarch64 path: subconverter/ - name: Package Release - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} run: tar czf subconverter_aarch64.tar.gz subconverter - name: Draft Release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} with: files: subconverter_aarch64.tar.gz draft: true @@ -110,23 +114,23 @@ jobs: name: macOS Build runs-on: macos-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i -e 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h - name: Build run: bash scripts/build.macos.release.sh - name: Upload - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: subconverter_darwin64 path: subconverter/ - name: Package Release - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} run: tar czf subconverter_darwin64.tar.gz subconverter - name: Draft Release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} with: files: subconverter_darwin64.tar.gz draft: true @@ -138,10 +142,10 @@ jobs: run: shell: msys2 {0} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2-beta + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: - node-version: '12' + node-version: '16' - uses: msys2/setup-msys2@v2 with: update: true @@ -154,16 +158,16 @@ jobs: - name: Build run: bash scripts/build.windows.release.sh - name: Upload - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: subconverter_win64 path: subconverter/ - name: Package Release - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} run: 7z a subconverter_win64.7z subconverter/ - name: Draft Release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} with: files: subconverter_win64.7z draft: true @@ -175,10 +179,10 @@ jobs: run: shell: msys2 {0} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2-beta + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: - node-version: '12' + node-version: '16' - uses: msys2/setup-msys2@v2 with: update: true @@ -191,16 +195,16 @@ jobs: - name: Build run: bash scripts/build.windows.release.sh - name: Upload - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: subconverter_win32 path: subconverter/ - name: Package Release - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} run: 7z a subconverter_win32.7z subconverter/ - name: Draft Release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/') }} with: files: subconverter_win32.7z draft: true diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b77876a7b..759c26ef7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,5 +1,7 @@ name: Publish Docker Image -on: [push] +on: + push: + branches: [ master ] concurrency: group: ${{ github.ref }}-${{ github.workflow }} @@ -11,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout base - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 @@ -42,7 +44,7 @@ jobs: - name: Replace tag without `v` if: startsWith(github.ref, 'refs/tags/') - uses: actions/github-script@v1 + uses: actions/github-script@v6 id: version with: script: | @@ -78,7 +80,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout base - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 @@ -109,7 +111,7 @@ jobs: - name: Replace tag without `v` if: startsWith(github.ref, 'refs/tags/') - uses: actions/github-script@v1 + uses: actions/github-script@v6 id: version with: script: | @@ -145,7 +147,7 @@ jobs: runs-on: [self-hosted, linux, ARM64] steps: - name: Checkout base - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 @@ -180,7 +182,7 @@ jobs: - name: Replace tag without `v` if: startsWith(github.ref, 'refs/tags/') - uses: actions/github-script@v1 + uses: actions/github-script@v6 id: version with: script: | @@ -218,7 +220,7 @@ jobs: runs-on: [self-hosted, linux, ARM64] steps: - name: Checkout base - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 @@ -253,7 +255,7 @@ jobs: - name: Replace tag without `v` if: startsWith(github.ref, 'refs/tags/') - uses: actions/github-script@v1 + uses: actions/github-script@v6 id: version with: script: | @@ -292,7 +294,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout base - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 @@ -347,7 +349,7 @@ jobs: - name: Replace tag without `v` if: startsWith(github.ref, 'refs/tags/') - uses: actions/github-script@v1 + uses: actions/github-script@v6 id: version with: script: | diff --git a/scripts/build.alpine.release.sh b/scripts/build.alpine.release.sh index 192a005a2..9c729d461 100644 --- a/scripts/build.alpine.release.sh +++ b/scripts/build.alpine.release.sh @@ -30,17 +30,12 @@ git clone https://github.com/PerMalmberg/libcron --depth=1 cd libcron git submodule update --init cmake -DCMAKE_BUILD_TYPE=Release . -make libcron -j2 -install -m644 libcron/out/Release/liblibcron.a /usr/lib/ -install -d /usr/include/libcron/ -install -m644 libcron/include/libcron/* /usr/include/libcron/ -install -d /usr/include/date/ -install -m644 libcron/externals/date/include/date/* /usr/include/date/ +make libcron install -j2 cd .. git clone https://github.com/ToruNiina/toml11 --depth=1 cd toml11 -cmake . +cmake -DCMAKE_CXX_STANDARD=11 . make install -j4 cd .. diff --git a/scripts/build.macos.release.sh b/scripts/build.macos.release.sh index 01aa7ec21..176463971 100644 --- a/scripts/build.macos.release.sh +++ b/scripts/build.macos.release.sh @@ -31,7 +31,7 @@ git clone https://github.com/PerMalmberg/libcron --depth=1 cd libcron git submodule update --init cmake -DCMAKE_BUILD_TYPE=Release . -make libcron -j8 +make libcron install -j8 install -m644 libcron/out/Release/liblibcron.a /usr/local/lib/ install -d /usr/local/include/libcron/ install -m644 libcron/include/libcron/* /usr/local/include/libcron/ @@ -41,7 +41,7 @@ cd .. git clone https://github.com/ToruNiina/toml11 --depth=1 cd toml11 -cmake . +cmake -DCMAKE_CXX_STANDARD=11 . make install -j4 cd .. diff --git a/scripts/build.windows.release.sh b/scripts/build.windows.release.sh index 2833671ee..3de242c4c 100644 --- a/scripts/build.windows.release.sh +++ b/scripts/build.windows.release.sh @@ -27,13 +27,8 @@ cd .. git clone https://github.com/PerMalmberg/libcron --depth=1 cd libcron git submodule update --init -cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release . -make libcron -j4 -install -m644 libcron/out/Release/liblibcron.a "$MINGW_PREFIX/lib/" -install -d "$MINGW_PREFIX/include/libcron/" -install -m644 libcron/include/libcron/* "$MINGW_PREFIX/include/libcron/" -install -d "$MINGW_PREFIX/include/date/" -install -m644 libcron/externals/date/include/date/* "$MINGW_PREFIX/include/date/" +cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" . +make libcron install -j4 cd .. git clone https://github.com/Tencent/rapidjson --depth=1 @@ -44,7 +39,7 @@ cd .. git clone https://github.com/ToruNiina/toml11 --depth=1 cd toml11 -cmake -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" -G "Unix Makefiles" . +cmake -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" -G "Unix Makefiles" -DCMAKE_CXX_STANDARD=11 . make install -j4 cd .. diff --git a/scripts/config.termux.sh b/scripts/config.termux.sh index b83bb9b65..ba151539c 100644 --- a/scripts/config.termux.sh +++ b/scripts/config.termux.sh @@ -29,17 +29,12 @@ cd .. git clone https://github.com/PerMalmberg/libcron --depth=1 cd libcron git submodule update --init -cmake -DCMAKE_BUILD_TYPE=Release . -make libcron -j3 -install -m644 libcron/out/Release/liblibcron.a $PREFIX/lib/ -install -d $PREFIX/include/libcron/ -install -m644 libcron/include/libcron/* $PREFIX/include/libcron/ -install -d $PREFIX/include/date/ -install -m644 libcron/externals/date/include/date/* $PREFIX/include/date/ +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$PREFIX . +make libcron install -j3 cd .. git clone https://github.com/ToruNiina/toml11 --depth=1 cd toml11 -cmake -DCMAKE_INSTALL_PREFIX=$PREFIX . +cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DCMAKE_CXX_STANDARD=11 . make install -j3 cd .. From 3813c3925357abca7f5009d69f07281e738d837f Mon Sep 17 00:00:00 2001 From: Howard Wu <40033067+Howard20181@users.noreply.github.com> Date: Fri, 13 Jan 2023 21:35:07 +0800 Subject: [PATCH 002/120] Fix Docker build (#570) --- scripts/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Dockerfile b/scripts/Dockerfile index 3443b1d28..66e54ce55 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -30,7 +30,7 @@ RUN apk add --no-cache --virtual .build-tools git g++ build-base linux-headers c cd .. && \ git clone https://github.com/ToruNiina/toml11 --depth=1 && \ cd toml11 && \ - cmake . && \ + cmake -DCMAKE_CXX_STANDARD=11 . && \ make install -j $THREADS && \ cd .. && \ git clone https://github.com/tindy2013/subconverter --depth=1 && \ From a60551ac11e341a630cfed82320d3faabdcff75f Mon Sep 17 00:00:00 2001 From: TaurusXin Date: Wed, 22 Mar 2023 06:46:23 +0000 Subject: [PATCH 003/120] Remove VMess aead option in Quantumult X (#538) * Remove VMess aead option in Quantumult X * Only add aead option in QuanX when alterid=0 --- src/generator/config/subexport.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index c6685eecf..3e3c46b08 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -1306,7 +1306,9 @@ void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector Date: Wed, 22 Mar 2023 14:46:43 +0800 Subject: [PATCH 004/120] Make indentation in Clash profile consistent (#559) --- src/generator/config/ruleconvert.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/generator/config/ruleconvert.cpp b/src/generator/config/ruleconvert.cpp index b84692de3..64a53d73d 100644 --- a/src/generator/config/ruleconvert.cpp +++ b/src/generator/config/ruleconvert.cpp @@ -177,7 +177,7 @@ std::string rulesetToClashStr(YAML::Node &base_rule, std::vector if(!overwrite_original_rules && base_rule[field_name].IsDefined()) { for(size_t i = 0; i < base_rule[field_name].size(); i++) - output_content += " - " + safe_as(base_rule[field_name][i]) + "\n"; + output_content += " - " + safe_as(base_rule[field_name][i]) + "\n"; } base_rule.remove(field_name); @@ -200,7 +200,7 @@ std::string rulesetToClashStr(YAML::Node &base_rule, std::vector strLine += "," + rule_group; if(count_least(strLine, ',', 3)) strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2"); - output_content += " - " + strLine + "\n"; + output_content += " - " + strLine + "\n"; total_rules++; continue; } @@ -228,7 +228,7 @@ std::string rulesetToClashStr(YAML::Node &base_rule, std::vector strLine += "," + rule_group; if(count_least(strLine, ',', 3)) strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2"); - output_content += " - " + strLine + "\n"; + output_content += " - " + strLine + "\n"; total_rules++; } } From 4205dee94a303385692ffe51e8f9aac4e7a4abc9 Mon Sep 17 00:00:00 2001 From: Howard Wu Date: Wed, 22 Mar 2023 14:58:42 +0800 Subject: [PATCH 005/120] Stay at curl 7.88.1 until we adapt it (#590) curl 7 -> 8 is a breaking change --- scripts/build.alpine.release.sh | 2 +- scripts/build.macos.release.sh | 2 +- scripts/build.windows.release.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/build.alpine.release.sh b/scripts/build.alpine.release.sh index 9c729d461..ebeec709d 100644 --- a/scripts/build.alpine.release.sh +++ b/scripts/build.alpine.release.sh @@ -4,7 +4,7 @@ set -xe apk add gcc g++ build-base linux-headers cmake make autoconf automake libtool python2 apk add mbedtls-dev mbedtls-static zlib-dev rapidjson-dev libevent-dev libevent-static zlib-static pcre2-dev -git clone https://github.com/curl/curl --depth=1 +git clone https://github.com/curl/curl --depth=1 --branch curl-7_88_1 cd curl cmake -DCURL_USE_MBEDTLS=ON -DHTTP_ONLY=ON -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_USE_LIBSSH2=OFF -DBUILD_CURL_EXE=OFF . > /dev/null make install -j2 > /dev/null diff --git a/scripts/build.macos.release.sh b/scripts/build.macos.release.sh index 176463971..3e9c1a00a 100644 --- a/scripts/build.macos.release.sh +++ b/scripts/build.macos.release.sh @@ -3,7 +3,7 @@ set -xe brew reinstall rapidjson libevent zlib pcre2 pkgconfig -#git clone https://github.com/curl/curl --depth=1 +#git clone https://github.com/curl/curl --depth=1 --branch curl-7_88_1 #cd curl #./buildconf > /dev/null #./configure --with-ssl=/usr/local/opt/openssl@1.1 --without-mbedtls --disable-ldap --disable-ldaps --disable-rtsp --without-libidn2 > /dev/null diff --git a/scripts/build.windows.release.sh b/scripts/build.windows.release.sh index 3de242c4c..8becc96b8 100644 --- a/scripts/build.windows.release.sh +++ b/scripts/build.windows.release.sh @@ -1,7 +1,7 @@ #!/bin/bash set -xe -git clone https://github.com/curl/curl --depth=1 +git clone https://github.com/curl/curl --depth=1 --branch curl-7_88_1 cd curl cmake -DCMAKE_BUILD_TYPE=Release -DCURL_USE_LIBSSH2=OFF -DHTTP_ONLY=ON -DCURL_USE_SCHANNEL=ON -DBUILD_SHARED_LIBS=OFF -DBUILD_CURL_EXE=OFF -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" -G "Unix Makefiles" -DHAVE_LIBIDN2=OFF -DCURL_USE_LIBPSL=OFF . make install -j4 From deb73c56694a1d6eaf6fa27a4569aed4be941367 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Mon, 3 Jul 2023 13:52:52 +0800 Subject: [PATCH 006/120] Enhancements Fix support for generating Loon configs. Refactor libraries. --- cmake/FindQuickJS.cmake | 2 +- scripts/Dockerfile | 3 +- scripts/build.alpine.release.sh | 3 +- scripts/build.macos.release.sh | 3 +- scripts/build.windows.release.sh | 3 +- scripts/config.termux.sh | 3 +- src/generator/config/nodemanip.cpp | 2 +- src/generator/config/ruleconvert.cpp | 26 +- src/generator/config/subexport.cpp | 327 ++++++++++----------- src/generator/template/templates.cpp | 6 +- src/handler/interfaces.cpp | 48 +-- src/handler/settings.cpp | 290 +++++++++--------- src/handler/upload.cpp | 32 +- src/handler/webget.cpp | 4 +- src/lib/wrapper.cpp | 2 +- src/main.cpp | 10 +- src/parser/subparser.cpp | 16 +- src/utils/file.cpp | 4 +- src/utils/ini_reader/ini_reader.h | 422 +++++++++++++-------------- src/utils/network.cpp | 2 +- src/utils/rapidjson_extra.h | 4 +- src/utils/string.cpp | 6 +- src/utils/string.h | 1 + src/utils/system.cpp | 8 +- 24 files changed, 607 insertions(+), 620 deletions(-) diff --git a/cmake/FindQuickJS.cmake b/cmake/FindQuickJS.cmake index 3880c9346..22a1c42f1 100644 --- a/cmake/FindQuickJS.cmake +++ b/cmake/FindQuickJS.cmake @@ -1,6 +1,6 @@ find_path(QUICKJS_INCLUDE_DIRS quickjs/quickjs.h) -find_library(QUICKJS_LIBRARY quickjs) +find_library(QUICKJS_LIBRARY quickjs/libquickjs.a) set(QUICKJS_LIBRARIES "${QUICKJS_LIBRARY}") diff --git a/scripts/Dockerfile b/scripts/Dockerfile index 66e54ce55..fc1b9baf2 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -12,7 +12,8 @@ RUN apk add --no-cache --virtual .build-tools git g++ build-base linux-headers c git submodule update --init && \ cmake -DCMAKE_BUILD_TYPE=Release . && \ make quickjs -j $THREADS && \ - install -m644 quickjs/libquickjs.a /usr/lib && \ + install -d /usr/lib/quickjs/ && \ + install -m644 quickjs/libquickjs.a /usr/lib/quickjs/ && \ install -d /usr/include/quickjs/ && \ install -m644 quickjs/quickjs.h quickjs/quickjs-libc.h /usr/include/quickjs/ && \ install -m644 quickjspp.hpp /usr/include && \ diff --git a/scripts/build.alpine.release.sh b/scripts/build.alpine.release.sh index ebeec709d..21cb50c05 100644 --- a/scripts/build.alpine.release.sh +++ b/scripts/build.alpine.release.sh @@ -20,7 +20,8 @@ git clone https://github.com/ftk/quickjspp --depth=1 cd quickjspp cmake -DCMAKE_BUILD_TYPE=Release . make quickjs -j2 -install -m644 quickjs/libquickjs.a /usr/lib/ +install -d /usr/lib/quickjs/ +install -m644 quickjs/libquickjs.a /usr/lib/quickjs/ install -d /usr/include/quickjs/ install -m644 quickjs/quickjs.h quickjs/quickjs-libc.h /usr/include/quickjs/ install -m644 quickjspp.hpp /usr/include/ diff --git a/scripts/build.macos.release.sh b/scripts/build.macos.release.sh index 3e9c1a00a..f774ea34a 100644 --- a/scripts/build.macos.release.sh +++ b/scripts/build.macos.release.sh @@ -21,7 +21,8 @@ git clone https://github.com/ftk/quickjspp --depth=1 cd quickjspp cmake -DCMAKE_BUILD_TYPE=Release . make quickjs -j8 -install -m644 quickjs/libquickjs.a /usr/local/lib/ +install -d /usr/local/lib/quickjs/ +install -m644 quickjs/libquickjs.a /usr/local/lib/quickjs/ install -d /usr/local/include/quickjs/ install -m644 quickjs/quickjs.h quickjs/quickjs-libc.h /usr/local/include/quickjs/ install -m644 quickjspp.hpp /usr/local/include/ diff --git a/scripts/build.windows.release.sh b/scripts/build.windows.release.sh index 8becc96b8..ad4dd2730 100644 --- a/scripts/build.windows.release.sh +++ b/scripts/build.windows.release.sh @@ -18,7 +18,8 @@ cd quickjspp patch quickjs/quickjs-libc.c -i ../scripts/patches/0001-quickjs-libc-add-realpath-for-Windows.patch cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release . make quickjs -j4 -install -m644 quickjs/libquickjs.a "$MINGW_PREFIX/lib/" +install -d "$MINGW_PREFIX/lib/quickjs/" +install -m644 quickjs/libquickjs.a "$MINGW_PREFIX/lib/quickjs/" install -d "$MINGW_PREFIX/include/quickjs" install -m644 quickjs/quickjs.h quickjs/quickjs-libc.h "$MINGW_PREFIX/include/quickjs/" install -m644 quickjspp.hpp "$MINGW_PREFIX/include/" diff --git a/scripts/config.termux.sh b/scripts/config.termux.sh index ba151539c..7f991bebd 100644 --- a/scripts/config.termux.sh +++ b/scripts/config.termux.sh @@ -20,7 +20,8 @@ git clone https://github.com/ftk/quickjspp --depth=1 cd quickjspp cmake -DCMAKE_BUILD_TYPE=Release . make quickjs -j3 -install -m644 quickjs/libquickjs.a $PREFIX/lib/ +install -d $PREFIX/lib/quickjs/ +install -m644 quickjs/libquickjs.a $PREFIX/lib/quickjs/ install -d $PREFIX/include/quickjs/ install -m644 quickjs/quickjs.h quickjs/quickjs-libc.h $PREFIX/include/quickjs/ install -m644 quickjspp.hpp $PREFIX/include/ diff --git a/src/generator/config/nodemanip.cpp b/src/generator/config/nodemanip.cpp index 6d6d4259d..f7174ea6f 100644 --- a/src/generator/config/nodemanip.cpp +++ b/src/generator/config/nodemanip.cpp @@ -68,7 +68,7 @@ int addNodes(std::string link, std::vector &allNodes, int groupID, parse_ switch(args.size()) { case 0: - link = parse(std::string(), string_array()); + link = parse("", string_array()); break; case 1: link = parse(args[0], string_array()); diff --git a/src/generator/config/ruleconvert.cpp b/src/generator/config/ruleconvert.cpp index 64a53d73d..92fb40a88 100644 --- a/src/generator/config/ruleconvert.cpp +++ b/src/generator/config/ruleconvert.cpp @@ -245,28 +245,28 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_c switch(surge_ver) //other version: -3 for Surfboard, -4 for Loon { case 0: - base_rule.SetCurrentSection("RoutingRule"); //Mellow + base_rule.set_current_section("RoutingRule"); //Mellow break; case -1: - base_rule.SetCurrentSection("filter_local"); //Quantumult X + base_rule.set_current_section("filter_local"); //Quantumult X break; case -2: - base_rule.SetCurrentSection("TCP"); //Quantumult + base_rule.set_current_section("TCP"); //Quantumult break; default: - base_rule.SetCurrentSection("Rule"); + base_rule.set_current_section("Rule"); } if(overwrite_original_rules) { - base_rule.EraseSection(); + base_rule.erase_section(); switch(surge_ver) { case -1: - base_rule.EraseSection("filter_remote"); + base_rule.erase_section("filter_remote"); break; case -4: - base_rule.EraseSection("Remote Rule"); + base_rule.erase_section("Remote Rule"); break; } } @@ -308,7 +308,7 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_c if(surge_ver == -1 && x.rule_type == RULESET_QUANX && isLink(rule_path)) { strLine = rule_path + ", tag=" + rule_group + ", force-policy=" + rule_group + ", enabled=true"; - base_rule.Set("filter_remote", "{NONAME}", strLine); + base_rule.set("filter_remote", "{NONAME}", strLine); continue; } if(fileExist(rule_path)) @@ -325,13 +325,13 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_c { strLine = remote_path_prefix + "/getruleset?type=2&url=" + urlSafeBase64Encode(rule_path_typed) + "&group=" + urlSafeBase64Encode(rule_group); strLine += ", tag=" + rule_group + ", enabled=true"; - base_rule.Set("filter_remote", "{NONAME}", strLine); + base_rule.set("filter_remote", "{NONAME}", strLine); continue; } else if(surge_ver == -4 && remote_path_prefix.size()) { strLine = remote_path_prefix + "/getruleset?type=1&url=" + urlSafeBase64Encode(rule_path_typed) + "," + rule_group; - base_rule.Set("Remote Rule", "{NONAME}", strLine); + base_rule.set("Remote Rule", "{NONAME}", strLine); continue; } } @@ -359,13 +359,13 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_c { strLine = remote_path_prefix + "/getruleset?type=2&url=" + urlSafeBase64Encode(rule_path_typed) + "&group=" + urlSafeBase64Encode(rule_group); strLine += ", tag=" + rule_group + ", enabled=true"; - base_rule.Set("filter_remote", "{NONAME}", strLine); + base_rule.set("filter_remote", "{NONAME}", strLine); continue; } else if(surge_ver == -4) { strLine = rule_path + "," + rule_group; - base_rule.Set("Remote Rule", "{NONAME}", strLine); + base_rule.set("Remote Rule", "{NONAME}", strLine); continue; } } @@ -450,6 +450,6 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_c for(std::string &x : allRules) { - base_rule.Set("{NONAME}", x); + base_rule.set("{NONAME}", x); } } diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index 3e3c46b08..6dcb9fc8f 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -159,7 +159,7 @@ void processRemark(std::string &oldremark, std::string &newremark, string_array { if(proc_comma) { - if(oldremark.find(',') != oldremark.npos) + if(oldremark.find(',') != std::string::npos) { oldremark.insert(0, "\""); oldremark.append("\""); @@ -213,9 +213,9 @@ void groupGenerate(const std::string &rule, std::vector &nodelist, string void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGroupConfigs &extra_proxy_group, bool clashR, extra_settings &ext) { - YAML::Node proxies, singleproxy, singlegroup, original_groups; + YAML::Node proxies, original_groups; std::vector nodelist; - string_array remarks_list, filtered_nodelist; + string_array remarks_list; /// proxies style bool block = false, compact = false; switch(hash_(ext.clash_proxies_style)) @@ -233,7 +233,7 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr for(Proxy &x : nodes) { - singleproxy.reset(); + YAML::Node singleproxy; std::string type = getProxyTypeName(x.Type); std::string remark, pluginopts = replaceAllDistinct(x.PluginOption, ";", "&"); @@ -424,6 +424,8 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr } break; case ProxyType::Snell: + if (x.SnellVersion >= 4) + continue; singleproxy["type"] = "snell"; singleproxy["psk"] = x.Password; if(x.SnellVersion != 0) @@ -471,8 +473,8 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr for(const ProxyGroupConfig &x : extra_proxy_group) { - singlegroup.reset(); - eraseElements(filtered_nodelist); + YAML::Node singlegroup; + string_array filtered_nodelist; singlegroup["name"] = x.Name; singlegroup["type"] = x.TypeStr(); @@ -517,11 +519,11 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr //singlegroup.SetStyle(YAML::EmitterStyle::Flow); bool replace_flag = false; - for(unsigned int i = 0; i < original_groups.size(); i++) + for(auto && original_group : original_groups) { - if(original_groups[i]["name"].as() == x.Name) + if(original_group["name"].as() == x.Name) { - original_groups[i] = singlegroup; + original_group.reset(singlegroup); replace_flag = true; break; } @@ -547,7 +549,7 @@ std::string proxyToClash(std::vector &nodes, const std::string &base_conf catch (std::exception &e) { writeLog(0, std::string("Clash base loader failed with error: ") + e.what(), LOG_LEVEL_ERROR); - return std::string(); + return ""; } proxyToClash(nodes, yamlnode, extra_proxy_group, clashR, ext); @@ -589,33 +591,30 @@ std::string proxyToClash(std::vector &nodes, const std::string &base_conf std::string proxyToSurge(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, int surge_ver, extra_settings &ext) { INIReader ini; - std::string proxy; std::string output_nodelist; - tribool udp, tfo, scv, tls13; std::vector nodelist; unsigned short local_port = 1080; - - string_array remarks_list, filtered_nodelist, args; + string_array remarks_list; ini.store_any_line = true; // filter out sections that requires direct-save - ini.AddDirectSaveSection("General"); - ini.AddDirectSaveSection("Replica"); - ini.AddDirectSaveSection("Rule"); - ini.AddDirectSaveSection("MITM"); - ini.AddDirectSaveSection("Script"); - ini.AddDirectSaveSection("Host"); - ini.AddDirectSaveSection("URL Rewrite"); - ini.AddDirectSaveSection("Header Rewrite"); - if(ini.Parse(base_conf) != 0 && !ext.nodelist) + ini.add_direct_save_section("General"); + ini.add_direct_save_section("Replica"); + ini.add_direct_save_section("Rule"); + ini.add_direct_save_section("MITM"); + ini.add_direct_save_section("Script"); + ini.add_direct_save_section("Host"); + ini.add_direct_save_section("URL Rewrite"); + ini.add_direct_save_section("Header Rewrite"); + if(ini.parse(base_conf) != 0 && !ext.nodelist) { - writeLog(0, "Surge base loader failed with error: " + ini.GetLastError(), LOG_LEVEL_ERROR); - return std::string(); + writeLog(0, "Surge base loader failed with error: " + ini.get_last_error(), LOG_LEVEL_ERROR); + return ""; } - ini.SetCurrentSection("Proxy"); - ini.EraseSection(); - ini.Set("{NONAME}", "DIRECT = direct"); + ini.set_current_section("Proxy"); + ini.erase_section(); + ini.set("{NONAME}", "DIRECT = direct"); for(Proxy &x : nodes) { @@ -632,16 +631,14 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf std::string port = std::to_string(x.Port); bool &tlssecure = x.TLSSecure; - udp = ext.udp; - tfo = ext.tfo; - scv = ext.skip_cert_verify; - tls13 = ext.tls13; + tribool udp = ext.udp, tfo = ext.tfo, scv = ext.skip_cert_verify, tls13 = ext.tls13; udp.define(x.UDP); tfo.define(x.TCPFastOpen); scv.define(x.AllowInsecure); tls13.define(x.TLS13); - proxy.clear(); + std::string proxy; + string_array args; switch(x.Type) { @@ -772,7 +769,7 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf output_nodelist += remark + " = " + proxy + "\n"; else { - ini.Set("{NONAME}", remark + " = " + proxy); + ini.set("{NONAME}", remark + " = " + proxy); nodelist.emplace_back(x); } remarks_list.emplace_back(std::move(remark)); @@ -781,12 +778,12 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf if(ext.nodelist) return output_nodelist; - ini.SetCurrentSection("Proxy Group"); - ini.EraseSection(); + ini.set_current_section("Proxy Group"); + ini.erase_section(); for(const ProxyGroupConfig &x : extra_proxy_group) { - eraseElements(filtered_nodelist); - proxy.clear(); + string_array filtered_nodelist; + std::string group; switch(x.Type) { @@ -799,9 +796,9 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf continue; break; case ProxyGroupType::SSID: - proxy = x.TypeStr() + ",default=" + x.Proxies[0] + ","; - proxy += join(x.Proxies.begin() + 1, x.Proxies.end(), ","); - ini.Set("{NONAME}", x.Name + " = " + proxy); //insert order + group = x.TypeStr() + ",default=" + x.Proxies[0] + ","; + group += join(x.Proxies.begin() + 1, x.Proxies.end(), ","); + ini.set("{NONAME}", x.Name + " = " + group); //insert order continue; default: continue; @@ -815,54 +812,50 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf if(filtered_nodelist.size() == 1) { - proxy = toLower(filtered_nodelist[0]); - switch(hash_(proxy)) + group = toLower(filtered_nodelist[0]); + switch(hash_(group)) { case "direct"_hash: case "reject"_hash: case "reject-tinygif"_hash: - ini.Set("Proxy", "{NONAME}", x.Name + " = " + proxy); + ini.set("Proxy", "{NONAME}", x.Name + " = " + group); continue; } } - proxy = x.TypeStr() + ","; - proxy += join(filtered_nodelist, ","); + group = x.TypeStr() + ","; + group += join(filtered_nodelist, ","); if(x.Type == ProxyGroupType::URLTest || x.Type == ProxyGroupType::Fallback || x.Type == ProxyGroupType::LoadBalance) { - proxy += ",url=" + x.Url + ",interval=" + std::to_string(x.Interval); + group += ",url=" + x.Url + ",interval=" + std::to_string(x.Interval); if(x.Tolerance > 0) - proxy += ",tolerance=" + std::to_string(x.Tolerance); + group += ",tolerance=" + std::to_string(x.Tolerance); if(x.Timeout > 0) - proxy += ",timeout=" + std::to_string(x.Timeout); + group += ",timeout=" + std::to_string(x.Timeout); if(!x.Persistent.is_undef()) - proxy += ",persistent=" + x.Persistent.get_str(); + group += ",persistent=" + x.Persistent.get_str(); if(!x.EvaluateBeforeUse.is_undef()) - proxy += ",evaluate-before-use=" + x.EvaluateBeforeUse.get_str(); + group += ",evaluate-before-use=" + x.EvaluateBeforeUse.get_str(); } - ini.Set("{NONAME}", x.Name + " = " + proxy); //insert order + ini.set("{NONAME}", x.Name + " = " + group); //insert order } if(ext.enable_rule_generator) rulesetToSurge(ini, ruleset_content_array, surge_ver, ext.overwrite_original_rules, ext.managed_config_prefix); - return ini.ToString(); + return ini.to_string(); } std::string proxyToSingle(std::vector &nodes, int types, extra_settings &ext) { /// types: SS=1 SSR=2 VMess=4 Trojan=8 - std::string remark, hostname, port, password, method; - std::string plugin, pluginopts; - std::string protocol, protoparam, obfs, obfsparam; - std::string id, aid, transproto, faketype, host, path, quicsecure, quicsecret; std::string proxyStr, allLinks; bool ss = GETBIT(types, 1), ssr = GETBIT(types, 2), vmess = GETBIT(types, 3), trojan = GETBIT(types, 4); for(Proxy &x : nodes) { - remark = x.Remark; + std::string remark = x.Remark; std::string &hostname = x.Hostname, &password = x.Password, &method = x.EncryptMethod, &plugin = x.Plugin, &pluginopts = x.PluginOption, &protocol = x.Protocol, &protoparam = x.ProtocolParam, &obfs = x.OBFS, &obfsparam = x.OBFSParam, &id = x.UserId, &transproto = x.TransferProtocol, &host = x.Host, &path = x.Path, &faketype = x.FakeType; bool &tlssecure = x.TLSSecure; std::string port = std::to_string(x.Port); @@ -938,12 +931,9 @@ std::string proxyToSingle(std::vector &nodes, int types, extra_settings & std::string proxyToSSSub(std::string base_conf, std::vector &nodes, extra_settings &ext) { rapidjson::Document json, base; - std::string remark, hostname, password, method; - std::string plugin, pluginopts; - std::string protocol, obfs; std::string output_content; - rapidjson::Document::AllocatorType &alloc = json.GetAllocator(); + auto &alloc = json.GetAllocator(); json.SetObject(); json.AddMember("remarks", "", alloc); json.AddMember("server", "", alloc); @@ -971,8 +961,8 @@ std::string proxyToSSSub(std::string base_conf, std::vector &nodes, extra output_content = "["; for(Proxy &x : nodes) { - remark = x.Remark; - hostname = x.Hostname; + std::string &remark = x.Remark; + std::string &hostname = x.Hostname; std::string &password = x.Password; std::string &method = x.EncryptMethod; std::string &plugin = x.Plugin; @@ -1012,10 +1002,10 @@ std::string proxyToQuan(std::vector &nodes, const std::string &base_conf, { INIReader ini; ini.store_any_line = true; - if(!ext.nodelist && ini.Parse(base_conf) != 0) + if(!ext.nodelist && ini.parse(base_conf) != 0) { - writeLog(0, "Quantumult base loader failed with error: " + ini.GetLastError(), LOG_LEVEL_ERROR); - return std::string(); + writeLog(0, "Quantumult base loader failed with error: " + ini.get_last_error(), LOG_LEVEL_ERROR); + return ""; } proxyToQuan(nodes, ini, ruleset_content_array, extra_proxy_group, ext); @@ -1024,23 +1014,22 @@ std::string proxyToQuan(std::vector &nodes, const std::string &base_conf, { string_array allnodes; std::string allLinks; - ini.GetAll("SERVER", "{NONAME}", allnodes); + ini.get_all("SERVER", "{NONAME}", allnodes); if(!allnodes.empty()) allLinks = join(allnodes, "\n"); return base64Encode(allLinks); } - return ini.ToString(); + return ini.to_string(); } void proxyToQuan(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { - std::string type, proxyStr; - tribool scv; + std::string proxyStr; std::vector nodelist; string_array remarks_list; - ini.SetCurrentSection("SERVER"); - ini.EraseSection(); + ini.set_current_section("SERVER"); + ini.erase_section(); for(Proxy &x : nodes) { std::string remark = x.Remark; @@ -1056,6 +1045,7 @@ void proxyToQuan(std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { INIReader ini; ini.store_any_line = true; - ini.AddDirectSaveSection("general"); - ini.AddDirectSaveSection("dns"); - ini.AddDirectSaveSection("rewrite_remote"); - ini.AddDirectSaveSection("rewrite_local"); - ini.AddDirectSaveSection("task_local"); - ini.AddDirectSaveSection("mitm"); - ini.AddDirectSaveSection("server_remote"); - if(!ext.nodelist && ini.Parse(base_conf) != 0) + ini.add_direct_save_section("general"); + ini.add_direct_save_section("dns"); + ini.add_direct_save_section("rewrite_remote"); + ini.add_direct_save_section("rewrite_local"); + ini.add_direct_save_section("task_local"); + ini.add_direct_save_section("mitm"); + ini.add_direct_save_section("server_remote"); + if(!ext.nodelist && ini.parse(base_conf) != 0) { - writeLog(0, "QuantumultX base loader failed with error: " + ini.GetLastError(), LOG_LEVEL_ERROR); - return std::string(); + writeLog(0, "QuantumultX base loader failed with error: " + ini.get_last_error(), LOG_LEVEL_ERROR); + return ""; } proxyToQuanX(nodes, ini, ruleset_content_array, extra_proxy_group, ext); @@ -1259,28 +1249,25 @@ std::string proxyToQuanX(std::vector &nodes, const std::string &base_conf { string_array allnodes; std::string allLinks; - ini.GetAll("server_local", "{NONAME}", allnodes); + ini.get_all("server_local", "{NONAME}", allnodes); if(!allnodes.empty()) allLinks = join(allnodes, "\n"); return allLinks; } - return ini.ToString(); + return ini.to_string(); } void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { std::string type; - std::string remark, hostname, port, method; - std::string password, plugin, pluginopts; - std::string id, transproto, host, path; - std::string protocol, protoparam, obfs, obfsparam; + std::string remark; std::string proxyStr; tribool udp, tfo, scv, tls13; std::vector nodelist; string_array remarks_list; - ini.SetCurrentSection("server_local"); - ini.EraseSection(); + ini.set_current_section("server_local"); + ini.erase_section(); for(Proxy &x : nodes) { if(ext.append_proxy_type) @@ -1338,7 +1325,7 @@ void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, std::string &group, std::strin { rapidjson::StringBuffer sb; rapidjson::Writer writer(sb); - size_t index = 0; + int index = 0; if(group.empty()) group = "SSD"; @@ -1603,29 +1590,29 @@ std::string proxyToMellow(std::vector &nodes, const std::string &base_con { INIReader ini; ini.store_any_line = true; - if(ini.Parse(base_conf) != 0) + if(ini.parse(base_conf) != 0) { - writeLog(0, "Mellow base loader failed with error: " + ini.GetLastError(), LOG_LEVEL_ERROR); - return std::string(); + writeLog(0, "Mellow base loader failed with error: " + ini.get_last_error(), LOG_LEVEL_ERROR); + return ""; } proxyToMellow(nodes, ini, ruleset_content_array, extra_proxy_group, ext); - return ini.ToString(); + return ini.to_string(); } void proxyToMellow(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { std::string proxy; - std::string type, remark, hostname, port, username, password, method; + std::string remark, username, password, method; std::string plugin, pluginopts; std::string id, aid, transproto, faketype, host, path, quicsecure, quicsecret, tlssecure; std::string url; tribool tfo, scv; std::vector nodelist; - string_array vArray, remarks_list, filtered_nodelist; + string_array vArray, remarks_list; - ini.SetCurrentSection("Endpoint"); + ini.set_current_section("Endpoint"); for(Proxy &x : nodes) { @@ -1637,8 +1624,7 @@ void proxyToMellow(std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { rapidjson::Document json; INIReader ini; - std::string proxy; std::string output_nodelist; - tribool scv; std::vector nodelist; - string_array vArray, remarks_list, filtered_nodelist; + string_array remarks_list; ini.store_any_line = true; - if(ini.Parse(base_conf) != INIREADER_EXCEPTION_NONE && !ext.nodelist) + if(ini.parse(base_conf) != INIREADER_EXCEPTION_NONE && !ext.nodelist) { - writeLog(0, "Loon base loader failed with error: " + ini.GetLastError(), LOG_LEVEL_ERROR); - return std::string(); + writeLog(0, "Loon base loader failed with error: " + ini.get_last_error(), LOG_LEVEL_ERROR); + return ""; } - - ini.SetCurrentSection("Proxy"); - ini.EraseSection(); + ini.set_current_section("Proxy"); + ini.erase_section(); for(Proxy &x : nodes) { @@ -1795,7 +1777,7 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, tribool scv = ext.skip_cert_verify; scv.define(x.AllowInsecure); - proxy.clear(); + std::string proxy; switch(x.Type) { @@ -1813,59 +1795,58 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, if(method == "auto") method = "chacha20-ietf-poly1305"; - proxy = "vmess," + hostname + "," + port + "," + method + ",\"" + id + "\",over-tls:" + (tlssecure ? "true" : "false"); + proxy = "vmess," + hostname + "," + port + "," + method + ",\"" + id + "\",over-tls=" + (tlssecure ? "true" : "false"); if(tlssecure) - proxy += ",tls-name:" + host; + proxy += ",tls-name=" + host; switch(hash_(transproto)) { case "tcp"_hash: - proxy += ",transport:tcp"; + proxy += ",transport=tcp"; break; case "ws"_hash: - proxy += ",transport:ws,path:" + path + ",host:" + host; + proxy += ",transport=ws,path=" + path + ",host=" + host; break; default: continue; } if(!scv.is_undef()) - proxy += ",skip-cert-verify:" + std::string(scv.get() ? "1" : "0"); + proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); break; case ProxyType::ShadowsocksR: - proxy = "ShadowsocksR," + hostname + "," + port + "," + method + ",\"" + password + "\"," + protocol + ",{" + protoparam + "}," + obfs + ",{" + obfsparam + "}"; + proxy = "ShadowsocksR," + hostname + "," + port + "," + method + ",\"" + password + "\",protocol=" + protocol + ",protocol-param=" + protoparam + ",obfs=" + obfs + ",obfs-param=" + obfsparam; break; - /* - case ProxyType::SOCKS5: - proxy = "socks5, " + hostname + ", " + port + ", " + username + ", " + password; - if(ext.skip_cert_verify) - proxy += ", skip-cert-verify:1"; - break; - */ case ProxyType::HTTP: - proxy = "http," + hostname + "," + port + "," + username + "," + password; + proxy = "http," + hostname + "," + port + "," + username + ",\"" + password + "\""; + break; + case ProxyType::HTTPS: + proxy = "https," + hostname + "," + port + "," + username + ",\"" + password + "\""; + if(!host.empty()) + proxy += ",tls-name=" + host; + if(!scv.is_undef()) + proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); break; case ProxyType::Trojan: - proxy = "trojan," + hostname + "," + port + "," + password; + proxy = "trojan," + hostname + "," + port + ",\"" + password + "\""; if(!host.empty()) - proxy += ",tls-name:" + host; + proxy += ",tls-name=" + host; if(!scv.is_undef()) - proxy += ",skip-cert-verify:" + std::string(scv.get() ? "1" : "0"); + proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); break; default: continue; } - /* if(ext.tfo) - proxy += ", tfo=true"; + proxy += ",fast-open=true"; if(ext.udp) - proxy += ", udp-relay=true"; - */ + proxy += ",udp=true"; + if(ext.nodelist) output_nodelist += remark + " = " + proxy + "\n"; else { - ini.Set("{NONAME}", remark + " = " + proxy); + ini.set("{NONAME}", remark + " = " + proxy); nodelist.emplace_back(x); remarks_list.emplace_back(std::move(remark)); } @@ -1874,25 +1855,26 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, if(ext.nodelist) return output_nodelist; - ini.SetCurrentSection("Proxy Group"); - ini.EraseSection(); + ini.set_current_section("Proxy Group"); + ini.erase_section(); for(const ProxyGroupConfig &x : extra_proxy_group) { - eraseElements(filtered_nodelist); - proxy.clear(); + string_array filtered_nodelist; + std::string group, group_extra; switch(x.Type) { case ProxyGroupType::Select: + case ProxyGroupType::LoadBalance: case ProxyGroupType::URLTest: case ProxyGroupType::Fallback: break; case ProxyGroupType::SSID: if(x.Proxies.size() < 2) continue; - proxy = x.TypeStr() + ",default=" + x.Proxies[0] + ","; - proxy += join(x.Proxies.begin() + 1, x.Proxies.end(), ","); - ini.Set("{NONAME}", x.Name + " = " + proxy); //insert order + group = x.TypeStr() + ",default=" + x.Proxies[0] + ","; + group += join(x.Proxies.begin() + 1, x.Proxies.end(), ","); + ini.set("{NONAME}", x.Name + " = " + group); //insert order continue; default: continue; @@ -1904,20 +1886,23 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, if(filtered_nodelist.empty()) filtered_nodelist.emplace_back("DIRECT"); - proxy = x.TypeStr() + ","; + group = x.TypeStr() + ","; /* for(std::string &y : filtered_nodelist) - proxy += "," + y; + group += "," + y; */ - proxy += join(filtered_nodelist, ","); - if(x.Type == ProxyGroupType::URLTest || x.Type == ProxyGroupType::Fallback) - proxy += ",url=" + x.Url + ",interval=" + std::to_string(x.Interval); + group += join(filtered_nodelist, ","); + if(x.Type != ProxyGroupType::Select) { + group += ",url=" + x.Url + ",interval=" + std::to_string(x.Interval); + if (x.Type == ProxyGroupType::LoadBalance) + group += ",strategy=" + std::string(x.Strategy == BalanceStrategy::RoundRobin ? "round-robin" : "pcc"); + } - ini.Set("{NONAME}", x.Name + " = " + proxy); //insert order + ini.set("{NONAME}", x.Name + " = " + group); //insert order } if(ext.enable_rule_generator) rulesetToSurge(ini, ruleset_content_array, -4, ext.overwrite_original_rules, ext.managed_config_prefix); - return ini.ToString(); + return ini.to_string(); } diff --git a/src/generator/template/templates.cpp b/src/generator/template/templates.cpp index 9a3469ad6..d3f475895 100644 --- a/src/generator/template/templates.cpp +++ b/src/generator/template/templates.cpp @@ -151,7 +151,7 @@ int render_template(const std::string &content, const template_args &vars, std:: { std::string key = args.at(0)->get(), value = args.at(1)->get(); parse_json_pointer(data, key, value); - return std::string(); + return ""; }); env.add_callback("split", 3, [&data](inja::Arguments &args) { @@ -159,7 +159,7 @@ int render_template(const std::string &content, const template_args &vars, std:: string_array vArray = split(content, delim); for(size_t index = 0; index < vArray.size(); index++) parse_json_pointer(data, dest + "." + std::to_string(index), vArray[index]); - return std::string(); + return ""; }); env.add_callback("append", 2, [&data](inja::Arguments &args) { @@ -175,7 +175,7 @@ int render_template(const std::string &content, const template_args &vars, std:: } output_content.append(value); data[nlohmann::json::json_pointer(pointer)] = output_content; - return std::string(); + return ""; }); env.add_callback("getLink", 1, [](inja::Arguments &args) { diff --git a/src/handler/interfaces.cpp b/src/handler/interfaces.cpp index 8da055760..262538730 100644 --- a/src/handler/interfaces.cpp +++ b/src/handler/interfaces.cpp @@ -958,15 +958,15 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) base_content = fetchFile(url, proxy, global.cacheConfig); - if(ini.Parse(base_content) != INIREADER_EXCEPTION_NONE) + if(ini.parse(base_content) != INIREADER_EXCEPTION_NONE) { - std::string errmsg = "Parsing Surge config failed! Reason: " + ini.GetLastError(); + std::string errmsg = "Parsing Surge config failed! Reason: " + ini.get_last_error(); //std::cerr<::from_ini(tempArray, "@"); safe_set_renames(configs); @@ -900,20 +900,20 @@ void readConf() } } - if(ini.SectionExist("userinfo")) + if(ini.section_exist("userinfo")) { - ini.EnterSection("userinfo"); - if(ini.ItemPrefixExist("stream_rule")) + ini.enter_section("userinfo"); + if(ini.item_prefix_exist("stream_rule")) { - ini.GetAll("stream_rule", tempArray); + ini.get_all("stream_rule", tempArray); importItems(tempArray, false); auto configs = INIBinding::from::from_ini(tempArray, "|"); safe_set_streams(configs); eraseElements(tempArray); } - if(ini.ItemPrefixExist("time_rule")) + if(ini.item_prefix_exist("time_rule")) { - ini.GetAll("time_rule", tempArray); + ini.get_all("time_rule", tempArray); importItems(tempArray, false); auto configs = INIBinding::from::from_ini(tempArray, "|"); safe_set_times(configs); @@ -921,45 +921,45 @@ void readConf() } } - ini.EnterSection("managed_config"); - ini.GetBoolIfExist("write_managed_config", global.writeManagedConfig); - ini.GetIfExist("managed_config_prefix", global.managedConfigPrefix); - ini.GetIntIfExist("config_update_interval", global.updateInterval); - ini.GetBoolIfExist("config_update_strict", global.updateStrict); - ini.GetIfExist("quanx_device_id", global.quanXDevID); - - ini.EnterSection("emojis"); - ini.GetBoolIfExist("add_emoji", global.addEmoji); - ini.GetBoolIfExist("remove_old_emoji", global.removeEmoji); - if(ini.ItemPrefixExist("rule")) + ini.enter_section("managed_config"); + ini.get_bool_if_exist("write_managed_config", global.writeManagedConfig); + ini.get_if_exist("managed_config_prefix", global.managedConfigPrefix); + ini.get_int_if_exist("config_update_interval", global.updateInterval); + ini.get_bool_if_exist("config_update_strict", global.updateStrict); + ini.get_if_exist("quanx_device_id", global.quanXDevID); + + ini.enter_section("emojis"); + ini.get_bool_if_exist("add_emoji", global.addEmoji); + ini.get_bool_if_exist("remove_old_emoji", global.removeEmoji); + if(ini.item_prefix_exist("rule")) { - ini.GetAll("rule", tempArray); + ini.get_all("rule", tempArray); importItems(tempArray, false); auto configs = INIBinding::from::from_ini(tempArray, ","); safe_set_emojis(configs); eraseElements(tempArray); } - if(ini.SectionExist("rulesets")) - ini.EnterSection("rulesets"); + if(ini.section_exist("rulesets")) + ini.enter_section("rulesets"); else - ini.EnterSection("ruleset"); - global.enableRuleGen = ini.GetBool("enabled"); + ini.enter_section("ruleset"); + global.enableRuleGen = ini.get_bool("enabled"); if(global.enableRuleGen) { - ini.GetBoolIfExist("overwrite_original_rules", global.overwriteOriginalRules); - ini.GetBoolIfExist("update_ruleset_on_request", global.updateRulesetOnRequest); - if(ini.ItemPrefixExist("ruleset")) + ini.get_bool_if_exist("overwrite_original_rules", global.overwriteOriginalRules); + ini.get_bool_if_exist("update_ruleset_on_request", global.updateRulesetOnRequest); + if(ini.item_prefix_exist("ruleset")) { string_array vArray; - ini.GetAll("ruleset", vArray); + ini.get_all("ruleset", vArray); importItems(vArray, false); global.customRulesets = INIBinding::from::from_ini(vArray); } - else if(ini.ItemPrefixExist("surge_ruleset")) + else if(ini.item_prefix_exist("surge_ruleset")) { string_array vArray; - ini.GetAll("surge_ruleset", vArray); + ini.get_all("surge_ruleset", vArray); importItems(vArray, false); global.customRulesets = INIBinding::from::from_ini(vArray); } @@ -970,22 +970,22 @@ void readConf() global.updateRulesetOnRequest = false; } - if(ini.SectionExist("proxy_groups")) - ini.EnterSection("proxy_groups"); + if(ini.section_exist("proxy_groups")) + ini.enter_section("proxy_groups"); else - ini.EnterSection("clash_proxy_group"); - if(ini.ItemPrefixExist("custom_proxy_group")) + ini.enter_section("clash_proxy_group"); + if(ini.item_prefix_exist("custom_proxy_group")) { string_array vArray; - ini.GetAll("custom_proxy_group", vArray); + ini.get_all("custom_proxy_group", vArray); importItems(vArray, false); global.customProxyGroups = INIBinding::from::from_ini(vArray); } - ini.EnterSection("template"); - ini.GetIfExist("template_path", global.templatePath); + ini.enter_section("template"); + ini.get_if_exist("template_path", global.templatePath); string_multimap tempmap; - ini.GetItems(tempmap); + ini.get_items(tempmap); eraseElements(global.templateVars); for(auto &x : tempmap) { @@ -995,36 +995,36 @@ void readConf() } global.templateVars["managed_config_prefix"] = global.managedConfigPrefix; - if(ini.SectionExist("aliases")) + if(ini.section_exist("aliases")) { - ini.EnterSection("aliases"); - ini.GetItems(tempmap); + ini.enter_section("aliases"); + ini.get_items(tempmap); webServer.reset_redirect(); for(auto &x : tempmap) webServer.append_redirect(x.first, x.second); } - if(ini.SectionExist("tasks")) + if(ini.section_exist("tasks")) { string_array vArray; - ini.EnterSection("tasks"); - ini.GetAll("task", vArray); + ini.enter_section("tasks"); + ini.get_all("task", vArray); importItems(vArray, false); global.enableCron = !vArray.empty(); global.cronTasks = INIBinding::from::from_ini(vArray); refresh_schedule(); } - ini.EnterSection("server"); - ini.GetIfExist("listen", global.listenAddress); - ini.GetIntIfExist("port", global.listenPort); - webServer.serve_file_root = ini.Get("serve_file_root"); + ini.enter_section("server"); + ini.get_if_exist("listen", global.listenAddress); + ini.get_int_if_exist("port", global.listenPort); + webServer.serve_file_root = ini.get("serve_file_root"); webServer.serve_file = !webServer.serve_file_root.empty(); - ini.EnterSection("advanced"); + ini.enter_section("advanced"); std::string log_level; - ini.GetIfExist("log_level", log_level); - ini.GetBoolIfExist("print_debug_info", global.printDbgInfo); + ini.get_if_exist("log_level", log_level); + ini.get_bool_if_exist("print_debug_info", global.printDbgInfo); if(global.printDbgInfo) global.logLevel = LOG_LEVEL_VERBOSE; else @@ -1050,19 +1050,19 @@ void readConf() global.logLevel = LOG_LEVEL_INFO; } } - ini.GetIntIfExist("max_pending_connections", global.maxPendingConns); - ini.GetIntIfExist("max_concurrent_threads", global.maxConcurThreads); - ini.GetNumberIfExist("max_allowed_rulesets", global.maxAllowedRulesets); - ini.GetNumberIfExist("max_allowed_rules", global.maxAllowedRules); - ini.GetNumberIfExist("max_allowed_download_size", global.maxAllowedDownloadSize); - if(ini.ItemExist("enable_cache")) + ini.get_int_if_exist("max_pending_connections", global.maxPendingConns); + ini.get_int_if_exist("max_concurrent_threads", global.maxConcurThreads); + ini.get_number_if_exist("max_allowed_rulesets", global.maxAllowedRulesets); + ini.get_number_if_exist("max_allowed_rules", global.maxAllowedRules); + ini.get_number_if_exist("max_allowed_download_size", global.maxAllowedDownloadSize); + if(ini.item_exist("enable_cache")) { - if(ini.GetBool("enable_cache")) + if(ini.get_bool("enable_cache")) { - ini.GetIntIfExist("cache_subscription", global.cacheSubscription); - ini.GetIntIfExist("cache_config", global.cacheConfig); - ini.GetIntIfExist("cache_ruleset", global.cacheRuleset); - ini.GetBoolIfExist("serve_cache_on_fetch_fail", global.serveCacheOnFetchFail); + ini.get_int_if_exist("cache_subscription", global.cacheSubscription); + ini.get_int_if_exist("cache_config", global.cacheConfig); + ini.get_int_if_exist("cache_ruleset", global.cacheRuleset); + ini.get_bool_if_exist("serve_cache_on_fetch_fail", global.serveCacheOnFetchFail); } else { @@ -1070,9 +1070,9 @@ void readConf() global.serveCacheOnFetchFail = false; } } - ini.GetBoolIfExist("script_clean_context", global.scriptCleanContext); - ini.GetBoolIfExist("async_fetch_ruleset", global.asyncFetchRuleset); - ini.GetBoolIfExist("skip_failed_links", global.skipFailedLinks); + ini.get_bool_if_exist("script_clean_context", global.scriptCleanContext); + ini.get_bool_if_exist("async_fetch_ruleset", global.asyncFetchRuleset); + ini.get_bool_if_exist("skip_failed_links", global.skipFailedLinks); writeLog(0, "Load preference settings in INI format completed.", LOG_LEVEL_INFO); } @@ -1227,27 +1227,27 @@ int loadExternalConfig(std::string &path, ExternalConfig &ext) INIReader ini; ini.store_isolated_line = true; - ini.SetIsolatedItemsSection("custom"); - if(ini.Parse(base_content) != INIREADER_EXCEPTION_NONE) + ini.set_isolated_items_section("custom"); + if(ini.parse(base_content) != INIREADER_EXCEPTION_NONE) { - //std::cerr<<"Load external configuration failed. Reason: "<::from_ini(vArray); } - std::string ruleset_name = ini.ItemPrefixExist("ruleset") ? "ruleset" : "surge_ruleset"; - if(ini.ItemPrefixExist(ruleset_name)) + std::string ruleset_name = ini.item_prefix_exist("ruleset") ? "ruleset" : "surge_ruleset"; + if(ini.item_prefix_exist(ruleset_name)) { string_array vArray; - ini.GetAll(ruleset_name, vArray); + ini.get_all(ruleset_name, vArray); importItems(vArray, global.APIMode); if(global.maxAllowedRulesets && vArray.size() > global.maxAllowedRulesets) { @@ -1257,44 +1257,44 @@ int loadExternalConfig(std::string &path, ExternalConfig &ext) ext.surge_ruleset = INIBinding::from::from_ini(vArray); } - ini.GetIfExist("clash_rule_base", ext.clash_rule_base); - ini.GetIfExist("surge_rule_base", ext.surge_rule_base); - ini.GetIfExist("surfboard_rule_base", ext.surfboard_rule_base); - ini.GetIfExist("mellow_rule_base", ext.mellow_rule_base); - ini.GetIfExist("quan_rule_base", ext.quan_rule_base); - ini.GetIfExist("quanx_rule_base", ext.quanx_rule_base); - ini.GetIfExist("loon_rule_base", ext.loon_rule_base); - ini.GetIfExist("sssub_rule_base", ext.sssub_rule_base); + ini.get_if_exist("clash_rule_base", ext.clash_rule_base); + ini.get_if_exist("surge_rule_base", ext.surge_rule_base); + ini.get_if_exist("surfboard_rule_base", ext.surfboard_rule_base); + ini.get_if_exist("mellow_rule_base", ext.mellow_rule_base); + ini.get_if_exist("quan_rule_base", ext.quan_rule_base); + ini.get_if_exist("quanx_rule_base", ext.quanx_rule_base); + ini.get_if_exist("loon_rule_base", ext.loon_rule_base); + ini.get_if_exist("sssub_rule_base", ext.sssub_rule_base); - ini.GetBoolIfExist("overwrite_original_rules", ext.overwrite_original_rules); - ini.GetBoolIfExist("enable_rule_generator", ext.enable_rule_generator); + ini.get_bool_if_exist("overwrite_original_rules", ext.overwrite_original_rules); + ini.get_bool_if_exist("enable_rule_generator", ext.enable_rule_generator); - if(ini.ItemPrefixExist("rename")) + if(ini.item_prefix_exist("rename")) { string_array vArray; - ini.GetAll("rename", vArray); + ini.get_all("rename", vArray); importItems(vArray, global.APIMode); ext.rename = INIBinding::from::from_ini(vArray, "@"); } - ext.add_emoji = ini.Get("add_emoji"); - ext.remove_old_emoji = ini.Get("remove_old_emoji"); - if(ini.ItemPrefixExist("emoji")) + ext.add_emoji = ini.get("add_emoji"); + ext.remove_old_emoji = ini.get("remove_old_emoji"); + if(ini.item_prefix_exist("emoji")) { string_array vArray; - ini.GetAll("emoji", vArray); + ini.get_all("emoji", vArray); importItems(vArray, global.APIMode); ext.emoji = INIBinding::from::from_ini(vArray, ","); } - if(ini.ItemPrefixExist("include_remarks")) - ini.GetAll("include_remarks", ext.include); - if(ini.ItemPrefixExist("exclude_remarks")) - ini.GetAll("exclude_remarks", ext.exclude); + if(ini.item_prefix_exist("include_remarks")) + ini.get_all("include_remarks", ext.include); + if(ini.item_prefix_exist("exclude_remarks")) + ini.get_all("exclude_remarks", ext.exclude); - if(ini.SectionExist("template") && ext.tpl_args != nullptr) + if(ini.section_exist("template") && ext.tpl_args != nullptr) { - ini.EnterSection("template"); + ini.enter_section("template"); string_multimap tempmap; - ini.GetItems(tempmap); + ini.get_items(tempmap); for(auto &x : tempmap) ext.tpl_args->local_vars[x.first] = x.second; } diff --git a/src/handler/upload.cpp b/src/handler/upload.cpp index 39f11010d..1d689f606 100644 --- a/src/handler/upload.cpp +++ b/src/handler/upload.cpp @@ -41,15 +41,15 @@ int uploadGist(std::string name, std::string path, std::string content, bool wri return -1; } - ini.ParseFile("gistconf.ini"); - if(ini.EnterSection("common") != 0) + ini.parse_file("gistconf.ini"); + if(ini.enter_section("common") != 0) { //std::cerr<<"gistconf.ini has incorrect format. Skipping...\n"; writeLog(0, "gistconf.ini has incorrect format. Skipping...", LOG_LEVEL_ERROR); return -1; } - token = ini.Get("token"); + token = ini.get("token"); if(!token.size()) { //std::cerr<<"No token is provided. Skipping...\n"; @@ -57,12 +57,12 @@ int uploadGist(std::string name, std::string path, std::string content, bool wri return -1; } - id = ini.Get("id"); - username = ini.Get("username"); + id = ini.get("id"); + username = ini.get("username"); if(!path.size()) { - if(ini.ItemExist("path")) - path = ini.Get(name, "path"); + if(ini.item_exist("path")) + path = ini.get(name, "path"); else path = name; } @@ -102,16 +102,16 @@ int uploadGist(std::string name, std::string path, std::string content, bool wri //std::cerr<<"Writing to Gist success!\nGenerator: "< std::string { - if(global.accessToken.size()) + if(!global.accessToken.empty()) { std::string token = getUrlArg(request.argument, "token"); if(token != global.accessToken) @@ -206,7 +206,7 @@ int main(int argc, char *argv[]) webServer.append_response("GET", "/readconf", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string { - if(global.accessToken.size()) + if(!global.accessToken.empty()) { std::string token = getUrlArg(request.argument, "token"); if(token != global.accessToken) @@ -223,7 +223,7 @@ int main(int argc, char *argv[]) webServer.append_response("POST", "/updateconf", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string { - if(global.accessToken.size()) + if(!global.accessToken.empty()) { std::string token = getUrlArg(request.argument, "token"); if(token != global.accessToken) diff --git a/src/parser/subparser.cpp b/src/parser/subparser.cpp index 4a2cf4baa..6a51eaf50 100644 --- a/src/parser/subparser.cpp +++ b/src/parser/subparser.cpp @@ -1293,17 +1293,17 @@ bool explodeSurge(std::string surge, std::vector &nodes) ini.store_isolated_line = true; ini.keep_empty_section = false; ini.allow_dup_section_titles = true; - ini.SetIsolatedItemsSection("Proxy"); - ini.IncludeSection("Proxy"); - ini.AddDirectSaveSection("Proxy"); + ini.set_isolated_items_section("Proxy"); + ini.include_section("Proxy"); + ini.add_direct_save_section("Proxy"); if(surge.find("[Proxy]") != surge.npos) - surge = regReplace(surge, "^[\\S\\s]*?\\[", "[", false); - ini.Parse(surge); + surge = regReplace(surge, R"(^[\S\s]*?\[)", "[", false); + ini.parse(surge); - if(!ini.SectionExist("Proxy")) + if(!ini.section_exist("Proxy")) return false; - ini.EnterSection("Proxy"); - ini.GetItems(proxies); + ini.enter_section("Proxy"); + ini.get_items(proxies); const std::string proxystr = "(.*?)\\s*=\\s*(.*)"; diff --git a/src/utils/file.cpp b/src/utils/file.cpp index f6d059a8f..3d1928a22 100644 --- a/src/utils/file.cpp +++ b/src/utils/file.cpp @@ -2,7 +2,7 @@ #include #include -#include "string.h" +#include "utils/string.h" bool isInScope(const std::string &path) { @@ -22,7 +22,7 @@ std::string fileGet(const std::string &path, bool scope_limit) std::string content; if(scope_limit && !isInScope(path)) - return std::string(); + return ""; std::FILE *fp = std::fopen(path.c_str(), "rb"); if(fp) diff --git a/src/utils/ini_reader/ini_reader.h b/src/utils/ini_reader/ini_reader.h index eca6cc875..bb76a1548 100644 --- a/src/utils/ini_reader/ini_reader.h +++ b/src/utils/ini_reader/ini_reader.h @@ -51,17 +51,17 @@ class INIReader int last_error = INIREADER_EXCEPTION_NONE; unsigned int last_error_index = 0; - inline int __priv_save_error_and_return(int x) + inline int save_error_and_return(int x) { last_error = x; return last_error; } - inline bool __priv_chk_ignore(const std::string §ion) + inline bool chk_ignore(const std::string §ion) { bool excluded = false, included = false; excluded = std::find(exclude_sections.begin(), exclude_sections.end(), section) != exclude_sections.end(); - if(include_sections.size()) + if(!include_sections.empty()) included = std::find(include_sections.begin(), include_sections.end(), section) != include_sections.end(); else included = true; @@ -69,12 +69,12 @@ class INIReader return excluded || !included; } - inline bool __priv_chk_direct_save(const std::string §ion) + inline bool chk_direct_save(const std::string §ion) { return std::find(direct_save_sections.cbegin(), direct_save_sections.cend(), section) != direct_save_sections.cend(); } - inline std::string __priv_get_err_str(int error) + inline std::string get_err_str(int error) { switch(error) { @@ -87,30 +87,30 @@ class INIReader case INIREADER_EXCEPTION_OUTOFBOUND: return "Item exists outside of any section"; case INIREADER_EXCEPTION_NOTPARSED: - return "Parse error"; + return "parse error"; default: return "Undefined"; } } - template inline void eraseElements(std::vector &target) + template inline void erase_elements(std::vector &target) { target.clear(); target.shrink_to_fit(); } - template inline void eraseElements(T &target) + template inline void erase_elements(T &target) { T().swap(target); } public: /** - * @brief Set this flag to true to do a UTF8-To-GBK conversion before parsing data. Only useful in Windows. + * @brief set this flag to true to do a UTF8-To-GBK conversion before parsing data. Only useful in Windows. */ bool do_utf8_to_gbk = false; /** - * @brief Set this flag to true so any line within the section will be stored even it doesn't follow the "name=value" format. + * @brief set this flag to true so any line within the section will be stored even it doesn't follow the "name=value" format. * These lines will store as the name "{NONAME}". */ bool store_any_line = false; @@ -139,12 +139,12 @@ class INIReader } /** - * @brief Parse a file during initialization. + * @brief parse a file during initialization. */ explicit INIReader(const std::string &filePath) { parsed = false; - ParseFile(filePath); + parse_file(filePath); } ~INIReader() = default; @@ -171,18 +171,18 @@ class INIReader INIReader(const INIReader &src) = default; - std::string GetLastError() + std::string get_last_error() { if(parsed) - return __priv_get_err_str(last_error); + return get_err_str(last_error); else - return "line " + std::to_string(last_error_index) + ": " + __priv_get_err_str(last_error); + return "line " + std::to_string(last_error_index) + ": " + get_err_str(last_error); } /** * @brief Exclude a section with the given name. */ - void ExcludeSection(const std::string §ion) + void exclude_section(const std::string §ion) { exclude_sections.emplace_back(section); } @@ -190,7 +190,7 @@ class INIReader /** * @brief Include a section with the given name. */ - void IncludeSection(const std::string §ion) + void include_section(const std::string §ion) { include_sections.emplace_back(section); } @@ -198,28 +198,28 @@ class INIReader /** * @brief Add a section to the direct-save sections list. */ - void AddDirectSaveSection(const std::string §ion) + void add_direct_save_section(const std::string §ion) { direct_save_sections.emplace_back(section); } /** - * @brief Set isolated items to given section. + * @brief set isolated items to given section. */ - void SetIsolatedItemsSection(const std::string §ion) + void set_isolated_items_section(const std::string §ion) { isolated_items_section = section; } /** - * @brief Parse INI content into mapped data structure. + * @brief parse INI content into mapped data structure. * If exclude sections are set, these sections will not be stored. * If include sections are set, only these sections will be stored. */ - int Parse(std::string content) //parse content into mapped data + int parse(std::string content) //parse content into mapped data { - if(!content.size()) //empty content - return __priv_save_error_and_return(INIREADER_EXCEPTION_EMPTY); + if(content.empty()) //empty content + return save_error_and_return(INIREADER_EXCEPTION_EMPTY); //remove UTF-8 BOM if(content.compare(0, 3, "\xEF\xBB\xBF") == 0) @@ -232,16 +232,16 @@ class INIReader std::stringstream strStrm; char delimiter = getLineBreak(content); - EraseAll(); //first erase all data + erase_all(); //first erase all data if(do_utf8_to_gbk && isStrUTF8(content)) content = utf8ToACP(content); //do conversion if flag is set - if(store_isolated_line && isolated_items_section.size()) + if(store_isolated_line && !isolated_items_section.empty()) { curSection = isolated_items_section; //items before any section define will be store in this section //check this section first - inExcludedSection = __priv_chk_ignore(curSection); //check if this section is excluded - inDirectSaveSection = __priv_chk_direct_save(curSection); //check if this section requires direct-save + inExcludedSection = chk_ignore(curSection); //check if this section is excluded + inDirectSaveSection = chk_direct_save(curSection); //check if this section requires direct-save inIsolatedSection = true; } strStrm<= 2 && strLine[0] == '/' && strLine[1] == '/')) && !inDirectSaveSection) //empty lines and comments are ignored continue; processEscapeChar(strLine); if(strLine[0] == '[' && strLine[lineSize - 1] == ']') //is a section title { thisSection = strLine.substr(1, lineSize - 2); //save section title - inExcludedSection = __priv_chk_ignore(thisSection); //check if this section is excluded - inDirectSaveSection = __priv_chk_direct_save(thisSection); //check if this section requires direct-save + inExcludedSection = chk_ignore(thisSection); //check if this section is excluded + inDirectSaveSection = chk_direct_save(thisSection); //check if this section requires direct-save - if(curSection.size() && (keep_empty_section || itemGroup.size())) //just finished reading a section + if(!curSection.empty() && (keep_empty_section || !itemGroup.empty())) //just finished reading a section { if(ini_content.find(curSection) != ini_content.end()) //a section with the same name has been inserted { - if(allow_dup_section_titles || !ini_content.at(curSection).size()) + if(allow_dup_section_titles || ini_content.at(curSection).empty()) { auto iter = ini_content.at(curSection); //get the existing section iter.merge(itemGroup); //move new items to this section } - else if(ini_content.at(curSection).size()) - return __priv_save_error_and_return(INIREADER_EXCEPTION_DUPLICATE); //not allowed, stop + else if(!ini_content.at(curSection).empty()) + return save_error_and_return(INIREADER_EXCEPTION_DUPLICATE); //not allowed, stop } else if(!inIsolatedSection || isolated_items_section != thisSection) { - if(itemGroup.size()) + if(!itemGroup.empty()) read_sections.push_back(curSection); //add to read sections list if(std::find(section_order.cbegin(), section_order.cend(), curSection) == section_order.cend()) section_order.emplace_back(curSection); //add to section order if not added before @@ -282,33 +282,33 @@ class INIReader } } inIsolatedSection = false; - eraseElements(itemGroup); //reset section storage + erase_elements(itemGroup); //reset section storage curSection = thisSection; //start a new section } - else if(((store_any_line && pos_equal == strLine.npos) || inDirectSaveSection) && !inExcludedSection && curSection.size()) //store a line without name + else if(((store_any_line && pos_equal == std::string::npos) || inDirectSaveSection) && !inExcludedSection && !curSection.empty()) //store a line without name { itemGroup.emplace("{NONAME}", strLine); } - else if(pos_equal != strLine.npos) //is an item + else if(pos_equal != std::string::npos) //is an item { if(inExcludedSection) //this section is excluded continue; - if(!curSection.size()) //not in any section - return __priv_save_error_and_return(INIREADER_EXCEPTION_OUTOFBOUND); + if(curSection.empty()) //not in any section + return save_error_and_return(INIREADER_EXCEPTION_OUTOFBOUND); string_size pos_value = strLine.find_first_not_of(' ', pos_equal + 1); itemName = trim(strLine.substr(0, pos_equal)); - if(pos_value != strLine.npos) //not a key with empty value + if(pos_value != std::string::npos) //not a key with empty value { itemVal = strLine.substr(pos_value); itemGroup.emplace(std::move(itemName), std::move(itemVal)); //insert to current section } else - itemGroup.emplace(std::move(itemName), std::string()); + itemGroup.emplace(std::move(itemName), ""); } - if(include_sections.size() && include_sections == read_sections) //all included sections has been read + if(!include_sections.empty() && include_sections == read_sections) //all included sections has been read break; //exit now } - if(curSection.size() && (keep_empty_section || itemGroup.size())) //final section + if(!curSection.empty() && (keep_empty_section || !itemGroup.empty())) //final section { if(ini_content.find(curSection) != ini_content.end()) //a section with the same name has been inserted { @@ -317,12 +317,12 @@ class INIReader auto &iter = ini_content.at(curSection); //get the existing section iter.merge(itemGroup); //move new items to this section } - else if(ini_content.at(curSection).size()) - return __priv_save_error_and_return(INIREADER_EXCEPTION_DUPLICATE); //not allowed, stop + else if(!ini_content.at(curSection).empty()) + return save_error_and_return(INIREADER_EXCEPTION_DUPLICATE); //not allowed, stop } else if(!inIsolatedSection || isolated_items_section != thisSection) { - if(itemGroup.size()) + if(!itemGroup.empty()) read_sections.emplace_back(curSection); //add to read sections list if(std::find(section_order.cbegin(), section_order.cend(), curSection) == section_order.cend()) section_order.emplace_back(curSection); //add to section order if not added before @@ -330,23 +330,23 @@ class INIReader } } parsed = true; - return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE); //all done + return save_error_and_return(INIREADER_EXCEPTION_NONE); //all done } /** - * @brief Parse an INI file into mapped data structure. + * @brief parse an INI file into mapped data structure. */ - int ParseFile(const std::string &filePath) + int parse_file(const std::string &filePath) { if(!fileExist(filePath)) - return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); - return Parse(fileGet(filePath)); + return save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); + return parse(fileGet(filePath)); } /** * @brief Check whether a section exist. */ - bool SectionExist(const std::string §ion) + bool section_exist(const std::string §ion) { return ini_content.find(section) != ini_content.end(); } @@ -354,7 +354,7 @@ class INIReader /** * @brief Count of sections in the whole INI. */ - unsigned int SectionCount() + unsigned int section_count() { return ini_content.size(); } @@ -362,7 +362,7 @@ class INIReader /** * @brief Return all section names inside INI. */ - string_array GetSections() + string_array get_section_names() { return section_order; } @@ -370,19 +370,19 @@ class INIReader /** * @brief Enter a section with the given name. Section name and data will be cached to speed up the following reading process. */ - int EnterSection(const std::string §ion) + int enter_section(const std::string §ion) { - if(!SectionExist(section)) - return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); + if(!section_exist(section)) + return save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); current_section = cached_section = section; cached_section_content = ini_content.find(section); - return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE); + return save_error_and_return(INIREADER_EXCEPTION_NONE); } /** - * @brief Set current section. + * @brief set current section. */ - void SetCurrentSection(const std::string §ion) + void set_current_section(const std::string §ion) { current_section = section; } @@ -390,9 +390,9 @@ class INIReader /** * @brief Check whether an item exist in the given section. Return false if the section does not exist. */ - bool ItemExist(const std::string §ion, const std::string &itemName) + bool item_exist(const std::string §ion, const std::string &itemName) { - if(!SectionExist(section)) + if(!section_exist(section)) return false; if(section != cached_section) @@ -407,17 +407,17 @@ class INIReader /** * @brief Check whether an item exist in current section. Return false if the section does not exist. */ - bool ItemExist(const std::string &itemName) + bool item_exist(const std::string &itemName) { - return current_section.size() ? ItemExist(current_section, itemName) : false; + return !current_section.empty() && item_exist(current_section, itemName); } /** * @brief Check whether an item with the given name prefix exist in the given section. Return false if the section does not exist. */ - bool ItemPrefixExist(const std::string §ion, const std::string &itemName) + bool item_prefix_exists(const std::string §ion, const std::string &itemName) { - if(!SectionExist(section)) + if(!section_exist(section)) return false; if(section != cached_section) @@ -426,49 +426,47 @@ class INIReader cached_section_content = ini_content.find(section); } - for(auto &x : cached_section_content->second) - { - if(x.first.find(itemName) == 0) - return true; - } + auto &items = cached_section_content->second; - return false; + return std::any_of(items.cbegin(), items.cend(), [&](auto &x) { + return x.first.find(itemName) == 0; + }); } /** * @brief Check whether an item with the given name prefix exist in current section. Return false if the section does not exist. */ - bool ItemPrefixExist(const std::string &itemName) + bool item_prefix_exist(const std::string &itemName) { - return current_section.size() ? ItemPrefixExist(current_section, itemName) : false; + return !current_section.empty() && item_prefix_exists(current_section, itemName); } /** * @brief Count of items in the given section. Return 0 if the section does not exist. */ - unsigned int ItemCount(const std::string §ion) + unsigned int item_count(const std::string §ion) { - if(!parsed || !SectionExist(section)) - return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTPARSED); + if(!parsed || !section_exist(section)) + return save_error_and_return(INIREADER_EXCEPTION_NOTPARSED); return ini_content.at(section).size(); } /** - * @brief Erase all data from the data structure and reset parser status. + * @brief erase all data from the data structure and reset parser status. */ - void EraseAll() + void erase_all() { - eraseElements(ini_content); - eraseElements(section_order); + erase_elements(ini_content); + erase_elements(section_order); cached_section.clear(); cached_section_content = ini_content.end(); parsed = false; } - ini_data_struct::iterator GetItemsRef(const std::string §ion) + ini_data_struct::iterator get_items_ref(const std::string §ion) { - if(!parsed || !SectionExist(section)) + if(!parsed || !section_exist(section)) return ini_content.end(); if(cached_section != section) @@ -482,35 +480,35 @@ class INIReader /** * @brief Retrieve all items in the given section. */ - int GetItems(const std::string §ion, string_multimap &data) + int get_items(const std::string §ion, string_multimap &data) { - auto section_ref = GetItemsRef(section); + auto section_ref = get_items_ref(section); if(section_ref == ini_content.end()) - return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); + return save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); data = section_ref->second; - return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE); + return save_error_and_return(INIREADER_EXCEPTION_NONE); } /** * @brief Retrieve all items in current section. */ - int GetItems(string_multimap &data) + int get_items(string_multimap &data) { - return current_section.size() ? GetItems(current_section, data) : -1; + return !current_section.empty() ? get_items(current_section, data) : -1; } /** * @brief Retrieve item(s) with the same name prefix in the given section. */ - int GetAll(const std::string §ion, const std::string &itemName, string_array &results) //retrieve item(s) with the same itemName prefix + int get_all(const std::string §ion, const std::string &itemName, string_array &results) //retrieve item(s) with the same itemName prefix { if(!parsed) - return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTPARSED); + return save_error_and_return(INIREADER_EXCEPTION_NOTPARSED); - auto section_ref = GetItemsRef(section); + auto section_ref = get_items_ref(section); if(section_ref == ini_content.end()) - return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); + return save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); for(auto &x : section_ref->second) { @@ -518,24 +516,24 @@ class INIReader results.emplace_back(x.second); } - return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE); + return save_error_and_return(INIREADER_EXCEPTION_NONE); } /** * @brief Retrieve item(s) with the same name prefix in current section. */ - int GetAll(const std::string &itemName, string_array &results) + int get_all(const std::string &itemName, string_array &results) { - return current_section.size() ? GetAll(current_section, itemName, results) : -1; + return !current_section.empty() ? get_all(current_section, itemName, results) : -1; } /** * @brief Retrieve one item with the exact same name in the given section. */ - std::string Get(const std::string §ion, const std::string &itemName) //retrieve one item with the exact same itemName + std::string get(const std::string §ion, const std::string &itemName) //retrieve one item with the exact same itemName { - if(!parsed || !SectionExist(section)) - return std::string(); + if(!parsed || !section_exist(section)) + return ""; if(cached_section != section) { @@ -548,68 +546,68 @@ class INIReader if(iter != cache.end()) return iter->second; - return std::string(); + return ""; } /** * @brief Retrieve one item with the exact same name in current section. */ - std::string Get(const std::string &itemName) + std::string get(const std::string &itemName) { - return current_section.size() ? Get(current_section, itemName) : std::string(); + return !current_section.empty() ? get(current_section, itemName) : ""; } /** * @brief Retrieve one item with the exact same name in the given section, if exist. */ - int GetIfExist(const std::string §ion, const std::string &itemName, std::string &target) //retrieve one item with the exact same itemName + int get_if_exist(const std::string §ion, const std::string &itemName, std::string &target) //retrieve one item with the exact same itemName { if(!parsed) - return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTPARSED); + return save_error_and_return(INIREADER_EXCEPTION_NOTPARSED); - if(ItemExist(section, itemName)) + if(item_exist(section, itemName)) { - target = Get(section, itemName); - return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE); + target = get(section, itemName); + return save_error_and_return(INIREADER_EXCEPTION_NONE); } - return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); + return save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); } /** * @brief Retrieve one item with the exact same name in current section, if exist. */ - int GetIfExist(const std::string &itemName, std::string &target) + int get_if_exist(const std::string &itemName, std::string &target) { - return current_section.size() ? GetIfExist(current_section, itemName, target) : INIREADER_EXCEPTION_NOTEXIST; + return !current_section.empty() ? get_if_exist(current_section, itemName, target) : INIREADER_EXCEPTION_NOTEXIST; } /** * @brief Retrieve one boolean item value with the exact same name in the given section. */ - bool GetBool(const std::string §ion, const std::string &itemName) + bool get_bool(const std::string §ion, const std::string &itemName) { - return Get(section, itemName) == "true"; + return get(section, itemName) == "true"; } /** * @brief Retrieve one boolean item value with the exact same name in current section. */ - bool GetBool(const std::string &itemName) + bool get_bool(const std::string &itemName) { - return current_section.size() ? Get(current_section, itemName) == "true" : false; + return !current_section.empty() && get(current_section, itemName) == "true"; } /** * @brief Retrieve one boolean item value with the exact same name in the given section. */ - int GetBoolIfExist(const std::string §ion, const std::string &itemName, bool &target) + int get_bool_if_exist(const std::string §ion, const std::string &itemName, bool &target) { std::string result; - int retval = GetIfExist(section, itemName, result); - if(retval != INIREADER_EXCEPTION_NONE) - return retval; - if(result.size()) + int retVal = get_if_exist(section, itemName, result); + if(retVal != INIREADER_EXCEPTION_NONE) + return retVal; + if(!result.empty()) target = result == "true"; return INIREADER_EXCEPTION_NONE; } @@ -617,21 +615,21 @@ class INIReader /** * @brief Retrieve one boolean item value with the exact same name in current section. */ - int GetBoolIfExist(const std::string &itemName, bool &target) + int get_bool_if_exist(const std::string &itemName, bool &target) { - return current_section.size() ? GetBoolIfExist(current_section, itemName, target) : INIREADER_EXCEPTION_NOTEXIST; + return !current_section.empty() ? get_bool_if_exist(current_section, itemName, target) : INIREADER_EXCEPTION_NOTEXIST; } /** * @brief Retrieve one number item value with the exact same name in the given section. */ - template int GetNumberIfExist(const std::string §ion, const std::string &itemName, T &target) + template int get_number_if_exist(const std::string §ion, const std::string &itemName, T &target) { std::string result; - int retval = GetIfExist(section, itemName, result); - if(retval != INIREADER_EXCEPTION_NONE) - return retval; - if(result.size()) + int retVal = get_if_exist(section, itemName, result); + if(retVal != INIREADER_EXCEPTION_NONE) + return retVal; + if(!result.empty()) target = to_number(result, target); return INIREADER_EXCEPTION_NONE; } @@ -639,73 +637,72 @@ class INIReader /** * @brief Retrieve one number item value with the exact same name in current section. */ - template int GetNumberIfExist(const std::string &itemName, T &target) + template int get_number_if_exist(const std::string &itemName, T &target) { - return current_section.size() ? GetNumberIfExist(current_section, itemName, target) : INIREADER_EXCEPTION_NOTEXIST; + return !current_section.empty() ? get_number_if_exist(current_section, itemName, target) : INIREADER_EXCEPTION_NOTEXIST; } /** * @brief Retrieve one integer item value with the exact same name in the given section. */ - int GetIntIfExist(const std::string §ion, const std::string &itemName, int &target) + int get_int_if_exist(const std::string §ion, const std::string &itemName, int &target) { - return GetNumberIfExist(section, itemName, target); + return get_number_if_exist(section, itemName, target); } /** * @brief Retrieve one integer item value with the exact same name in current section. */ - int GetIntIfExist(const std::string &itemName, int &target) + int get_int_if_exist(const std::string &itemName, int &target) { - return GetNumberIfExist(itemName, target); + return get_number_if_exist(itemName, target); } /** * @brief Retrieve one integer item value with the exact same name in the given section. */ - int GetInt(const std::string §ion, const std::string &itemName) + int get_int(const std::string §ion, const std::string &itemName) { - return to_int(Get(section, itemName), 0); + return to_int(get(section, itemName), 0); } /** * @brief Retrieve one integer item value with the exact same name in current section. */ - int GetInt(const std::string &itemName) + int get_int(const std::string &itemName) { - return GetInt(current_section, itemName); + return get_int(current_section, itemName); } /** * @brief Retrieve the first item found in the given section. */ - std::string GetFirst(const std::string §ion, const std::string &itemName) //return the first item value found in section + std::string get_first(const std::string §ion, const std::string &itemName) //return the first item value found in section { if(!parsed) - return std::string(); + return ""; string_array result; - if(GetAll(section, itemName, result) != -1) + if(get_all(section, itemName, result) != -1) return result[0]; else - return std::string(); + return ""; } /** * @brief Retrieve the first item found in current section. */ - std::string GetFirst(const std::string &itemName) + std::string get_first(const std::string &itemName) { - return current_section.size() ? GetFirst(current_section, itemName) : std::string(); + return !current_section.empty() ? get_first(current_section, itemName) : ""; } /** * @brief Retrieve a string style array with specific separator and write into integer array. */ - template void GetIntArray(const std::string §ion, const std::string &itemName, const std::string &separator, T &Array) + template void get_int_array(const std::string §ion, const std::string &itemName, const std::string &separator, T& Array) { - string_array vArray; - unsigned int index, UBound = sizeof(Array) / sizeof(Array[0]); - vArray = split(Get(section, itemName), separator); + unsigned int index, UBound = sizeof(Array[0]) / sizeof(Array); + string_array vArray = split(get(section, itemName), separator); for(index = 0; index < vArray.size() && index < UBound; index++) Array[index] = stoi(vArray[index]); for(; index < UBound; index++) @@ -715,24 +712,24 @@ class INIReader /** * @brief Retrieve a string style array with specific separator and write into integer array. */ - template void GetIntArray(const std::string &itemName, const std::string &separator, T &Array) + template void get_int_array(const std::string &itemName, const std::string &separator, T& Array) { - if(current_section.size()) - GetIntArray(current_section, itemName, separator, Array); + if(!current_section.empty()) + get_int_array(current_section, itemName, separator, Array); } /** * @brief Add a std::string value with given values. */ - int Set(const std::string §ion, std::string itemName, std::string itemVal) + int set(const std::string §ion, std::string itemName, std::string itemVal) { - if(!section.size()) - return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); + if(section.empty()) + return save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); if(!parsed) parsed = true; - if(SectionExist(section)) + if(section_exist(section)) { string_multimap &mapTemp = ini_content.at(section); mapTemp.insert(std::pair(std::move(itemName), std::move(itemVal))); @@ -745,110 +742,109 @@ class INIReader section_order.emplace_back(section); } - return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE); + return save_error_and_return(INIREADER_EXCEPTION_NONE); } /** * @brief Add a string value with given values. */ - int Set(std::string itemName, std::string itemVal) + int set(std::string itemName, std::string itemVal) { - if(!current_section.size()) - return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); - return Set(current_section, std::move(itemName), std::move(itemVal)); + if(current_section.empty()) + return save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); + return set(current_section, std::move(itemName), std::move(itemVal)); } /** * @brief Add a boolean value with given values. */ - int SetBool(const std::string §ion, std::string itemName, bool itemVal) + int set_bool(const std::string §ion, std::string itemName, bool itemVal) { - return Set(section, std::move(itemName), itemVal ? "true" : "false"); + return set(section, std::move(itemName), itemVal ? "true" : "false"); } /** * @brief Add a boolean value with given values. */ - int SetBool(std::string itemName, bool itemVal) + int set_bool(std::string itemName, bool itemVal) { - return SetBool(current_section, std::move(itemName), itemVal); + return set_bool(current_section, std::move(itemName), itemVal); } /** * @brief Add a double value with given values. */ - int SetDouble(const std::string §ion, std::string itemName, double itemVal) + int set_double(const std::string §ion, std::string itemName, double itemVal) { - return Set(section, std::move(itemName), std::to_string(itemVal)); + return set(section, std::move(itemName), std::to_string(itemVal)); } /** * @brief Add a double value with given values. */ - int SetDouble(std::string itemName, double itemVal) + int set_double(std::string itemName, double itemVal) { - return SetDouble(current_section, std::move(itemName), itemVal); + return set_double(current_section, std::move(itemName), itemVal); } /** * @brief Add a long value with given values. */ - int SetLong(const std::string §ion, std::string itemName, long itemVal) + int set_long(const std::string §ion, std::string itemName, long itemVal) { - return Set(section, std::move(itemName), std::to_string(itemVal)); + return set(section, std::move(itemName), std::to_string(itemVal)); } /** * @brief Add a long value with given values. */ - int SetLong(std::string itemName, long itemVal) + int set_long(std::string itemName, long itemVal) { - return SetLong(current_section, std::move(itemName), itemVal); + return set_long(current_section, std::move(itemName), itemVal); } /** * @brief Add an array with the given separator. */ - template int SetArray(const std::string §ion, std::string itemName, const std::string &separator, T &Array) + template int set_array(const std::string §ion, std::string itemName, const std::string &separator, T &Array) { std::string data; data = std::accumulate(std::begin(Array), std::end(Array), std::string(), [&](auto a, auto b) { return std::move(a) + std::to_string(b) + separator; }); data.erase(data.size() - 1); - return Set(section, std::move(itemName), data); + return set(section, std::move(itemName), data); } /** * @brief Add an array with the given separator. */ - template int SetArray(std::string itemName, const std::string &separator, T &Array) + template int set_array(std::string itemName, const std::string &separator, T &Array) { - return current_section.size() ? SetArray(current_section, std::move(itemName), separator, Array) : -1; + return !current_section.empty() ? set_array(current_section, std::move(itemName), separator, Array) : -1; } /** * @brief Rename an existing section. */ - int RenameSection(const std::string &oldName, std::string newName) + int rename_section(const std::string &oldName, const std::string& newName) { - if(!SectionExist(oldName) || SectionExist(newName)) - return __priv_save_error_and_return(INIREADER_EXCEPTION_DUPLICATE); + if(!section_exist(oldName) || section_exist(newName)) + return save_error_and_return(INIREADER_EXCEPTION_DUPLICATE); auto nodeHandler = ini_content.extract(oldName); - nodeHandler.key() = std::move(newName); + nodeHandler.key() = newName; ini_content.insert(std::move(nodeHandler)); std::replace(section_order.begin(), section_order.end(), oldName, newName); - return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE); + return save_error_and_return(INIREADER_EXCEPTION_NONE); } /** - * @brief Erase all items with the given name. + * @brief erase all items with the given name. */ - int Erase(const std::string §ion, const std::string &itemName) + int erase(const std::string §ion, const std::string &itemName) { - int retVal; - if(!SectionExist(section)) - return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); + if(!section_exist(section)) + return save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); - retVal = ini_content.at(section).erase(itemName); + auto retVal = ini_content.at(section).erase(itemName); if(retVal && cached_section == section) { cached_section_content = ini_content.find(section); @@ -857,47 +853,47 @@ class INIReader } /** - * @brief Erase all items with the given name. + * @brief erase all items with the given name. */ - int Erase(const std::string &itemName) + int erase(const std::string &itemName) { - return current_section.size() ? Erase(current_section, itemName) : -1; + return !current_section.empty() ? erase(current_section, itemName) : -1; } /** - * @brief Erase the first item with the given name. + * @brief erase the first item with the given name. */ - int EraseFirst(const std::string §ion, const std::string &itemName) + int erase_first(const std::string §ion, const std::string &itemName) { string_multimap &mapTemp = ini_content.at(section); - string_multimap::iterator iter = mapTemp.find(itemName); + auto iter = mapTemp.find(itemName); if(iter != mapTemp.end()) { mapTemp.erase(iter); - return __priv_save_error_and_return(INIREADER_EXCEPTION_NONE); + return save_error_and_return(INIREADER_EXCEPTION_NONE); } else { - return __priv_save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); + return save_error_and_return(INIREADER_EXCEPTION_NOTEXIST); } } /** - * @brief Erase the first item with the given name. + * @brief erase the first item with the given name. */ - int EraseFirst(const std::string &itemName) + int erase_first(const std::string &itemName) { - return current_section.size() ? EraseFirst(current_section, itemName) : -1; + return !current_section.empty() ? erase_first(current_section, itemName) : -1; } /** - * @brief Erase all items in the given section. + * @brief erase all items in the given section. */ - void EraseSection(const std::string §ion) + void erase_section(const std::string §ion) { if(ini_content.find(section) == ini_content.end()) return; - eraseElements(ini_content.at(section)); + erase_elements(ini_content.at(section)); if(cached_section == section) { cached_section_content = ini_content.end(); @@ -906,18 +902,18 @@ class INIReader } /** - * @brief Erase all items in current section. + * @brief erase all items in current section. */ - void EraseSection() + void erase_section() { - if(current_section.size()) - EraseSection(current_section); + if(!current_section.empty()) + erase_section(current_section); } /** * @brief Remove a section from INI. */ - void RemoveSection(const std::string §ion) + void remove_section(const std::string §ion) { if(ini_content.find(section) == ini_content.end()) return; @@ -933,21 +929,21 @@ class INIReader /** * @brief Remove current section from INI. */ - void RemoveSection() + void remove_section() { - if(current_section.size()) - RemoveSection(current_section); + if(!current_section.empty()) + remove_section(current_section); } /** * @brief Export the whole INI data structure into a string. */ - std::string ToString() + std::string to_string() { std::string content, itemVal; if(!parsed) - return std::string(); + return ""; for(auto &x : section_order) { @@ -981,9 +977,9 @@ class INIReader /** * @brief Export the whole INI data structure into a file. */ - int ToFile(const std::string &filePath) + int to_file(const std::string &filePath) { - return fileWrite(filePath, ToString(), true); + return fileWrite(filePath, to_string(), true); } }; diff --git a/src/utils/network.cpp b/src/utils/network.cpp index bd7d47a60..da28454d1 100644 --- a/src/utils/network.cpp +++ b/src/utils/network.cpp @@ -18,7 +18,7 @@ std::string hostnameToIPAddr(const std::string &host) if(retVal != 0) { freeaddrinfo(retAddrInfo); - return std::string(); + return ""; } for(cur = retAddrInfo; cur != NULL; cur = cur->ai_next) diff --git a/src/utils/rapidjson_extra.h b/src/utils/rapidjson_extra.h index 38090409f..591f350a5 100644 --- a/src/utils/rapidjson_extra.h +++ b/src/utils/rapidjson_extra.h @@ -21,7 +21,7 @@ template void exception_thrower(T e) inline void operator >> (const rapidjson::Value& value, std::string& i) { if(value.IsNull()) - i = std::string(); + i = ""; else if(value.IsString()) i = std::string(value.GetString()); else if(value.IsInt64()) @@ -31,7 +31,7 @@ inline void operator >> (const rapidjson::Value& value, std::string& i) else if(value.IsDouble()) i = std::to_string(value.GetDouble()); else - i = std::string(); + i = ""; } inline void operator >> (const rapidjson::Value& value, int& i) diff --git a/src/utils/string.cpp b/src/utils/string.cpp index b1d9bf221..0730f84c6 100644 --- a/src/utils/string.cpp +++ b/src/utils/string.cpp @@ -234,13 +234,13 @@ std::string trimWhitespace(const std::string &str, bool before, bool after) { epos = str.find_last_not_of(whitespaces); if(epos == std::string::npos) - return std::string(); + return ""; } if(before) { bpos = str.find_first_not_of(whitespaces); if(bpos == std::string::npos) - return std::string(); + return ""; } return str.substr(bpos, epos - bpos + 1); } @@ -295,7 +295,7 @@ std::string getUrlArg(const std::string &url, const std::string &request) break; pos--; } - return std::string(); + return ""; } std::string replaceAllDistinct(std::string str, const std::string &old_value, const std::string &new_value) diff --git a/src/utils/string.h b/src/utils/string.h index 05fa52a3a..7c16bec34 100644 --- a/src/utils/string.h +++ b/src/utils/string.h @@ -7,6 +7,7 @@ #include #include +using string = std::string; using string_size = std::string::size_type; using string_array = std::vector; using string_map = std::map; diff --git a/src/utils/system.cpp b/src/utils/system.cpp index beaa15d36..222b9af01 100644 --- a/src/utils/system.cpp +++ b/src/utils/system.cpp @@ -50,7 +50,7 @@ std::string getSystemProxy() if(ret != ERROR_SUCCESS) { //std::cout << "open failed: " << ret << std::endl; - return std::string(); + return ""; } DWORD values_count, max_value_name_len, max_value_len; @@ -59,7 +59,7 @@ std::string getSystemProxy() if(ret != ERROR_SUCCESS) { //std::cout << "query failed" << std::endl; - return std::string(); + return ""; } std::vector, DWORD, std::shared_ptr>> values; @@ -103,7 +103,7 @@ std::string getSystemProxy() } */ //return 0; - return std::string(); + return ""; #else string_array proxy_env = {"all_proxy", "ALL_PROXY", "http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY"}; for(std::string &x : proxy_env) @@ -112,6 +112,6 @@ std::string getSystemProxy() if(proxy != NULL) return std::string(proxy); } - return std::string(); + return ""; #endif // _WIN32 } From 3ea4cad55dee88c28beb42f3aa69bfa52b51abf0 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Mon, 3 Jul 2023 14:35:59 +0800 Subject: [PATCH 007/120] Fix build error --- scripts/build.alpine.release.sh | 2 +- scripts/build.windows.release.sh | 2 +- src/config/def.h | 1 + src/utils/string_hash.h | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/build.alpine.release.sh b/scripts/build.alpine.release.sh index 21cb50c05..24af4948b 100644 --- a/scripts/build.alpine.release.sh +++ b/scripts/build.alpine.release.sh @@ -44,7 +44,7 @@ export PKG_CONFIG_PATH=/usr/lib64/pkgconfig cmake -DCMAKE_BUILD_TYPE=Release . make -j2 rm subconverter -g++ -o base/subconverter $(find CMakeFiles/subconverter.dir/src/ -name "*.o") -static -lpcre2-8 -levent -lyaml-cpp -L/usr/lib64 -lcurl -lmbedtls -lmbedcrypto -lmbedx509 -lz -lquickjs -llibcron -O3 -s +g++ -o base/subconverter $(find CMakeFiles/subconverter.dir/src/ -name "*.o") -static -lpcre2-8 -levent -lyaml-cpp -L/usr/lib64 -lcurl -lmbedtls -lmbedcrypto -lmbedx509 -lz -l:quickjs/libquickjs.a -llibcron -O3 -s cd base chmod +rx subconverter diff --git a/scripts/build.windows.release.sh b/scripts/build.windows.release.sh index ad4dd2730..f6bb94349 100644 --- a/scripts/build.windows.release.sh +++ b/scripts/build.windows.release.sh @@ -48,5 +48,5 @@ rm -f C:/Strawberry/perl/bin/pkg-config C:/Strawberry/perl/bin/pkg-config.bat cmake -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" . make -j4 rm subconverter.exe -g++ $(find CMakeFiles/subconverter.dir/src -name "*.obj") curl/lib/libcurl.a -o base/subconverter.exe -static -lbcrypt -levent -lpcre2-8 -lquickjs -llibcron -lyaml-cpp -liphlpapi -lcrypt32 -lws2_32 -lwsock32 -lz -s +g++ $(find CMakeFiles/subconverter.dir/src -name "*.obj") curl/lib/libcurl.a -o base/subconverter.exe -static -lbcrypt -levent -lpcre2-8 -l:quickjs/libquickjs.a -llibcron -lyaml-cpp -liphlpapi -lcrypt32 -lws2_32 -lwsock32 -lz -s mv base subconverter diff --git a/src/config/def.h b/src/config/def.h index b588bd7d7..596e392a7 100644 --- a/src/config/def.h +++ b/src/config/def.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "../utils/string.h" #include "../utils/tribool.h" diff --git a/src/utils/string_hash.h b/src/utils/string_hash.h index 20ed89c9c..8891929b7 100644 --- a/src/utils/string_hash.h +++ b/src/utils/string_hash.h @@ -2,6 +2,7 @@ #define STRING_HASH_H_INCLUDED #include +#include using hash_t = uint64_t; From 49bc71e4f5b88947e7795fdedaf0265232f0452d Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Tue, 4 Jul 2023 00:46:07 +0800 Subject: [PATCH 008/120] Fix build error --- src/utils/md5/md5.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/md5/md5.h b/src/utils/md5/md5.h index f54495364..e98be5cc9 100644 --- a/src/utils/md5/md5.h +++ b/src/utils/md5/md5.h @@ -1,6 +1,7 @@ #ifndef __MD5_H__ #define __MD5_H__ +#include /* * Size of a standard MD5 signature in bytes. This definition is for * external programs only. The MD5 routines themselves reference the From 7d9881b9bc8bf994838696ea2f3ce706b52c18f4 Mon Sep 17 00:00:00 2001 From: GC Chen Date: Wed, 5 Jul 2023 13:32:25 +0800 Subject: [PATCH 009/120] Add docker compose deploy example (#599) Add a docker compose deploy example. --- README-docker.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README-docker.md b/README-docker.md index c6b24bf0e..65b65444b 100644 --- a/README-docker.md +++ b/README-docker.md @@ -10,6 +10,18 @@ docker run -d --restart=always -p 25500:25500 tindy2013/subconverter:latest curl http://localhost:25500/version # if you see `subconverter vx.x.x backend` then the container is up and running ``` +Or run in docker-compose: +```yaml +--- +version: '3' +services: + subconverter: + image: tindy2013/subconverter:latest + container_name: subconverter + ports: + - "15051:25500" + restart: always +``` If you want to update `pref` configuration inside the docker, you can use the following command: ```bash From a7e441a2fe6741957d6d4514581d59f366308677 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Wed, 26 Jul 2023 13:07:30 +0800 Subject: [PATCH 010/120] Do not add obfs-host to Surge Snell node if empty (#626) * fix surge obfs-host empty error * fix comma error --- src/generator/config/subexport.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index 6dcb9fc8f..dbeff7fac 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -754,7 +754,9 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf case ProxyType::Snell: proxy = "snell, " + hostname + ", " + port + ", psk=" + password; if(!obfs.empty()) - proxy += ", obfs=" + obfs + ", obfs-host=" + host; + proxy += ", obfs=" + obfs; + if(!host.empty()) + proxy += ", obfs-host=" + host; break; default: continue; From 0ba0cdd18ea265529f53d24e1cf9138028fcdfb0 Mon Sep 17 00:00:00 2001 From: enximi <70036307+enximi@users.noreply.github.com> Date: Tue, 3 Oct 2023 18:22:48 +0800 Subject: [PATCH 011/120] Fix #569 #448 #431 (#637) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持v2rayN等使用的trojan链接格式 --- src/parser/subparser.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/parser/subparser.cpp b/src/parser/subparser.cpp index 6a51eaf50..1434e6a68 100644 --- a/src/parser/subparser.cpp +++ b/src/parser/subparser.cpp @@ -759,6 +759,15 @@ void explodeTrojan(std::string trojan, Proxy &node) path = getUrlArg(addition, "wspath"); network = "ws"; } + // support the trojan link format used by v2ryaN and X-ui. + // format: trojan://{password}@{server}:{port}?type=ws&security=tls&path={path (urlencoded)}&sni={host}#{name} + else if(getUrlArg(addition, "type") == "ws") + { + path = getUrlArg(addition, "path"); + if(path.substr(0, 3) == "%2F") + path = urlDecode(path); + network = "ws"; + } if(remark.empty()) remark = server + ":" + port; From 09bb2909d071623b4f53e5301916bf5f8b6d8c7a Mon Sep 17 00:00:00 2001 From: Mi Tom Date: Tue, 3 Oct 2023 18:24:32 +0800 Subject: [PATCH 012/120] Fix client parse errors with '=' in remark strings (#632) Replace every '=' with '-' in the remark string to avoid parse errors from the clients. --- src/generator/config/subexport.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index dbeff7fac..59d441339 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -157,6 +157,11 @@ bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy & void processRemark(std::string &oldremark, std::string &newremark, string_array &remarks_list, bool proc_comma = true) { + // Replace every '=' with '-' in the remark string to avoid parse errors from the clients. + // Surge is tested to yield an error when handling '=' in the remark string, + // not sure if other clients have the same problem. + std::replace(oldremark.begin(), oldremark.end(), '=', '-'); + if(proc_comma) { if(oldremark.find(',') != std::string::npos) From b3ea72de6ee23e21d059798e68c01e08f34cb023 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Tue, 3 Oct 2023 05:25:16 -0500 Subject: [PATCH 013/120] UDP is not supported yet in clash when using snell. (#627) * fix surge obfs-host empty error * fix comma error * UDP is not supported for clash when using snell * fix brace warning --- src/generator/config/subexport.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index 59d441339..434a17796 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -448,7 +448,9 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr continue; } - if(udp) + // UDP is not supported yet in clash using snell + // sees in https://dreamacro.github.io/clash/configuration/outbound.html#snell + if(udp && x.Type != ProxyType::Snell) singleproxy["udp"] = true; if(block) singleproxy.SetStyle(YAML::EmitterStyle::Block); @@ -759,9 +761,11 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf case ProxyType::Snell: proxy = "snell, " + hostname + ", " + port + ", psk=" + password; if(!obfs.empty()) + { proxy += ", obfs=" + obfs; if(!host.empty()) proxy += ", obfs-host=" + host; + } break; default: continue; From 1b18bcab083fe7f62ee4eee7bbae8c0b172edfbb Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Tue, 3 Oct 2023 22:41:44 +0800 Subject: [PATCH 014/120] Add auto-sync script for rules Update build scripts. --- .github/workflows/build.yml | 21 ++++++++++ scripts/Dockerfile | 5 ++- scripts/build.alpine.release.sh | 6 ++- scripts/build.macos.release.sh | 4 ++ scripts/build.windows.release.sh | 4 ++ scripts/rules_config.conf | 23 +++++++++++ scripts/update_rules.py | 66 ++++++++++++++++++++++++++++++++ 7 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 scripts/rules_config.conf create mode 100644 scripts/update_rules.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ed3bfdca..04ac084d2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h @@ -40,6 +43,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h @@ -65,6 +71,9 @@ jobs: runs-on: [self-hosted, linux, ARM64] steps: - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h @@ -90,6 +99,9 @@ jobs: runs-on: [self-hosted, linux, ARM64] steps: - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h @@ -115,6 +127,9 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i -e 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h @@ -143,6 +158,9 @@ jobs: shell: msys2 {0} steps: - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' - uses: actions/setup-node@v3 with: node-version: '16' @@ -180,6 +198,9 @@ jobs: shell: msys2 {0} steps: - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' - uses: actions/setup-node@v3 with: node-version: '16' diff --git a/scripts/Dockerfile b/scripts/Dockerfile index fc1b9baf2..84127d2d9 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -5,7 +5,7 @@ ARG SHA="" # build minimized WORKDIR / -RUN apk add --no-cache --virtual .build-tools git g++ build-base linux-headers cmake && \ +RUN apk add --no-cache --virtual .build-tools git g++ build-base linux-headers cmake python3 && \ apk add --no-cache --virtual .build-deps curl-dev rapidjson-dev libevent-dev pcre2-dev yaml-cpp-dev && \ git clone https://github.com/ftk/quickjspp --depth=1 && \ cd quickjspp && \ @@ -37,6 +37,9 @@ RUN apk add --no-cache --virtual .build-tools git g++ build-base linux-headers c git clone https://github.com/tindy2013/subconverter --depth=1 && \ cd subconverter && \ [ -n "$SHA" ] && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h;\ + python3 -m ensurepip && \ + python3 -m pip install gitpython && \ + python3 scripts/update_rules.py -c scripts/rules_config.conf && \ cmake -DCMAKE_BUILD_TYPE=Release . && \ make -j $THREADS && \ mv subconverter /usr/bin && \ diff --git a/scripts/build.alpine.release.sh b/scripts/build.alpine.release.sh index 24af4948b..cea0a2a5f 100644 --- a/scripts/build.alpine.release.sh +++ b/scripts/build.alpine.release.sh @@ -1,7 +1,7 @@ #!/bin/bash set -xe -apk add gcc g++ build-base linux-headers cmake make autoconf automake libtool python2 +apk add gcc g++ build-base linux-headers cmake make autoconf automake libtool python2 python3 apk add mbedtls-dev mbedtls-static zlib-dev rapidjson-dev libevent-dev libevent-static zlib-static pcre2-dev git clone https://github.com/curl/curl --depth=1 --branch curl-7_88_1 @@ -46,6 +46,10 @@ make -j2 rm subconverter g++ -o base/subconverter $(find CMakeFiles/subconverter.dir/src/ -name "*.o") -static -lpcre2-8 -levent -lyaml-cpp -L/usr/lib64 -lcurl -lmbedtls -lmbedcrypto -lmbedx509 -lz -l:quickjs/libquickjs.a -llibcron -O3 -s +python3 -m ensurepip +python3 -m pip install gitpython +python3 scripts/update_rules.py -c scripts/rules_config.conf + cd base chmod +rx subconverter chmod +r ./* diff --git a/scripts/build.macos.release.sh b/scripts/build.macos.release.sh index f774ea34a..ab4a489a8 100644 --- a/scripts/build.macos.release.sh +++ b/scripts/build.macos.release.sh @@ -55,6 +55,10 @@ make -j8 rm subconverter c++ -Xlinker -unexported_symbol -Xlinker "*" -o base/subconverter -framework CoreFoundation -framework Security $(find CMakeFiles/subconverter.dir/src/ -name "*.o") $(find . -name "*.a") -lcurl -O3 +python -m ensurepip +python -m pip install gitpython +python scripts/update_rules.py -c scripts/rules_config.conf + cd base chmod +rx subconverter chmod +r ./* diff --git a/scripts/build.windows.release.sh b/scripts/build.windows.release.sh index f6bb94349..1dce9aadc 100644 --- a/scripts/build.windows.release.sh +++ b/scripts/build.windows.release.sh @@ -44,6 +44,10 @@ cmake -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" -G "Unix Makefiles" -DCMAKE_CXX_STA make install -j4 cd .. +python -m ensurepip +python -m pip install gitpython +python scripts/update_rules.py -c scripts/rules_config.conf + rm -f C:/Strawberry/perl/bin/pkg-config C:/Strawberry/perl/bin/pkg-config.bat cmake -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" . make -j4 diff --git a/scripts/rules_config.conf b/scripts/rules_config.conf new file mode 100644 index 000000000..31b5fdda0 --- /dev/null +++ b/scripts/rules_config.conf @@ -0,0 +1,23 @@ +[ACL4SSR] +name=ACL4SSR +url=https://github.com/ACL4SSR/ACL4SSR +checkout=1dc5c92b0c8ceaaecbc66530c309961f53e52c8c +match=Clash/*.list|Clash/Ruleset/** + +[ACL4SSR_config] +name=ACL4SSR +url=https://github.com/ACL4SSR/ACL4SSR +checkout=1dc5c92b0c8ceaaecbc66530c309961f53e52c8c +match=Clash/config/** +dest=base/config/ +keep_tree=false + +[DivineEngine] +url=https://github.com/DivineEngine/Profiles +checkout=f4d75f7d48a3f42129e030bef751d4d22bca02da +match=Surge/Ruleset/** + +[NobyDa] +url=https://github.com/NobyDa/Script +checkout=ae4c12f23de8078e02c373c9969b19af28257fcb +match=Surge/*.list diff --git a/scripts/update_rules.py b/scripts/update_rules.py new file mode 100644 index 000000000..d2520e7a6 --- /dev/null +++ b/scripts/update_rules.py @@ -0,0 +1,66 @@ +import configparser, shutil, glob, os, random, string +import stat, logging, argparse +from git import InvalidGitRepositoryError, repo as GitRepo + +def del_rw(action, name: str, exc): + os.chmod(name, stat.S_IWRITE) + os.remove(name) + +def open_repo(path: str): + if os.path.exists(repo_path) == False: return None + try: + return GitRepo.Repo(path) + except InvalidGitRepositoryError: + return None + +parser = argparse.ArgumentParser() +parser.add_argument("-c", "--config", default="rules_config.conf") +args = parser.parse_args() + +config = configparser.ConfigParser() +config.read(args.config) +logging.basicConfig(format="%(asctime)s %(message)s", level=logging.DEBUG) + +for section in config.sections(): + repo = config.get(section, "name", fallback=section) + url = config.get(section, "url") + commit = config.get(section, "checkout") + matches = config.get(section, "match").split("|") + save_path = config.get(section, "dest", fallback="base/rules/{}".format(repo)) + keep_tree = config.getboolean(section, "keep_tree", fallback=True) + + logging.info("reading files from url {} with commit {} and matches {}, save to {} keep_tree {}".format(url, commit, matches, save_path, keep_tree)) + + # ran_str = ''.join(random.sample(string.ascii_letters + string.digits, 16)) + repo_path = os.path.join("./tmp/repo/", repo) + + r = open_repo(repo_path) + if r != None: + logging.info("repo {} exists, checking out...".format(repo_path)) + r.git.checkout(commit) + else: + logging.info("repo {} not exist, cloning...".format(repo_path)) + shutil.rmtree(repo_path, ignore_errors=True) + os.makedirs(repo_path, exist_ok=True) + r = GitRepo.Repo.clone_from(url, repo_path) + r.git.checkout(commit) + + os.makedirs(save_path, exist_ok=True) + + for pattern in matches: + files = glob.glob("{}/{}".format(repo_path, pattern), recursive=True) + for file in files: + if os.path.isdir(file): continue + + (file_rel_path, file_name) = os.path.split(file.removeprefix(repo_path)) + if keep_tree: + file_dest_dir = "{}{}".format(save_path, file_rel_path) + file_dest_path = "{}/{}".format(file_dest_dir, file_name) + os.makedirs(file_dest_dir, exist_ok=True) + else: + file_dest_path = "{}{}".format(save_path, file_name) + + shutil.copyfile(file, file_dest_path) + logging.info("copied {} to {}".format(file, file_dest_path)) + +shutil.rmtree("./tmp", onerror=del_rw) From f7d512d9eb716ced518e038037deb85798d1def3 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Tue, 3 Oct 2023 23:27:08 +0800 Subject: [PATCH 015/120] Fix build script --- .github/workflows/build.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 04ac084d2..c733a6b9b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.11' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h @@ -45,7 +45,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.11' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h @@ -71,9 +71,9 @@ jobs: runs-on: [self-hosted, linux, ARM64] steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: deadsnakes/action@v3.0.1 with: - python-version: '3.10' + python-version: '3.11' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h @@ -99,9 +99,9 @@ jobs: runs-on: [self-hosted, linux, ARM64] steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: deadsnakes/action@v3.0.1 with: - python-version: '3.10' + python-version: '3.11' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h @@ -129,7 +129,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.11' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i -e 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h @@ -160,7 +160,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.11' - uses: actions/setup-node@v3 with: node-version: '16' @@ -200,7 +200,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.11' - uses: actions/setup-node@v3 with: node-version: '16' From 69cf9c3268dd051fb4e49a7f2450a091d74ccd23 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Wed, 4 Oct 2023 00:51:26 +0800 Subject: [PATCH 016/120] Fix build script --- .github/workflows/build.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c733a6b9b..c80cf9ee0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,9 +15,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: '3.11' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h @@ -43,9 +40,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: '3.11' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h @@ -71,9 +65,6 @@ jobs: runs-on: [self-hosted, linux, ARM64] steps: - uses: actions/checkout@v3 - - uses: deadsnakes/action@v3.0.1 - with: - python-version: '3.11' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h @@ -99,9 +90,6 @@ jobs: runs-on: [self-hosted, linux, ARM64] steps: - uses: actions/checkout@v3 - - uses: deadsnakes/action@v3.0.1 - with: - python-version: '3.11' - name: Add commit id into version if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h From f6e5cbbba53378555920dab15c091213d17ddeeb Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Wed, 4 Oct 2023 02:40:00 +0800 Subject: [PATCH 017/120] Add new proxy group options for Loon and Quantumult X --- src/generator/config/subexport.cpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index 434a17796..e55897a8d 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -1491,6 +1491,12 @@ void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector 0) + singlegroup += ", tolerance=" + std::to_string(x.Tolerance); + } ini.set("{NONAME}", singlegroup); } @@ -1903,10 +1909,22 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, group += "," + y; */ group += join(filtered_nodelist, ","); - if(x.Type != ProxyGroupType::Select) { + if(x.Type != ProxyGroupType::Select) + { group += ",url=" + x.Url + ",interval=" + std::to_string(x.Interval); - if (x.Type == ProxyGroupType::LoadBalance) - group += ",strategy=" + std::string(x.Strategy == BalanceStrategy::RoundRobin ? "round-robin" : "pcc"); + if(x.Type == ProxyGroupType::LoadBalance) + { + group += ",algorithm=" + std::string(x.Strategy == BalanceStrategy::RoundRobin ? "round-robin" : "pcc"); + if(x.Timeout > 0) + group += ",max-timeout=" + std::to_string(x.Timeout); + } + if(x.Type == ProxyGroupType::URLTest) + { + if(x.Tolerance > 0) + group += ",tolerance=" + std::to_string(x.Tolerance); + } + if(x.Type == ProxyGroupType::Fallback) + group += ",max-timeout=" + std::to_string(x.Timeout); } ini.set("{NONAME}", x.Name + " = " + group); //insert order From 5de1a3fef0395f220c9ae4e747a7e712be37c423 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Sun, 8 Oct 2023 02:16:22 +0800 Subject: [PATCH 018/120] Remove unused interface --- base/base/all_base.tpl | 83 +++++++++++++++++++++++++++++++------- src/handler/interfaces.cpp | 6 --- src/handler/interfaces.h | 1 - src/main.cpp | 2 - 4 files changed, 68 insertions(+), 24 deletions(-) diff --git a/base/base/all_base.tpl b/base/base/all_base.tpl index 1addd1d77..966e7136a 100644 --- a/base/base/all_base.tpl +++ b/base/base/all_base.tpl @@ -39,35 +39,88 @@ http-request https?:\/\/.*\.iqiyi\.com\/.*authcookie= script-path=https://raw.gi {% if request.target == "loon" %} [General] -skip-proxy = 192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,e.crashlynatics.com -bypass-tun = 10.0.0.0/8,100.64.0.0/10,127.0.0.0/8,169.254.0.0/16,172.16.0.0/12,192.0.0.0/24,192.0.2.0/24,192.88.99.0/24,192.168.0.0/16,198.18.0.0/15,198.51.100.0/24,203.0.113.0/24,224.0.0.0/4,255.255.255.255/32 -dns-server = system,119.29.29.29,223.5.5.5 -allow-udp-proxy = false -host = 127.0.0.1 +# IPV6 启动与否 +ipv6 = false +# udp 类的 dns 服务器,用,隔开多个服务器,system 表示系统 dns +dns-server = 119.29.29.29, 223.5.5.5 +# DNS over HTTPS服务器,用,隔开多个服务器 +doh-server = https://223.5.5.5/resolve, https://sm2.doh.pub/dns-query +# 是否开启局域网代理访问 +allow-wifi-access = false +# 开启局域网访问后的 http 代理端口 +wifi-access-http-port = 7222 +# 开启局域网访问后的 socks5 代理端口 +wifi-access-socks5-port = 7221 +# 测速所用的测试链接,如果策略组没有自定义测试链接就会使用这里配置的 +proxy-test-url = http://connectivitycheck.gstatic.com +# 节点测速时的超时秒数 +test-timeout = 2 +# 指定流量使用哪个网络接口进行转发 +interface-mode = auto +sni-sniffing = true +# 禁用 stun 是否禁用 stun 协议的 udp 数据,禁用后可以有效解决 webrtc 的 ip 泄露 +disable-stun = true +# 策略改变时候打断连接 +disconnect-on-policy-change = true +# 一个节点连接失败几次后会进行节点切换,默认 3 次 +switch-node-after-failure-times = 3 +# 订阅资源解析器链接 +resource-parser = https://gitlab.com/lodepuly/vpn_tool/-/raw/main/Resource/Script/Sub-Store/sub-store-parser_for_loon.js +# 自定义 geoip 数据库的 url +geoip-url = https://gitlab.com/Masaiki/GeoIP2-CN/-/raw/release/Country.mmdb +# 配置了该参数,那么所配置的这些IP段、域名将不会转发到Loon,而是由系统处理 +skip-proxy = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, localhost, *.local, captive.apple.com, e.crashlynatics.com +# 配置了该参数,那么所配置的这些IP段、域名就会不交给Loon来处理,系统直接处理 +bypass-tun = 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.88.99.0/24, 192.168.0.0/16, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 239.255.255.250/32, 255.255.255.255/32 +# 当切换到某一特定的WiFi下时改变Loon的流量模式,如"loon-wifi5g":DIRECT,表示在loon-wifi5g这个wifi网络下使用直连模式,"cellular":PROXY,表示在蜂窝网络下使用代理模式,"default":RULE,默认使用分流模式 +ssid-trigger = "Ccccccc":DIRECT,"cellular":RULE,"default":RULE [Proxy] [Remote Proxy] +[Remote Filter] + [Proxy Group] [Rule] [Remote Rule] -[URL Rewrite] -enable = true -^https?:\/\/(www.)?(g|google)\.cn https://www.google.com 302 +[Rewrite] -[Remote Rewrite] -https://raw.githubusercontent.com/Loon0x00/LoonExampleConfig/master/Rewrite/AutoRewrite_Example.list,auto +[Host] + +[Script] +# 多看阅读 (By @chavyleung) +# 我的 > 签到任务 等到提示获取 Cookie 成功即可 +http-request ^https:\/\/www\.duokan\.com\/checkin\/v0\/status script-path=https://raw.githubusercontent.com/chavyleung/scripts/master/duokan/duokan.cookie.js, requires-body=true, tag=多看_cookie +cron "16 9 * * *" script-path=https://raw.githubusercontent.com/chavyleung/scripts/master/duokan/duokan.js, tag=多看阅读 + +# 飞客茶馆 (By @chavyleung) +# 打开 APP, 访问下个人中心 +http-request ^https:\/\/www\.flyertea\.com\/source\/plugin\/mobile\/mobile\.php\?module=getdata&.* script-path=https://raw.githubusercontent.com/chavyleung/scripts/master/flyertea/flyertea.cookie.js, tag=飞客茶馆_cookie +cron "17 9 * * * *" script-path=https://raw.githubusercontent.com/chavyleung/scripts/master/flyertea/flyertea.js,tag=飞客茶馆 + +# 10010 (By @chavyleung) +# 打开 APP , 进入签到页面, 系统提示: 获取刷新链接: 成功 +# 然后手动签到 1 次, 系统提示: 获取Cookie: 成功 (每日签到) +# 首页>天天抽奖, 系统提示 2 次: 获取Cookie: 成功 (登录抽奖) 和 获取Cookie: 成功 (抽奖次数) +http-request ^https?:\/\/act.10010.com\/SigninApp\/signin\/querySigninActivity.htm script-path=https://raw.githubusercontent.com/chavyleung/scripts/master/10010/10010.cookie.js, tag=中国联通_cookie1 +http-request ^https?:\/\/act.10010.com\/SigninApp(.*?)\/signin\/daySign script-path=https://raw.githubusercontent.com/chavyleung/scripts/master/10010/10010.cookie.js, tag=中国联通_cookie2 +http-request ^https?:\/\/m.client.10010.com\/dailylottery\/static\/(textdl\/userLogin|active\/findActivityInfo) script-path=https://raw.githubusercontent.com/chavyleung/scripts/master/10010/10010.cookie.js, tag=中国联通_cookie3 +cron "18 9 * * *" script-path=https://raw.githubusercontent.com/chavyleung/scripts/master/10010/10010.js, tag=中国联通 + +# 万达电影 (By @chavyleung) +# 进入签到页面获取,网页端:https://act-m.wandacinemas.com/2005/17621a8caacc4d190dadd/ +http-request ^https:\/\/user-api-prd-mx\.wandafilm\.com script-path=https://raw.githubusercontent.com/chavyleung/scripts/master/wanda/wanda.cookie.js, tag=万达电影_cookie +cron "19 9 * * *" script-path=https://raw.githubusercontent.com/chavyleung/scripts/master/wanda/wanda.js, tag=万达电影 [MITM] -hostname = *.example.com,*.sample.com -enable = true -skip-server-cert-verify = true -#ca-p12 = -#ca-passphrase = +hostname = m.client.10010.com, act.10010.com, www.flyertea.com, www.duokan.com, tieba.baidu.com +ca-p12 = MIIKGQIBAzCCCeMGCSqGSIb3DQEHAaCCCdQEggnQMIIJzDCCBBcGCSqGSIb3DQEHBqCCBAgwggQEAgEAMIID/QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQImj1O53xwYioCAggAgIID0HZE8LBl4XFV6NulqdzN58vwAkhwiiES++WDPqsE+NHCIa8VCBlfd6/MV21vO2zw8X90mSaO2/PEW7hyH6890zrF11J3rxDzkVtUnV7e8rq5vOdivjWl4s5Nx5zgyJ0AOHJU7Xe2f8OMb4VzsAqeqF/D6FwNGZBJhBn0nPCRFIIgEpOFUrcwvErPbySY6w8mmHm0DVbKvBFGqOth3fco6gIBpZBILgaQ8t9eLep3IiBFcyH1ezILwgOJ0G0qOJwRxOIXRYT3SaTD65rL90w2nW3xcD8jU5raF3PBDEpWf2+xis69nRU8QiWLjJEJkedE+GpZ/CEKR2BL02E9uB+IFF1/Y4bXk17Ty7D8D0WbIgKeLvRcKxFZoQEZfr/vEpdzedt704NBjDRPe3TPDApQgBtvXFvKZ9RB7uo17AJkLZbTGicFVP+a33+e0B1594zNy30eZ3zwwgpsdZ7S23JX/90FQwsTJWxpO4f9qaDqUHVcsSVlG21U4ujIPWkpIi51XE9gM+JmL6nWaU8cRY2CI0ETLnsSWIOJfQG4s6sy0P5liJfqVUtIpZqrSxdzmGlLe2HsOQYo+M6SVpwx8Liopqu5vrvZhuUlUAwmjDodianY57AObCYP5/fM/3yKeZW7v9JH0pQY9eQ5qT6+oWIWoxnERYbXqpEGUDWN6vUG/JkJ6paHIyJ07mCLs4hXXWCin3dAXzmwyMNyGPH3SH03EKK2o/aMWTQNSfSyzFSDS+xXrj3wAZLdzTlyLA4l0iZhzvWLcgfzqHaj922hFhuO3zxQr2cVQihMwXd0gCPsNA4b0Uqaor2GF3qHxctscIGyKafNpmsVM7pSvYmqi0lMijjVfYsx3zV4FgYfQBOQAEaD6VXIHHeg/JBDbfatoQOp6j+GW/Mz5djaeHarA6QdZVeKiGLkKOXT3JYLtxL8QUx2SINlLgWpR3XvMY7f8cIyPMsTrJdLix5wXVRtUVx2A83GyAOt3QxP/rtM+b+86YtAhBdSTRhJfuDL4sjW4//wtnU0B0CzpOlB1CXRprcnUSUeGyOD4eiOaBYnPpY5wUYyQ+eJYQvYdXWDiFx2sBSxyZMAiXMLtBxBoGoyirzFZKK3cw6DdjXrOGepcqFlesEzraz8yfXerOcPwgI4JD13oDKSiw3iUhjTnfrXpoAX+3rEhNfJeqFf7nooGd30z//v4u09KM3l2gEA9WJt60leoDkp3PjL8LPsgBjO5f+odey9O/YqHmxt3dpRD02HvL5VhnJG/kBeZpGd81yX0ceM8x5f2HKzMy38osE6Q/Ru+L0wggWtBgkqhkiG9w0BBwGgggWeBIIFmjCCBZYwggWSBgsqhkiG9w0BDAoBAqCCBO4wggTqMBwGCiqGSIb3DQEMAQMwDgQIJsPUIRvXx3ACAggABIIEyJxMbTjKmMs37xEKKy5d8HBJzPs30yLXeSbO0taa3o6XGEGt6rbBIF3MIGSKAOLuLOwhddVqkFxdUkYiAUTMptSrN8YyR9yhn06mkZPViPHrKNMXIKlAomg87rD54e8AnQPxKvOVPUYne7WBu4QWrUnbuBTOnoWLQAY6dRRE4EDAdQbMRx34sWpjVBvNrgO1h36T11wnCIGDC+FNchV/zs0Xfpt+JB2HGe1KXxH2lO9QKo0ONQlx/GtKBto1HRyN0pzEbdifUBqy1hgVjb5KnK7z3ah3lcZITYQqprn85Mrc8sMfDJRWZlXJM4t4Tz27XbHIlGxnvSmSHGFl74yKbIGCgz/mr9LCwQt8HAeG5QR4+KpImehYGEZeqysAh1ywPTmWnojmdHrrjuUowPZPdihzKgONsiDgCHTRYzmAlDcPGNlipjIOacSC/hgf6lIZL/QelH8eC3lefpAbyE1paruw2a39yLRX4rb4DWcWk0n3dsy23PElhLBTwGQQsaHTbz7EIabEOb8/tPsOM9P/LaHrD3A3nODPvmgMyAdGsXJ+sHPTjFXOGn2vuB5edJvVARZnQZIpPskcDvcL/Ho+SEITaSYREm2iNkRya0jTBoQ7mtrR+DmE7plvWdjcDceOafDTs81rtrsJ5zdcxOHOmw4QTUtOiebnulbu6kChC5pddgVY9ahTSjQsnxJ5xkAn2AJeS/2GdmIV0edXdK0ojHxYgLWfDjv6WNZ3mag9+ntZw+m7dIwqLTQHPC+Q+YWJMHU8l8Mfu4vSAfG0k15GMjy40Pavi+6UdadTgKajm3N8ieCTyDoSsdf8HGUZkCNB2nAU2UhTwrCB/2APoKy7Mwg+DHIb6G5o9OCeA9ZmSov2dDsWrxTD6rlkjveGGfhIqvlotcpqKBMf752pj/qtCMJq1+SqcIWZEW20jL7AF5ZkEBNcDWkAaBAl1rvTqH8d6vjYQtQm3v9RD3z0cF/xu+og84O3OrKXp8vb3uTn7lOX42RsObEWKW7rBfvkiseSZH8QMzPcmy1oBt6R0mZlmqD/gOGN0V/ipkEY1+YGFmIkgvECziZjHOIvdeTKG09duCsbmm9lHIFcnRSNjVJC/z+ITpjzhh1LNPiKRGSu+pzMkO+nv6mKSXZRrZBI1suhidVSeISK5OqbH+EGYe5nQbG+8LEnWNyKPsMTZlG3v3RRKIi1Qe0blmqqISzfID+KmHjK1/aJIZP7QKhlfyGDfqlbl/hT3Pbxl85AI1iU4DeMrTbKfZgAHNExukebLZbZjumZ1PRKGruc5gIGFF9pc0QBt1O1DSNBoWCNiqsZWm1MlJ1o6sDKRZArHU2dvonkOfkk6h4wfHV2Pn2hBZnIubYvuOZ1vCfM9ghPeVGzilxhh2arerkC9E60VUJx1iMpPTfjU1uw94gA30GSrx2dWRo6HcP3gW9s/va/2NxrsjswVO9qEmOLLZS9BF+e2PQecncoDUsbbunZ8+sdtm/OXQOazWGS5W/Pl315yzH0o0bYcolAUWDYt1hPCFvwOAfxWNZFoTFYEw4dJUAYMGvaRdg3ywQ/jK2k1MOMv+gbHc8p/jpbHNVQQtbBIuwAsvICQNX6PCSDbCMS/K/AiKivnffQ8kSDMFX9ijGBkDAjBgkqhkiG9w0BCRUxFgQUlgCJh1d8WORIThv+Ju2NkD9fS0gwaQYJKoZIhvcNAQkUMVweWgBRAHUAYQBuAHQAdQBtAHUAbAB0ACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAARgBBADEAQQA5ADgANAA5ACAAKAAxADEAIABPAGMAdAAgADIAMAAxADkAKTAtMCEwCQYFKw4DAhoFAAQU8gunnEf1jIaelyXFamHM4uv0avgECFTS7nopsZ+Z +ca-passphrase = FA1A9849 +skip-server-cert-verify = false {% endif %} {% if request.target == "quan" %} diff --git a/src/handler/interfaces.cpp b/src/handler/interfaces.cpp index 262538730..a3cfaa382 100644 --- a/src/handler/interfaces.cpp +++ b/src/handler/interfaces.cpp @@ -141,12 +141,6 @@ void matchUserAgent(const std::string &user_agent, std::string &target, tribool return; } -std::string getConvertedRuleset(RESPONSE_CALLBACK_ARGS) -{ - std::string url = urlDecode(getUrlArg(request.argument, "url")), type = getUrlArg(request.argument, "type"); - return convertRuleset(fetchFile(url, parseProxy(global.proxyRuleset), global.cacheRuleset), to_int(type)); -} - std::string getRuleset(RESPONSE_CALLBACK_ARGS) { std::string &argument = request.argument; diff --git a/src/handler/interfaces.h b/src/handler/interfaces.h index 08102fb3c..da03810c1 100644 --- a/src/handler/interfaces.h +++ b/src/handler/interfaces.h @@ -16,7 +16,6 @@ void readConf(); int simpleGenerator(); std::string convertRuleset(const std::string &content, int type); -std::string getConvertedRuleset(RESPONSE_CALLBACK_ARGS); std::string getProfile(RESPONSE_CALLBACK_ARGS); std::string getRuleset(RESPONSE_CALLBACK_ARGS); diff --git a/src/main.cpp b/src/main.cpp index c059cee71..cf66cd83b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -274,8 +274,6 @@ int main(int argc, char *argv[]) webServer.append_response("GET", "/render", "text/plain;charset=utf-8", renderTemplate); - webServer.append_response("GET", "/convert", "text/plain;charset=utf-8", getConvertedRuleset); - if(!global.APIMode) { webServer.append_response("GET", "/get", "text/plain;charset=utf-8", [](RESPONSE_CALLBACK_ARGS) -> std::string From c3e8e88fb6afd7ade6cff13c80e01c595900ea25 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Sun, 8 Oct 2023 02:40:27 +0800 Subject: [PATCH 019/120] Add support for SOCKS5 nodes in Loon configs --- src/generator/config/subexport.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index e55897a8d..51080c22a 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -1849,6 +1849,19 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, if(!scv.is_undef()) proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); break; + case ProxyType::SOCKS5: + proxy = "socks5," + hostname + "," + port; + if (!username.empty() && !password.empty()) + proxy += "," + username + ",\"" + password + "\""; + proxy += ",over-tls=" + std::string(tlssecure ? "true" : "false"); + if (tlssecure) + { + if(!host.empty()) + proxy += ",tls-name=" + host; + if(!scv.is_undef()) + proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); + } + break; default: continue; } From 3cb07c33254632d02fb17f82bd3f04fbe93f4901 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Sun, 8 Oct 2023 02:57:56 +0800 Subject: [PATCH 020/120] Enhancements Refresh cron schedule after loading TOML preferences (#629). Keep line order of Plugin section in Loon configs. --- src/generator/config/subexport.cpp | 1 + src/handler/settings.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index 51080c22a..2c5d3e249 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -1768,6 +1768,7 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, string_array remarks_list; ini.store_any_line = true; + ini.add_direct_save_section("Plugin"); if(ini.parse(base_conf) != INIREADER_EXCEPTION_NONE && !ext.nodelist) { writeLog(0, "Loon base loader failed with error: " + ini.get_last_error(), LOG_LEVEL_ERROR); diff --git a/src/handler/settings.cpp b/src/handler/settings.cpp index 5f872a649..4052aa501 100644 --- a/src/handler/settings.cpp +++ b/src/handler/settings.cpp @@ -718,6 +718,7 @@ void readTOMLConf(toml::value &root) auto tasks = toml::find_or>(root, "tasks", {}); importItems(tasks, "tasks", false); global.cronTasks = toml::get(toml::value(tasks)); + refresh_schedule(); const auto §ion_server = toml::find(root, "server"); From 0e07f44f07406887622110e4484a332cf3a3681b Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Mon, 9 Oct 2023 15:34:59 +0800 Subject: [PATCH 021/120] Enhancements Add Snell version to Surge configs. Optimize codes. --- src/generator/config/subexport.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index 2c5d3e249..63e3d644f 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -645,7 +645,7 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf tls13.define(x.TLS13); std::string proxy; - string_array args; + string_array args, headers; switch(x.Type) { @@ -683,12 +683,13 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf case "tcp"_hash: break; case "ws"_hash: - if(host.empty()) - proxy += ", ws=true, ws-path=" + path + ", sni=" + hostname; - else - proxy += ", ws=true, ws-path=" + path + ", sni=" + hostname + ", ws-headers=Host:" + host; + proxy += ", ws=true, ws-path=" + path + ", sni=" + hostname; + if(!host.empty()) + headers.push_back("Host:" + host); if(!edge.empty()) - proxy += "|Edge:" + edge; + headers.push_back("Edge:" + edge); + if(!headers.empty()) + proxy += ", ws-headers=" + join(headers, "|"); break; default: continue; @@ -766,6 +767,8 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf if(!host.empty()) proxy += ", obfs-host=" + host; } + if(x.SnellVersion != 0) + proxy += ", version=" + std::to_string(x.SnellVersion); break; default: continue; From cb409ddf7efb977ad51a9ea87da0aac9cbd9b342 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Mon, 9 Oct 2023 16:06:06 +0800 Subject: [PATCH 022/120] Optimize codes --- src/handler/settings.cpp | 68 +++++++++++++---------------------- src/script/script_quickjs.cpp | 6 ++-- src/utils/rapidjson_extra.h | 7 ++-- 3 files changed, 32 insertions(+), 49 deletions(-) diff --git a/src/handler/settings.cpp b/src/handler/settings.cpp index 4052aa501..d7723e368 100644 --- a/src/handler/settings.cpp +++ b/src/handler/settings.cpp @@ -30,7 +30,7 @@ int importItems(string_array &target, bool scope_limit) unsigned int itemCount = 0; for(std::string &x : target) { - if(x.find("!!import:") == x.npos) + if(x.find("!!import:") == std::string::npos) { result.emplace_back(x); continue; @@ -46,7 +46,7 @@ int importItems(string_array &target, bool scope_limit) content = webGet(path, proxy, global.cacheConfig); else writeLog(0, "File not found or not a valid URL: " + path, LOG_LEVEL_ERROR); - if(!content.size()) + if(content.empty()) return -1; ss << content; @@ -98,7 +98,7 @@ void importItems(std::vector &root, const std::string &import_key, content = webGet(path, proxy, global.cacheConfig); else writeLog(0, "File not found or not a valid URL: " + path, LOG_LEVEL_ERROR); - if(content.size()) + if(!content.empty()) { auto items = parseToml(content, path); auto list = toml::find>(items, import_key); @@ -110,32 +110,28 @@ void importItems(std::vector &root, const std::string &import_key, } root.swap(newRoot); writeLog(0, "Imported " + std::to_string(count) + " item(s)."); - return; } void readRegexMatch(YAML::Node node, const std::string &delimiter, string_array &dest, bool scope_limit = true) { - YAML::Node object; - std::string script, url, match, rep, strLine; - - for(unsigned i = 0; i < node.size(); i++) + for(auto && object : node) { - object = node[i]; + std::string script, url, match, rep, strLine; object["script"] >>= script; - if(script.size()) + if(!script.empty()) { dest.emplace_back("!!script:" + script); continue; } object["import"] >>= url; - if(url.size()) + if(!url.empty()) { dest.emplace_back("!!import:" + url); continue; } object["match"] >>= match; object["replace"] >>= rep; - if(match.size() && rep.size()) + if(!match.empty() && !rep.empty()) strLine = match + delimiter + rep; else continue; @@ -146,20 +142,17 @@ void readRegexMatch(YAML::Node node, const std::string &delimiter, string_array void readEmoji(YAML::Node node, string_array &dest, bool scope_limit = true) { - YAML::Node object; - std::string script, url, match, rep, strLine; - - for(unsigned i = 0; i < node.size(); i++) + for(auto && object : node) { - object = node[i]; + std::string script, url, match, rep, strLine; object["script"] >>= script; - if(script.size()) + if(!script.empty()) { dest.emplace_back("!!script:" + script); continue; } object["import"] >>= url; - if(url.size()) + if(!url.empty()) { url = "!!import:" + url; dest.emplace_back(url); @@ -167,7 +160,7 @@ void readEmoji(YAML::Node node, string_array &dest, bool scope_limit = true) } object["match"] >>= match; object["emoji"] >>= rep; - if(match.size() && rep.size()) + if(!match.empty() && !rep.empty()) strLine = match + "," + rep; else continue; @@ -178,17 +171,12 @@ void readEmoji(YAML::Node node, string_array &dest, bool scope_limit = true) void readGroup(YAML::Node node, string_array &dest, bool scope_limit = true) { - std::string strLine, name, type; - string_array tempArray; - YAML::Node object; - unsigned int i, j; - - for(i = 0; i < node.size(); i++) + for(YAML::Node && object : node) { - eraseElements(tempArray); - object = node[i]; + string_array tempArray; + std::string name, type; object["import"] >>= name; - if(name.size()) + if(!name.empty()) { dest.emplace_back("!!import:" + name); continue; @@ -202,7 +190,7 @@ void readGroup(YAML::Node node, string_array &dest, bool scope_limit = true) object["interval"] >>= interval; object["tolerance"] >>= tolerance; object["timeout"] >>= timeout; - for(j = 0; j < object["rule"].size(); j++) + for(std::size_t j = 0; j < object["rule"].size(); j++) tempArray.emplace_back(safe_as(object["rule"][j])); switch(hash_(type)) { @@ -221,10 +209,7 @@ void readGroup(YAML::Node node, string_array &dest, bool scope_limit = true) tempArray.emplace_back(interval + "," + timeout + "," + tolerance); } - strLine = std::accumulate(std::next(tempArray.begin()), tempArray.end(), tempArray[0], [](std::string a, std::string b) -> std::string - { - return std::move(a) + "`" + std::move(b); - }); + std::string strLine = join(tempArray, "`"); dest.emplace_back(std::move(strLine)); } importItems(dest, scope_limit); @@ -232,14 +217,11 @@ void readGroup(YAML::Node node, string_array &dest, bool scope_limit = true) void readRuleset(YAML::Node node, string_array &dest, bool scope_limit = true) { - std::string strLine, name, url, group, interval; - YAML::Node object; - - for(unsigned int i = 0; i < node.size(); i++) + for(auto && object : node) { - object = node[i]; + std::string strLine, name, url, group, interval; object["import"] >>= name; - if(name.size()) + if(!name.empty()) { dest.emplace_back("!!import:" + name); continue; @@ -248,13 +230,13 @@ void readRuleset(YAML::Node node, string_array &dest, bool scope_limit = true) object["group"] >>= group; object["rule"] >>= name; object["interval"] >>= interval; - if(url.size()) + if(!url.empty()) { strLine = group + "," + url; - if(interval.size()) + if(!interval.empty()) strLine += "," + interval; } - else if(name.size()) + else if(!name.empty()) strLine = group + ",[]" + name; else continue; diff --git a/src/script/script_quickjs.cpp b/src/script/script_quickjs.cpp index afb469d3c..438adfa7f 100644 --- a/src/script/script_quickjs.cpp +++ b/src/script/script_quickjs.cpp @@ -190,7 +190,7 @@ export function require (path) { class qjs_fetch_Headers { public: - qjs_fetch_Headers() {} + qjs_fetch_Headers() = default; string_icase_map headers; @@ -218,7 +218,7 @@ class qjs_fetch_Headers class qjs_fetch_Request { public: - qjs_fetch_Request() {} + qjs_fetch_Request() = default; std::string method = "GET"; std::string url; std::string proxy; @@ -231,7 +231,7 @@ class qjs_fetch_Request class qjs_fetch_Response { public: - qjs_fetch_Response() {} + qjs_fetch_Response() = default; int status_code = 200; std::string content; std::string cookies; diff --git a/src/utils/rapidjson_extra.h b/src/utils/rapidjson_extra.h index 591f350a5..240712afb 100644 --- a/src/utils/rapidjson_extra.h +++ b/src/utils/rapidjson_extra.h @@ -3,16 +3,17 @@ #include -template void exception_thrower(T e) +template void exception_thrower(T e, const std::string &cond, const std::string &file, int line) { if(!e) - throw std::runtime_error("rapidjson assertion failed"); + throw std::runtime_error("rapidjson assertion failed: " + cond + " (" + file + ":" + std::to_string(line) + ")"); } #ifdef RAPIDJSON_ASSERT #undef RAPIDJSON_ASSERT #endif // RAPIDJSON_ASSERT -#define RAPIDJSON_ASSERT(x) exception_thrower(x) +#define VALUE(x) #x +#define RAPIDJSON_ASSERT(x) exception_thrower(x, VALUE(x), __FILE__, __LINE__) #include #include #include From c4cd7fb520892bcb5507b64a5af732f8268762a5 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Mon, 9 Oct 2023 16:34:25 +0800 Subject: [PATCH 023/120] Add img-url support for Loon proxy groups --- src/generator/config/subexport.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index 63e3d644f..bb17bf6f5 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -1453,8 +1453,8 @@ void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_conf, if(ext.nodelist) return output_nodelist; + string_multimap original_groups; ini.set_current_section("Proxy Group"); + ini.get_items(original_groups); ini.erase_section(); + for(const ProxyGroupConfig &x : extra_proxy_group) { string_array filtered_nodelist; @@ -1920,6 +1923,21 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, if(filtered_nodelist.empty()) filtered_nodelist.emplace_back("DIRECT"); + auto iter = std::find_if(original_groups.begin(), original_groups.end(), [&](const string_multimap::value_type &n) + { + return trim(n.first) == x.Name; + }); + + if(iter != original_groups.end()) + { + string_array vArray = split(iter->second, ","); + if(vArray.size() > 1) + { + if(trim(vArray[vArray.size() - 1]).find("img-url") == 0) + filtered_nodelist.emplace_back(trim(vArray[vArray.size() - 1])); + } + } + group = x.TypeStr() + ","; /* for(std::string &y : filtered_nodelist) From 206deb4be01c7d8f28e9238ba21d7e6ae560263e Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Mon, 9 Oct 2023 16:52:58 +0800 Subject: [PATCH 024/120] Fix build script --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c80cf9ee0..3d1dedfb1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,7 +44,7 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h - name: Build - run: docker run -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:amd64-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" + run: docker run --rm -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:amd64-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" - name: Upload uses: actions/upload-artifact@v3 with: @@ -69,7 +69,7 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h - name: Build - run: docker run -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:armv7-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" + run: docker run --rm -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:armv7-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" - name: Upload uses: actions/upload-artifact@v3 with: @@ -94,7 +94,7 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h - name: Build - run: docker run -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:aarch64-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" + run: docker run --rm -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:aarch64-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" - name: Upload uses: actions/upload-artifact@v3 with: From ea01a414ebf88d853b8d60b375d21328b4c46b19 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Mon, 9 Oct 2023 17:29:02 +0800 Subject: [PATCH 025/120] Bump version to v0.8.0 --- base/base/all_base.tpl | 10 ++++++++++ src/version.h | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/base/base/all_base.tpl b/base/base/all_base.tpl index 966e7136a..c88c907c1 100644 --- a/base/base/all_base.tpl +++ b/base/base/all_base.tpl @@ -82,6 +82,16 @@ ssid-trigger = "Ccccccc":DIRECT,"cellular":RULE,"default":RULE [Remote Filter] [Proxy Group] +♻️ 自动选择=select, direct, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Auto.png +🔰 节点选择=select, direct, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Proxy.png +🌍 国外媒体=select, direct, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/GlobalMedia.png +🌏 国内媒体=select, direct, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/DomesticMedia.png +Ⓜ️ 微软服务=select, direct, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Microsoft.png +📲 电报信息=select, direct, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Telegram.png +🍎 苹果服务=select, direct, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Apple.png +🎯 全球直连=select, direct, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Direct.png +🛑 全球拦截=select, direct, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Advertising.png +🐟 漏网之鱼=select, direct, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Final.png [Rule] diff --git a/src/version.h b/src/version.h index f8a97922c..4315deac7 100644 --- a/src/version.h +++ b/src/version.h @@ -1,6 +1,6 @@ #ifndef VERSION_H_INCLUDED #define VERSION_H_INCLUDED -#define VERSION "v0.7.2" +#define VERSION "v0.8.0" #endif // VERSION_H_INCLUDED From e847df357bd9b4a2bb1b9188029d6f43a6da1041 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Mon, 9 Oct 2023 17:41:53 +0800 Subject: [PATCH 026/120] Add tag trigger for actions --- .github/workflows/build.yml | 2 ++ .github/workflows/docker.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d1dedfb1..c97034634 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,8 @@ name: GitHub CI on: push: branches: [ master ] + tags: + - '**' workflow_dispatch: pull_request: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 759c26ef7..5a37b64e6 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -2,6 +2,8 @@ name: Publish Docker Image on: push: branches: [ master ] + tags: + - '**' concurrency: group: ${{ github.ref }}-${{ github.workflow }} From b2688880ea6fbfb630d7fcee28bb9193a99f9816 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Wed, 11 Oct 2023 18:51:58 +0800 Subject: [PATCH 027/120] Use latest release of curl --- scripts/build.alpine.release.sh | 2 +- scripts/build.windows.release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build.alpine.release.sh b/scripts/build.alpine.release.sh index cea0a2a5f..00236d573 100644 --- a/scripts/build.alpine.release.sh +++ b/scripts/build.alpine.release.sh @@ -4,7 +4,7 @@ set -xe apk add gcc g++ build-base linux-headers cmake make autoconf automake libtool python2 python3 apk add mbedtls-dev mbedtls-static zlib-dev rapidjson-dev libevent-dev libevent-static zlib-static pcre2-dev -git clone https://github.com/curl/curl --depth=1 --branch curl-7_88_1 +git clone https://github.com/curl/curl --depth=1 --branch curl-8_4_1 cd curl cmake -DCURL_USE_MBEDTLS=ON -DHTTP_ONLY=ON -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_USE_LIBSSH2=OFF -DBUILD_CURL_EXE=OFF . > /dev/null make install -j2 > /dev/null diff --git a/scripts/build.windows.release.sh b/scripts/build.windows.release.sh index 1dce9aadc..f341b3bda 100644 --- a/scripts/build.windows.release.sh +++ b/scripts/build.windows.release.sh @@ -1,7 +1,7 @@ #!/bin/bash set -xe -git clone https://github.com/curl/curl --depth=1 --branch curl-7_88_1 +git clone https://github.com/curl/curl --depth=1 --branch curl-8_4_1 cd curl cmake -DCMAKE_BUILD_TYPE=Release -DCURL_USE_LIBSSH2=OFF -DHTTP_ONLY=ON -DCURL_USE_SCHANNEL=ON -DBUILD_SHARED_LIBS=OFF -DBUILD_CURL_EXE=OFF -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" -G "Unix Makefiles" -DHAVE_LIBIDN2=OFF -DCURL_USE_LIBPSL=OFF . make install -j4 From 705e94e53f4df33e32953719fad54599b4dd754f Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Wed, 11 Oct 2023 18:53:38 +0800 Subject: [PATCH 028/120] Fix build scripts --- scripts/build.alpine.release.sh | 2 +- scripts/build.windows.release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build.alpine.release.sh b/scripts/build.alpine.release.sh index 00236d573..a6d829687 100644 --- a/scripts/build.alpine.release.sh +++ b/scripts/build.alpine.release.sh @@ -4,7 +4,7 @@ set -xe apk add gcc g++ build-base linux-headers cmake make autoconf automake libtool python2 python3 apk add mbedtls-dev mbedtls-static zlib-dev rapidjson-dev libevent-dev libevent-static zlib-static pcre2-dev -git clone https://github.com/curl/curl --depth=1 --branch curl-8_4_1 +git clone https://github.com/curl/curl --depth=1 --branch curl-8_4_0 cd curl cmake -DCURL_USE_MBEDTLS=ON -DHTTP_ONLY=ON -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_USE_LIBSSH2=OFF -DBUILD_CURL_EXE=OFF . > /dev/null make install -j2 > /dev/null diff --git a/scripts/build.windows.release.sh b/scripts/build.windows.release.sh index f341b3bda..d64de978f 100644 --- a/scripts/build.windows.release.sh +++ b/scripts/build.windows.release.sh @@ -1,7 +1,7 @@ #!/bin/bash set -xe -git clone https://github.com/curl/curl --depth=1 --branch curl-8_4_1 +git clone https://github.com/curl/curl --depth=1 --branch curl-8_4_0 cd curl cmake -DCMAKE_BUILD_TYPE=Release -DCURL_USE_LIBSSH2=OFF -DHTTP_ONLY=ON -DCURL_USE_SCHANNEL=ON -DBUILD_SHARED_LIBS=OFF -DBUILD_CURL_EXE=OFF -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" -G "Unix Makefiles" -DHAVE_LIBIDN2=OFF -DCURL_USE_LIBPSL=OFF . make install -j4 From b281c9960b9c0217a46957a8a746b044e4a3f288 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Wed, 11 Oct 2023 19:51:28 +0800 Subject: [PATCH 029/120] Fix warning: possibly dangling reference to a temporary --- src/handler/settings.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/handler/settings.cpp b/src/handler/settings.cpp index d7723e368..79bb5fd16 100644 --- a/src/handler/settings.cpp +++ b/src/handler/settings.cpp @@ -573,14 +573,14 @@ void operate_toml_kv_table(const std::vector &arr, const toml::key { for(const toml::table &table : arr) { - const auto &key = table.at(key_name), value = table.at(value_name); + const auto &key = table.at(key_name), &value = table.at(value_name); binary_op(key, value); } } void readTOMLConf(toml::value &root) { - const auto §ion_common = toml::find(root, "common"); + auto section_common = toml::find(root, "common"); string_array default_url, insert_url; find_if_exist(section_common, "default_url", default_url, "insert_url", insert_url); @@ -619,7 +619,7 @@ void readTOMLConf(toml::value &root) safe_set_streams(toml::find_or(root, "userinfo", "stream_rule", RegexMatchConfigs{})); safe_set_times(toml::find_or(root, "userinfo", "time_rule", RegexMatchConfigs{})); - const auto §ion_node_pref = toml::find(root, "node_pref"); + auto section_node_pref = toml::find(root, "node_pref"); find_if_exist(section_node_pref, "udp_flag", global.UDPFlag, @@ -638,7 +638,7 @@ void readTOMLConf(toml::value &root) importItems(renameconfs, "rename_node", false); safe_set_renames(toml::get(toml::value(renameconfs))); - const auto §ion_managed = toml::find(root, "managed_config"); + auto section_managed = toml::find(root, "managed_config"); find_if_exist(section_managed, "write_managed_config", global.writeManagedConfig, @@ -648,13 +648,13 @@ void readTOMLConf(toml::value &root) "quanx_device_id", global.quanXDevID ); - const auto §ion_surge_external = toml::find(root, "surge_external_proxy"); + auto section_surge_external = toml::find(root, "surge_external_proxy"); find_if_exist(section_surge_external, "surge_ssr_path", global.surgeSSRPath, "resolve_hostname", global.surgeResolveHostname ); - const auto §ion_emojis = toml::find(root, "emojis"); + auto section_emojis = toml::find(root, "emojis"); find_if_exist(section_emojis, "add_emoji", global.addEmoji, @@ -669,7 +669,7 @@ void readTOMLConf(toml::value &root) importItems(groups, "custom_groups", false); global.customProxyGroups = toml::get(toml::value(groups)); - const auto §ion_ruleset = toml::find(root, "ruleset"); + auto section_ruleset = toml::find(root, "ruleset"); find_if_exist(section_ruleset, "enabled", global.enableRuleGen, @@ -681,7 +681,7 @@ void readTOMLConf(toml::value &root) importItems(rulesets, "rulesets", false); global.customRulesets = toml::get(toml::value(rulesets)); - const auto §ion_template = toml::find(root, "template"); + auto section_template = toml::find(root, "template"); global.templatePath = toml::find_or(section_template, "template_path", "template"); @@ -702,7 +702,7 @@ void readTOMLConf(toml::value &root) global.cronTasks = toml::get(toml::value(tasks)); refresh_schedule(); - const auto §ion_server = toml::find(root, "server"); + auto section_server = toml::find(root, "server"); find_if_exist(section_server, "listen", global.listenAddress, @@ -711,7 +711,7 @@ void readTOMLConf(toml::value &root) ); webServer.serve_file = !webServer.serve_file_root.empty(); - const auto §ion_advanced = toml::find(root, "advanced"); + auto section_advanced = toml::find(root, "advanced"); std::string log_level; bool enable_cache = true; @@ -1135,7 +1135,7 @@ int loadExternalYAML(YAML::Node &node, ExternalConfig &ext) int loadExternalTOML(toml::value &root, ExternalConfig &ext) { - const auto §ion = toml::find(root, "custom"); + auto section = toml::find(root, "custom"); find_if_exist(section, "enable_rule_generator", ext.enable_rule_generator, From e7380d8a9c5fd977c6f0d2a91c2dc8943059d741 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Wed, 11 Oct 2023 21:11:22 +0800 Subject: [PATCH 030/120] Bump version to v0.8.1 --- src/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h b/src/version.h index 4315deac7..1188570e5 100644 --- a/src/version.h +++ b/src/version.h @@ -1,6 +1,6 @@ #ifndef VERSION_H_INCLUDED #define VERSION_H_INCLUDED -#define VERSION "v0.8.0" +#define VERSION "v0.8.1" #endif // VERSION_H_INCLUDED From 80c2a968d1891d9234e2ce13918ca248fc873355 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Mon, 16 Oct 2023 04:12:41 +0800 Subject: [PATCH 031/120] Add support for WireGuard nodes in Clash, Surge and Loon configs --- src/generator/config/subexport.cpp | 74 ++++++++++++++++- src/parser/config/proxy.h | 18 ++++- src/parser/subparser.cpp | 126 ++++++++++++++++++++++++++++- src/utils/regexp.cpp | 50 ++++++++---- src/utils/regexp.h | 1 + src/utils/string.cpp | 2 +- src/utils/string.h | 2 +- 7 files changed, 254 insertions(+), 19 deletions(-) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index bb17bf6f5..57c197f13 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -444,6 +444,20 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) singleproxy["password"].SetTag("str"); break; + case ProxyType::WireGuard: + singleproxy["type"] = "wireguard"; + singleproxy["public-key"] = x.PublicKey; + singleproxy["private-key"] = x.PrivateKey; + singleproxy["ip"] = x.SelfIP; + if(!x.SelfIPv6.empty()) + singleproxy["ipv6"] = x.SelfIPv6; + if(!x.PreSharedKey.empty()) + singleproxy["preshared-key"] = x.PreSharedKey; + if(!x.DnsServers.empty()) + singleproxy["dns"] = x.DnsServers; + if(x.Mtu > 0) + singleproxy["mtu"] = x.Mtu; + break; default: continue; } @@ -595,6 +609,24 @@ std::string proxyToClash(std::vector &nodes, const std::string &base_conf return output_content; } +// peer = (public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, allowed-ips = "0.0.0.0/0, ::/0", endpoint = engage.cloudflareclient.com:2408, client-id = 139/184/125),(public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, endpoint = engage.cloudflareclient.com:2408) +std::string generatePeer(Proxy &node, bool client_id_as_reserved = false) +{ + std::string result; + result += "public-key = " + node.PublicKey; + result += ", endpoint = " + node.Hostname + ":" + std::to_string(node.Port); + if(!node.AllowedIPs.empty()) + result += ", allowed-ips = \"" + node.AllowedIPs + "\""; + if(!node.ClientId.empty()) + { + if(client_id_as_reserved) + result += ", reserved = [" + node.ClientId + "]"; + else + result += ", client-id = " + node.ClientId; + } + return result; +} + std::string proxyToSurge(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, int surge_ver, extra_settings &ext) { INIReader ini; @@ -644,7 +676,7 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf scv.define(x.AllowInsecure); tls13.define(x.TLS13); - std::string proxy; + std::string proxy, section, real_section; string_array args, headers; switch(x.Type) @@ -770,6 +802,28 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf if(x.SnellVersion != 0) proxy += ", version=" + std::to_string(x.SnellVersion); break; + case ProxyType::WireGuard: + if(surge_ver < 4 && surge_ver != -3) + continue; + section = randomStr(5); + real_section = "WireGuard " + section; + proxy = "wireguard, section-name=" + section; + if(!x.TestUrl.empty()) + proxy += ", test-url=" + x.TestUrl; + ini.set(real_section, "private-key", x.PrivateKey); + ini.set(real_section, "self-ip", x.SelfIP); + if(!x.SelfIPv6.empty()) + ini.set(real_section, "self-ip-v6", x.SelfIPv6); + if(!x.PreSharedKey.empty()) + ini.set(real_section, "preshared-key", x.PreSharedKey); + if(!x.DnsServers.empty()) + ini.set(real_section, "dns-server", join(x.DnsServers, ",")); + if(x.Mtu > 0) + ini.set(real_section, "mtu", std::to_string(x.Mtu)); + if(x.KeepAlive > 0) + ini.set(real_section, "keepalive", std::to_string(x.KeepAlive)); + ini.set(real_section, "peer", "(" + generatePeer(x) + ")"); + break; default: continue; } @@ -1866,6 +1920,24 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); } break; + case ProxyType::WireGuard: + proxy = "wireguard, interface-ip=" + x.SelfIP; + if(!x.SelfIPv6.empty()) + proxy += ", interface-ipv6=" + x.SelfIPv6; + proxy += ", private-key=" + x.PrivateKey; + for(const auto &y : x.DnsServers) + { + if(isIPv4(y)) + proxy += ", dns=" + y; + else if(isIPv6(y)) + proxy += ", dnsv6=" + y; + } + if(x.Mtu > 0) + proxy += ", mtu=" + std::to_string(x.Mtu); + if(x.KeepAlive > 0) + proxy += ", keepalive=" + std::to_string(x.KeepAlive); + proxy += ", peers=[{" + generatePeer(x, true) + "}]"; + break; default: continue; } diff --git a/src/parser/config/proxy.h b/src/parser/config/proxy.h index 5edc1ded9..94f6886cb 100644 --- a/src/parser/config/proxy.h +++ b/src/parser/config/proxy.h @@ -2,10 +2,12 @@ #define PROXY_H_INCLUDED #include +#include #include "../../utils/tribool.h" using String = std::string; +using StringArray = std::vector; enum ProxyType { @@ -17,7 +19,8 @@ enum ProxyType Snell, HTTP, HTTPS, - SOCKS5 + SOCKS5, + WireGuard }; inline String getProxyTypeName(int type) @@ -84,6 +87,18 @@ struct Proxy uint16_t SnellVersion = 0; String ServerName; + + String SelfIP; + String SelfIPv6; + String PublicKey; + String PrivateKey; + String PreSharedKey; + StringArray DnsServers; + uint16_t Mtu = 0; + String AllowedIPs = "0.0.0.0/0, ::/0"; + uint16_t KeepAlive = 0; + String TestUrl; + String ClientId; }; #define SS_DEFAULT_GROUP "SSProvider" @@ -93,5 +108,6 @@ struct Proxy #define HTTP_DEFAULT_GROUP "HTTPProvider" #define TROJAN_DEFAULT_GROUP "TrojanProvider" #define SNELL_DEFAULT_GROUP "SnellProvider" +#define WG_DEFAULT_GROUP "WireGuardProvider" #endif // PROXY_H_INCLUDED diff --git a/src/parser/subparser.cpp b/src/parser/subparser.cpp index 1434e6a68..35cbf7c66 100644 --- a/src/parser/subparser.cpp +++ b/src/parser/subparser.cpp @@ -115,6 +115,20 @@ void snellConstruct(Proxy &node, const std::string &group, const std::string &re node.SnellVersion = version; } +void wireguardConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &selfIp, const std::string &selfIpv6, const std::string &privKey, const std::string &pubKey, const std::string &psk, const string_array &dns, const std::string &mtu, const std::string &keepalive, const std::string &testUrl, const std::string &clientId, const tribool &udp) { + commonConstruct(node, ProxyType::WireGuard, group, remarks, server, port, udp, tribool(), tribool(), tribool()); + node.SelfIP = selfIp; + node.SelfIPv6 = selfIpv6; + node.PrivateKey = privKey; + node.PublicKey = pubKey; + node.PreSharedKey = psk; + node.DnsServers = dns; + node.Mtu = to_int(mtu); + node.KeepAlive = to_int(keepalive); + node.TestUrl = testUrl; + node.ClientId = clientId; +} + void explodeVmess(std::string vmess, Proxy &node) { std::string version, ps, add, port, type, id, aid, net, path, host, tls, sni; @@ -953,6 +967,8 @@ void explodeClash(Node yamlnode, std::vector &nodes) std::string plugin, pluginopts, pluginopts_mode, pluginopts_host, pluginopts_mux; //ss std::string protocol, protoparam, obfs, obfsparam; //ssr std::string user; //socks + std::string ip, ipv6, private_key, public_key, mtu; //wireguard + string_array dns_server; tribool udp, tfo, scv; Node singleproxy; uint32_t index = nodes.size(); @@ -1151,6 +1167,18 @@ void explodeClash(Node yamlnode, std::vector &nodes) snellConstruct(node, group, ps, server, port, password, obfs, host, to_int(aid, 0), udp, tfo, scv); break; + case "wireguard"_hash: + group = WG_DEFAULT_GROUP; + singleproxy["public-key"] >>= public_key; + singleproxy["private-key"] >>= private_key; + singleproxy["dns"] >>= dns_server; + singleproxy["mtu"] >>= mtu; + singleproxy["preshared-key"] >>= password; + singleproxy["ip"] >>= ip; + singleproxy["ipv6"] >>= ipv6; + + wireguardConstruct(node, group, ps, server, port, ip, ipv6, private_key, public_key, password, dns_server, mtu, "0", "", "", udp); + break; default: continue; } @@ -1288,6 +1316,41 @@ void explodeKitsunebi(std::string kit, Proxy &node) vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, add, port, type, id, aid, net, cipher, path, host, "", tls, ""); } +// peer = (public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, allowed-ips = "0.0.0.0/0, ::/0", endpoint = engage.cloudflareclient.com:2408, client-id = 139/184/125),(public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, endpoint = engage.cloudflareclient.com:2408) +void parsePeers(Proxy &node, const std::string &data) +{ + auto peers = regGetAllMatch(data, R"(\((.*?)\))", true); + if(peers.empty()) + return; + auto peer = peers[0]; + auto peerdata = regGetAllMatch(peer, R"(([a-z-]+) ?= ?([^" ),]+|".*?"),? ?)", true); + if(peerdata.size() % 2 != 0) + return; + for(size_t i = 0; i < peerdata.size(); i += 2) + { + auto key = peerdata[i]; + auto val = peerdata[i + 1]; + switch(hash_(key)) + { + case "public-key"_hash: + node.PublicKey = val; + break; + case "endpoint"_hash: + node.Hostname = val.substr(0, val.rfind(':')); + node.Port = to_int(val.substr(val.rfind(':') + 1)); + break; + case "client-id"_hash: + node.ClientId = val; + break; + case "allowed-ips"_hash: + node.AllowedIPs = trimOf(val, '"'); + break; + default: + break; + } + } +} + bool explodeSurge(std::string surge, std::vector &nodes) { std::multimap proxies; @@ -1303,7 +1366,6 @@ bool explodeSurge(std::string surge, std::vector &nodes) ini.keep_empty_section = false; ini.allow_dup_section_titles = true; ini.set_isolated_items_section("Proxy"); - ini.include_section("Proxy"); ini.add_direct_save_section("Proxy"); if(surge.find("[Proxy]") != surge.npos) surge = regReplace(surge, R"(^[\S\s]*?\[)", "[", false); @@ -1322,6 +1384,9 @@ bool explodeSurge(std::string surge, std::vector &nodes) std::string plugin, pluginopts, pluginopts_mode, pluginopts_host, mod_url, mod_md5; //ss std::string id, net, tls, host, edge, path; //v2 std::string protocol, protoparam; //ssr + std::string section, ip, ipv6, private_key, public_key, mtu, test_url, client_id, peer, keepalive; //wireguard + string_array dns_servers; + string_multimap wireguard_config; std::string version, aead = "1"; std::string itemName, itemVal, config; std::vector configs, vArray, headers, header; @@ -1660,6 +1725,65 @@ bool explodeSurge(std::string surge, std::vector &nodes) snellConstruct(node, SNELL_DEFAULT_GROUP, remarks, server, port, password, plugin, host, to_int(version, 0), udp, tfo, scv); break; + case "wireguard"_hash: + for (i = 1; i < configs.size(); i++) + { + vArray = split(trim(configs[i]), "="); + if(vArray.size() != 2) + continue; + itemName = trim(vArray[0]); + itemVal = trim(vArray[1]); + switch(hash_(itemName)) + { + case "section-name"_hash: + section = itemVal; + break; + case "test-url"_hash: + test_url = itemVal; + break; + } + } + if(section.empty()) + continue; + ini.get_items("WireGuard " + section, wireguard_config); + if(wireguard_config.empty()) + continue; + + for (auto &c : wireguard_config) + { + itemName = trim(c.first); + itemVal = trim(c.second); + switch(hash_(itemName)) + { + case "self-ip"_hash: + ip = itemVal; + break; + case "self-ip-v6"_hash: + ipv6 = itemVal; + break; + case "private-key"_hash: + private_key = itemVal; + break; + case "dns-server"_hash: + vArray = split(itemVal, ","); + for (auto &y : vArray) + dns_servers.emplace_back(trim(y)); + break; + case "mtu"_hash: + mtu = itemVal; + break; + case "peer"_hash: + peer = itemVal; + break; + case "keepalive"_hash: + keepalive = itemVal; + break; + } + } + + wireguardConstruct(node, WG_DEFAULT_GROUP, remarks, "", "0", ip, ipv6, private_key, "", "", dns_servers, mtu, keepalive, test_url, "", udp); + parsePeers(node, peer); + break; default: switch(hash_(remarks)) { diff --git a/src/utils/regexp.cpp b/src/utils/regexp.cpp index 8e809ae1c..e0ddaa2d3 100644 --- a/src/utils/regexp.cpp +++ b/src/utils/regexp.cpp @@ -10,6 +10,8 @@ using jp = jpcre2::select; //#endif // USE_STD_REGEX +#include "regexp.h" + /* #ifdef USE_STD_REGEX bool regValid(const std::string ®) @@ -165,33 +167,53 @@ bool regValid(const std::string ®) int regGetMatch(const std::string &src, const std::string &match, size_t group_count, ...) { - jp::Regex reg; - reg.setPattern(match).addModifier("m").addPcre2Option(PCRE2_UTF|PCRE2_ALT_BSUX).compile(); - jp::VecNum vec_num; - jp::RegexMatch rm; - size_t count = rm.setRegexObject(®).setSubject(src).setNumberedSubstringVector(&vec_num).setModifier("g").match(); - if(!count) + auto result = regGetAllMatch(src, match, false); + if(result.empty()) return -1; va_list vl; va_start(vl, group_count); - size_t index = 0, match_index = 0; + size_t index = 0; while(group_count) { std::string* arg = va_arg(vl, std::string*); - if(arg != NULL) - *arg = std::move(vec_num[match_index][index]); + if(arg != nullptr) + *arg = std::move(result[index]); index++; group_count--; + if(result.size() <= index) + break; + } + va_end(vl); + return 0; +} + +std::vector regGetAllMatch(const std::string &src, const std::string &match, bool group_only) +{ + jp::Regex reg; + reg.setPattern(match).addModifier("m").addPcre2Option(PCRE2_UTF|PCRE2_ALT_BSUX).compile(); + jp::VecNum vec_num; + jp::RegexMatch rm; + size_t count = rm.setRegexObject(®).setSubject(src).setNumberedSubstringVector(&vec_num).setModifier("g").match(); + std::vector result; + if(!count) + return result; + size_t begin = 0; + if(group_only) + begin = 1; + size_t index = begin, match_index = 0; + while(true) + { + if(vec_num.size() <= match_index) + break; if(vec_num[match_index].size() <= index) { match_index++; - index = 0; + index = begin; } - if(vec_num.size() <= match_index) - break; + result.push_back(std::move(vec_num[match_index][index])); + index++; } - va_end(vl); - return 0; + return result; } //#endif // USE_STD_REGEX diff --git a/src/utils/regexp.h b/src/utils/regexp.h index 994709ab6..06558a532 100644 --- a/src/utils/regexp.h +++ b/src/utils/regexp.h @@ -8,6 +8,7 @@ bool regFind(const std::string &src, const std::string &match); std::string regReplace(const std::string &src, const std::string &match, const std::string &rep, bool global = true, bool multiline = true); bool regMatch(const std::string &src, const std::string &match); int regGetMatch(const std::string &src, const std::string &match, size_t group_count, ...); +std::vector regGetAllMatch(const std::string &src, const std::string &match, bool group_only = false); std::string regTrim(const std::string &src); #endif // REGEXP_H_INCLUDED diff --git a/src/utils/string.cpp b/src/utils/string.cpp index 0730f84c6..7e2789eba 100644 --- a/src/utils/string.cpp +++ b/src/utils/string.cpp @@ -354,7 +354,7 @@ bool isStrUTF8(const std::string &data) return true; } -std::string randomStr(const int len) +std::string randomStr(int len) { std::string retData; srand(time(NULL)); diff --git a/src/utils/string.h b/src/utils/string.h index 7c16bec34..1a4248729 100644 --- a/src/utils/string.h +++ b/src/utils/string.h @@ -33,7 +33,7 @@ std::string trim(const std::string& str, bool before = true, bool after = true); std::string trimQuote(const std::string &str, bool before = true, bool after = true); void trimSelfOf(std::string &str, char target, bool before = true, bool after = true); std::string trimWhitespace(const std::string &str, bool before = false, bool after = true); -std::string randomStr(const int len); +std::string randomStr(int len); bool isStrUTF8(const std::string &data); void removeUTF8BOM(std::string &data); From 25b25f669d9c4b0195be72fbf5e1d3bd752a44a4 Mon Sep 17 00:00:00 2001 From: TAKO <20227709+HynoR@users.noreply.github.com> Date: Mon, 6 Nov 2023 21:58:11 +0800 Subject: [PATCH 032/120] Fix wrong flag identification When converting Nigeria node (#654) * Update emoji.toml * Update emoji.txt --- base/snippets/emoji.toml | 13 ++++--------- base/snippets/emoji.txt | 3 +-- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/base/snippets/emoji.toml b/base/snippets/emoji.toml index 0fc4e77f3..a277aebed 100644 --- a/base/snippets/emoji.toml +++ b/base/snippets/emoji.toml @@ -14,6 +14,10 @@ emoji = "🇨🇳" match = "(?i:\\bSG[P]?\\b|Singapore|新加坡|狮城|[^-]新)" emoji = "🇸🇬" +[[emoji]] +match = "(尼日利亚|Nigeria)" +emoji = "🇳🇬" + [[emoji]] match = "(?i:\\bJP[N]?\\b|Japan|Tokyo|Osaka|Saitama|日本|东京|大阪|埼玉|[^-]日)" emoji = "🇯🇵" @@ -243,10 +247,6 @@ emoji = "🇲🇽" match = "(Malaysia|马来|MY)" emoji = "🇲🇾" -[[emoji]] -match = "(尼日利亚|Nigeria)" -emoji = "🇳🇬" - [[emoji]] match = "(?i:\\bNL[D]?\\b|Netherlands|荷兰|阿姆斯特丹)" emoji = "🇳🇱" @@ -353,11 +353,6 @@ match = "(Morocco|摩洛哥)" emoji = "🇲🇦" -[[emoji]] -match = "(Nigeria|尼日利亚)" -emoji = "🇳🇬" - - [[emoji]] match = "(Nepal|尼泊尔)" emoji = "🇳🇵" diff --git a/base/snippets/emoji.txt b/base/snippets/emoji.txt index a195ee264..236de3172 100644 --- a/base/snippets/emoji.txt +++ b/base/snippets/emoji.txt @@ -2,6 +2,7 @@ (?i:\bHK[G]?\b|Hong.*?Kong|\bHKT\b|\bHKBN\b|\bHGC\b|\bWTT\b|\bCMI\b|[^-]港),🇭🇰 (?i:\bTW[N]?\b|Taiwan|新北|彰化|\bCHT\b|台湾|[^-]台|\bHINET\b),🇨🇳 (?i:\bSG[P]?\b|Singapore|新加坡|狮城|[^-]新),🇸🇬 +(尼日利亚|Nigeria),🇳🇬 (?i:\bJP[N]?\b|Japan|Tokyo|Osaka|Saitama|日本|东京|大阪|埼玉|[^-]日),🇯🇵 (?i:\bK[O]?R\b|Korea|首尔|韩|韓),🇰🇷 (?i:\bUS[A]?\b|America|United.*?States|美国|[^-]美|波特兰|达拉斯|俄勒冈|凤凰城|费利蒙|硅谷|拉斯维加斯|洛杉矶|圣何塞|圣克拉拉|西雅图|芝加哥),🇺🇸 @@ -59,7 +60,6 @@ (Macao|澳门|\bCTM\b),🇲🇴 (墨西哥|Mexico),🇲🇽 (Malaysia|马来|MY),🇲🇾 -(尼日利亚|Nigeria),🇳🇬 (?i:\bNL[D]?\b|Netherlands|荷兰|阿姆斯特丹),🇳🇱 (挪威|Norway),🇳🇴 (新西兰|纽西兰|New Zealand),🇳🇿 @@ -87,7 +87,6 @@ (Ecuador|厄瓜多尔),🇪🇨 (Venezuela|委内瑞拉),🇻🇪 (Morocco|摩洛哥),🇲🇦 -(Nigeria|尼日利亚),🇳🇬 (Nepal|尼泊尔),🇳🇵 (Bengal|孟加拉),🇧🇩 (?i:\bC[H]?N\b|China|back|回国|中国[^-]|江苏[^-]|北京[^-]|上海[^-]|广州[^-]|深圳[^-]|杭州[^-]|常州[^-]|徐州[^-]|青岛[^-]|宁波[^-]|镇江[^-]|成都[^-]|河北[^-]|山西[^-]|辽宁[^-]|吉林[^-]|黑龙江[^-]|江苏[^-]|浙江[^-]|安徽[^-]|福建[^-]|江西[^-]|山东[^-]|河南[^-]|湖北[^-]|湖南[^-]|广东[^-]|海南[^-]|四川[^-]|贵州[^-]|云南[^-]|陕西[^-]|甘肃[^-]|青海[^-]|内蒙古[^-]|广西[^-]|西藏[^-]|宁夏[^-]|新疆[^-]),🇨🇳 From d8075aa701e8262c860c058d701bdd32a4d3c2d9 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:02:47 +0800 Subject: [PATCH 033/120] Fix build error --- .github/workflows/docker.yml | 8 +-- include/quickjspp.hpp | 5 +- scripts/Dockerfile | 28 +++++---- scripts/build.alpine.release.sh | 13 ++-- scripts/update_rules.py | 108 ++++++++++++++++++-------------- 5 files changed, 91 insertions(+), 71 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5a37b64e6..3da8bc0c9 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -179,7 +179,7 @@ jobs: tags: tindy2013/subconverter:latest build-args: | SHA=${{ steps.vars.outputs.sha_short }} - THREADS=2 + THREADS=4 outputs: type=image,push=true - name: Replace tag without `v` @@ -200,7 +200,7 @@ jobs: context: scripts/ tags: tindy2013/subconverter:${{steps.version.outputs.result}} build-args: | - THREADS=2 + THREADS=4 outputs: type=image,push=true - name: Save digest @@ -252,7 +252,7 @@ jobs: tags: tindy2013/subconverter:latest build-args: | SHA=${{ steps.vars.outputs.sha_short }} - THREADS=2 + THREADS=4 outputs: type=image,push=true - name: Replace tag without `v` @@ -273,7 +273,7 @@ jobs: context: scripts/ tags: tindy2013/subconverter:${{steps.version.outputs.result}} build-args: | - THREADS=2 + THREADS=4 outputs: type=image,push=true - name: Save digest diff --git a/include/quickjspp.hpp b/include/quickjspp.hpp index a476cdce3..651c4dc3c 100644 --- a/include/quickjspp.hpp +++ b/include/quickjspp.hpp @@ -495,6 +495,7 @@ T unwrap_free(JSContext * ctx, JSValue val) template Tuple unwrap_args_impl(JSContext * ctx, JSValueConst * argv, std::index_sequence) { + (void)ctx; return Tuple{js_traits>>::unwrap(ctx, argv[I])...}; } @@ -1207,12 +1208,12 @@ class Context JSModuleDef * m; JSContext * ctx; - const char * name; + /*const char * name;*/ using nvp = std::pair; std::vector exports; public: - Module(JSContext * ctx, const char * name) : ctx(ctx), name(name) + Module(JSContext * ctx, const char * name) : ctx(ctx)/*, name(name)*/ { m = JS_NewCModule(ctx, name, [](JSContext * ctx, JSModuleDef * m) noexcept { auto& context = Context::get(ctx); diff --git a/scripts/Dockerfile b/scripts/Dockerfile index 84127d2d9..b44713fac 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -1,11 +1,12 @@ -FROM alpine:3.16 -LABEL maintainer "tindy.it@gmail.com" +FROM alpine:3.16 AS builder +LABEL maintainer="tindy.it@gmail.com" ARG THREADS="4" ARG SHA="" # build minimized WORKDIR / -RUN apk add --no-cache --virtual .build-tools git g++ build-base linux-headers cmake python3 && \ +RUN set -xe && \ + apk add --no-cache --virtual .build-tools git g++ build-base linux-headers cmake python3 && \ apk add --no-cache --virtual .build-deps curl-dev rapidjson-dev libevent-dev pcre2-dev yaml-cpp-dev && \ git clone https://github.com/ftk/quickjspp --depth=1 && \ cd quickjspp && \ @@ -29,7 +30,7 @@ RUN apk add --no-cache --virtual .build-tools git g++ build-base linux-headers c install -d /usr/include/date/ && \ install -m644 libcron/externals/date/include/date/* /usr/include/date/ && \ cd .. && \ - git clone https://github.com/ToruNiina/toml11 --depth=1 && \ + git clone https://github.com/ToruNiina/toml11 --branch="v3.7.1" --depth=1 && \ cd toml11 && \ cmake -DCMAKE_CXX_STANDARD=11 . && \ make install -j $THREADS && \ @@ -41,14 +42,19 @@ RUN apk add --no-cache --virtual .build-tools git g++ build-base linux-headers c python3 -m pip install gitpython && \ python3 scripts/update_rules.py -c scripts/rules_config.conf && \ cmake -DCMAKE_BUILD_TYPE=Release . && \ - make -j $THREADS && \ - mv subconverter /usr/bin && \ - mv base ../ && \ - cd .. && \ - rm -rf subconverter quickjspp libcron toml11 /usr/lib/lib*.a /usr/include/* /usr/local/include/lib*.a /usr/local/include/* && \ - apk add --no-cache --virtual subconverter-deps pcre2 libcurl yaml-cpp libevent && \ - apk del .build-tools .build-deps + make -j $THREADS + +# build final image +FROM alpine:3.16 +LABEL maintainer="tindy.it@gmail.com" + +RUN apk add --no-cache --virtual subconverter-deps pcre2 libcurl yaml-cpp libevent + +COPY --from=builder /subconverter/subconverter /usr/bin/ +COPY --from=builder /subconverter/base /base/ # set entry WORKDIR /base CMD subconverter + +EXPOSE 25500/tcp diff --git a/scripts/build.alpine.release.sh b/scripts/build.alpine.release.sh index a6d829687..b250177ad 100644 --- a/scripts/build.alpine.release.sh +++ b/scripts/build.alpine.release.sh @@ -13,13 +13,13 @@ cd .. git clone https://github.com/jbeder/yaml-cpp --depth=1 cd yaml-cpp cmake -DCMAKE_BUILD_TYPE=Release -DYAML_CPP_BUILD_TESTS=OFF -DYAML_CPP_BUILD_TOOLS=OFF . > /dev/null -make install -j2 > /dev/null +make install -j3 > /dev/null cd .. git clone https://github.com/ftk/quickjspp --depth=1 cd quickjspp cmake -DCMAKE_BUILD_TYPE=Release . -make quickjs -j2 +make quickjs -j3 > /dev/null install -d /usr/lib/quickjs/ install -m644 quickjs/libquickjs.a /usr/lib/quickjs/ install -d /usr/include/quickjs/ @@ -31,10 +31,10 @@ git clone https://github.com/PerMalmberg/libcron --depth=1 cd libcron git submodule update --init cmake -DCMAKE_BUILD_TYPE=Release . -make libcron install -j2 +make libcron install -j3 cd .. -git clone https://github.com/ToruNiina/toml11 --depth=1 +git clone https://github.com/ToruNiina/toml11 --branch="v3.7.1" --depth=1 cd toml11 cmake -DCMAKE_CXX_STANDARD=11 . make install -j4 @@ -42,9 +42,10 @@ cd .. export PKG_CONFIG_PATH=/usr/lib64/pkgconfig cmake -DCMAKE_BUILD_TYPE=Release . -make -j2 +make -j3 rm subconverter -g++ -o base/subconverter $(find CMakeFiles/subconverter.dir/src/ -name "*.o") -static -lpcre2-8 -levent -lyaml-cpp -L/usr/lib64 -lcurl -lmbedtls -lmbedcrypto -lmbedx509 -lz -l:quickjs/libquickjs.a -llibcron -O3 -s +# shellcheck disable=SC2046 +g++ -o base/subconverter $(find CMakeFiles/subconverter.dir/src/ -name "*.o") -static -lpcre2-8 -levent -lyaml-cpp -L/usr/lib64 -lcurl -lmbedtls -lmbedcrypto -lmbedx509 -lz -l:quickjs/libquickjs.a -llibcron -O3 -s python3 -m ensurepip python3 -m pip install gitpython diff --git a/scripts/update_rules.py b/scripts/update_rules.py index d2520e7a6..25b5711f1 100644 --- a/scripts/update_rules.py +++ b/scripts/update_rules.py @@ -1,66 +1,78 @@ -import configparser, shutil, glob, os, random, string -import stat, logging, argparse -from git import InvalidGitRepositoryError, repo as GitRepo +import argparse +import configparser +import glob +import logging +import os +import shutil +import stat +from git import InvalidGitRepositoryError, Repo + def del_rw(action, name: str, exc): os.chmod(name, stat.S_IWRITE) os.remove(name) + def open_repo(path: str): - if os.path.exists(repo_path) == False: return None + if not os.path.exists(path): + return None try: - return GitRepo.Repo(path) + return Repo(path) except InvalidGitRepositoryError: return None -parser = argparse.ArgumentParser() -parser.add_argument("-c", "--config", default="rules_config.conf") -args = parser.parse_args() - -config = configparser.ConfigParser() -config.read(args.config) -logging.basicConfig(format="%(asctime)s %(message)s", level=logging.DEBUG) - -for section in config.sections(): - repo = config.get(section, "name", fallback=section) - url = config.get(section, "url") - commit = config.get(section, "checkout") - matches = config.get(section, "match").split("|") - save_path = config.get(section, "dest", fallback="base/rules/{}".format(repo)) - keep_tree = config.getboolean(section, "keep_tree", fallback=True) - - logging.info("reading files from url {} with commit {} and matches {}, save to {} keep_tree {}".format(url, commit, matches, save_path, keep_tree)) - - # ran_str = ''.join(random.sample(string.ascii_letters + string.digits, 16)) - repo_path = os.path.join("./tmp/repo/", repo) - - r = open_repo(repo_path) - if r != None: - logging.info("repo {} exists, checking out...".format(repo_path)) - r.git.checkout(commit) - else: - logging.info("repo {} not exist, cloning...".format(repo_path)) - shutil.rmtree(repo_path, ignore_errors=True) - os.makedirs(repo_path, exist_ok=True) - r = GitRepo.Repo.clone_from(url, repo_path) - r.git.checkout(commit) +def update_rules(repo_path, save_path, commit, matches, keep_tree): os.makedirs(save_path, exist_ok=True) - for pattern in matches: - files = glob.glob("{}/{}".format(repo_path, pattern), recursive=True) + files = glob.glob(os.path.join(repo_path, pattern), recursive=True) for file in files: - if os.path.isdir(file): continue - - (file_rel_path, file_name) = os.path.split(file.removeprefix(repo_path)) - if keep_tree: - file_dest_dir = "{}{}".format(save_path, file_rel_path) - file_dest_path = "{}/{}".format(file_dest_dir, file_name) + if os.path.isdir(file): + continue + file_rel_path, file_name = os.path.split(os.path.relpath(file, repo_path)) + if keep_tree: + file_dest_dir = os.path.join(save_path, file_rel_path) os.makedirs(file_dest_dir, exist_ok=True) + file_dest_path = os.path.join(file_dest_dir, file_name) else: - file_dest_path = "{}{}".format(save_path, file_name) + file_dest_path = os.path.join(save_path, file_name) + shutil.copy2(file, file_dest_path) + logging.info(f"copied {file} to {file_dest_path}") + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("-c", "--config", default="rules_config.conf") + args = parser.parse_args() + + config = configparser.ConfigParser() + config.read(args.config) + logging.basicConfig(format="%(asctime)s %(message)s", level=logging.DEBUG) + + for section in config.sections(): + repo = config.get(section, "name", fallback=section) + url = config.get(section, "url") + commit = config.get(section, "checkout") + matches = config.get(section, "match").split("|") + save_path = config.get(section, "dest", fallback=f"base/rules/{repo}") + keep_tree = config.getboolean(section, "keep_tree", fallback=True) + + logging.info(f"reading files from url {url} with commit {commit} and matches {matches}, save to {save_path} keep_tree {keep_tree}") + + repo_path = os.path.join("./tmp/repo/", repo) + + r = open_repo(repo_path) + if r is None: + logging.info(f"cloning {url} to {repo_path}") + r = Repo.clone_from(url, repo_path) + else: + logging.info(f"pulling changes for {repo} from {url}") + r.remotes.origin.pull() + + r.git.checkout(commit) + update_rules(repo_path, save_path, commit, matches, keep_tree) + + shutil.rmtree("./tmp", ignore_errors=True) - shutil.copyfile(file, file_dest_path) - logging.info("copied {} to {}".format(file, file_dest_path)) -shutil.rmtree("./tmp", onerror=del_rw) +if __name__ == "__main__": + main() From f99748b1268f4a61baa2fb10f1bd1e9e4e57c02a Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:21:02 +0800 Subject: [PATCH 034/120] Fix build error --- scripts/update_rules.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/update_rules.py b/scripts/update_rules.py index 25b5711f1..7633c76ac 100644 --- a/scripts/update_rules.py +++ b/scripts/update_rules.py @@ -62,12 +62,11 @@ def main(): r = open_repo(repo_path) if r is None: - logging.info(f"cloning {url} to {repo_path}") + logging.info(f"cloning repo {url} to {repo_path}") r = Repo.clone_from(url, repo_path) else: - logging.info(f"pulling changes for {repo} from {url}") - r.remotes.origin.pull() - + logging.info(f"repo {repo_path} exists") + r.git.checkout(commit) update_rules(repo_path, save_path, commit, matches, keep_tree) From bbcb6434e1235b8d26a663b6b7fd726618e2c4ed Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Tue, 7 Nov 2023 22:12:01 +0800 Subject: [PATCH 035/120] Enhancements Add basic support for generating sing-box configs. Use cpp-httplib as web server. Optimize codes. --- .github/workflows/build.yml | 4 +- CMakeLists.txt | 19 +- base/base/all_base.tpl | 30 + base/base/singbox.json | 26 + base/config/example_external_config.ini | 3 + base/config/example_external_config.toml | 3 + base/config/example_external_config.yml | 3 + base/pref.example.ini | 6 +- base/pref.example.toml | 11 +- base/pref.example.yml | 2 + include/httplib.h | 9262 ++++++++++++++++++++++ scripts/Dockerfile | 4 +- scripts/build.alpine.release.sh | 4 +- scripts/build.macos.release.sh | 4 +- scripts/build.windows.release.sh | 3 +- scripts/config.termux.sh | 2 +- src/generator/config/ruleconvert.cpp | 130 +- src/generator/config/ruleconvert.h | 4 +- src/generator/config/subexport.cpp | 301 +- src/generator/config/subexport.h | 8 +- src/handler/interfaces.cpp | 62 +- src/handler/settings.cpp | 9 + src/handler/settings.h | 3 +- src/main.cpp | 12 +- src/script/script_quickjs.h | 172 +- src/server/webserver.cpp | 4 +- src/server/webserver.h | 6 +- src/server/webserver_httplib.cpp | 223 + src/server/webserver_libevent.cpp | 65 +- src/utils/rapidjson_extra.h | 21 + src/utils/regexp.cpp | 7 +- src/utils/tribool.h | 111 +- 32 files changed, 10288 insertions(+), 236 deletions(-) create mode 100644 base/base/singbox.json create mode 100644 include/httplib.h create mode 100644 src/server/webserver_httplib.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c97034634..e6977227c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -157,7 +157,7 @@ jobs: - uses: msys2/setup-msys2@v2 with: update: true - install: base-devel git mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-libevent mingw-w64-x86_64-pcre2 patch + install: base-devel git mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-pcre2 patch msystem: MINGW64 path-type: inherit - name: Add commit id into version @@ -197,7 +197,7 @@ jobs: - uses: msys2/setup-msys2@v2 with: update: true - install: base-devel git mingw-w64-i686-gcc mingw-w64-i686-cmake mingw-w64-i686-libevent mingw-w64-i686-pcre2 patch + install: base-devel git mingw-w64-i686-gcc mingw-w64-i686-cmake mingw-w64-i686-pcre2 patch msystem: MINGW32 path-type: inherit - name: Add commit id into version diff --git a/CMakeLists.txt b/CMakeLists.txt index 3258125b2..5b1a6481e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,7 +46,8 @@ ADD_EXECUTABLE(${BUILD_TARGET_NAME} src/parser/subparser.cpp src/script/cron.cpp src/script/script_quickjs.cpp - src/server/webserver_libevent.cpp +# src/server/webserver_libevent.cpp + src/server/webserver_httplib.cpp src/utils/base64/base64.cpp src/utils/codepage.cpp src/utils/file.cpp @@ -66,12 +67,12 @@ SET(THREADS_PREFER_PTHREAD_FLAG ON) FIND_PACKAGE(Threads REQUIRED) TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} ${CMAKE_THREAD_LIBS_INIT}) -PKG_CHECK_MODULES(LIBEVENT libevent>=2.1.10 REQUIRED) -FIND_PATH(LIBEVENT_INCLUDE_DIR NAMES event.h PATHS ${LIBEVENT_INCLUDE_DIRS}) -FIND_LIBRARY(LIBEVENT_LIBRARY NAMES event PATHS ${LIBEVENT_LIBRARY_DIRS}) -TARGET_LINK_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${LIBEVENT_LIBRARY_DIRS}) -TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${LIBEVENT_INCLUDE_DIR}) -TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} ${LIBEVENT_LIBRARY}) +#PKG_CHECK_MODULES(LIBEVENT libevent>=2.1.10 REQUIRED) +#FIND_PATH(LIBEVENT_INCLUDE_DIR NAMES event.h PATHS ${LIBEVENT_INCLUDE_DIRS}) +#FIND_LIBRARY(LIBEVENT_LIBRARY NAMES event PATHS ${LIBEVENT_LIBRARY_DIRS}) +#TARGET_LINK_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${LIBEVENT_LIBRARY_DIRS}) +#TARGET_INCLUDE_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${LIBEVENT_INCLUDE_DIR}) +#TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} ${LIBEVENT_LIBRARY}) FIND_PACKAGE(CURL 7.54.0 REQUIRED) TARGET_LINK_DIRECTORIES(${BUILD_TARGET_NAME} PRIVATE ${CURL_LIBRARY_DIRS}) @@ -113,7 +114,7 @@ ELSE() INSTALL(DIRECTORY base/ DESTINATION ${CMAKE_INSTALL_BINDIR}/${BUILD_TARGET_NAME} FILES_MATCHING PATTERN "*") ENDIF() -ELSE() +ELSE() #BUILD_STATIC_LIBRARY ADD_LIBRARY(${BUILD_TARGET_NAME} STATIC src/generator/config/ruleconvert.cpp @@ -154,7 +155,7 @@ IF(WIN32) TARGET_LINK_LIBRARIES(${BUILD_TARGET_NAME} PRIVATE ws2_32) ENDIF() -ENDIF() +ENDIF() #BUILD_STATIC_LIBRARY IF(HAVE_TO_STRING) TARGET_COMPILE_DEFINITIONS(${BUILD_TARGET_NAME} PRIVATE -DHAVE_TO_STRING) diff --git a/base/base/all_base.tpl b/base/base/all_base.tpl index c88c907c1..e3eb53189 100644 --- a/base/base/all_base.tpl +++ b/base/base/all_base.tpl @@ -266,3 +266,33 @@ enhanced-mode-by-rule = true } {% endif %} +{% if request.target == "singbox" %} + +{ + "log": { + "disabled": false, + "level": "info", + "output": "box.log", + "timestamp": true + }, + "dns": {}, + "ntp": { + "enabled": false, + "server": "time.apple.com", + "server_port": 123, + "interval": "30m" + }, + "inbounds": [ + { + "type": "socks", + "tag": "socks-in", + "listen": "127.0.0.1", + "listen_port": 2080 + } + ], + "outbounds": [], + "route": {}, + "experimental": {} +} + +{% endif %} diff --git a/base/base/singbox.json b/base/base/singbox.json new file mode 100644 index 000000000..4068bfab9 --- /dev/null +++ b/base/base/singbox.json @@ -0,0 +1,26 @@ +{ + "log": { + "disabled": false, + "level": "info", + "output": "box.log", + "timestamp": true + }, + "dns": {}, + "ntp": { + "enabled": false, + "server": "time.apple.com", + "server_port": 123, + "interval": "30m" + }, + "inbounds": [ + { + "type": "socks", + "tag": "socks-in", + "listen": "127.0.0.1", + "listen_port": 2080 + } + ], + "outbounds": [], + "route": {}, + "experimental": {} +} \ No newline at end of file diff --git a/base/config/example_external_config.ini b/base/config/example_external_config.ini index 18e8db9f8..149dea0a5 100644 --- a/base/config/example_external_config.ini +++ b/base/config/example_external_config.ini @@ -43,6 +43,9 @@ clash_rule_base=base/forcerule.yml ;mellow_rule_base=base/mellow.conf ;quan_rule_base=base/quan.conf ;quanx_rule_base=base/quanx.conf +;loon_rule_base=base/loon.conf +;sssub_rule_base=base/shadowsocks_base.json +;singbox_rule_base=base/singbox.json ;Options for renaming nodes ;rename=Test-(.*?)-(.*?)-(.*?)\((.*?)\)@\1\4x测试线路_自\2到\3 diff --git a/base/config/example_external_config.toml b/base/config/example_external_config.toml index fd12d43cf..0e446af18 100644 --- a/base/config/example_external_config.toml +++ b/base/config/example_external_config.toml @@ -10,6 +10,9 @@ clash_rule_base = "base/forcerule.yml" #mellow_rule_base = "base/mellow.conf" #quan_rule_base = "base/quan.conf" #quanx_rule_base = "base/quanx.conf" +#loon_rule_base = "base/loon.conf" +#sssub_rule_base = "base/shadowsocks_base.json" +#singbox_rule_base = "base/singbox.json" # Options for adding emojis #add_emoji = true diff --git a/base/config/example_external_config.yml b/base/config/example_external_config.yml index 3201eb627..6c3ef2f24 100644 --- a/base/config/example_external_config.yml +++ b/base/config/example_external_config.yml @@ -14,6 +14,9 @@ custom: # mellow_rule_base: base/mellow.conf # quan_rule_base: base/quan.conf # quanx_rule_base: base/quanx.conf +# loon_rule_base: base/loon.conf +# sssub_rule_base: base/shadowsocks_base.json +# singbox_rule_base: base/singbox.json # rename_node: # - {import: snippet/rename.txt} diff --git a/base/pref.example.ini b/base/pref.example.ini index cbfb12f82..7ca2a4cfb 100644 --- a/base/pref.example.ini +++ b/base/pref.example.ini @@ -26,7 +26,7 @@ exclude_remarks=(到期|剩余流量|时间|官网|产品|平台) enable_filter=false ;Script used for filtering nodes. Supports inline script and script path. A "filter" function with 1 argument which is a node should be defined in the script. ;Example: Inline script: Set value to content of script. Replace all line break with "\n". -; Script path: Set value to "path:/path/to/script.js". +; Script path: set value to "path:/path/to/script.js". ;filter_script=function filter(node) {\n const info = JSON.parse(node.ProxyInfo);\n if(info.EncryptMethod.includes('chacha20'))\n return true;\n return false;\n} ;Setting an external config file as default when none is specified, supports local files/URL @@ -59,6 +59,9 @@ loon_rule_base=base/all_base.tpl ;Shadowsocks Android config base used by the generator, supports local files/URL sssub_rule_base=base/all_base.tpl +;sing-box config base used by the generator, supports local files/URL +singbox_rule_base=base/all_base.tpl + ;Proxy used to download configs, rulesets or subscriptions, set to NONE or empty to disable it, set to SYSTEM to use system proxy. ;Accept cURL-supported proxies (http:// https:// socks4a:// socks5://) ;Additional support for CORS proxy ( https://github.com/Rob--W/cors-anywhere https://github.com/Zibri/cloudflare-cors-anywhere etc.), prefix the address with "cors:" to recognize the address as CORS proxy. @@ -237,6 +240,7 @@ clash.log_level=info /mellow=/sub?target=mellow /surfboard=/sub?target=surfboard /loon=/sub?target=loon +/singbox=/sub?target=singbox /ss=/sub?target=ss /ssd=/sub?target=ssd /sssub=/sub?target=sssub diff --git a/base/pref.example.toml b/base/pref.example.toml index cb352a8e9..a8bf398d6 100644 --- a/base/pref.example.toml +++ b/base/pref.example.toml @@ -25,8 +25,8 @@ exclude_remarks = ["(到期|剩余流量|时间|官网|产品)"] # Enable script support for filtering nodes enable_filter = false # Script used for filtering nodes. Supports inline script and script path. A "filter" function with 1 argument which is a node should be defined in the script. -# Example: Inline script: Set value to content of script. -# Script path: Set value to "path:/path/to/script.js". +# Example: Inline script: set value to content of script. +# Script path: set value to "path:/path/to/script.js". #filter_script = ''' #function filter(node) { # const info = JSON.parse(node.ProxyInfo); @@ -66,6 +66,9 @@ loon_rule_base = "base/all_base.tpl" # Shadowsocks Android config base used by the generator, supports local files/URL sssub_rule_base = "base/all_base.tpl" +# sing-box config base used by the generator, supports local files/URL +singbox_rule_base = "base/all_base.tpl" + # Proxy used to download rulesets or subscriptions, set to NONE or empty to disable it, set to SYSTEM to use system proxy. # Accept cURL-supported proxies (http:// https:// socks4a:// socks5://) @@ -266,6 +269,10 @@ target = "/sub?target=surfboard" uri = "/loon" target = "/sub?target=loon" +[[aliases]] +uri = "/singbox" +target = "/sub?target=singbox" + [[aliases]] uri = "/ss" target = "/sub?target=ss" diff --git a/base/pref.example.yml b/base/pref.example.yml index 06475549e..f9b6c3d51 100644 --- a/base/pref.example.yml +++ b/base/pref.example.yml @@ -19,6 +19,7 @@ common: quanx_rule_base: base/all_base.tpl loon_rule_base: base/all_base.tpl sssub_rule_base: base/all_base.tpl + singbox_rule_base: base/all_base.tpl proxy_config: SYSTEM proxy_ruleset: SYSTEM proxy_subscription: NONE @@ -116,6 +117,7 @@ aliases: - {uri: /mellow, target: "/sub?target=mellow"} - {uri: /surfboard, target: "/sub?target=surfboard"} - {uri: /loon, target: "/sub?target=loon"} + - {uri: /singbox, target: "/sub?target=singbox"} - {uri: /ss, target: "/sub?target=ss"} - {uri: /ssd, target: "/sub?target=ssd"} - {uri: /sssub, target: "/sub?target=sssub"} diff --git a/include/httplib.h b/include/httplib.h new file mode 100644 index 000000000..1c78c35ec --- /dev/null +++ b/include/httplib.h @@ -0,0 +1,9262 @@ +// +// httplib.h +// +// Copyright (c) 2023 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_HTTPLIB_H + +#define CPPHTTPLIB_VERSION "0.14.1" + +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + +/* + * Headers + */ + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif //_CRT_SECURE_NO_WARNINGS + +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif //_CRT_NONSTDC_NO_DEPRECATE + +#if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported +#endif + +#pragma comment(lib, "ws2_32.lib") + +#ifdef _WIN64 +using ssize_t = __int64; +#else +using ssize_t = long; +#endif +#endif // _MSC_VER + +#ifndef S_ISREG +#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG) +#endif // S_ISREG + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR) +#endif // S_ISDIR + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include +#include + +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 +#endif + +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif // strcasecmp + +using socket_t = SOCKET; +#ifdef CPPHTTPLIB_USE_POLL +#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) +#endif + +#else // not _WIN32 + +#include +#if !defined(_AIX) && !defined(__MVS__) +#include +#endif +#ifdef __MVS__ +#include +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif +#endif +#include +#include +#include +#ifdef __linux__ +#include +#endif +#include +#ifdef CPPHTTPLIB_USE_POLL +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +using socket_t = int; +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif +#endif //_WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "cryptui.lib") +#endif +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#include +#if TARGET_OS_OSX +#include +#include +#endif // TARGET_OS_OSX +#endif // _WIN32 + +#include +#include +#include +#include + +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if OPENSSL_VERSION_NUMBER < 0x1010100fL +#error Sorry, OpenSSL versions prior to 1.1.1 are not supported +#elif OPENSSL_VERSION_NUMBER < 0x30000000L +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#endif + +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +#include +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + +/* + * Declaration + */ +namespace httplib { + +namespace detail { + +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + +struct ci { + bool operator()(const std::string &s1, const std::string &s2) const { + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), + s2.end(), + [](unsigned char c1, unsigned char c2) { + return ::tolower(c1) < ::tolower(c2); + }); + } +}; + +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} + + scope_exit(scope_exit &&rhs) + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + +} // namespace detail + +using Headers = std::multimap; + +using Params = std::multimap; +using Match = std::smatch; + +using Progress = std::function; + +struct Response; +using ResponseHandler = std::function; + +struct MultipartFormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; +}; +using MultipartFormDataItems = std::vector; +using MultipartFormDataMap = std::multimap; + +class DataSink { +public: + DataSink() : os(&sb_), sb_(*this) {} + + DataSink(const DataSink &) = delete; + DataSink &operator=(const DataSink &) = delete; + DataSink(DataSink &&) = delete; + DataSink &operator=(DataSink &&) = delete; + + std::function write; + std::function done; + std::function done_with_trailer; + std::ostream os; + +private: + class data_sink_streambuf : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) { + sink_.write(s, static_cast(n)); + return n; + } + + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; +}; + +using ContentProvider = + std::function; + +using ContentProviderWithoutLength = + std::function; + +using ContentProviderResourceReleaser = std::function; + +struct MultipartFormDataProvider { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; +}; +using MultipartFormDataProviderItems = std::vector; + +using ContentReceiverWithProgress = + std::function; + +using ContentReceiver = + std::function; + +using MultipartContentHeader = + std::function; + +class ContentReader { +public: + using Reader = std::function; + using MultipartReader = std::function; + + ContentReader(Reader reader, MultipartReader multipart_reader) + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} + + bool operator()(MultipartContentHeader header, + ContentReceiver receiver) const { + return multipart_reader_(std::move(header), std::move(receiver)); + } + + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } + + Reader reader_; + MultipartReader multipart_reader_; +}; + +using Range = std::pair; +using Ranges = std::vector; + +struct Request { + std::string method; + std::string path; + Headers headers; + std::string body; + + std::string remote_addr; + int remote_port = -1; + std::string local_addr; + int local_port = -1; + + // for server + std::string version; + std::string target; + Params params; + MultipartFormDataMap files; + Ranges ranges; + Match matches; + std::unordered_map path_params; + + // for client + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; + Progress progress; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + const SSL *ssl = nullptr; +#endif + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + bool has_param(const std::string &key) const; + std::string get_param_value(const std::string &key, size_t id = 0) const; + size_t get_param_value_count(const std::string &key) const; + + bool is_multipart_form_data() const; + + bool has_file(const std::string &key) const; + MultipartFormData get_file_value(const std::string &key) const; + std::vector get_file_values(const std::string &key) const; + + // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; +}; + +struct Response { + std::string version; + int status = -1; + std::string reason; + Headers headers; + std::string body; + std::string location; // Redirect location + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + void set_redirect(const std::string &url, int status = 302); + void set_content(const char *s, size_t n, const std::string &content_type); + void set_content(const std::string &s, const std::string &content_type); + + void set_content_provider( + size_t length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + Response() = default; + Response(const Response &) = default; + Response &operator=(const Response &) = default; + Response(Response &&) = default; + Response &operator=(Response &&) = default; + ~Response() { + if (content_provider_resource_releaser_) { + content_provider_resource_releaser_(content_provider_success_); + } + } + + // private members... + size_t content_length_ = 0; + ContentProvider content_provider_; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; +}; + +class Stream { +public: + virtual ~Stream() = default; + + virtual bool is_readable() const = 0; + virtual bool is_writable() const = 0; + + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; + + template + ssize_t write_format(const char *fmt, const Args &...args); + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); +}; + +class TaskQueue { +public: + TaskQueue() = default; + virtual ~TaskQueue() = default; + + virtual void enqueue(std::function fn) = 0; + virtual void shutdown() = 0; + + virtual void on_idle() {} +}; + +class ThreadPool : public TaskQueue { +public: + explicit ThreadPool(size_t n) : shutdown_(false) { + while (n) { + threads_.emplace_back(worker(*this)); + n--; + } + } + + ThreadPool(const ThreadPool &) = delete; + ~ThreadPool() override = default; + + void enqueue(std::function fn) override { + { + std::unique_lock lock(mutex_); + jobs_.push_back(std::move(fn)); + } + + cond_.notify_one(); + } + + void shutdown() override { + // Stop all worker threads... + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + + cond_.notify_all(); + + // Join... + for (auto &t : threads_) { + t.join(); + } + } + +private: + struct worker { + explicit worker(ThreadPool &pool) : pool_(pool) {} + + void operator()() { + for (;;) { + std::function fn; + { + std::unique_lock lock(pool_.mutex_); + + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); + + if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } + + fn = std::move(pool_.jobs_.front()); + pool_.jobs_.pop_front(); + } + + assert(true == static_cast(fn)); + fn(); + } + } + + ThreadPool &pool_; + }; + friend struct worker; + + std::vector threads_; + std::list> jobs_; + + bool shutdown_; + + std::condition_variable cond_; + std::mutex mutex_; +}; + +using Logger = std::function; + +using SocketOptions = std::function; + +void default_socket_options(socket_t sock); + +const char *status_message(int status); + +namespace detail { + +class MatcherBase { +public: + virtual ~MatcherBase() = default; + + // Match request path and populate its matches and + virtual bool match(Request &request) const = 0; +}; + +/** + * Captures parameters in request path and stores them in Request::path_params + * + * Capture name is a substring of a pattern from : to /. + * The rest of the pattern is matched agains the request path directly + * Parameters are captured starting from the next character after + * the end of the last matched static pattern fragment until the next /. + * + * Example pattern: + * "/path/fragments/:capture/more/fragments/:second_capture" + * Static fragments: + * "/path/fragments/", "more/fragments/" + * + * Given the following request path: + * "/path/fragments/:1/more/fragments/:2" + * the resulting capture will be + * {{"capture", "1"}, {"second_capture", "2"}} + */ +class PathParamsMatcher : public MatcherBase { +public: + PathParamsMatcher(const std::string &pattern); + + bool match(Request &request) const override; + +private: + static constexpr char marker = ':'; + // Treat segment separators as the end of path parameter capture + // Does not need to handle query parameters as they are parsed before path + // matching + static constexpr char separator = '/'; + + // Contains static path fragments to match against, excluding the '/' after + // path params + // Fragments are separated by path params + std::vector static_fragments_; + // Stores the names of the path parameters to be used as keys in the + // Request::path_params map + std::vector param_names_; +}; + +/** + * Performs std::regex_match on request path + * and stores the result in Request::matches + * + * Note that regex match is performed directly on the whole request. + * This means that wildcard patterns may match multiple path segments with /: + * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end". + */ +class RegexMatcher : public MatcherBase { +public: + RegexMatcher(const std::string &pattern) : regex_(pattern) {} + + bool match(Request &request) const override; + +private: + std::regex regex_; +}; + +ssize_t write_headers(Stream &strm, const Headers &headers); + +} // namespace detail + +class Server { +public: + using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = + std::function; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server &Get(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, HandlerWithContentReader handler); + Server &Put(const std::string &pattern, Handler handler); + Server &Put(const std::string &pattern, HandlerWithContentReader handler); + Server &Patch(const std::string &pattern, Handler handler); + Server &Patch(const std::string &pattern, HandlerWithContentReader handler); + Server &Delete(const std::string &pattern, Handler handler); + Server &Delete(const std::string &pattern, HandlerWithContentReader handler); + Server &Options(const std::string &pattern, Handler handler); + + bool set_base_dir(const std::string &dir, + const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, + Headers headers = Headers()); + bool remove_mount_point(const std::string &mount_point); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime); + Server &set_default_file_mimetype(const std::string &mime); + Server &set_file_request_handler(Handler handler); + + Server &set_error_handler(HandlerWithResponse handler); + Server &set_error_handler(Handler handler); + Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); + + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); + + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_socket_options(SocketOptions socket_options); + + Server &set_default_headers(Headers headers); + Server & + set_header_writer(std::function const &writer); + + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const std::string &host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string &host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const std::string &host, int port, int socket_flags = 0); + + bool is_running() const; + void wait_until_ready() const; + void stop(); + + std::function new_task_queue; + +protected: + bool process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request); + + std::atomic svr_sock_{INVALID_SOCKET}; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + +private: + using Handlers = + std::vector, Handler>>; + using HandlersForContentReader = + std::vector, + HandlerWithContentReader>>; + + static std::unique_ptr + make_matcher(const std::string &pattern); + + socket_t create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const; + int bind_internal(const std::string &host, int port, int socket_flags); + bool listen_internal(); + + bool routing(Request &req, Response &res, Stream &strm); + bool handle_file_request(const Request &req, Response &res, + bool head = false); + bool dispatch_request(Request &req, Response &res, const Handlers &handlers); + bool + dispatch_request_for_content_reader(Request &req, Response &res, + ContentReader content_reader, + const HandlersForContentReader &handlers); + + bool parse_request_line(const char *s, Request &req); + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary); + bool write_response(Stream &strm, bool close_connection, const Request &req, + Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges); + bool write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type); + bool read_content(Stream &strm, Request &req, Response &res); + bool + read_content_with_content_receiver(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + + virtual bool process_and_close_socket(socket_t sock); + + std::atomic is_running_{false}; + std::atomic done_{false}; + + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + std::map file_extension_and_mimetype_map_; + std::string default_file_mimetype_ = "application/octet-stream"; + Handler file_request_handler_; + + Handlers get_handlers_; + Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; + Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; + Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; + Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; + Handlers options_handlers_; + + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + Expect100ContinueHandler expect_100_continue_handler_; + + Logger logger_; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; + std::function header_writer_ = + detail::write_headers; +}; + +enum class Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + ProxyConnection, + + // For internal use only + SSLPeerCouldBeClosed_, +}; + +std::string to_string(const Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + +class Result { +public: + Result() = default; + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + + // Request Headers + bool has_request_header(const std::string &key) const; + std::string get_request_header_value(const std::string &key, + size_t id = 0) const; + uint64_t get_request_header_value_u64(const std::string &key, + size_t id = 0) const; + size_t get_request_header_value_count(const std::string &key) const; + +private: + std::unique_ptr res_; + Error err_ = Error::Unknown; + Headers request_headers_; +}; + +class ClientImpl { +public: + explicit ClientImpl(const std::string &host); + + explicit ClientImpl(const std::string &host, int port); + + explicit ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + virtual ~ClientImpl(); + + virtual bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void + set_header_writer(std::function const &writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_store(X509_STORE *ca_cert_store); + X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + +protected: + struct Socket { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + virtual bool create_and_connect_socket(Socket &socket, Error &error); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket); + void close_socket(Socket &socket); + + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error); + + void copy_settings(const ClientImpl &rhs); + + // Socket endpoint information + const std::string host_; + const int port_; + const std::string host_and_port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname-IP map + std::map addr_map_; + + // Default headers + Headers default_headers_; + + // Header writer + std::function header_writer_ = + detail::write_headers; + + // Settings + std::string client_cert_path_; + std::string client_key_path_; + + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string digest_auth_username_; + std::string digest_auth_password_; +#endif + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool url_encode_ = true; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + std::string interface_; + + std::string proxy_host_; + int proxy_port_ = -1; + + std::string proxy_basic_auth_username_; + std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + + X509_STORE *ca_cert_store_ = nullptr; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; +#endif + + Logger logger_; + +private: + bool send_(Request &req, Response &res, Error &error); + Result send_(Request &&req); + + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, Response &res); + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error); + Result send_with_content_provider( + const std::string &method, const std::string &path, + const Headers &headers, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + std::string adjust_host_string(const std::string &host) const; + + virtual bool process_socket(const Socket &socket, + std::function callback); + virtual bool is_ssl() const; +}; + +class Client { +public: + // Universal interface + explicit Client(const std::string &scheme_host_port); + + explicit Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path); + + // HTTP only interface + explicit Client(const std::string &host, int port); + + explicit Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + Client(Client &&) = default; + + ~Client(); + + bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void + set_header_writer(std::function const &writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif + +private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLServer : public Server { +public: + SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path = nullptr, + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); + + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + + SSLServer( + const std::function &setup_ssl_ctx_callback); + + ~SSLServer() override; + + bool is_valid() const override; + + SSL_CTX *ssl_context() const; + +private: + bool process_and_close_socket(socket_t sock) override; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; +}; + +class SSLClient : public ClientImpl { +public: + explicit SSLClient(const std::string &host); + + explicit SSLClient(const std::string &host, int port); + + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; + +private: + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_socket); + + bool process_socket(const Socket &socket, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + bool load_certs(); + + bool verify_host(X509 *server_cert) const; + bool verify_host_with_subject_alt_name(X509 *server_cert) const; + bool verify_host_with_common_name(X509 *server_cert) const; + bool check_host_name(const char *pattern, size_t pattern_len) const; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + + std::vector host_components_; + + long verify_result_ = 0; + + friend class ClientImpl; +}; +#endif + +/* + * Implementation of template methods. + */ + +namespace detail { + +template +inline void duration_to_sec_and_usec(const T &duration, U callback) { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(static_cast(sec), static_cast(usec)); +} + +inline uint64_t get_header_value_u64(const Headers &headers, + const std::string &key, size_t id, + uint64_t def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return std::strtoull(it->second.data(), nullptr, 10); + } + return def; +} + +} // namespace detail + +inline uint64_t Request::get_header_value_u64(const std::string &key, + size_t id) const { + return detail::get_header_value_u64(headers, key, id, 0); +} + +inline uint64_t Response::get_header_value_u64(const std::string &key, + size_t id) const { + return detail::get_header_value_u64(headers, key, id, 0); +} + +template +inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { + const auto bufsiz = 2048; + std::array buf{}; + + auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); + if (sn <= 0) { return sn; } + + auto n = static_cast(sn); + + if (n >= buf.size() - 1) { + std::vector glowable_buf(buf.size()); + + while (n >= glowable_buf.size() - 1) { + glowable_buf.resize(glowable_buf.size() * 2); + n = static_cast( + snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); + } + return write(&glowable_buf[0], n); + } else { + return write(buf.data(), n); + } +} + +inline void default_socket_options(socket_t sock) { + int yes = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&yes), sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&yes), sizeof(yes)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, + reinterpret_cast(&yes), sizeof(yes)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&yes), sizeof(yes)); +#endif +#endif +} + +inline const char *status_message(int status) { + switch (status) { + case 100: return "Continue"; + case 101: return "Switching Protocol"; + case 102: return "Processing"; + case 103: return "Early Hints"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + case 226: return "IM Used"; + case 300: return "Multiple Choice"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "unused"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Payload Too Large"; + case 414: return "URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 421: return "Misdirected Request"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 425: return "Too Early"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 451: return "Unavailable For Legal Reasons"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + + default: + case 500: return "Internal Server Error"; + } +} + +template +inline Server & +Server::set_read_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_write_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_idle_interval(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; +} + +inline std::string to_string(const Error error) { + switch (error) { + case Error::Success: return "Success (no error)"; + case Error::Connection: return "Could not establish connection"; + case Error::BindIPAddress: return "Failed to bind IP address"; + case Error::Read: return "Failed to read connection"; + case Error::Write: return "Failed to write connection"; + case Error::ExceedRedirectCount: return "Maximum redirect count exceeded"; + case Error::Canceled: return "Connection handling canceled"; + case Error::SSLConnection: return "SSL connection failed"; + case Error::SSLLoadingCerts: return "SSL certificate loading failed"; + case Error::SSLServerVerification: return "SSL server verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: return "Compression failed"; + case Error::ConnectionTimeout: return "Connection timed out"; + case Error::ProxyConnection: return "Proxy connection failed"; + case Error::Unknown: return "Unknown"; + default: break; + } + + return "Invalid"; +} + +inline std::ostream &operator<<(std::ostream &os, const Error &obj) { + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; +} + +inline uint64_t Result::get_request_header_value_u64(const std::string &key, + size_t id) const { + return detail::get_header_value_u64(request_headers_, key, id, 0); +} + +template +inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); +} + +template +inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { + cli_->set_connection_timeout(duration); +} + +template +inline void +Client::set_read_timeout(const std::chrono::duration &duration) { + cli_->set_read_timeout(duration); +} + +template +inline void +Client::set_write_timeout(const std::chrono::duration &duration) { + cli_->set_write_timeout(duration); +} + +/* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + +std::string hosted_at(const std::string &hostname); + +void hosted_at(const std::string &hostname, std::vector &addrs); + +std::string append_query_params(const std::string &path, const Params ¶ms); + +std::pair make_range_header(Ranges ranges); + +std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false); + +namespace detail { + +std::string encode_query_param(const std::string &value); + +std::string decode_url(const std::string &s, bool convert_plus_to_space); + +void read_file(const std::string &path, std::string &out); + +std::string trim_copy(const std::string &s); + +void split(const char *b, const char *e, char d, + std::function fn); + +bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback); + +socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error); + +const char *get_header_value(const Headers &headers, const std::string &key, + size_t id = 0, const char *def = nullptr); + +std::string params_to_query_str(const Params ¶ms); + +void parse_query_text(const std::string &s, Params ¶ms); + +bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary); + +bool parse_range_header(const std::string &s, Ranges &ranges); + +int close_socket(socket_t sock); + +ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); + +ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); + +enum class EncodingType { None = 0, Gzip, Brotli }; + +EncodingType encoding_type(const Request &req, const Response &res); + +class BufferStream : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +class compressor { +public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor : public compressor { +public: + virtual ~nocompressor() = default; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override; +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor : public compressor { +public: + gzip_compressor(); + ~gzip_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor : public compressor { +public: + brotli_compressor(); + ~brotli_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size); + const char *ptr() const; + size_t size() const; + bool end_with_crlf() const; + bool getline(); + +private: + void append(char c); + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string glowable_buffer_; +}; + +class mmap { +public: + mmap(const char *path); + ~mmap(); + + bool open(const char *path); + void close(); + + bool is_open() const; + size_t size() const; + const char *data() const; + +private: +#if defined(_WIN32) + HANDLE hFile_; + HANDLE hMapping_; +#else + int fd_; +#endif + size_t size_; + void *addr_; +}; + +} // namespace detail + +// ---------------------------------------------------------------------------- + +/* + * Implementation that will be part of the .cc file if split into .h + .cc. + */ + +namespace detail { + +inline bool is_hex(char c, int &v) { + if (0x20 <= c && isdigit(c)) { + v = c - '0'; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } + return false; +} + +inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, + int &val) { + if (i >= s.size()) { return false; } + + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { return false; } + auto v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; + } else { + return false; + } + } + return true; +} + +inline std::string from_i_to_hex(size_t n) { + static const auto charset = "0123456789abcdef"; + std::string ret; + do { + ret = charset[n & 15] + ret; + n >>= 4; + } while (n > 0); + return ret; +} + +inline size_t to_utf8(int code, char *buff) { + if (code < 0x0080) { + buff[0] = (code & 0x7F); + return 1; + } else if (code < 0x0800) { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } else if (code < 0xD800) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } else if (code < 0x10000) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0x110000) { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; +} + +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c +inline std::string base64_encode(const std::string &in) { + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(in.size()); + + auto val = 0; + auto valb = -6; + + for (auto c : in) { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } + + while (out.size() % 4) { + out.push_back('='); + } + + return out; +} + +inline bool is_file(const std::string &path) { +#ifdef _WIN32 + return _access_s(path.c_str(), 0) == 0; +#else + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); +#endif +} + +inline bool is_dir(const std::string &path) { + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); +} + +inline bool is_valid_path(const std::string &path) { + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + + while (i < path.size()) { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') { + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) { + ; + } else if (!path.compare(beg, len, "..")) { + if (level == 0) { return false; } + level--; + } else { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + } + + return true; +} + +inline std::string encode_query_param(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string encode_url(const std::string &s) { + std::string result; + result.reserve(s.size()); + + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': result += "%20"; break; + case '+': result += "%2B"; break; + case '\r': result += "%0D"; break; + case '\n': result += "%0A"; break; + case '\'': result += "%27"; break; + case ',': result += "%2C"; break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': result += "%3B"; break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline std::string decode_url(const std::string &s, + bool convert_plus_to_space) { + std::string result; + + for (size_t i = 0; i < s.size(); i++) { + if (s[i] == '%' && i + 1 < s.size()) { + if (s[i + 1] == 'u') { + auto val = 0; + if (from_hex_to_i(s, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } else { + result += s[i]; + } + } else { + auto val = 0; + if (from_hex_to_i(s, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast(val); + i += 2; // '00' + } else { + result += s[i]; + } + } + } else if (convert_plus_to_space && s[i] == '+') { + result += ' '; + } else { + result += s[i]; + } + } + + return result; +} + +inline void read_file(const std::string &path, std::string &out) { + std::ifstream fs(path, std::ios_base::binary); + fs.seekg(0, std::ios_base::end); + auto size = fs.tellg(); + fs.seekg(0); + out.resize(static_cast(size)); + fs.read(&out[0], static_cast(size)); +} + +inline std::string file_extension(const std::string &path) { + std::smatch m; + static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { return m[1].str(); } + return std::string(); +} + +inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } + +inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); +} + +inline std::string trim_copy(const std::string &s) { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); +} + +inline std::string trim_double_quotes_copy(const std::string &s) { + if (s.length() >= 2 && s.front() == '"' && s.back() == '"') { + return s.substr(1, s.size() - 2); + } + return s; +} + +inline void split(const char *b, const char *e, char d, + std::function fn) { + size_t i = 0; + size_t beg = 0; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + beg = i + 1; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } +} + +inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} + +inline const char *stream_line_reader::ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); + } +} + +inline size_t stream_line_reader::size() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return glowable_buffer_.size(); + } +} + +inline bool stream_line_reader::end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; +} + +inline bool stream_line_reader::getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); + + for (size_t i = 0;; i++) { + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { + return false; + } else { + break; + } + } + + append(byte); + + if (byte == '\n') { break; } + } + + return true; +} + +inline void stream_line_reader::append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + glowable_buffer_ += c; + } +} + +inline mmap::mmap(const char *path) +#if defined(_WIN32) + : hFile_(NULL), hMapping_(NULL) +#else + : fd_(-1) +#endif + , + size_(0), addr_(nullptr) { + open(path); +} + +inline mmap::~mmap() { close(); } + +inline bool mmap::open(const char *path) { + close(); + +#if defined(_WIN32) + hFile_ = ::CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (hFile_ == INVALID_HANDLE_VALUE) { return false; } + + size_ = ::GetFileSize(hFile_, NULL); + + hMapping_ = ::CreateFileMapping(hFile_, NULL, PAGE_READONLY, 0, 0, NULL); + + if (hMapping_ == NULL) { + close(); + return false; + } + + addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0); +#else + fd_ = ::open(path, O_RDONLY); + if (fd_ == -1) { return false; } + + struct stat sb; + if (fstat(fd_, &sb) == -1) { + close(); + return false; + } + size_ = static_cast(sb.st_size); + + addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0); +#endif + + if (addr_ == nullptr) { + close(); + return false; + } + + return true; +} + +inline bool mmap::is_open() const { return addr_ != nullptr; } + +inline size_t mmap::size() const { return size_; } + +inline const char *mmap::data() const { return (const char *)addr_; } + +inline void mmap::close() { +#if defined(_WIN32) + if (addr_) { + ::UnmapViewOfFile(addr_); + addr_ = nullptr; + } + + if (hMapping_) { + ::CloseHandle(hMapping_); + hMapping_ = NULL; + } + + if (hFile_ != INVALID_HANDLE_VALUE) { + ::CloseHandle(hFile_); + hFile_ = INVALID_HANDLE_VALUE; + } +#else + if (addr_ != nullptr) { + munmap(addr_, size_); + addr_ = nullptr; + } + + if (fd_ != -1) { + ::close(fd_); + fd_ = -1; + } +#endif + size_ = 0; +} +inline int close_socket(socket_t sock) { +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +template inline ssize_t handle_EINTR(T fn) { + ssize_t res = 0; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { continue; } + break; + } + return res; +} + +inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) { + return handle_EINTR([&]() { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, + int flags) { + return handle_EINTR([&]() { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + }); +#endif +} + +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + }); +#endif +} + +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + + if (poll_res == 0) { return Error::ConnectionTimeout; } + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return Error::Connection; } +#endif + + fd_set fdsr; + FD_ZERO(&fdsr); + FD_SET(sock, &fdsr); + + auto fdsw = fdsr; + auto fdse = fdsr; + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); + + if (ret == 0) { return Error::ConnectionTimeout; } + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + return Error::Connection; +#endif +} + +inline bool is_socket_alive(socket_t sock) { + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) { + return true; + } else if (val < 0 && errno == EBADF) { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; +} + +class SocketStream : public Stream { +public: + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec); + ~SocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + + static const size_t read_buff_size_ = 1024 * 4; +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLSocketStream : public Stream { +public: + SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + SSL *ssl_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; +}; +#endif + +inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { + using namespace std::chrono; + auto start = steady_clock::now(); + while (true) { + auto val = select_read(sock, 0, 10000); + if (val < 0) { + return false; + } else if (val == 0) { + auto current = steady_clock::now(); + auto duration = duration_cast(current - start); + auto timeout = keep_alive_timeout_sec * 1000; + if (duration.count() > timeout) { return false; } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + return true; + } + } +} + +template +inline bool +process_server_socket_core(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (svr_sock != INVALID_SOCKET && count > 0 && + keep_alive(sock, keep_alive_timeout_sec)) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } + return ret; +} + +template +inline bool +process_server_socket(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +inline int shutdown_socket(socket_t sock) { +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif +} + +template +socket_t create_socket(const std::string &host, const std::string &ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + SocketOptions socket_options, + BindOrConnect bind_or_connect) { + // Get address info + const char *node = nullptr; + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (!ip.empty()) { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + } else { + if (!host.empty()) { node = host.c_str(); } + hints.ai_family = address_family; + hints.ai_flags = socket_flags; + } + +#ifndef _WIN32 + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) return INVALID_SOCKET; + + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + std::copy(host.begin(), host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); + + fcntl(sock, F_SETFD, FD_CLOEXEC); + if (socket_options) { socket_options(sock); } + + if (!bind_or_connect(sock, hints)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + + auto service = std::to_string(port); + + if (getaddrinfo(node, service.c_str(), &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return INVALID_SOCKET; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket +#ifdef _WIN32 + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + if (sock == INVALID_SOCKET) { continue; } + +#ifndef _WIN32 + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } +#endif + + if (tcp_nodelay) { + auto yes = 1; +#ifdef _WIN32 + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast(&yes), sizeof(yes)); +#else + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast(&yes), sizeof(yes)); +#endif + } + + if (socket_options) { socket_options(sock); } + + if (rp->ai_family == AF_INET6) { + auto no = 0; +#ifdef _WIN32 + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + reinterpret_cast(&no), sizeof(no)); +#else + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + reinterpret_cast(&no), sizeof(no)); +#endif + } + + // bind or connect + if (bind_or_connect(sock, *rp)) { + freeaddrinfo(result); + return sock; + } + + close_socket(sock); + } + + freeaddrinfo(result); + return INVALID_SOCKET; +} + +inline void set_nonblocking(socket_t sock, bool nonblocking) { +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif +} + +inline bool is_connection_error() { +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif +} + +inline bool bind_ip_address(socket_t sock, const std::string &host) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + ret = true; + break; + } + } + + freeaddrinfo(result); + return ret; +} + +#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__ +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP +inline std::string if2ip(int address_family, const std::string &ifn) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + std::string addr_candidate; + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + freeifaddrs(ifap); + return std::string(buf, INET_ADDRSTRLEN); + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } else { + freeifaddrs(ifap); + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } + } + } + } + freeifaddrs(ifap); + return addr_candidate; +} +#endif + +inline socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { + auto sock = create_socket( + host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai) -> bool { + if (!intf.empty()) { +#ifdef USE_IF2IP + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { ip_from_if = intf; } + if (!bind_ip_address(sock2, ip_from_if.c_str())) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock2, true); + + auto ret = + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) { + if (is_connection_error()) { + error = Error::Connection; + return false; + } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { return false; } + } + + set_nonblocking(sock2, false); + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec * 1000 + + read_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec); + tv.tv_usec = static_cast(read_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec * 1000 + + write_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec); + tv.tv_usec = static_cast(write_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + + error = Error::Success; + return true; + }); + + if (sock != INVALID_SOCKET) { + error = Error::Success; + } else { + if (error == Error::Success) { error = Error::Connection; } + } + + return sock; +} + +inline bool get_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, int &port) { + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return false; + } + + std::array ipstr{}; + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + return false; + } + + ip = ipstr.data(); + return true; +} + +inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) { + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator"" _t(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + +inline std::string +find_content_type(const std::string &path, + const std::map &user_data, + const std::string &default_content_type) { + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { return it->second; } + + using udl::operator""_t; + + switch (str2tag(ext)) { + default: return default_content_type; + + case "css"_t: return "text/css"; + case "csv"_t: return "text/csv"; + case "htm"_t: + case "html"_t: return "text/html"; + case "js"_t: + case "mjs"_t: return "text/javascript"; + case "txt"_t: return "text/plain"; + case "vtt"_t: return "text/vtt"; + + case "apng"_t: return "image/apng"; + case "avif"_t: return "image/avif"; + case "bmp"_t: return "image/bmp"; + case "gif"_t: return "image/gif"; + case "png"_t: return "image/png"; + case "svg"_t: return "image/svg+xml"; + case "webp"_t: return "image/webp"; + case "ico"_t: return "image/x-icon"; + case "tif"_t: return "image/tiff"; + case "tiff"_t: return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: return "image/jpeg"; + + case "mp4"_t: return "video/mp4"; + case "mpeg"_t: return "video/mpeg"; + case "webm"_t: return "video/webm"; + + case "mp3"_t: return "audio/mp3"; + case "mpga"_t: return "audio/mpeg"; + case "weba"_t: return "audio/webm"; + case "wav"_t: return "audio/wave"; + + case "otf"_t: return "font/otf"; + case "ttf"_t: return "font/ttf"; + case "woff"_t: return "font/woff"; + case "woff2"_t: return "font/woff2"; + + case "7z"_t: return "application/x-7z-compressed"; + case "atom"_t: return "application/atom+xml"; + case "pdf"_t: return "application/pdf"; + case "json"_t: return "application/json"; + case "rss"_t: return "application/rss+xml"; + case "tar"_t: return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: return "application/xhtml+xml"; + case "xslt"_t: return "application/xslt+xml"; + case "xml"_t: return "application/xml"; + case "gz"_t: return "application/gzip"; + case "zip"_t: return "application/zip"; + case "wasm"_t: return "application/wasm"; + } +} + +inline bool can_compress_content_type(const std::string &content_type) { + using udl::operator""_t; + + auto tag = str2tag(content_type); + + switch (tag) { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/json"_t: + case "application/xml"_t: + case "application/protobuf"_t: + case "application/xhtml+xml"_t: return true; + + default: + return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t; + } +} + +inline EncodingType encoding_type(const Request &req, const Response &res) { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } + + const auto &s = req.get_header_value("Accept-Encoding"); + (void)(s); + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif + + return EncodingType::None; +} + +inline bool nocompressor::compress(const char *data, size_t data_length, + bool /*last*/, Callback callback) { + if (!data_length) { return true; } + return callback(data, data_length); +} + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +inline gzip_compressor::gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; +} + +inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } + +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + assert(is_valid_); + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; + auto ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); + assert(strm_.avail_in == 0); + } while (data_length > 0); + + return true; +} + +inline gzip_decompressor::gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} + +inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } + +inline bool gzip_decompressor::is_valid() const { return is_valid_; } + +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + assert(is_valid_); + + auto ret = Z_OK; + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + std::array buff{}; + while (strm_.avail_in > 0 && ret == Z_OK) { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = inflate(&strm_, Z_NO_FLUSH); + + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: inflateEnd(&strm_); return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + if (ret != Z_OK && ret != Z_STREAM_END) return false; + + } while (data_length > 0); + + return true; +} +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} + +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} + +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; +} + +inline brotli_decompressor::brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } +} + +inline bool brotli_decompressor::is_valid() const { return decoder_s; } + +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + auto next_in = reinterpret_cast(data); + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} +#endif + +inline bool has_header(const Headers &headers, const std::string &key) { + return headers.find(key) != headers.end(); +} + +inline const char *get_header_value(const Headers &headers, + const std::string &key, size_t id, + const char *def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } + return def; +} + +inline bool compare_case_ignore(const std::string &a, const std::string &b) { + if (a.size() != b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; +} + +template +inline bool parse_header(const char *beg, const char *end, T fn) { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p < end) { + auto key = std::string(beg, key_end); + auto val = compare_case_ignore(key, "Location") + ? std::string(p, end) + : decode_url(std::string(p, end), false); + fn(std::move(key), std::move(val)); + return true; + } + + return false; +} + +inline bool read_headers(Stream &strm, Headers &headers) { + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + for (;;) { + if (!line_reader.getline()) { return false; } + + // Check if the line ends with CRLF. + auto line_terminator_len = 2; + if (line_reader.end_with_crlf()) { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { break; } +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + } else { + // Blank line indicates end of headers. + if (line_reader.size() == 1) { break; } + line_terminator_len = 1; + } +#else + } else { + continue; // Skip invalid line. + } +#endif + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + headers.emplace(std::move(key), std::move(val)); + }); + } + + return true; +} + +inline bool read_content_with_length(Stream &strm, uint64_t len, + Progress progress, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); + + if (progress) { + if (!progress(r, len)) { return false; } + } + } + + return true; +} + +inline void skip_content_with_length(Stream &strm, uint64_t len) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return; } + r += static_cast(n); + } +} + +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n < 0) { + return false; + } else if (n == 0) { + return true; + } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); + } + + return true; +} + +template +inline bool read_content_chunked(Stream &strm, T &x, + ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + + if (!line_reader.getline()) { return false; } + + unsigned long chunk_len; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return false; } + if (chunk_len == ULONG_MAX) { return false; } + + if (chunk_len == 0) { break; } + + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { + return false; + } + + if (!line_reader.getline()) { return false; } + + if (strcmp(line_reader.ptr(), "\r\n")) { return false; } + + if (!line_reader.getline()) { return false; } + } + + assert(chunk_len == 0); + + // Trailer + if (!line_reader.getline()) { return false; } + + while (strcmp(line_reader.ptr(), "\r\n")) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + x.headers.emplace(std::move(key), std::move(val)); + }); + + if (!line_reader.getline()) { return false; } + } + + return true; +} + +inline bool is_chunked_transfer_encoding(const Headers &headers) { + return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""), + "chunked"); +} + +template +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (encoding == "gzip" || encoding == "deflate") { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } else if (encoding.find("br") != std::string::npos) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } + + if (decompressor) { + if (decompressor->is_valid()) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); + }; + return callback(std::move(out)); + } else { + status = 500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); +} + +template +bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + ret = read_content_chunked(strm, x, out); + } else if (!has_header(x.headers, "Content-Length")) { + ret = read_content_without_length(strm, out); + } else { + auto len = get_header_value_u64(x.headers, "Content-Length", 0, 0); + if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { status = exceed_payload_max_length ? 413 : 400; } + return ret; + }); +} // namespace detail + +inline ssize_t write_headers(Stream &strm, const Headers &headers) { + ssize_t write_len = 0; + for (const auto &x : headers) { + auto len = + strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); + if (len < 0) { return len; } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { return len; } + write_len += len; + return write_len; +} + +inline bool write_data(Stream &strm, const char *d, size_t l) { + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + size_t end_offset = offset + length; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + if (strm.is_writable() && write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } + } + return ok; + }; + + while (offset < end_offset && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); +} + +template +inline bool +write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + offset += l; + if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; } + } + return ok; + }; + + data_sink.done = [&](void) { data_available = false; }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + return false; + } else if (!content_provider(offset, 0, data_sink)) { + return false; + } else if (!ok) { + return false; + } + } + return true; +} + +template +inline bool +write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; + + std::string payload; + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + } + } + } else { + ok = false; + } + } + return ok; + }; + + auto done_with_trailer = [&](const Headers *trailer) { + if (!ok) { return; } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + + static const std::string done_marker("0\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) { + ok = false; + } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + static const std::string crlf("\r\n"); + if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } + }; + + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); + }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); +} + +template +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + + if (res.location.empty()) res.location = location; + } + return ret; +} + +inline std::string params_to_query_str(const Params ¶ms) { + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { query += "&"; } + query += it->first; + query += "="; + query += encode_query_param(it->second); + } + return query; +} + +inline void parse_query_text(const std::string &s, Params ¶ms) { + std::set cache; + split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(decode_url(key, true), decode_url(val, true)); + } + }); +} + +inline bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary) { + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); + if (pos == std::string::npos) { return false; } + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = trim_double_quotes_copy(content_type.substr(beg, end - beg)); + return !boundary.empty(); +} + +inline void parse_disposition_params(const std::string &s, Params ¶ms) { + std::set cache; + split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(trim_double_quotes_copy((key)), + trim_double_quotes_copy((val))); + } + }); +} + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +inline bool parse_range_header(const std::string &s, Ranges &ranges) { +#else +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { +#endif + static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); + std::smatch m; + if (std::regex_match(s, m, re_first_range)) { + auto pos = static_cast(m.position(1)); + auto len = static_cast(m.length(1)); + auto all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { + if (!all_valid_ranges) return; + static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))"); + std::cmatch cm; + if (std::regex_match(b, e, cm, re_another_range)) { + ssize_t first = -1; + if (!cm.str(1).empty()) { + first = static_cast(std::stoll(cm.str(1))); + } + + ssize_t last = -1; + if (!cm.str(2).empty()) { + last = static_cast(std::stoll(cm.str(2))); + } + + if (first != -1 && last != -1 && first > last) { + all_valid_ranges = false; + return; + } + ranges.emplace_back(std::make_pair(first, last)); + } + }); + return all_valid_ranges; + } + return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +} +#else +} catch (...) { return false; } +#endif + +class MultipartFormDataParser { +public: + MultipartFormDataParser() = default; + + void set_boundary(std::string &&boundary) { + boundary_ = boundary; + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } + + bool is_valid() const { return is_valid_; } + + bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, + const MultipartContentHeader &header_callback) { + + buf_append(buf, n); + + while (buf_size() > 0) { + switch (state_) { + case 0: { // Initial boundary + buf_erase(buf_find(dash_boundary_crlf_)); + if (dash_boundary_crlf_.size() > buf_size()) { return true; } + if (!buf_start_with(dash_boundary_crlf_)) { return false; } + buf_erase(dash_boundary_crlf_.size()); + state_ = 1; + break; + } + case 1: { // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: { // Headers + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + while (pos < buf_size()) { + // Empty line + if (pos == 0) { + if (!header_callback(file_)) { + is_valid_ = false; + return false; + } + buf_erase(crlf_.size()); + state_ = 3; + break; + } + + static const std::string header_name = "content-type:"; + const auto header = buf_head(pos); + if (start_with_case_ignore(header, header_name)) { + file_.content_type = trim_copy(header.substr(header_name.size())); + } else { + static const std::regex re_content_disposition( + R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~", + std::regex_constants::icase); + + std::smatch m; + if (std::regex_match(header, m, re_content_disposition)) { + Params params; + parse_disposition_params(m[1], params); + + auto it = params.find("name"); + if (it != params.end()) { + file_.name = it->second; + } else { + is_valid_ = false; + return false; + } + + it = params.find("filename"); + if (it != params.end()) { file_.filename = it->second; } + + it = params.find("filename*"); + if (it != params.end()) { + // Only allow UTF-8 enconnding... + static const std::regex re_rfc5987_encoding( + R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase); + + std::smatch m2; + if (std::regex_match(it->second, m2, re_rfc5987_encoding)) { + file_.filename = decode_url(m2[1], false); // override... + } else { + is_valid_ = false; + return false; + } + } + } else { + is_valid_ = false; + return false; + } + } + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); + } + if (state_ != 3) { return true; } + break; + } + case 3: { // Body + if (crlf_dash_boundary_.size() > buf_size()) { return true; } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) { + if (!content_callback(buf_data(), pos)) { + is_valid_ = false; + return false; + } + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } else { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) { + if (!content_callback(buf_data(), len)) { + is_valid_ = false; + return false; + } + buf_erase(len); + } + return true; + } + break; + } + case 4: { // Boundary + if (crlf_.size() > buf_size()) { return true; } + if (buf_start_with(crlf_)) { + buf_erase(crlf_.size()); + state_ = 1; + } else { + if (dash_.size() > buf_size()) { return true; } + if (buf_start_with(dash_)) { + buf_erase(dash_.size()); + is_valid_ = true; + buf_erase(buf_size()); // Remove epilogue + } else { + return true; + } + } + break; + } + } + } + + return true; + } + +private: + void clear_file_info() { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + } + + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; + + size_t state_ = 0; + bool is_valid_ = false; + MultipartFormData file_; + + // Buffer + bool start_with(const std::string &a, size_t spos, size_t epos, + const std::string &b) const { + if (epos - spos < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + spos] != b[i]) { return false; } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char *buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string &s) const { + return start_with(buf_, buf_spos_, buf_epos_, s); + } + + size_t buf_find(const std::string &s) const { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) { + auto pos = off; + while (true) { + if (pos == buf_epos_) { return buf_size(); } + if (buf_[pos] == c) { break; } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { return buf_size(); } + + if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char *data, size_t n) { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) { + for (size_t i = 0; i < remaining_size; i++) { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); } + + for (size_t i = 0; i < n; i++) { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; +}; + +inline std::string to_lower(const char *beg, const char *end) { + std::string out; + auto it = beg; + while (it != end) { + out += static_cast(::tolower(*it)); + it++; + } + return out; +} + +inline std::string make_multipart_data_boundary() { + static const char data[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + std::random_device seed_gen; + + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + std::mt19937 engine(seed_sequence); + + std::string result = "--cpp-httplib-multipart-data-"; + + for (auto i = 0; i < 16; i++) { + result += data[engine() % (sizeof(data) - 1)]; + } + + return result; +} + +inline bool is_multipart_boundary_chars_valid(const std::string &boundary) { + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) { + auto c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + valid = false; + break; + } + } + return valid; +} + +template +inline std::string +serialize_multipart_formdata_item_begin(const T &item, + const std::string &boundary) { + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + + return body; +} + +inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } + +inline std::string +serialize_multipart_formdata_finish(const std::string &boundary) { + return "--" + boundary + "--\r\n"; +} + +inline std::string +serialize_multipart_formdata_get_content_type(const std::string &boundary) { + return "multipart/form-data; boundary=" + boundary; +} + +inline std::string +serialize_multipart_formdata(const MultipartFormDataItems &items, + const std::string &boundary, bool finish = true) { + std::string body; + + for (const auto &item : items) { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) body += serialize_multipart_formdata_finish(boundary); + + return body; +} + +inline std::pair +get_range_offset_and_length(const Request &req, size_t content_length, + size_t index) { + auto r = req.ranges[index]; + + if (r.first == -1 && r.second == -1) { + return std::make_pair(0, content_length); + } + + auto slen = static_cast(content_length); + + if (r.first == -1) { + r.first = (std::max)(static_cast(0), slen - r.second); + r.second = slen - 1; + } + + if (r.second == -1) { r.second = slen - 1; } + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); +} + +inline std::string +make_content_range_header_field(const std::pair &range, + size_t content_length) { + std::string field = "bytes "; + if (range.first != -1) { field += std::to_string(range.first); } + field += "-"; + if (range.second != -1) { field += std::to_string(range.second); } + field += "/"; + field += std::to_string(content_length); + return field; +} + +template +bool process_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + SToken stoken, CToken ctoken, + Content content) { + for (size_t i = 0; i < req.ranges.size(); i++) { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + ctoken("Content-Range: "); + const auto &range = req.ranges[i]; + stoken(make_content_range_header_field(range, res.content_length_)); + ctoken("\r\n"); + ctoken("\r\n"); + + auto offsets = get_range_offset_and_length(req, res.content_length_, i); + auto offset = offsets.first; + auto length = offsets.second; + if (!content(offset, length)) { return false; } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--"); + + return true; +} + +inline bool make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + std::string &data) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data += token; }, + [&](const std::string &token) { data += token; }, + [&](size_t offset, size_t length) { + if (offset < res.body.size()) { + data += res.body.substr(offset, length); + return true; + } + return false; + }); +} + +inline size_t +get_multipart_ranges_data_length(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type) { + size_t data_length = 0; + + process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data_length += token.size(); }, + [&](const std::string &token) { data_length += token.size(); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); + + return data_length; +} + +template +inline bool write_multipart_ranges_data(Stream &strm, const Request &req, + Response &res, + const std::string &boundary, + const std::string &content_type, + const T &is_shutting_down) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { strm.write(token); }, + [&](const std::string &token) { strm.write(token); }, + [&](size_t offset, size_t length) { + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + }); +} + +inline std::pair +get_range_offset_and_length(const Request &req, const Response &res, + size_t index) { + auto r = req.ranges[index]; + + if (r.second == -1) { + r.second = static_cast(res.content_length_) - 1; + } + + return std::make_pair(r.first, r.second - r.first + 1); +} + +inline bool expect_content(const Request &req) { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "PRI" || req.method == "DELETE") { + return true; + } + // TODO: check if Content-Length is set + return false; +} + +inline bool has_crlf(const std::string &s) { + auto p = s.c_str(); + while (*p) { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::string message_digest(const std::string &s, const EVP_MD *algo) { + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); + + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; + + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') + << static_cast(hash[i]); + } + + return ss.str(); +} + +inline std::string MD5(const std::string &s) { + return message_digest(s, EVP_md5()); +} + +inline std::string SHA_256(const std::string &s) { + return message_digest(s, EVP_sha256()); +} + +inline std::string SHA_512(const std::string &s) { + return message_digest(s, EVP_sha512()); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + if (!hStore) { return false; } + + auto result = false; + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); + return true; +} + +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (auto i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // TARGET_OS_OSX +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 +class WSInit { +public: + WSInit() { + WSADATA wsaData; + if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true; + } + + ~WSInit() { + if (is_valid_) WSACleanup(); + } + + bool is_valid_ = false; +}; + +static WSInit wsinit_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} +#endif + +inline bool parse_www_authenticate(const Response &res, + std::map &auth, + bool is_proxy) { + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) { + static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) { + auto type = s.substr(0, pos); + if (type == "Basic") { + return false; + } else if (type == "Digest") { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) { + const auto &m = *i; + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); + auth[key] = val; + } + return true; + } + } + } + return false; +} + +// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240 +inline std::string random_string(size_t length) { + auto randchar = []() -> char { + const char charset[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + const size_t max_index = (sizeof(charset) - 1); + return charset[static_cast(std::rand()) % max_index]; + }; + std::string str(length, 0); + std::generate_n(str.begin(), length, randchar); + return str; +} + +class ContentProviderAdapter { +public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) + : content_provider_(content_provider) {} + + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } + +private: + ContentProviderWithoutLength content_provider_; +}; + +} // namespace detail + +inline std::string hosted_at(const std::string &hostname) { + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { return std::string(); } + return addrs[0]; +} + +inline void hosted_at(const std::string &hostname, + std::vector &addrs) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &addr = + *reinterpret_cast(rp->ai_addr); + std::string ip; + auto dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) { + addrs.push_back(ip); + } + } + + freeaddrinfo(result); +} + +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + +// Header utilities +inline std::pair make_range_header(Ranges ranges) { + std::string field = "bytes="; + auto i = 0; + for (auto r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, bool is_proxy) { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +inline std::pair +make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +// Request implementation +inline bool Request::has_header(const std::string &key) const { + return detail::has_header(headers, key); +} + +inline std::string Request::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Request::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Request::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline bool Request::has_param(const std::string &key) const { + return params.find(key) != params.end(); +} + +inline std::string Request::get_param_value(const std::string &key, + size_t id) const { + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_param_value_count(const std::string &key) const { + auto r = params.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline bool Request::is_multipart_form_data() const { + const auto &content_type = get_header_value("Content-Type"); + return !content_type.rfind("multipart/form-data", 0); +} + +inline bool Request::has_file(const std::string &key) const { + return files.find(key) != files.end(); +} + +inline MultipartFormData Request::get_file_value(const std::string &key) const { + auto it = files.find(key); + if (it != files.end()) { return it->second; } + return MultipartFormData(); +} + +inline std::vector +Request::get_file_values(const std::string &key) const { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; +} + +// Response implementation +inline bool Response::has_header(const std::string &key) const { + return headers.find(key) != headers.end(); +} + +inline std::string Response::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Response::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Response::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline void Response::set_redirect(const std::string &url, int stat) { + if (!detail::has_crlf(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } else { + this->status = 302; + } + } +} + +inline void Response::set_content(const char *s, size_t n, + const std::string &content_type) { + body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content(const std::string &s, + const std::string &content_type) { + set_content(s.data(), s.size(), content_type); +} + +inline void Response::set_content_provider( + size_t in_length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = in_length; + if (in_length > 0) { content_provider_ = std::move(provider); } + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void Response::set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void Response::set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = true; +} + +// Result implementation +inline bool Result::has_request_header(const std::string &key) const { + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, ""); +} + +inline size_t +Result::get_request_header_value_count(const std::string &key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Stream implementation +inline ssize_t Stream::write(const char *ptr) { + return write(ptr, strlen(ptr)); +} + +inline ssize_t Stream::write(const std::string &s) { + return write(s.data(), s.size()); +} + +namespace detail { + +// Socket stream implementation +inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {} + +inline SocketStream::~SocketStream() {} + +inline bool SocketStream::is_readable() const { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SocketStream::read(char *ptr, size_t size) { +#ifdef _WIN32 + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } else { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + + if (!is_readable()) { return -1; } + + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + return n; + } else if (n <= static_cast(size)) { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } else { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } else { + return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); + } +} + +inline ssize_t SocketStream::write(const char *ptr, size_t size) { + if (!is_writable()) { return -1; } + +#if defined(_WIN32) && !defined(_WIN64) + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); +} + +inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + return detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + return detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const { return sock_; } + +// Buffer stream implementation +inline bool BufferStream::is_readable() const { return true; } + +inline bool BufferStream::is_writable() const { return true; } + +inline ssize_t BufferStream::read(char *ptr, size_t size) { +#if defined(_MSC_VER) && _MSC_VER < 1910 + auto len_read = buffer._Copy_s(ptr, size, size, position); +#else + auto len_read = buffer.copy(ptr, size, position); +#endif + position += static_cast(len_read); + return static_cast(len_read); +} + +inline ssize_t BufferStream::write(const char *ptr, size_t size) { + buffer.append(ptr, size); + return static_cast(size); +} + +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline socket_t BufferStream::socket() const { return 0; } + +inline const std::string &BufferStream::get_buffer() const { return buffer; } + +inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) { + // One past the last ending position of a path param substring + std::size_t last_param_end = 0; + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + // Needed to ensure that parameter names are unique during matcher + // construction + // If exceptions are disabled, only last duplicate path + // parameter will be set + std::unordered_set param_name_set; +#endif + + while (true) { + const auto marker_pos = pattern.find(marker, last_param_end); + if (marker_pos == std::string::npos) { break; } + + static_fragments_.push_back( + pattern.substr(last_param_end, marker_pos - last_param_end)); + + const auto param_name_start = marker_pos + 1; + + auto sep_pos = pattern.find(separator, param_name_start); + if (sep_pos == std::string::npos) { sep_pos = pattern.length(); } + + auto param_name = + pattern.substr(param_name_start, sep_pos - param_name_start); + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + if (param_name_set.find(param_name) != param_name_set.cend()) { + std::string msg = "Encountered path parameter '" + param_name + + "' multiple times in route pattern '" + pattern + "'."; + throw std::invalid_argument(msg); + } +#endif + + param_names_.push_back(std::move(param_name)); + + last_param_end = sep_pos + 1; + } + + if (last_param_end < pattern.length()) { + static_fragments_.push_back(pattern.substr(last_param_end)); + } +} + +inline bool PathParamsMatcher::match(Request &request) const { + request.matches = std::smatch(); + request.path_params.clear(); + request.path_params.reserve(param_names_.size()); + + // One past the position at which the path matched the pattern last time + std::size_t starting_pos = 0; + for (size_t i = 0; i < static_fragments_.size(); ++i) { + const auto &fragment = static_fragments_[i]; + + if (starting_pos + fragment.length() > request.path.length()) { + return false; + } + + // Avoid unnecessary allocation by using strncmp instead of substr + + // comparison + if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(), + fragment.length()) != 0) { + return false; + } + + starting_pos += fragment.length(); + + // Should only happen when we have a static fragment after a param + // Example: '/users/:id/subscriptions' + // The 'subscriptions' fragment here does not have a corresponding param + if (i >= param_names_.size()) { continue; } + + auto sep_pos = request.path.find(separator, starting_pos); + if (sep_pos == std::string::npos) { sep_pos = request.path.length(); } + + const auto ¶m_name = param_names_[i]; + + request.path_params.emplace( + param_name, request.path.substr(starting_pos, sep_pos - starting_pos)); + + // Mark everythin up to '/' as matched + starting_pos = sep_pos + 1; + } + // Returns false if the path is longer than the pattern + return starting_pos >= request.path.length(); +} + +inline bool RegexMatcher::match(Request &request) const { + request.path_params.clear(); + return std::regex_match(request.path, request.matches, regex_); +} + +} // namespace detail + +// HTTP server implementation +inline Server::Server() + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif +} + +inline Server::~Server() {} + +inline std::unique_ptr +Server::make_matcher(const std::string &pattern) { + if (pattern.find("/:") != std::string::npos) { + return detail::make_unique(pattern); + } else { + return detail::make_unique(pattern); + } +} + +inline Server &Server::Get(const std::string &pattern, Handler handler) { + get_handlers_.push_back( + std::make_pair(make_matcher(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, Handler handler) { + post_handlers_.push_back( + std::make_pair(make_matcher(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, + HandlerWithContentReader handler) { + post_handlers_for_content_reader_.push_back( + std::make_pair(make_matcher(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, Handler handler) { + put_handlers_.push_back( + std::make_pair(make_matcher(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, + HandlerWithContentReader handler) { + put_handlers_for_content_reader_.push_back( + std::make_pair(make_matcher(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, Handler handler) { + patch_handlers_.push_back( + std::make_pair(make_matcher(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, + HandlerWithContentReader handler) { + patch_handlers_for_content_reader_.push_back( + std::make_pair(make_matcher(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, Handler handler) { + delete_handlers_.push_back( + std::make_pair(make_matcher(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.push_back( + std::make_pair(make_matcher(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Options(const std::string &pattern, Handler handler) { + options_handlers_.push_back( + std::make_pair(make_matcher(pattern), std::move(handler))); + return *this; +} + +inline bool Server::set_base_dir(const std::string &dir, + const std::string &mount_point) { + return set_mount_point(mount_point, dir); +} + +inline bool Server::set_mount_point(const std::string &mount_point, + const std::string &dir, Headers headers) { + if (detail::is_dir(dir)) { + std::string mnt = !mount_point.empty() ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') { + base_dirs_.push_back({mnt, dir, std::move(headers)}); + return true; + } + } + return false; +} + +inline bool Server::remove_mount_point(const std::string &mount_point) { + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { + if (it->mount_point == mount_point) { + base_dirs_.erase(it); + return true; + } + } + return false; +} + +inline Server & +Server::set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime) { + file_extension_and_mimetype_map_[ext] = mime; + return *this; +} + +inline Server &Server::set_default_file_mimetype(const std::string &mime) { + default_file_mimetype_ = mime; + return *this; +} + +inline Server &Server::set_file_request_handler(Handler handler) { + file_request_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(HandlerWithResponse handler) { + error_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(Handler handler) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; +} + +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + return *this; +} + +inline Server & +Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { + expect_100_continue_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_address_family(int family) { + address_family_ = family; + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + return *this; +} + +inline Server &Server::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + return *this; +} + +inline Server &Server::set_header_writer( + std::function const &writer) { + header_writer_ = writer; + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) { + keep_alive_max_count_ = count; + return *this; +} + +inline Server &Server::set_keep_alive_timeout(time_t sec) { + keep_alive_timeout_sec_ = sec; + return *this; +} + +inline Server &Server::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_idle_interval(time_t sec, time_t usec) { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + return *this; +} + +inline Server &Server::set_payload_max_length(size_t length) { + payload_max_length_ = length; + return *this; +} + +inline bool Server::bind_to_port(const std::string &host, int port, + int socket_flags) { + if (bind_internal(host, port, socket_flags) < 0) return false; + return true; +} +inline int Server::bind_to_any_port(const std::string &host, int socket_flags) { + return bind_internal(host, 0, socket_flags); +} + +inline bool Server::listen_after_bind() { + auto se = detail::scope_exit([&]() { done_ = true; }); + return listen_internal(); +} + +inline bool Server::listen(const std::string &host, int port, + int socket_flags) { + auto se = detail::scope_exit([&]() { done_ = true; }); + return bind_to_port(host, port, socket_flags) && listen_internal(); +} + +inline bool Server::is_running() const { return is_running_; } + +inline void Server::wait_until_ready() const { + while (!is_running() && !done_) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + +inline void Server::stop() { + if (is_running_) { + assert(svr_sock_ != INVALID_SOCKET); + std::atomic sock(svr_sock_.exchange(INVALID_SOCKET)); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } +} + +inline bool Server::parse_request_line(const char *s, Request &req) { + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; } + len -= 2; + + { + size_t count = 0; + + detail::split(s, s + len, ' ', [&](const char *b, const char *e) { + switch (count) { + case 0: req.method = std::string(b, e); break; + case 1: req.target = std::string(b, e); break; + case 2: req.version = std::string(b, e); break; + default: break; + } + count++; + }); + + if (count != 3) { return false; } + } + + static const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { return false; } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) { + if (req.target[i] == '#') { + req.target.erase(i); + break; + } + } + + size_t count = 0; + + detail::split(req.target.data(), req.target.data() + req.target.size(), '?', + [&](const char *b, const char *e) { + switch (count) { + case 0: + req.path = detail::decode_url(std::string(b, e), false); + break; + case 1: { + if (e - b > 0) { + detail::parse_query_text(std::string(b, e), req.params); + } + break; + } + default: break; + } + count++; + }); + + if (count > 2) { return false; } + } + + return true; +} + +inline bool Server::write_response(Stream &strm, bool close_connection, + const Request &req, Response &res) { + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { + assert(res.status != -1); + + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; + } + + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close") { + res.set_header("Connection", "close"); + } else { + std::stringstream ss; + ss << "timeout=" << keep_alive_timeout_sec_ + << ", max=" << keep_alive_max_count_; + res.set_header("Keep-Alive", ss.str()); + } + + if (!res.has_header("Content-Type") && + (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { + res.set_header("Content-Type", "text/plain"); + } + + if (!res.has_header("Content-Length") && res.body.empty() && + !res.content_length_ && !res.content_provider_) { + res.set_header("Content-Length", "0"); + } + + if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { post_routing_handler_(req, res); } + + // Response line and headers + { + detail::BufferStream bstrm; + + if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, + status_message(res.status))) { + return false; + } + + if (!header_writer_(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + detail::write_data(strm, data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") { + if (!res.body.empty()) { + if (!detail::write_data(strm, res.body.data(), res.body.size())) { + ret = false; + } + } else if (res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } else { + res.content_provider_success_ = false; + ret = false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return ret; +} + +inline bool +Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + auto length = offsets.second; + return detail::write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + MultipartFormDataMap::iterator cur; + auto file_count = 0; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + return false; + } + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { + res.status = 413; // NOTE: should be 414? + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); +} + +inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = 400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = (std::min)(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, multipart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + multipart_header); + }; + } else { + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = 400; + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + if (detail::is_file(path)) { + for (const auto &kv : entry.headers) { + res.set_header(kv.first, kv.second); + } + + auto mm = std::make_shared(path.c_str()); + if (!mm->is_open()) { return false; } + + res.set_content_provider( + mm->size(), + detail::find_content_type(path, file_extension_and_mimetype_map_, + default_file_mimetype_), + [mm](size_t offset, size_t length, DataSink &sink) -> bool { + sink.write(mm->data() + offset, length); + return true; + }); + + if (!head && file_request_handler_) { + file_request_handler_(req, res); + } + + return true; + } + } + } + } + return false; +} + +inline socket_t +Server::create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + std::move(socket_options), + [](socket_t sock, struct addrinfo &ai) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; } + return true; + }); +} + +inline int Server::bind_internal(const std::string &host, int port, + int socket_flags) { + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() { + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + socket_t sock = accept(svr_sock_, nullptr, nullptr); + + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } else if (errno == EINTR || errno == EAGAIN) { + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; + } + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec_ * 1000 + + read_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec_); + tv.tv_usec = static_cast(read_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec_ * 1000 + + write_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec_); + tv.tv_usec = static_cast(write_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + + task_queue->enqueue([this, sock]() { process_and_close_socket(sock); }); + } + + task_queue->shutdown(); + } + + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // File handler + auto is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = 400; + return false; +} + +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) { + for (const auto &x : handlers) { + const auto &matcher = x.first; + const auto &handler = x.second; + + if (matcher->match(req)) { + handler(req, res); + return true; + } + } + return false; +} + +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) { + if (req.ranges.size() > 1) { + boundary = detail::make_multipart_data_boundary(); + + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) { + content_type = it->second; + res.headers.erase(it); + } + + res.set_header("Content-Type", + "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) { + if (res.content_length_ > 0) { + size_t length = 0; + if (req.ranges.empty()) { + length = res.content_length_; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + length = offsets.second; + + auto content_range = detail::make_content_range_header_field( + req.ranges[0], res.content_length_); + res.set_header("Content-Range", content_range); + } else { + length = detail::get_multipart_ranges_data_length(req, res, boundary, + content_type); + } + res.set_header("Content-Length", std::to_string(length)); + } else { + if (res.content_provider_) { + if (res.is_chunked_content_provider_) { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) { + res.set_header("Content-Encoding", "gzip"); + } else if (type == detail::EncodingType::Brotli) { + res.set_header("Content-Encoding", "br"); + } + } + } + } + } else { + if (req.ranges.empty()) { + ; + } else if (req.ranges.size() == 1) { + auto content_range = detail::make_content_range_header_field( + req.ranges[0], res.body.size()); + res.set_header("Content-Range", content_range); + + auto offsets = + detail::get_range_offset_and_length(req, res.body.size(), 0); + auto offset = offsets.first; + auto length = offsets.second; + + if (offset < res.body.size()) { + res.body = res.body.substr(offset, length); + } else { + res.body.clear(); + res.status = 416; + } + } else { + std::string data; + if (detail::make_multipart_ranges_data(req, res, boundary, content_type, + data)) { + res.body.swap(data); + } else { + res.body.clear(); + res.status = 416; + } + } + + if (type != detail::EncodingType::None) { + std::unique_ptr compressor; + std::string content_encoding; + + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); + content_encoding = "gzip"; +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + content_encoding = "br"; +#endif + } + + if (compressor) { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); + } + } + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length); + } +} + +inline bool Server::dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) { + for (const auto &x : handlers) { + const auto &matcher = x.first; + const auto &handler = x.second; + + if (matcher->match(req)) { + handler(req, res, content_reader); + return true; + } + } + return false; +} + +inline bool +Server::process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + // Connection has been closed on client + if (!line_reader.getline()) { return false; } + + Request req; + + Response res; + res.version = "HTTP/1.1"; + res.headers = default_headers_; + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + + // Check if the request URI doesn't exceed the limit + if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 414; + return write_response(strm, close_connection, req, res); + } + + // Request line and headers + if (!parse_request_line(line_reader.ptr(), req) || + !detail::read_headers(strm, req.headers)) { + res.status = 400; + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") { + connection_closed = true; + } + + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") { + connection_closed = true; + } + + strm.get_remote_ip_and_port(req.remote_addr, req.remote_port); + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + + strm.get_local_ip_and_port(req.local_addr, req.local_port); + req.set_header("LOCAL_ADDR", req.local_addr); + req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + + if (req.has_header("Range")) { + const auto &range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) { + res.status = 416; + return write_response(strm, close_connection, req, res); + } + } + + if (setup_request) { setup_request(req); } + + if (req.get_header_value("Expect") == "100-continue") { + auto status = 100; + if (expect_100_continue_handler_) { + status = expect_100_continue_handler_(req, res); + } + switch (status) { + case 100: + case 417: + strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, + status_message(status)); + break; + default: return write_response(strm, close_connection, req, res); + } + } + + // Rounting + auto routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + std::string val; + auto s = e.what(); + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case '\r': val += "\\r"; break; + case '\n': val += "\\n"; break; + default: val += s[i]; break; + } + } + res.set_header("EXCEPTION_WHAT", val); + } + } catch (...) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + } +#endif + + if (routed) { + if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } + return write_response_with_content(strm, close_connection, req, res); + } else { + if (res.status == -1) { res.status = 404; } + return write_response(strm, close_connection, req, res); + } +} + +inline bool Server::is_valid() const { return true; } + +inline bool Server::process_and_close_socket(socket_t sock) { + auto ret = detail::process_server_socket( + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + nullptr); + }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// HTTP client implementation +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : host_(host), port_(port), + host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)), + client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} + +inline ClientImpl::~ClientImpl() { + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline bool ClientImpl::is_valid() const { return true; } + +inline void ClientImpl::copy_settings(const ClientImpl &rhs) { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + url_encode_ = rhs.url_encode_; + address_family_ = rhs.address_family_; + tcp_nodelay_ = rhs.tcp_nodelay_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + ca_cert_store_ = rhs.ca_cert_store_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; +#endif + logger_ = rhs.logger_; +} + +inline socket_t ClientImpl::create_client_socket(Error &error) const { + if (!proxy_host_.empty() && proxy_port_ != -1) { + return detail::create_client_socket( + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); + } + + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) ip = it->second; + + return detail::create_client_socket( + host_, ip, port_, address_family_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, + error); +} + +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; +} + +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); +} + +inline void ClientImpl::shutdown_socket(Socket &socket) { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { return false; } + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#else + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#endif + + std::cmatch m; + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == 100) { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + } + + return true; +} + +inline bool ClientImpl::send(Request &req, Response &res, Error &error) { + std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + } + return ret; +} + +inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { + { + std::lock_guard guard(socket_mutex_); + + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; + + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::is_socket_alive(socket_.sock); + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!is_alive) { + if (!create_and_connect_socket(socket_, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + auto success = false; + if (!scli.connect_with_proxy(socket_, res, success, error)) { + return success; + } + } + + if (!scli.initialize_ssl(socket_, error)) { return false; } + } +#endif + } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto ret = false; + auto close_connection = !keep_alive_; + + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + }); + + ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); + + if (!ret) { + if (error == Error::Success) { error = Error::Unknown; } + } + + return ret; +} + +inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); +} + +inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +} + +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + if (req.path.empty()) { + error = Error::Connection; + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { + auto req2 = req; + req2.path = "http://" + host_and_port_ + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; + } else { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { return false; } + + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. + + // This is safe to call because handle_request is only called by send_ + // which locks the request mutex during the process. It would be a bug + // to call it from a different thread since it's a thread-safety issue + // to do these things to the socket if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + if (300 < res.status && res.status < 400 && follow_location_) { + req = req_save; + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if ((res.status == 401 || res.status == 407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == 407; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { res = new_res; } + } + } + } +#endif + + return ret; +} + +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; + return false; + } + + auto location = res.get_header_value("location"); + if (location.empty()) { return false; } + + const static std::regex re( + R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); + + std::smatch m; + if (!std::regex_match(location, m, re)) { return false; } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + if (next_host.empty()) { next_host = m[3].str(); } + auto port_str = m[4].str(); + auto next_path = m[5].str(); + auto next_query = m[6].str(); + + auto next_port = port_; + if (!port_str.empty()) { + next_port = std::stoi(port_str); + } else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { next_scheme = scheme; } + if (next_host.empty()) { next_host = host_; } + if (next_path.empty()) { next_path = "/"; } + + auto path = detail::decode_url(next_path, true) + next_query; + + if (next_scheme == scheme && next_host == host_ && next_port == port_) { + return detail::redirect(*this, req, res, path, location, error); + } else { + if (next_scheme == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSLClient cli(next_host, next_port); + cli.copy_settings(*this); + if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } + return detail::redirect(cli, req, res, path, location, error); +#else + return false; +#endif + } else { + ClientImpl cli(next_host, next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, path, location, error); + } + } +} + +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) { + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) { + // TODO: Brotli support + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } +} + +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { + if (!req.has_header("Connection")) { + req.set_header("Connection", "close"); + } + } + + if (!req.has_header("Host")) { + if (is_ssl()) { + if (port_ == 443) { + req.set_header("Host", host_); + } else { + req.set_header("Host", host_and_port_); + } + } else { + if (port_ == 80) { + req.set_header("Host", host_); + } else { + req.set_header("Host", host_and_port_); + } + } + } + + if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.set_header("User-Agent", agent); + } +#endif + + if (req.body.empty()) { + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.content_length_); + req.set_header("Content-Length", length); + } + } + } else { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.set_header("Content-Length", "0"); + } + } + } else { + if (!req.has_header("Content-Type")) { + req.set_header("Content-Type", "text/plain"); + } + + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.body.size()); + req.set_header("Content-Length", length); + } + } + + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); + } + } + + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + } + + if (!bearer_token_auth_token_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + } + + if (!proxy_bearer_token_auth_token_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + } + + // Request line and headers + { + detail::BufferStream bstrm; + + const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path; + bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + + header_writer_(bstrm, req.headers); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + return false; + } + } + + // Body + if (req.body.empty()) { + return write_content_with_provider(strm, req, error); + } + + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + return false; + } + + return true; +} + +inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error) { + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { req.set_header("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support + detail::gzip_compressor compressor; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) -> bool { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor.compress( + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + return ok; + }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + return nullptr; + } + } + } else { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + return nullptr; + } + } + } else +#endif + { + if (content_provider) { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.set_header("Transfer-Encoding", "chunked"); + } else { + req.body.assign(body, content_length); + } + } + + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; +} + +inline Result ClientImpl::send_with_content_provider( + const std::string &method, const std::string &path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; +} + +inline std::string +ClientImpl::adjust_host_string(const std::string &host) const { + if (host.find(':') != std::string::npos) { return "[" + host + "]"; } + return host; +} + +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + // Send request + if (!write_request(strm, req, close_connection, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_ssl()) { + auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; + if (!is_proxy_enabled) { + char buf[1]; + if (SSL_peek(socket_.ssl, buf, 1) == 0 && + SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) { + error = Error::SSLPeerCouldBeClosed_; + return false; + } + } + } +#endif + + // Receive response and headers + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) { + error = Error::Read; + return false; + } + + // Body + if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + return false; + } + } + + auto out = + req.content_receiver + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + if (res.body.size() + n > res.body.max_size()) { + return false; + } + res.body.append(buf, n); + return true; + }); + + auto progress = [&](uint64_t current, uint64_t total) { + if (!req.progress || redirect) { return true; } + auto ret = req.progress(current, total); + if (!ret) { error = Error::Canceled; } + return ret; + }; + + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), std::move(out), + decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } + return false; + } + } + + // Log + if (logger_) { logger_(req, res); } + + return true; +} + +inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + size_t cur_item = 0, cur_start = 0; + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls + return [&, cur_item, cur_start](size_t offset, + DataSink &sink) mutable -> bool { + if (!offset && items.size()) { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } else if (cur_item < provider_items.size()) { + if (!cur_start) { + const auto &begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + auto has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) + return false; + + if (!has_data) { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } else { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; + } + }; +} + +inline bool +ClientImpl::process_socket(const Socket &socket, + std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); +} + +inline bool ClientImpl::is_ssl() const { return false; } + +inline Result ClientImpl::Get(const std::string &path) { + return Get(path, Headers(), Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, Progress progress) { + return Get(path, Headers(), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { + return Get(path, headers, Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver) { + return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return Get(path, headers, nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, headers, nullptr, content_receiver, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + if (params.empty()) { + return Get(path, headers, response_handler, content_receiver, progress); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, response_handler, + content_receiver, progress); +} + +inline Result ClientImpl::Head(const std::string &path) { + return Head(path, Headers()); +} + +inline Result ClientImpl::Head(const std::string &path, + const Headers &headers) { + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline Result ClientImpl::Post(const std::string &path) { + return Post(path, std::string(), std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers) { + return Post(path, headers, nullptr, 0, std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Post(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return Post(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { + return Post(path, Headers(), params); +} + +inline Result ClientImpl::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Post(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Post(const std::string &path, + const MultipartFormDataItems &items) { + return Post(path, Headers(), items); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type); +} + +inline Result +ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path) { + return Put(path, std::string(), std::string()); +} + +inline Result ClientImpl::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Put(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return Put(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Put(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { + return Put(path, Headers(), params); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Put(const std::string &path, + const MultipartFormDataItems &items) { + return Put(path, Headers(), items); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result +ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); +} + +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Patch(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Patch(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Patch(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path) { + return Delete(path, Headers(), std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers) { + return Delete(path, headers, std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Delete(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type) { + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + req.body.assign(body, content_length); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Delete(path, Headers(), body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type) { + return Delete(path, headers, body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Options(const std::string &path) { + return Options(path, Headers()); +} + +inline Result ClientImpl::Options(const std::string &path, + const Headers &headers) { + Request req; + req.method = "OPTIONS"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline void ClientImpl::stop() { + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + // Otherwise, still holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline std::string ClientImpl::host() const { return host_; } + +inline int ClientImpl::port() const { return port_; } + +inline size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); +} + +inline socket_t ClientImpl::socket() const { return socket_.sock; } + +inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; +} + +inline void ClientImpl::set_basic_auth(const std::string &username, + const std::string &password) { + basic_auth_username_ = username; + basic_auth_password_ = password; +} + +inline void ClientImpl::set_bearer_token_auth(const std::string &token) { + bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { + digest_auth_username_ = username; + digest_auth_password_ = password; +} +#endif + +inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } + +inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } + +inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } + +inline void +ClientImpl::set_hostname_addr_map(std::map addr_map) { + addr_map_ = std::move(addr_map); +} + +inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); +} + +inline void ClientImpl::set_header_writer( + std::function const &writer) { + header_writer_ = writer; +} + +inline void ClientImpl::set_address_family(int family) { + address_family_ = family; +} + +inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + +inline void ClientImpl::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); +} + +inline void ClientImpl::set_compress(bool on) { compress_ = on; } + +inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } + +inline void ClientImpl::set_interface(const std::string &intf) { + interface_ = intf; +} + +inline void ClientImpl::set_proxy(const std::string &host, int port) { + proxy_host_ = host; + proxy_port_ = port; +} + +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; +} + +inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) { + proxy_bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} + +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store && ca_cert_store != ca_cert_store_) { + ca_cert_store_ = ca_cert_store; + } +} + +inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert, + std::size_t size) { + auto mem = BIO_new_mem_buf(ca_cert, static_cast(size)); + if (!mem) return nullptr; + + auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr); + if (!inf) { + BIO_free_all(mem); + return nullptr; + } + + auto cts = X509_STORE_new(); + if (cts) { + for (auto i = 0; i < static_cast(sk_X509_INFO_num(inf)); i++) { + auto itmp = sk_X509_INFO_value(inf, i); + if (!itmp) { continue; } + + if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); } + if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); } + } + } + + sk_X509_INFO_pop_free(inf, X509_INFO_free); + BIO_free_all(mem); + return cts; +} + +inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} +#endif + +inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); +} + +/* + * SSL Implementation + */ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace detail { + +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { + SSL *ssl = nullptr; + { + std::lock_guard guard(ctx_mutex); + ssl = SSL_new(ctx); + } + + if (ssl) { + set_nonblocking(sock, true); + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); + SSL_set_bio(ssl, bio, bio); + + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); + } + set_nonblocking(sock, false); + return nullptr; + } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); + } + + return ssl; +} + +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { SSL_shutdown(ssl); } + + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} + +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + auto res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + +template +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool +process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +class SSLInit { +public: + SSLInit() { + OPENSSL_init_ssl( + OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); + } +}; + +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} + +inline SSLSocketStream::~SSLSocketStream() {} + +inline bool SSLSocketStream::is_readable() const { + return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SSLSocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { + if (is_writable()) { + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); + + auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { +#endif + if (is_writable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + +static SSLInit sslinit_; + +} // namespace detail + +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path, + const char *private_key_password) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + // add default password callback before opening encrypted private key + if (private_key_password != nullptr && (private_key_password[0] != '\0')) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, + reinterpret_cast(const_cast(private_key_password))); + } + + if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != + 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { + SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, + client_ca_cert_dir_path); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { + ctx_ = SSL_CTX_new(TLS_method()); + if (ctx_) { + if (!setup_ssl_ctx_callback(*ctx_)) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLServer::~SSLServer() { + if (ctx_) { SSL_CTX_free(ctx_); } +} + +inline bool SSLServer::is_valid() const { return ctx_; } + +inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; } + +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl2*/) { return true; }); + + auto ret = false; + if (ssl) { + ret = detail::process_server_socket_ssl( + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this, ssl](Stream &strm, bool close_connection, + bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + [&](Request &req) { req.ssl = ssl; }); + }); + + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully); + } + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : ClientImpl(host, port, client_cert_path, client_key_path) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + + if (!client_cert_path.empty() && !client_key_path.empty()) { + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), + SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + + if (client_cert != nullptr && client_key != nullptr) { + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::~SSLClient() { + if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); +} + +inline bool SSLClient::is_valid() const { return ctx_; } + +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } +} + +inline void SSLClient::load_ca_cert_store(const char *ca_cert, + std::size_t size) { + set_ca_cert_store(ClientImpl::create_ca_cert_store(ca_cert, size)); +} + +inline long SSLClient::get_openssl_verify_result() const { + return verify_result_; +} + +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } + +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); +} + +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, + bool &success, Error &error) { + success = true; + Response proxy_res; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + return process_request(strm, req2, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + + if (proxy_res.status == 407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(proxy_res, auth, true)) { + proxy_res = Response(); + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + return process_request(strm, req3, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } + } + + // If status code is not 200, proxy request is failed. + // Set error to ProxyConnection and return proxy response + // as the response of the request + if (proxy_res.status != 200) { + error = Error::ProxyConnection; + res = std::move(proxy_res); + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + return false; + } + + return true; +} + +inline bool SSLClient::load_certs() { + auto ret = true; + + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + ret = false; + } + } else { + auto loaded = false; +#ifdef _WIN32 + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // TARGET_OS_OSX +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } + } + }); + + return ret; +} + +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + return false; + } + SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); + } + + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; + return false; + } + + if (server_certificate_verification_) { + verify_result_ = SSL_get_verify_result(ssl2); + + if (verify_result_ != X509_V_OK) { + error = Error::SSLServerVerification; + return false; + } + + auto server_cert = SSL_get1_peer_certificate(ssl2); + + if (server_cert == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + if (!verify_host(server_cert)) { + X509_free(server_cert); + error = Error::SSLServerVerification; + return false; + } + X509_free(server_cert); + } + + return true; + }, + [&](SSL *ssl2) { + // NOTE: With -Wold-style-cast, this can produce a warning, since + // SSL_set_tlsext_host_name is a macro (in OpenSSL), which contains + // an old style cast. Short of doing compiler specific pragma's + // here, we can't get rid of this warning. :'( + SSL_set_tlsext_host_name(ssl2, host_.c_str()); + return true; + }); + + if (ssl) { + socket.ssl = ssl; + return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool +SSLClient::process_socket(const Socket &socket, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, std::move(callback)); +} + +inline bool SSLClient::is_ssl() const { return true; } + +inline bool SSLClient::verify_host(X509 *server_cert) const { + /* Quote from RFC2818 section 3.1 "Server Identity" + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. + + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. + + */ + return verify_host_with_subject_alt_name(server_cert) || + verify_host_with_common_name(server_cert); +} + +inline bool +SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { + auto ret = false; + + auto type = GEN_DNS; + + struct in6_addr addr6 {}; + struct in_addr addr {}; + size_t addr_len = 0; + +#ifndef __MINGW32__ + if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { + type = GEN_IPADD; + addr_len = sizeof(struct in6_addr); + } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { + type = GEN_IPADD; + addr_len = sizeof(struct in_addr); + } +#endif + + auto alt_names = static_cast( + X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); + + if (alt_names) { + auto dsn_matched = false; + auto ip_matched = false; + + auto count = sk_GENERAL_NAME_num(alt_names); + + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { + auto val = sk_GENERAL_NAME_value(alt_names, i); + if (val->type == type) { + auto name = + reinterpret_cast(ASN1_STRING_get0_data(val->d.ia5)); + auto name_len = static_cast(ASN1_STRING_length(val->d.ia5)); + + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_matched = true; + } + break; + } + } + } + + if (dsn_matched || ip_matched) { ret = true; } + } + + GENERAL_NAMES_free(const_cast( + reinterpret_cast(alt_names))); + return ret; +} + +inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { + const auto subject_name = X509_get_subject_name(server_cert); + + if (subject_name != nullptr) { + char name[BUFSIZ]; + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); + + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } + } + + return false; +} + +inline bool SSLClient::check_host_name(const char *pattern, + size_t pattern_len) const { + if (host_.size() == pattern_len && host_ == pattern) { return true; } + + // Wildcard match + // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 + std::vector pattern_components; + detail::split(&pattern[0], &pattern[pattern_len], '.', + [&](const char *b, const char *e) { + pattern_components.emplace_back(std::string(b, e)); + }); + + if (host_components_.size() != pattern_components.size()) { return false; } + + auto itr = pattern_components.begin(); + for (const auto &h : host_components_) { + auto &p = *itr; + if (p != h && p != "*") { + auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && + !p.compare(0, p.size() - 1, h)); + if (!partial_match) { return false; } + } + ++itr; + } + + return true; +} +#endif + +// Universal client implementation +inline Client::Client(const std::string &scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} + +inline Client::Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path) { + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); + + std::smatch m; + if (std::regex_match(scheme_host_port, m, re)) { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); +#endif + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + if (host.empty()) { host = m[3].str(); } + + auto port_str = m[4].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + is_ssl_ = is_ssl; +#endif + } else { + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + } + } else { + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } +} + +inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} + +inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} + +inline Client::~Client() {} + +inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); +} + +inline Result Client::Get(const std::string &path) { return cli_->Get(path); } +inline Result Client::Get(const std::string &path, const Headers &headers) { + return cli_->Get(path, headers); +} +inline Result Client::Get(const std::string &path, Progress progress) { + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + Progress progress) { + return cli_->Get(path, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + return cli_->Get(path, params, headers, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, content_receiver, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, response_handler, content_receiver, + progress); +} + +inline Result Client::Head(const std::string &path) { return cli_->Head(path); } +inline Result Client::Head(const std::string &path, const Headers &headers) { + return cli_->Head(path, headers); +} + +inline Result Client::Post(const std::string &path) { return cli_->Post(path); } +inline Result Client::Post(const std::string &path, const Headers &headers) { + return cli_->Post(path, headers); +} +inline Result Client::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Post(path, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Post(path, body, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_type); +} +inline Result Client::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Params ¶ms) { + return cli_->Post(path, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Post(path, headers, params); +} +inline Result Client::Post(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Post(path, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Post(path, headers, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); +} +inline Result +Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Post(path, headers, items, provider_items); +} +inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Put(path, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Put(path, body, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_type); +} +inline Result Client::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) { + return cli_->Put(path, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Put(path, headers, params); +} +inline Result Client::Put(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Put(path, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Put(path, headers, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Put(path, headers, items, boundary); +} +inline Result +Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Put(path, headers, items, provider_items); +} +inline Result Client::Patch(const std::string &path) { + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, body, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_type); +} +inline Result Client::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, std::move(content_provider), content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Delete(const std::string &path) { + return cli_->Delete(path); +} +inline Result Client::Delete(const std::string &path, const Headers &headers) { + return cli_->Delete(path, headers); +} +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, body, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_type); +} +inline Result Client::Options(const std::string &path) { + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) { + return cli_->Options(path, headers); +} + +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); +} + +inline Result Client::send(const Request &req) { return cli_->send(req); } + +inline void Client::stop() { cli_->stop(); } + +inline std::string Client::host() const { return cli_->host(); } + +inline int Client::port() const { return cli_->port(); } + +inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } + +inline socket_t Client::socket() const { return cli_->socket(); } + +inline void +Client::set_hostname_addr_map(std::map addr_map) { + cli_->set_hostname_addr_map(std::move(addr_map)); +} + +inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); +} + +inline void Client::set_header_writer( + std::function const &writer) { + cli_->set_header_writer(writer); +} + +inline void Client::set_address_family(int family) { + cli_->set_address_family(family); +} + +inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } + +inline void Client::set_socket_options(SocketOptions socket_options) { + cli_->set_socket_options(std::move(socket_options)); +} + +inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); +} + +inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); +} + +inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); +} + +inline void Client::set_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_basic_auth(username, password); +} +inline void Client::set_bearer_token_auth(const std::string &token) { + cli_->set_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_digest_auth(username, password); +} +#endif + +inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } +inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); +} + +inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } + +inline void Client::set_compress(bool on) { cli_->set_compress(on); } + +inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + +inline void Client::set_interface(const std::string &intf) { + cli_->set_interface(intf); +} + +inline void Client::set_proxy(const std::string &host, int port) { + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_basic_auth(username, password); +} +inline void Client::set_proxy_bearer_token_auth(const std::string &token) { + cli_->set_proxy_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} +#endif + +inline void Client::set_logger(Logger logger) { + cli_->set_logger(std::move(logger)); +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); +} + +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } else { + cli_->set_ca_cert_store(ca_cert_store); + } +} + +inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) { + set_ca_cert_store(cli_->create_ca_cert_store(ca_cert, size)); +} + +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? +} + +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; +} +#endif + +// ---------------------------------------------------------------------------- + +} // namespace httplib + +#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) +#undef poll +#endif + +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/scripts/Dockerfile b/scripts/Dockerfile index b44713fac..37d1016c6 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -7,7 +7,7 @@ ARG SHA="" WORKDIR / RUN set -xe && \ apk add --no-cache --virtual .build-tools git g++ build-base linux-headers cmake python3 && \ - apk add --no-cache --virtual .build-deps curl-dev rapidjson-dev libevent-dev pcre2-dev yaml-cpp-dev && \ + apk add --no-cache --virtual .build-deps curl-dev rapidjson-dev pcre2-dev yaml-cpp-dev && \ git clone https://github.com/ftk/quickjspp --depth=1 && \ cd quickjspp && \ git submodule update --init && \ @@ -48,7 +48,7 @@ RUN set -xe && \ FROM alpine:3.16 LABEL maintainer="tindy.it@gmail.com" -RUN apk add --no-cache --virtual subconverter-deps pcre2 libcurl yaml-cpp libevent +RUN apk add --no-cache --virtual subconverter-deps pcre2 libcurl yaml-cpp COPY --from=builder /subconverter/subconverter /usr/bin/ COPY --from=builder /subconverter/base /base/ diff --git a/scripts/build.alpine.release.sh b/scripts/build.alpine.release.sh index b250177ad..c159737a1 100644 --- a/scripts/build.alpine.release.sh +++ b/scripts/build.alpine.release.sh @@ -2,7 +2,7 @@ set -xe apk add gcc g++ build-base linux-headers cmake make autoconf automake libtool python2 python3 -apk add mbedtls-dev mbedtls-static zlib-dev rapidjson-dev libevent-dev libevent-static zlib-static pcre2-dev +apk add mbedtls-dev mbedtls-static zlib-dev rapidjson-dev zlib-static pcre2-dev git clone https://github.com/curl/curl --depth=1 --branch curl-8_4_0 cd curl @@ -45,7 +45,7 @@ cmake -DCMAKE_BUILD_TYPE=Release . make -j3 rm subconverter # shellcheck disable=SC2046 -g++ -o base/subconverter $(find CMakeFiles/subconverter.dir/src/ -name "*.o") -static -lpcre2-8 -levent -lyaml-cpp -L/usr/lib64 -lcurl -lmbedtls -lmbedcrypto -lmbedx509 -lz -l:quickjs/libquickjs.a -llibcron -O3 -s +g++ -o base/subconverter $(find CMakeFiles/subconverter.dir/src/ -name "*.o") -static -lpcre2-8 -lyaml-cpp -L/usr/lib64 -lcurl -lmbedtls -lmbedcrypto -lmbedx509 -lz -l:quickjs/libquickjs.a -llibcron -O3 -s python3 -m ensurepip python3 -m pip install gitpython diff --git a/scripts/build.macos.release.sh b/scripts/build.macos.release.sh index ab4a489a8..833418bf8 100644 --- a/scripts/build.macos.release.sh +++ b/scripts/build.macos.release.sh @@ -1,7 +1,7 @@ #!/bin/bash set -xe -brew reinstall rapidjson libevent zlib pcre2 pkgconfig +brew reinstall rapidjson zlib pcre2 pkgconfig #git clone https://github.com/curl/curl --depth=1 --branch curl-7_88_1 #cd curl @@ -46,13 +46,13 @@ cmake -DCMAKE_CXX_STANDARD=11 . make install -j4 cd .. -cp /usr/local/lib/libevent.a . cp /usr/local/opt/zlib/lib/libz.a . cp /usr/local/lib/libpcre2-8.a . cmake -DCMAKE_BUILD_TYPE=Release . make -j8 rm subconverter +# shellcheck disable=SC2046 c++ -Xlinker -unexported_symbol -Xlinker "*" -o base/subconverter -framework CoreFoundation -framework Security $(find CMakeFiles/subconverter.dir/src/ -name "*.o") $(find . -name "*.a") -lcurl -O3 python -m ensurepip diff --git a/scripts/build.windows.release.sh b/scripts/build.windows.release.sh index d64de978f..5de4c8dc3 100644 --- a/scripts/build.windows.release.sh +++ b/scripts/build.windows.release.sh @@ -52,5 +52,6 @@ rm -f C:/Strawberry/perl/bin/pkg-config C:/Strawberry/perl/bin/pkg-config.bat cmake -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" . make -j4 rm subconverter.exe -g++ $(find CMakeFiles/subconverter.dir/src -name "*.obj") curl/lib/libcurl.a -o base/subconverter.exe -static -lbcrypt -levent -lpcre2-8 -l:quickjs/libquickjs.a -llibcron -lyaml-cpp -liphlpapi -lcrypt32 -lws2_32 -lwsock32 -lz -s +# shellcheck disable=SC2046 +g++ $(find CMakeFiles/subconverter.dir/src -name "*.obj") curl/lib/libcurl.a -o base/subconverter.exe -static -lbcrypt -lpcre2-8 -l:quickjs/libquickjs.a -llibcron -lyaml-cpp -liphlpapi -lcrypt32 -lws2_32 -lwsock32 -lz -s mv base subconverter diff --git a/scripts/config.termux.sh b/scripts/config.termux.sh index 7f991bebd..026978360 100644 --- a/scripts/config.termux.sh +++ b/scripts/config.termux.sh @@ -3,7 +3,7 @@ set -xe apt update apt install -y git cmake clang pkg-config -apt install -y libevent libcurl openssl pcre2 +apt install -y libcurl openssl pcre2 git clone https://github.com/jbeder/yaml-cpp --depth=1 cd yaml-cpp diff --git a/src/generator/config/ruleconvert.cpp b/src/generator/config/ruleconvert.cpp index 92fb40a88..a38849189 100644 --- a/src/generator/config/ruleconvert.cpp +++ b/src/generator/config/ruleconvert.cpp @@ -5,6 +5,7 @@ #include "../../utils/network.h" #include "../../utils/regexp.h" #include "../../utils/string.h" +#include "../../utils/rapidjson_extra.h" #include "subexport.h" /// rule type lists @@ -28,7 +29,7 @@ std::string convertRuleset(const std::string &content, int type) if(regFind(content, "^payload:\\r?\\n")) /// Clash { - output = regReplace(regReplace(content, "payload:\\r?\\n", "", true), "\\s?^\\s*-\\s+('|\"?)(.*)\\1$", "\n$2", true); + output = regReplace(regReplace(content, "payload:\\r?\\n", "", true), R"(\s?^\s*-\s+('|"?)(.*)\1$)", "\n$2", true); if(type == RULESET_CLASH_CLASSICAL) /// classical type return output; std::stringstream ss; @@ -51,8 +52,8 @@ std::string convertRuleset(const std::string &content, int type) if(!strLine.empty() && (strLine[0] != ';' && strLine[0] != '#' && !(lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/'))) { - pos = strLine.find("/"); - if(pos != strLine.npos) /// ipcidr + pos = strLine.find('/'); + if(pos != std::string::npos) /// ipcidr { if(isIPv4(strLine.substr(0, pos))) output += "IP-CIDR,"; @@ -143,7 +144,7 @@ void rulesetToClash(YAML::Node &base_rule, std::vector &ruleset_ lineSize = strLine.size(); if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored continue; - if(std::none_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);})) + if(std::none_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);})) continue; if(strFind(strLine, "//")) { @@ -218,7 +219,7 @@ std::string rulesetToClashStr(YAML::Node &base_rule, std::vector lineSize = strLine.size(); if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored continue; - if(std::none_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [strLine](std::string type){ return startsWith(strLine, type); })) + if(std::none_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [strLine](const std::string& type){ return startsWith(strLine, type); })) continue; if(strFind(strLine, "//")) { @@ -235,7 +236,7 @@ std::string rulesetToClashStr(YAML::Node &base_rule, std::vector return output_content; } -void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_content_array, int surge_ver, bool overwrite_original_rules, std::string remote_path_prefix) +void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_content_array, int surge_ver, bool overwrite_original_rules, const std::string &remote_path_prefix) { string_array allRules; std::string rule_group, rule_path, rule_path_typed, retrieved_rules, strLine; @@ -268,6 +269,8 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_c case -4: base_rule.erase_section("Remote Rule"); break; + default: + break; } } @@ -313,7 +316,7 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_c } if(fileExist(rule_path)) { - if(surge_ver > 2 && remote_path_prefix.size()) + if(surge_ver > 2 && !remote_path_prefix.empty()) { strLine = "RULE-SET," + remote_path_prefix + "/getruleset?type=1&url=" + urlSafeBase64Encode(rule_path_typed) + "," + rule_group; if(x.update_interval) @@ -321,14 +324,14 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_c allRules.emplace_back(std::move(strLine)); continue; } - else if(surge_ver == -1 && remote_path_prefix.size()) + else if(surge_ver == -1 && !remote_path_prefix.empty()) { strLine = remote_path_prefix + "/getruleset?type=2&url=" + urlSafeBase64Encode(rule_path_typed) + "&group=" + urlSafeBase64Encode(rule_group); strLine += ", tag=" + rule_group + ", enabled=true"; base_rule.set("filter_remote", "{NONAME}", strLine); continue; } - else if(surge_ver == -4 && remote_path_prefix.size()) + else if(surge_ver == -4 && !remote_path_prefix.empty()) { strLine = remote_path_prefix + "/getruleset?type=1&url=" + urlSafeBase64Encode(rule_path_typed) + "," + rule_group; base_rule.set("Remote Rule", "{NONAME}", strLine); @@ -341,7 +344,7 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_c { if(x.rule_type != RULESET_SURGE) { - if(remote_path_prefix.size()) + if(!remote_path_prefix.empty()) strLine = "RULE-SET," + remote_path_prefix + "/getruleset?type=1&url=" + urlSafeBase64Encode(rule_path_typed) + "," + rule_group; else continue; @@ -355,7 +358,7 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_c allRules.emplace_back(std::move(strLine)); continue; } - else if(surge_ver == -1 && remote_path_prefix.size()) + else if(surge_ver == -1 && !remote_path_prefix.empty()) { strLine = remote_path_prefix + "/getruleset?type=2&url=" + urlSafeBase64Encode(rule_path_typed) + "&group=" + urlSafeBase64Encode(rule_group); strLine += ", tag=" + rule_group + ", enabled=true"; @@ -401,22 +404,22 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_c continue; [[fallthrough]]; case -1: - if(!std::any_of(QuanXRuleTypes.begin(), QuanXRuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);})) + if(!std::any_of(QuanXRuleTypes.begin(), QuanXRuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);})) continue; break; case -3: - if(!std::any_of(SurfRuleTypes.begin(), SurfRuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);})) + if(!std::any_of(SurfRuleTypes.begin(), SurfRuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);})) continue; break; default: if(surge_ver > 2) { - if(!std::any_of(SurgeRuleTypes.begin(), SurgeRuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);})) + if(!std::any_of(SurgeRuleTypes.begin(), SurgeRuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);})) continue; } else { - if(!std::any_of(Surge2RuleTypes.begin(), Surge2RuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);})) + if(!std::any_of(Surge2RuleTypes.begin(), Surge2RuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);})) continue; } } @@ -453,3 +456,100 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_c base_rule.set("{NONAME}", x); } } + +static rapidjson::Value transformRuleToSingBox(const std::string& rule, rapidjson::MemoryPoolAllocator<>& allocator) +{ + std::string type, value, group, option; + + regGetMatch(rule, "^(.*?),(.*?)(?:,(.*?))?(,.*)?$", 5, nullptr, &type, &value, &group, &option); + rapidjson::Value rule_obj(rapidjson::kObjectType); + type = replaceAllDistinct(toLower(type), "-", "_"); + type = replaceAllDistinct(type, "ip_cidr6", "ip_cidr"); + if (type == "match" || type == "final") { + rule_obj.AddMember("outbound", rapidjson::Value(value.c_str(), allocator), allocator); + } else { + rule_obj.AddMember(rapidjson::Value(type.c_str(), allocator), rapidjson::Value(value.c_str(), allocator), allocator); + rule_obj.AddMember("outbound", rapidjson::Value(group.c_str(), allocator), allocator); + } + return rule_obj; +} + +void rulesetToSingBox(rapidjson::Document &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules) +{ + std::string rule_group, retrieved_rules, strLine, final; + std::stringstream strStrm; + size_t total_rules = 0; + rapidjson::MemoryPoolAllocator<>& allocator = base_rule.GetAllocator(); + + rapidjson::Value rules(rapidjson::kArrayType); + if (!overwrite_original_rules) + { + if (base_rule.HasMember("route") && base_rule["route"].HasMember("rules") && base_rule["route"]["rules"].IsArray()) + rules.Swap(base_rule["route"]["rules"]); + } + + for(RulesetContent &x : ruleset_content_array) + { + if(global.maxAllowedRules && total_rules > global.maxAllowedRules) + break; + rule_group = x.rule_group; + retrieved_rules = x.rule_content.get(); + if(retrieved_rules.empty()) + { + writeLog(0, "Failed to fetch ruleset or ruleset is empty: '" + x.rule_path + "'!", LOG_LEVEL_WARNING); + continue; + } + if(startsWith(retrieved_rules, "[]")) + { + strLine = retrieved_rules.substr(2); + if(startsWith(strLine, "FINAL") || startsWith(strLine, "MATCH")) + { + final = rule_group; + continue; + } + strLine += "," + rule_group; + if(count_least(strLine, ',', 3)) + strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2"); + rules.PushBack(transformRuleToSingBox(strLine, allocator), allocator); + total_rules++; + continue; + } + retrieved_rules = convertRuleset(retrieved_rules, x.rule_type); + char delimiter = getLineBreak(retrieved_rules); + + strStrm.clear(); + strStrm< global.maxAllowedRules) + break; + strLine = trimWhitespace(strLine, true, true); //remove whitespaces + lineSize = strLine.size(); + if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored + continue; + if(std::none_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);})) + continue; + if(strFind(strLine, "//")) + { + strLine.erase(strLine.find("//")); + strLine = trimWhitespace(strLine); + } + strLine += "," + rule_group; + if(count_least(strLine, ',', 3)) + strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2"); + rules.PushBack(transformRuleToSingBox(strLine, allocator), allocator); + } + } + + if (!base_rule.HasMember("route")) + base_rule.AddMember("route", rapidjson::Value(rapidjson::kObjectType), allocator); + if (!base_rule["route"].HasMember("rules")) + base_rule["route"].AddMember("rules", rapidjson::Value(rapidjson::kArrayType), allocator); + base_rule["route"]["rules"].Swap(rules); + + if (!base_rule["route"].HasMember("final")) + base_rule["route"].AddMember("final", rapidjson::Value(final.c_str(), allocator), allocator); + else + base_rule["route"]["final"].SetString(final.c_str(), allocator); +} diff --git a/src/generator/config/ruleconvert.h b/src/generator/config/ruleconvert.h index 45a04bdf5..62103031f 100644 --- a/src/generator/config/ruleconvert.h +++ b/src/generator/config/ruleconvert.h @@ -6,6 +6,7 @@ #include #include +#include #include "../../utils/ini_reader/ini_reader.h" @@ -31,6 +32,7 @@ struct RulesetContent std::string convertRuleset(const std::string &content, int type); void rulesetToClash(YAML::Node &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules, bool new_field_name); std::string rulesetToClashStr(YAML::Node &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules, bool new_field_name); -void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_content_array, int surge_ver, bool overwrite_original_rules, std::string remote_path_prefix); +void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_content_array, int surge_ver, bool overwrite_original_rules, const std::string& remote_path_prefix); +void rulesetToSingBox(rapidjson::Document &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules); #endif // RULECONVERT_H_INCLUDED diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index 57c197f13..2be2e27ee 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -1817,7 +1817,6 @@ void proxyToMellow(std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { - rapidjson::Document json; INIReader ini; std::string output_nodelist; std::vector nodelist; @@ -2042,3 +2041,303 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, return ini.to_string(); } + +static std::string formatSingBoxInterval(Integer interval) +{ + std::string result; + if(interval >= 3600) + { + result += std::to_string(interval / 3600) + "h"; + interval %= 3600; + } + if(interval >= 60) + { + result += std::to_string(interval / 60) + "m"; + interval %= 60; + } + if(interval > 0) + result += std::to_string(interval) + "s"; + return result; +} + +static rapidjson::Value buildV2RayTransport(const Proxy& proxy, rapidjson::MemoryPoolAllocator<>& allocator) +{ + rapidjson::Value transport(rapidjson::kObjectType); + switch (hash_(proxy.TransferProtocol)) + { + case "http"_hash: + { + if (!proxy.Host.empty()) + transport.AddMember("host", rapidjson::StringRef(proxy.Host.c_str()), allocator); + [[fallthrough]]; + } + case "ws"_hash: + { + transport.AddMember("type", rapidjson::StringRef(proxy.TransferProtocol.c_str()), allocator); + if (proxy.Path.empty()) + transport.AddMember("path", "/", allocator); + else + transport.AddMember("path", rapidjson::StringRef(proxy.Path.c_str()), allocator); + + rapidjson::Value headers(rapidjson::kObjectType); + if (!proxy.Host.empty()) + headers.AddMember("Host", rapidjson::StringRef(proxy.Host.c_str()), allocator); + if (!proxy.Edge.empty()) + headers.AddMember("Edge", rapidjson::StringRef(proxy.Edge.c_str()), allocator); + transport.AddMember("headers", headers, allocator); + break; + } + case "grpc"_hash: + { + transport.AddMember("type", "grpc", allocator); + if (!proxy.Path.empty()) + transport.AddMember("service_name", rapidjson::StringRef(proxy.Path.c_str()), allocator); + break; + } + default: + break; + } + return transport; +} + +void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { + rapidjson::Document::AllocatorType &allocator = json.GetAllocator(); + rapidjson::Value outbounds(rapidjson::kArrayType), route(rapidjson::kArrayType); + std::vector nodelist; + + auto direct = buildObject(allocator, "type", "direct", "tag", "DIRECT"); + outbounds.PushBack(direct, allocator); + auto reject = buildObject(allocator, "type", "block", "tag", "REJECT"); + outbounds.PushBack(reject, allocator); + + for (Proxy &x : nodes) + { + std::string type = getProxyTypeName(x.Type); + if (ext.append_proxy_type) + x.Remark = "[" + type + "] " + x.Remark; + + tribool udp = ext.udp, tfo = ext.tfo, scv = ext.skip_cert_verify; + udp.define(x.UDP); + tfo.define(x.TCPFastOpen); + scv.define(x.AllowInsecure); + + rapidjson::Value proxy(rapidjson::kObjectType); + switch (x.Type) + { + case ProxyType::Shadowsocks: + { + proxy.AddMember("type", "shadowsocks", allocator); + proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); + proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); + proxy.AddMember("server_port", x.Port, allocator); + proxy.AddMember("method", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator); + proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); + if(!x.Plugin.empty() && !x.PluginOption.empty()) + { + proxy.AddMember("plugin", rapidjson::StringRef(x.Plugin.c_str()), allocator); + proxy.AddMember("plugin_opts", rapidjson::StringRef(x.PluginOption.c_str()), allocator); + } + break; + } + case ProxyType::ShadowsocksR: + { + proxy.AddMember("type", "shadowsocksr", allocator); + proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); + proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); + proxy.AddMember("server_port", x.Port, allocator); + proxy.AddMember("method", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator); + proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); + proxy.AddMember("protocol", rapidjson::StringRef(x.Protocol.c_str()), allocator); + proxy.AddMember("protocol_param", rapidjson::StringRef(x.ProtocolParam.c_str()), allocator); + proxy.AddMember("obfs", rapidjson::StringRef(x.OBFS.c_str()), allocator); + proxy.AddMember("obfs_param", rapidjson::StringRef(x.OBFSParam.c_str()), allocator); + break; + } + case ProxyType::VMess: + { + proxy.AddMember("type", "vmess", allocator); + proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); + proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); + proxy.AddMember("server_port", x.Port, allocator); + proxy.AddMember("uuid", rapidjson::StringRef(x.UserId.c_str()), allocator); + proxy.AddMember("alter_id", x.AlterId, allocator); + proxy.AddMember("cipher", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator); + + auto transport = buildV2RayTransport(x, allocator); + proxy.AddMember("transport", transport, allocator); + break; + } + case ProxyType::Trojan: + { + proxy.AddMember("type", "trojan", allocator); + proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); + proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); + proxy.AddMember("server_port", x.Port, allocator); + proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); + + auto transport = buildV2RayTransport(x, allocator); + proxy.AddMember("transport", transport, allocator); + break; + } + case ProxyType::WireGuard: + { + proxy.AddMember("type", "wireguard", allocator); + proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); + rapidjson::Value addresses(rapidjson::kArrayType); + addresses.PushBack(rapidjson::StringRef(x.SelfIP.c_str()), allocator); + if (!x.SelfIPv6.empty()) + addresses.PushBack(rapidjson::StringRef(x.SelfIPv6.c_str()), allocator); + proxy.AddMember("local_address", addresses, allocator); + proxy.AddMember("private_key", rapidjson::StringRef(x.PrivateKey.c_str()), allocator); + + rapidjson::Value peer(rapidjson::kObjectType); + peer.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); + peer.AddMember("server_port", x.Port, allocator); + peer.AddMember("public_key", rapidjson::StringRef(x.PublicKey.c_str()), allocator); + if (!x.PreSharedKey.empty()) + peer.AddMember("pre_shared_key", rapidjson::StringRef(x.PreSharedKey.c_str()), allocator); + + if (!x.AllowedIPs.empty()) + { + auto allowed = split(x.AllowedIPs, ","); + rapidjson::Value allowed_ips(rapidjson::kArrayType); + for (const auto &ip: allowed) { + allowed_ips.PushBack(rapidjson::Value(trim(ip).c_str(), allocator), allocator); + } + peer.AddMember("allowed_ips", allowed_ips, allocator); + } + + if (!x.ClientId.empty()) + { + auto client_id = split(x.ClientId, ","); + rapidjson::Value reserved(rapidjson::kArrayType); + for (const auto &id : client_id) + { + reserved.PushBack(to_int(trim(id)), allocator); + } + peer.AddMember("reserved", reserved, allocator); + } + + rapidjson::Value peers(rapidjson::kArrayType); + peers.PushBack(peer, allocator); + proxy.AddMember("peers", peers, allocator); + proxy.AddMember("mtu", x.Mtu, allocator); + break; + } + case ProxyType::HTTP: + case ProxyType::HTTPS: + { + proxy.AddMember("type", "http", allocator); + proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); + proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); + proxy.AddMember("server_port", x.Port, allocator); + if (x.TLSSecure) + { + proxy.AddMember("username", rapidjson::StringRef(x.Username.c_str()), allocator); + proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); + } + break; + } + case ProxyType::SOCKS5: + { + proxy.AddMember("type", "socks", allocator); + proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); + proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); + proxy.AddMember("port", x.Port, allocator); + proxy.AddMember("version", 5, allocator); + proxy.AddMember("username", rapidjson::StringRef(x.Username.c_str()), allocator); + proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); + break; + } + default: + continue; + } + if (x.TLSSecure) + { + rapidjson::Value tls(rapidjson::kObjectType); + tls.AddMember("enable", true, allocator); + if (!x.ServerName.empty()) + tls.AddMember("server_name", rapidjson::StringRef(x.ServerName.c_str()), allocator); + tls.AddMember("insecure", buildBooleanValue(scv), allocator); + proxy.AddMember("tls", tls, allocator); + } + if (!udp.is_undef() && !udp) + { + proxy.AddMember("network", "tcp", allocator); + } + if (!tfo.is_undef()) + { + proxy.AddMember("tcp_fast_open", buildBooleanValue(tfo), allocator); + } + nodelist.push_back(x); + outbounds.PushBack(proxy, allocator); + } + for (const ProxyGroupConfig& x: extra_proxy_group) + { + string_array filtered_nodelist; + std::string type; + switch (x.Type) + { + case ProxyGroupType::Select: + { + type = "selector"; + break; + } + case ProxyGroupType::URLTest: + case ProxyGroupType::Fallback: + case ProxyGroupType::LoadBalance: + { + type = "urltest"; + break; + } + default: + continue; + } + for (const auto &y : x.Proxies) + groupGenerate(y, nodelist, filtered_nodelist, true, ext); + + if (filtered_nodelist.empty()) + filtered_nodelist.emplace_back("DIRECT"); + + rapidjson::Value group(rapidjson::kObjectType); + + group.AddMember("type", rapidjson::Value(type.c_str(), allocator), allocator); + group.AddMember("tag", rapidjson::Value(x.Name.c_str(), allocator), allocator); + + rapidjson::Value group_outbounds(rapidjson::kArrayType); + for (const std::string& y: filtered_nodelist) + { + group_outbounds.PushBack(rapidjson::Value(y.c_str(), allocator), allocator); + } + group.AddMember("outbounds", group_outbounds, allocator); + + if (x.Type == ProxyGroupType::URLTest) + { + group.AddMember("url", rapidjson::Value(x.Url.c_str(), allocator), allocator); + group.AddMember("interval", rapidjson::Value(formatSingBoxInterval(x.Interval).c_str(), allocator), allocator); + if (x.Tolerance > 0) + group.AddMember("tolerance", x.Tolerance, allocator); + } + + outbounds.PushBack(group, allocator); + } + if (json.HasMember("outbounds")) + json.RemoveMember("outbounds"); + json.AddMember("outbounds", outbounds, allocator); +} + +std::string proxyToSingBox(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) +{ + rapidjson::Document json; + json.Parse(base_conf.data()); + if(json.HasParseError()) + { + writeLog(0, "sing-box base loader failed with error: " + std::string(rapidjson::GetParseError_En(json.GetParseError())), LOG_LEVEL_ERROR); + return ""; + } + + proxyToSingBox(nodes, json, ruleset_content_array, extra_proxy_group, ext); + rulesetToSingBox(json, ruleset_content_array, ext.overwrite_original_rules); + + return SerializeObject(json); +} diff --git a/src/generator/config/subexport.h b/src/generator/config/subexport.h index dce5bc5ec..9339b5168 100644 --- a/src/generator/config/subexport.h +++ b/src/generator/config/subexport.h @@ -37,11 +37,11 @@ struct extra_settings tribool skip_cert_verify = tribool(); tribool tls13 = tribool(); bool clash_classical_ruleset = false; - std::string sort_script = ""; + std::string sort_script; std::string clash_proxies_style = "flow"; bool authorized = false; - extra_settings() {}; + extra_settings() = default; extra_settings(const extra_settings&) = delete; extra_settings(extra_settings&&) = delete; @@ -57,9 +57,6 @@ struct extra_settings #endif // NO_JS_RUNTIME }; -void rulesetToClash(YAML::Node &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules, bool new_field_name); -void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_content_array, int surge_ver, bool overwrite_original_rules, std::string remote_path_prefix); - std::string proxyToClash(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, bool clashR, extra_settings &ext); void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGroupConfigs &extra_proxy_group, bool clashR, extra_settings &ext); std::string proxyToSurge(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, int surge_ver, extra_settings &ext); @@ -73,5 +70,6 @@ void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext); void proxyToQuan(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext); std::string proxyToSSD(std::vector &nodes, std::string &group, std::string &userinfo, extra_settings &ext); +std::string proxyToSingBox(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext); #endif // SUBEXPORT_H_INCLUDED diff --git a/src/handler/interfaces.cpp b/src/handler/interfaces.cpp index a3cfaa382..d3e32b006 100644 --- a/src/handler/interfaces.cpp +++ b/src/handler/interfaces.cpp @@ -89,30 +89,28 @@ const std::vector UAMatchList = { {"V2RayX","","","v2ray"} }; -bool verGreaterEqual(const std::string &src_ver, const std::string &target_ver) +bool verGreaterEqual(const std::string& src_ver, const std::string& target_ver) { - string_size src_pos_beg = 0, src_pos_end, target_pos_beg = 0, target_pos_end; - while(true) - { - src_pos_end = src_ver.find('.', src_pos_beg); - if(src_pos_end == src_ver.npos) - src_pos_end = src_ver.size(); - int part_src = std::stoi(src_ver.substr(src_pos_beg, src_pos_end - src_pos_beg)); - target_pos_end = target_ver.find('.', target_pos_beg); - if(target_pos_end == target_ver.npos) - target_pos_end = target_ver.size(); - int part_target = std::stoi(target_ver.substr(target_pos_beg, target_pos_end - target_pos_beg)); - if(part_src > part_target) - break; - else if(part_src < part_target) - return false; - else if(src_pos_end >= src_ver.size() - 1 || target_pos_end >= target_ver.size() - 1) - break; - src_pos_beg = src_pos_end + 1; - target_pos_beg = target_pos_end + 1; + std::istringstream src_stream(src_ver), target_stream(target_ver); + int src_part, target_part; + char dot; + while (src_stream >> src_part) { + if (target_stream >> target_part) { + if (src_part < target_part) { + return false; + } else if (src_part > target_part) { + return true; + } + // Skip the dot separator in both streams + src_stream >> dot; + target_stream >> dot; + } else { + // If we run out of target parts, the source version is greater only if it has more parts + return true; + } } - return true; - + // If we get here, the common parts are equal, so check if target_ver has more parts + return !bool(target_stream >> target_part); } void matchUserAgent(const std::string &user_agent, std::string &target, tribool &clash_new_name, int &surge_ver) @@ -321,7 +319,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) case "ss"_hash: case "ssd"_hash: case "ssr"_hash: case "sssub"_hash: case "v2ray"_hash: case "trojan"_hash: case "mixed"_hash: lSimpleSubscription = true; break; - case "clash"_hash: case "clashr"_hash: case "surge"_hash: case "quan"_hash: case "quanx"_hash: case "loon"_hash: case "surfboard"_hash: case "mellow"_hash: + case "clash"_hash: case "clashr"_hash: case "surge"_hash: case "quan"_hash: case "quanx"_hash: case "loon"_hash: case "surfboard"_hash: case "mellow"_hash: case "singbox"_hash: break; default: *status_code = 400; @@ -363,6 +361,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) /// for external configuration std::string lClashBase = global.clashBase, lSurgeBase = global.surgeBase, lMellowBase = global.mellowBase, lSurfboardBase = global.surfboardBase; std::string lQuanBase = global.quanBase, lQuanXBase = global.quanXBase, lLoonBase = global.loonBase, lSSSubBase = global.SSSubBase; + std::string lSingBoxBase = global.singBoxBase; /// validate urls argEnableInsert.define(global.enableInsert); @@ -458,6 +457,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) checkExternalBase(extconf.quan_rule_base, lQuanBase); checkExternalBase(extconf.quanx_rule_base, lQuanXBase); checkExternalBase(extconf.loon_rule_base, lLoonBase); + checkExternalBase(extconf.singbox_rule_base, lSingBoxBase); if(extconf.surge_ruleset.size()) lCustomRulesets = extconf.surge_ruleset; @@ -877,6 +877,22 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) if(argUpload) uploadGist("ssd", argUploadPath, output_content, false); break; + case "singbox"_hash: + writeLog(0, "Generate target: sing-box", LOG_LEVEL_INFO); + if(!ext.nodelist) + { + if(render_template(fetchFile(lSingBoxBase, proxy, global.cacheConfig), tpl_args, base_content, global.templatePath) != 0) + { + *status_code = 400; + return base_content; + } + } + + output_content = proxyToSingBox(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); + + if(argUpload) + uploadGist("singbox", argUploadPath, output_content, false); + break; default: writeLog(0, "Generate target: Unspecified", LOG_LEVEL_INFO); *status_code = 500; diff --git a/src/handler/settings.cpp b/src/handler/settings.cpp index 79bb5fd16..e0be580ae 100644 --- a/src/handler/settings.cpp +++ b/src/handler/settings.cpp @@ -331,6 +331,7 @@ void readYAMLConf(YAML::Node &node) section["quanx_rule_base"] >> global.quanXBase; section["loon_rule_base"] >> global.loonBase; section["sssub_rule_base"] >> global.SSSubBase; + section["singbox_rule_base"] >> global.singBoxBase; section["default_external_config"] >> global.defaultExtConfig; section["append_proxy_type"] >> global.appendType; @@ -605,6 +606,8 @@ void readTOMLConf(toml::value &root) "quan_rule_base", global.quanBase, "quanx_rule_base", global.quanXBase, "loon_rule_base", global.loonBase, + "sssub_rule_base", global.SSSubBase, + "singbox_rule_base", global.singBoxBase, "proxy_config", global.proxyConfig, "proxy_ruleset", global.proxyRuleset, "proxy_subscription", global.proxySubscription, @@ -842,6 +845,8 @@ void readConf() ini.get_if_exist("quan_rule_base", global.quanBase); ini.get_if_exist("quanx_rule_base", global.quanXBase); ini.get_if_exist("loon_rule_base", global.loonBase); + ini.get_if_exist("sssub_rule_base", global.SSSubBase); + ini.get_if_exist("singbox_rule_base", global.singBoxBase); ini.get_if_exist("default_external_config", global.defaultExtConfig); ini.get_bool_if_exist("append_proxy_type", global.appendType); ini.get_if_exist("proxy_config", global.proxyConfig); @@ -1074,6 +1079,7 @@ int loadExternalYAML(YAML::Node &node, ExternalConfig &ext) section["quanx_rule_base"] >> ext.quanx_rule_base; section["loon_rule_base"] >> ext.loon_rule_base; section["sssub_rule_base"] >> ext.sssub_rule_base; + section["singbox_rule_base"] >> ext.singbox_rule_base; section["enable_rule_generator"] >> ext.enable_rule_generator; section["overwrite_original_rules"] >> ext.overwrite_original_rules; @@ -1146,7 +1152,9 @@ int loadExternalTOML(toml::value &root, ExternalConfig &ext) "mellow_rule_base", ext.mellow_rule_base, "quan_rule_base", ext.quan_rule_base, "quanx_rule_base", ext.quanx_rule_base, + "loon_rule_base", ext.loon_rule_base, "sssub_rule_base", ext.sssub_rule_base, + "singbox_rule_base", ext.singbox_rule_base, "add_emoji", ext.add_emoji, "remove_old_emoji", ext.remove_old_emoji, "include_remarks", ext.include, @@ -1248,6 +1256,7 @@ int loadExternalConfig(std::string &path, ExternalConfig &ext) ini.get_if_exist("quanx_rule_base", ext.quanx_rule_base); ini.get_if_exist("loon_rule_base", ext.loon_rule_base); ini.get_if_exist("sssub_rule_base", ext.sssub_rule_base); + ini.get_if_exist("singbox_rule_base", ext.singbox_rule_base); ini.get_bool_if_exist("overwrite_original_rules", ext.overwrite_original_rules); ini.get_bool_if_exist("enable_rule_generator", ext.enable_rule_generator); diff --git a/src/handler/settings.h b/src/handler/settings.h index 837ff17ef..8e48ab983 100644 --- a/src/handler/settings.h +++ b/src/handler/settings.h @@ -54,7 +54,7 @@ struct Settings std::string clashBase; ProxyGroupConfigs customProxyGroups; - std::string surgeBase, surfboardBase, mellowBase, quanBase, quanXBase, loonBase, SSSubBase; + std::string surgeBase, surfboardBase, mellowBase, quanBase, quanXBase, loonBase, SSSubBase, singBoxBase; std::string surgeSSRPath, quanXDevID; //cache system @@ -83,6 +83,7 @@ struct ExternalConfig std::string quanx_rule_base; std::string loon_rule_base; std::string sssub_rule_base; + std::string singbox_rule_base; RegexMatchConfigs rename; RegexMatchConfigs emoji; string_array include; diff --git a/src/main.cpp b/src/main.cpp index cf66cd83b..e8bb4d676 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include @@ -29,7 +29,7 @@ WebServer webServer; #ifndef _WIN32 void SetConsoleTitle(const std::string &title) { - system(std::string("echo \"\\033]0;" + title + "\\007\\c\"").data()); + system(std::string("echo \"\\033]0;" + title + R"(\007\c")").data()); } #endif // _WIN32 @@ -46,10 +46,10 @@ void setcd(std::string &file) strrchr(szTemp, '\\')[1] = '\0'; #else char *ret = realpath(file.data(), szTemp); - if(ret == NULL) + if(ret == nullptr) return; ret = strcpy(filename, strrchr(szTemp, '/') + 1); - if(ret == NULL) + if(ret == nullptr) return; strrchr(szTemp, '/')[1] = '\0'; #endif // _WIN32 @@ -84,7 +84,7 @@ void chkArg(int argc, char *argv[]) else if(strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--log") == 0) { if(i < argc - 1) - if(freopen(argv[++i], "a", stderr) == NULL) + if(freopen(argv[++i], "a", stderr) == nullptr) std::cerr<<"Error redirecting output to file.\n"; } } @@ -293,7 +293,7 @@ int main(int argc, char *argv[]) //webServer.append_response("GET", "/list-profiles", "text/plain;charset=utf-8", listProfiles); std::string env_port = getEnv("PORT"); - if(env_port.size()) + if(!env_port.empty()) global.listenPort = to_int(env_port, global.listenPort); listener_args args = {global.listenAddress, global.listenPort, global.maxPendingConns, global.maxConcurThreads, cron_tick_caller, 200}; //std::cout<<"Serving HTTP @ http://"< + struct js_traits { + static StringArray unwrap(JSContext *ctx, JSValueConst v) { + StringArray arr; + uint32_t length = JS_GetPropertyToUInt32(ctx, v, "length"); + for (uint32_t i = 0; i < length; i++) { + arr.push_back(JS_GetPropertyToString(ctx, v, i)); + } + return arr; + } + + static JSValue wrap(JSContext *ctx, const StringArray& arr) { + JSValue jsArray = JS_NewArray(ctx); + for (std::size_t i = 0; i < arr.size(); i++) { + JS_SetPropertyUint32(ctx, jsArray, i, JS_NewString(ctx, arr[i])); + } + return jsArray; + } + }; + template<> struct js_traits { static JSValue wrap(JSContext *ctx, const Proxy &n) noexcept { - auto obj = JS_NewObject(ctx); - /* - JS_SetPropertyStr(ctx, obj, "LinkType", JS_NewInt32(ctx, n.linkType)); - JS_SetPropertyStr(ctx, obj, "ID", JS_NewInt32(ctx, n.id)); - JS_SetPropertyStr(ctx, obj, "GroupID", JS_NewInt32(ctx, n.groupID)); - JS_SetPropertyStr(ctx, obj, "Group", JS_NewStringLen(ctx, n.group.c_str(), n.group.size())); - JS_SetPropertyStr(ctx, obj, "Remark", JS_NewStringLen(ctx, n.remarks.c_str(), n.remarks.size())); - JS_SetPropertyStr(ctx, obj, "Server", JS_NewStringLen(ctx, n.server.c_str(), n.server.size())); - JS_SetPropertyStr(ctx, obj, "Port", JS_NewInt32(ctx, n.port)); - JS_SetPropertyStr(ctx, obj, "ProxyInfo", JS_NewStringLen(ctx, n.proxyStr.c_str(), n.proxyStr.size())); - */ - JS_SetPropertyStr(ctx, obj, "Type", JS_NewInt32(ctx, n.Type)); - JS_SetPropertyStr(ctx, obj, "Id", JS_NewInt32(ctx, n.Id)); - JS_SetPropertyStr(ctx, obj, "GroupId", JS_NewInt32(ctx, n.GroupId)); - JS_SetPropertyStr(ctx, obj, "Group", JS_NewStringLen(ctx, n.Group.c_str(), n.Group.size())); - JS_SetPropertyStr(ctx, obj, "Remark", JS_NewStringLen(ctx, n.Remark.c_str(), n.Remark.size())); - JS_SetPropertyStr(ctx, obj, "Server", JS_NewStringLen(ctx, n.Hostname.c_str(), n.Hostname.size())); - JS_SetPropertyStr(ctx, obj, "Port", JS_NewInt32(ctx, n.Port)); - - JS_SetPropertyStr(ctx, obj, "Username", JS_NewStringLen(ctx, n.Username.c_str(), n.Username.size())); - JS_SetPropertyStr(ctx, obj, "Password", JS_NewStringLen(ctx, n.Password.c_str(), n.Password.size())); - JS_SetPropertyStr(ctx, obj, "EncryptMethod", JS_NewStringLen(ctx, n.EncryptMethod.c_str(), n.EncryptMethod.size())); - JS_SetPropertyStr(ctx, obj, "Plugin", JS_NewStringLen(ctx, n.Plugin.c_str(), n.Plugin.size())); - JS_SetPropertyStr(ctx, obj, "PluginOption", JS_NewStringLen(ctx, n.PluginOption.c_str(), n.PluginOption.size())); - JS_SetPropertyStr(ctx, obj, "Protocol", JS_NewStringLen(ctx, n.Protocol.c_str(), n.Protocol.size())); - JS_SetPropertyStr(ctx, obj, "ProtocolParam", JS_NewStringLen(ctx, n.ProtocolParam.c_str(), n.ProtocolParam.size())); - JS_SetPropertyStr(ctx, obj, "OBFS", JS_NewStringLen(ctx, n.OBFS.c_str(), n.OBFS.size())); - JS_SetPropertyStr(ctx, obj, "OBFSParam", JS_NewStringLen(ctx, n.OBFSParam.c_str(), n.OBFSParam.size())); - JS_SetPropertyStr(ctx, obj, "UserId", JS_NewStringLen(ctx, n.UserId.c_str(), n.UserId.size())); - - JS_SetPropertyStr(ctx, obj, "AlterId", JS_NewInt32(ctx, n.AlterId)); - JS_SetPropertyStr(ctx, obj, "TransferProtocol", JS_NewStringLen(ctx, n.TransferProtocol.c_str(), n.TransferProtocol.size())); - JS_SetPropertyStr(ctx, obj, "FakeType", JS_NewStringLen(ctx, n.FakeType.c_str(), n.FakeType.size())); - JS_SetPropertyStr(ctx, obj, "TLSSecure", JS_NewBool(ctx, n.TLSSecure)); - - JS_SetPropertyStr(ctx, obj, "Host", JS_NewStringLen(ctx, n.Host.c_str(), n.Host.size())); - JS_SetPropertyStr(ctx, obj, "Path", JS_NewStringLen(ctx, n.Path.c_str(), n.Path.size())); - JS_SetPropertyStr(ctx, obj, "Edge", JS_NewStringLen(ctx, n.Edge.c_str(), n.Edge.size())); - - JS_SetPropertyStr(ctx, obj, "QUICSecure", JS_NewStringLen(ctx, n.QUICSecure.c_str(), n.QUICSecure.size())); - JS_SetPropertyStr(ctx, obj, "QUICSecret", JS_NewStringLen(ctx, n.QUICSecret.c_str(), n.QUICSecret.size())); - - JS_SetPropertyStr(ctx, obj, "UDP", js_traits::wrap(ctx, n.UDP)); - JS_SetPropertyStr(ctx, obj, "TCPFastOpen", js_traits::wrap(ctx, n.TCPFastOpen)); - JS_SetPropertyStr(ctx, obj, "AllowInsecure", js_traits::wrap(ctx, n.AllowInsecure)); - JS_SetPropertyStr(ctx, obj, "TLS13", js_traits::wrap(ctx, n.TLS13)); + JSValue obj = JS_NewObjectProto(ctx, JS_NULL); + if (JS_IsException(obj)) { + return obj; + } + + JS_DefinePropertyValueStr(ctx, obj, "Type", JS_NewInt32(ctx, n.Type), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "Id", JS_NewUint32(ctx, n.Id), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "GroupId", JS_NewUint32(ctx, n.GroupId), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "Group", JS_NewString(ctx, n.Group), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "Remark", JS_NewString(ctx, n.Remark), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "Server", JS_NewString(ctx, n.Hostname), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "Port", JS_NewInt32(ctx, n.Port), JS_PROP_C_W_E); + + JS_DefinePropertyValueStr(ctx, obj, "Username", JS_NewString(ctx, n.Username), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "Password", JS_NewString(ctx, n.Password), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "EncryptMethod", JS_NewString(ctx, n.EncryptMethod), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "Plugin", JS_NewString(ctx, n.Plugin), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "PluginOption", JS_NewString(ctx, n.PluginOption), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "Protocol", JS_NewString(ctx, n.Protocol), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ProtocolParam", JS_NewString(ctx, n.ProtocolParam), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "OBFS", JS_NewString(ctx, n.OBFS), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "OBFSParam", JS_NewString(ctx, n.OBFSParam), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "UserId", JS_NewString(ctx, n.UserId), JS_PROP_C_W_E); + + JS_DefinePropertyValueStr(ctx, obj, "AlterId", JS_NewInt32(ctx, n.AlterId), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "TransferProtocol", JS_NewString(ctx, n.TransferProtocol), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "FakeType", JS_NewString(ctx, n.FakeType), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "TLSSecure", JS_NewBool(ctx, n.TLSSecure), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "Host", JS_NewString(ctx, n.Host), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "Path", JS_NewString(ctx, n.Path), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "Edge", JS_NewString(ctx, n.Edge), JS_PROP_C_W_E); + + JS_DefinePropertyValueStr(ctx, obj, "QUICSecure", JS_NewString(ctx, n.QUICSecure), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "QUICSecret", JS_NewString(ctx, n.QUICSecret), JS_PROP_C_W_E); + + JS_DefinePropertyValueStr(ctx, obj, "UDP", js_traits::wrap(ctx, n.UDP), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "TCPFastOpen", js_traits::wrap(ctx, n.TCPFastOpen), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "AllowInsecure", js_traits::wrap(ctx, n.AllowInsecure), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "TLS13", js_traits::wrap(ctx, n.TLS13), JS_PROP_C_W_E); + + JS_DefinePropertyValueStr(ctx, obj, "SnellVersion", JS_NewInt32(ctx, n.SnellVersion), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ServerName", JS_NewString(ctx, n.ServerName), JS_PROP_C_W_E); + + JS_DefinePropertyValueStr(ctx, obj, "SelfIP", JS_NewString(ctx, n.SelfIP), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "SelfIPv6", JS_NewString(ctx, n.SelfIPv6), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "PublicKey", JS_NewString(ctx, n.PublicKey), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "PrivateKey", JS_NewString(ctx, n.PrivateKey), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "PreSharedKey", JS_NewString(ctx, n.PreSharedKey), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "DnsServers", js_traits::wrap(ctx, n.DnsServers), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "Mtu", JS_NewUint32(ctx, n.Mtu), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "AllowedIPs", JS_NewString(ctx, n.AllowedIPs), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "KeepAlive", JS_NewUint32(ctx, n.KeepAlive), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "TestUrl", JS_NewString(ctx, n.TestUrl), JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ClientId", JS_NewString(ctx, n.ClientId), JS_PROP_C_W_E); return obj; } + static Proxy unwrap(JSContext *ctx, JSValueConst v) { Proxy node; - /* - node.linkType = JS_GetPropertyToInt32(ctx, v, "LinkType"); - node.id = JS_GetPropertyToInt32(ctx, v, "ID"); - node.groupID = JS_GetPropertyToInt32(ctx, v, "GroupID"); - node.group = JS_GetPropertyToString(ctx, v, "Group"); - node.remarks = JS_GetPropertyToString(ctx, v, "Remark"); - node.server = JS_GetPropertyToString(ctx, v, "Server"); - node.port = JS_GetPropertyToInt32(ctx, v, "Port"); - node.proxyStr = JS_GetPropertyToString(ctx, v, "ProxyInfo"); - */ node.Type = JS_GetPropertyToInt32(ctx, v, "Type"); node.Id = JS_GetPropertyToInt32(ctx, v, "Id"); node.GroupId = JS_GetPropertyToInt32(ctx, v, "GroupId"); node.Group = JS_GetPropertyToString(ctx, v, "Group"); node.Remark = JS_GetPropertyToString(ctx, v, "Remark"); - node.Hostname = JS_GetPropertyToString(ctx, v, "Hostname"); + node.Hostname = JS_GetPropertyToString(ctx, v, "Server"); node.Port = JS_GetPropertyToUInt32(ctx, v, "Port"); node.Username = JS_GetPropertyToString(ctx, v, "Username"); @@ -187,6 +222,21 @@ namespace qjs node.AllowInsecure = js_traits::JS_GetPropertyToTriBool(ctx, v, "AllowInsecure"); node.TLS13 = js_traits::JS_GetPropertyToTriBool(ctx, v, "TLS13"); + node.SnellVersion = JS_GetPropertyToInt32(ctx, v, "SnellVersion"); + node.ServerName = JS_GetPropertyToString(ctx, v, "ServerName"); + + node.SelfIP = JS_GetPropertyToString(ctx, v, "SelfIP"); + node.SelfIPv6 = JS_GetPropertyToString(ctx, v, "SelfIPv6"); + node.PublicKey = JS_GetPropertyToString(ctx, v, "PublicKey"); + node.PrivateKey = JS_GetPropertyToString(ctx, v, "PrivateKey"); + node.PreSharedKey = JS_GetPropertyToString(ctx, v, "PreSharedKey"); + node.DnsServers = js_traits::unwrap(ctx, JS_GetPropertyStr(ctx, v, "DnsServers")); + node.Mtu = JS_GetPropertyToUInt32(ctx, v, "Mtu"); + node.AllowedIPs = JS_GetPropertyToString(ctx, v, "AllowedIPs"); + node.KeepAlive = JS_GetPropertyToUInt32(ctx, v, "KeepAlive"); + node.TestUrl = JS_GetPropertyToString(ctx, v, "TestUrl"); + node.ClientId = JS_GetPropertyToString(ctx, v, "ClientId"); + return node; } }; diff --git a/src/server/webserver.cpp b/src/server/webserver.cpp index 321b2a4d6..cbba00056 100644 --- a/src/server/webserver.cpp +++ b/src/server/webserver.cpp @@ -74,7 +74,7 @@ void file_not_found(std::string arguments, int sock) std::string response = "HTTP/1.1 404 Not Found\r\n" "Content-Type: text/plain\r\nConnection: close\r\n" "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: *\r\n" - "Content-Length: " + std::__cxx11::to_string(prompt_info.size()) + "\r\n\r\n" + prompt_info + "\r\n"; + "Content-Length: " + std::to_string(prompt_info.size()) + "\r\n\r\n" + prompt_info + "\r\n"; if (sendall(sock, response) == -1) { @@ -124,7 +124,7 @@ void serve_options(SOCKET sock) void serve_content(SOCKET sock, std::string type, std::string content) { send_header(sock, type.data()); - std::string extra_header = "Content-Length: " + std::__cxx11::to_string(content.size()) + "\r\n"; + std::string extra_header = "Content-Length: " + std::to_string(content.size()) + "\r\n"; sendall(sock, extra_header); send(sock, "\r\n", 2, 0); if (sendall(sock, content) == -1) diff --git a/src/server/webserver.h b/src/server/webserver.h index b53a2b60b..952945e6e 100644 --- a/src/server/webserver.h +++ b/src/server/webserver.h @@ -4,7 +4,6 @@ #include #include #include -#include #include #include "../utils/map_extra.h" @@ -69,10 +68,7 @@ class WebServer int start_web_server(void *argv); int start_web_server_multi(void *argv); void stop_web_server(); -private: - int serveFile(const std::string &filename, std::string &content_type, std::string &return_data); - inline int process_request(Request &request, Response &response, std::string &return_data); - void on_request(evhttp_request *req, void *args); + std::vector responses; string_map redirect_map; }; diff --git a/src/server/webserver_httplib.cpp b/src/server/webserver_httplib.cpp new file mode 100644 index 000000000..ce32017b6 --- /dev/null +++ b/src/server/webserver_httplib.cpp @@ -0,0 +1,223 @@ +#include +#ifdef MALLOC_TRIM +#include +#endif // MALLOC_TRIM +#include "httplib.h" + +#include "../utils/base64/base64.h" +#include "../utils/logger.h" +#include "../utils/string_hash.h" +#include "../utils/stl_extra.h" +#include "../utils/urlencode.h" +#include "webserver.h" + +int WebServer::start_web_server(void *argv) +{ + return start_web_server_multi(argv); +} + +void WebServer::stop_web_server() +{ + SERVER_EXIT_FLAG = true; +} + +static httplib::Server::Handler makeHandler(const responseRoute &rr) +{ + return [rr](const httplib::Request &request, httplib::Response &response) + { + Request req; + Response resp; + req.method = request.method; + req.url = request.path; + for (auto &h: request.headers) + { + req.headers[h.first] = h.second; + } + for (auto &p: request.params) + { + req.argument += p.first + "=" + p.second + "&"; + } + req.argument.pop_back(); + req.postdata = request.body; + if (request.get_header_value("Content-Type") == "application/x-www-form-urlencoded") + { + req.postdata = urlDecode(req.postdata); + } + auto result = rr.rc(req, resp); + response.status = resp.status_code; + for (auto &h: resp.headers) { + response.set_header(h.first, h.second); + } + auto content_type = resp.content_type; + if (content_type.empty()) + { + content_type = rr.content_type; + } + response.set_content(result, content_type); + }; +} + +int WebServer::start_web_server_multi(void *argv) +{ + httplib::Server server; + auto *args = (listener_args *)argv; + for (auto &x : responses) + { + switch (hash_(x.method)) + { + case "GET"_hash: case "HEAD"_hash: + server.Get(x.path, makeHandler(x)); + break; + case "POST"_hash: + server.Post(x.path, makeHandler(x)); + break; + case "PUT"_hash: + server.Put(x.path, makeHandler(x)); + break; + case "DELETE"_hash: + server.Delete(x.path, makeHandler(x)); + break; + case "PATCH"_hash: + server.Patch(x.path, makeHandler(x)); + break; + } + } + server.Options(R"(.*)", [&](const httplib::Request &req, httplib::Response &res) { + auto path = req.path; + std::string allowed; + for (auto &rr : responses) { + if (rr.path == path) + { + allowed += rr.method + ","; + } + } + if (!allowed.empty()) + { + allowed.pop_back(); + res.status = 200; + res.set_header("Access-Control-Allow-Methods", allowed); + res.set_header("Access-Control-Allow-Origin", "*"); + res.set_header("Access-Control-Allow-Headers", "Content-Type,Authorization"); + } + }); + server.set_pre_routing_handler([&](const httplib::Request &req, httplib::Response &res) { + std::string params; + for (auto &p: req.params) + { + params += p.first + "=" + p.second + "&"; + } + if (!params.empty()) + { + params.pop_back(); + params = "?" + params; + } + writeLog(0, "Accept connection from client " + req.remote_addr + ":" + std::to_string(req.remote_port), LOG_LEVEL_DEBUG); + writeLog(0, "handle_cmd: " + req.method + " handle_uri: " + req.path + params, LOG_LEVEL_VERBOSE); + + if (req.has_header("SubConverter-Request")) + { + res.status = 500; + res.set_content("Loop request detected!", "text/plain"); + return httplib::Server::HandlerResponse::Handled; + } + res.set_header("Server", "subconverter/" VERSION " cURL/" LIBCURL_VERSION); + if (require_auth) + { + static std::string auth_token = "Basic " + base64Encode(auth_user + ":" + auth_password); + auto auth = req.get_header_value("Authorization"); + if (auth != auth_token) + { + res.status = 401; + res.set_header("WWW-Authenticate", "Basic realm=" + auth_realm + ", charset=\"UTF-8\""); + res.set_content("Unauthorized", "text/plain"); + return httplib::Server::HandlerResponse::Handled; + } + } + res.set_header("X-Client-IP", req.remote_addr); + if (req.has_header("Access-Control-Request-Headers")) + { + res.set_header("Access-Control-Allow-Headers", req.get_header_value("Access-Control-Request-Headers")); + } + return httplib::Server::HandlerResponse::Unhandled; + }); + for (auto &x : redirect_map) + { + server.Get(x.first, [x](const httplib::Request &req, httplib::Response &res) { + res.set_redirect(x.second); + }); + } + server.set_exception_handler([](const httplib::Request &req, httplib::Response &res, const std::exception_ptr &e) { + try + { + std::rethrow_exception(e); + } + catch (const httplib::Error &err) + { + res.set_content(to_string(err), "text/plain"); + } + catch (const std::exception &ex) + { + std::string params; + for (auto &p: req.params) + { + params += p.first + "=" + p.second + "&"; + } + params.pop_back(); + std::string return_data = "Internal server error while processing request path '" + req.path + "' with arguments '" + params + "'!\n"; + return_data += "\n exception: "; + return_data += type(ex); + return_data += "\n what(): "; + return_data += ex.what(); + res.set_content(return_data, "text/plain"); + } + catch (...) + { + res.status = 500; + } + }); + if (serve_file) + { + server.set_mount_point("/", serve_file_root); + } + server.bind_to_port(args->listen_address, args->port, 0); + + pthread_t tid; + pthread_create(&tid, nullptr, [](void *args) -> void * { + auto *server = (httplib::Server *)args; + server->listen_after_bind(); + return nullptr; + }, &server); + + while (!SERVER_EXIT_FLAG) + { + if (args->looper_callback) + { + args->looper_callback(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(args->looper_interval)); + } + + server.stop(); + return 0; +} + +void WebServer::append_response(const std::string &method, const std::string &uri, const std::string &content_type, response_callback response) +{ + responseRoute rr; + rr.method = method; + rr.path = uri; + rr.content_type = content_type; + rr.rc = response; + responses.emplace_back(std::move(rr)); +} + +void WebServer::append_redirect(const std::string &uri, const std::string &target) +{ + redirect_map[uri] = target; +} + +void WebServer::reset_redirect() +{ + eraseElements(redirect_map); +} + diff --git a/src/server/webserver_libevent.cpp b/src/server/webserver_libevent.cpp index 9d821ed9b..8ed33158f 100644 --- a/src/server/webserver_libevent.cpp +++ b/src/server/webserver_libevent.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #ifdef MALLOC_TRIM @@ -8,13 +7,11 @@ #endif // MALLOC_TRIM #include -#include #include #include -#include +#include #include #include -#include #include "../utils/base64/base64.h" #include "../utils/file_extra.h" @@ -65,7 +62,7 @@ bool matchSpaceSeparatedList(const std::string& source, const std::string &targe while(pos_begin < total) { pos_end = source.find(' ', pos_begin); - if(pos_end == source.npos) + if(pos_end == std::string::npos) pos_end = total; if(source.compare(pos_begin, pos_end - pos_begin, target) == 0) return true; @@ -76,12 +73,12 @@ bool matchSpaceSeparatedList(const std::string& source, const std::string &targe std::string checkMIMEType(const std::string &filename) { - string_size name_begin = 0, name_end = 0; + string_size name_begin, name_end; name_begin = filename.rfind('/'); - if(name_begin == filename.npos) + if(name_begin == std::string::npos) name_begin = 0; name_end = filename.rfind('.'); - if(name_end == filename.npos || name_end < name_begin || name_end == filename.size() - 1) + if(name_end == std::string::npos || name_end < name_begin || name_end == filename.size() - 1) return "application/octet-stream"; std::string extension = filename.substr(name_end + 1); for(MIME_type &x : mime_types) @@ -90,10 +87,10 @@ std::string checkMIMEType(const std::string &filename) return "application/octet-stream"; } -int WebServer::serveFile(const std::string &filename, std::string &content_type, std::string &return_data) +int serveFile(WebServer *server, const std::string &filename, std::string &content_type, std::string &return_data) { - std::string realname = serve_file_root + filename; - if(filename.compare("/") == 0) + std::string realname = server->serve_file_root + filename; + if(filename == "/") realname += "index.html"; if(!fileExist(realname)) return 1; @@ -115,12 +112,12 @@ static inline void buffer_cleanup(struct evbuffer *eb) #endif // MALLOC_TRIM } -inline int WebServer::process_request(Request &request, Response &response, std::string &return_data) +inline int process_request(WebServer *server, Request &request, Response &response, std::string &return_data) { writeLog(0, "handle_cmd: " + request.method + " handle_uri: " + request.url, LOG_LEVEL_VERBOSE); - string_size pos = request.url.find("?"); - if(pos != request.url.npos) + string_size pos = request.url.find('?'); + if(pos != std::string::npos) { request.argument = request.url.substr(pos + 1); request.url.erase(pos); @@ -128,13 +125,13 @@ inline int WebServer::process_request(Request &request, Response &response, std: if(request.method == "OPTIONS") { - for(responseRoute &x : responses) + for(responseRoute &x : server->responses) if(matchSpaceSeparatedList(replaceAllDistinct(request.postdata, ",", ""), x.method) && x.path == request.url) return 1; return -1; } - for(responseRoute &x : responses) + for(responseRoute &x : server->responses) { if(x.method == request.method && x.path == request.url) { @@ -159,13 +156,13 @@ inline int WebServer::process_request(Request &request, Response &response, std: } } - auto iter = redirect_map.find(request.url); - if(iter != redirect_map.end()) + auto iter = server->redirect_map.find(request.url); + if(iter != server->redirect_map.end()) { return_data = iter->second; - if(request.argument.size()) + if(!request.argument.empty()) { - if(return_data.find("?") != return_data.npos) + if(return_data.find('?') != std::string::npos) return_data += "&" + request.argument; else return_data += "?" + request.argument; @@ -173,19 +170,19 @@ inline int WebServer::process_request(Request &request, Response &response, std: return 2; } - if(serve_file) + if(server->serve_file) { - if(request.method.compare("GET") == 0 && serveFile(request.url, response.content_type, return_data) == 0) + if(request.method == "GET" && serveFile(server, request.url, response.content_type, return_data) == 0) return 0; } return -1; } -void WebServer::on_request(evhttp_request *req, void *args) +void on_request(evhttp_request *req, void *args) { - (void)args; - static std::string auth_token = "Basic " + base64Encode(auth_user + ":" + auth_password); + auto server = (WebServer*) args; + static std::string auth_token = "Basic " + base64Encode(server->auth_user + ":" + server->auth_password); const char *req_content_type = evhttp_find_header(req->input_headers, "Content-Type"), *req_ac_method = evhttp_find_header(req->input_headers, "Access-Control-Request-Method"); const char *uri = req->uri, *internal_flag = evhttp_find_header(req->input_headers, "SubConverter-Request"); @@ -201,12 +198,12 @@ void WebServer::on_request(evhttp_request *req, void *args) return; } - if (require_auth) + if (server->require_auth) { const char *auth = evhttp_find_header(req->input_headers, "Authorization"); if (auth == nullptr || auth_token != auth) { - evhttp_add_header(req->output_headers, "WWW-Authenticate", ("Basic realm=\"" + auth_realm + "\"").data()); + evhttp_add_header(req->output_headers, "WWW-Authenticate", ("Basic realm=\"" + server->auth_realm + "\"").data()); auto buffer = evhttp_request_get_output_buffer(req); evbuffer_add_printf(buffer, "Unauthorized"); evhttp_send_reply(req, 401, nullptr, buffer); @@ -252,7 +249,7 @@ void WebServer::on_request(evhttp_request *req, void *args) request.headers.emplace("X-Client-IP", client_ip); std::string return_data; - int retVal = process_request(request, response, return_data); + int retVal = process_request(server, request, response, return_data); std::string &content_type = response.content_type; auto *output_buffer = evhttp_request_get_output_buffer(req); @@ -277,7 +274,7 @@ void WebServer::on_request(evhttp_request *req, void *args) evhttp_send_reply(req, HTTP_MOVETEMP, nullptr, nullptr); break; case 0: //found normal - if (content_type.size()) + if (!content_type.empty()) evhttp_add_header(req->output_headers, "Content-Type", content_type.c_str()); evhttp_add_header(req->output_headers, "Access-Control-Allow-Origin", "*"); evhttp_add_header(req->output_headers, "Connection", "close"); @@ -298,7 +295,7 @@ void WebServer::on_request(evhttp_request *req, void *args) int WebServer::start_web_server(void *argv) { - struct listener_args *args = reinterpret_cast(argv); + auto *args = reinterpret_cast(argv); std::string listen_address = args->listen_address; int port = args->port; if (!event_init()) @@ -320,7 +317,7 @@ int WebServer::start_web_server(void *argv) auto call_on_request = [&](evhttp_request *req, void *args) { on_request(req, args); }; evhttp_set_allowed_methods(server.get(), EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_OPTIONS | EVHTTP_REQ_PUT | EVHTTP_REQ_PATCH | EVHTTP_REQ_DELETE | EVHTTP_REQ_HEAD); - evhttp_set_gencb(server.get(), wrap(call_on_request), nullptr); + evhttp_set_gencb(server.get(), wrap(call_on_request), this); evhttp_set_timeout(server.get(), 30); if (event_dispatch() == -1) { @@ -360,7 +357,7 @@ int httpserver_bindsocket(std::string listen_address, int listen_port, int backl } #endif - struct sockaddr_in addr; + struct sockaddr_in addr {}; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(listen_address.data()); @@ -380,7 +377,7 @@ int httpserver_bindsocket(std::string listen_address, int listen_port, int backl int WebServer::start_web_server_multi(void *argv) { - struct listener_args *args = reinterpret_cast(argv); + auto *args = reinterpret_cast(argv); std::string listen_address = args->listen_address; int port = args->port, nthreads = args->max_workers, max_conn = args->max_conn; @@ -404,7 +401,7 @@ int WebServer::start_web_server_multi(void *argv) return -1; evhttp_set_allowed_methods(httpd, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_OPTIONS | EVHTTP_REQ_PUT | EVHTTP_REQ_PATCH | EVHTTP_REQ_DELETE | EVHTTP_REQ_HEAD); - evhttp_set_gencb(httpd, wrap(call_on_request), nullptr); + evhttp_set_gencb(httpd, wrap(call_on_request), this); evhttp_set_timeout(httpd, 30); if (pthread_create(&ths[i], nullptr, httpserver_dispatch, base[i]) != 0) return -1; diff --git a/src/utils/rapidjson_extra.h b/src/utils/rapidjson_extra.h index 240712afb..c3f580095 100644 --- a/src/utils/rapidjson_extra.h +++ b/src/utils/rapidjson_extra.h @@ -72,5 +72,26 @@ inline std::string SerializeObject(const rapidjson::Value& value) return sb.GetString(); } +template +inline rapidjson::Value buildObject(rapidjson::MemoryPoolAllocator<> & allocator, Args... kvs) +{ + static_assert(sizeof...(kvs) % 2 == 0, "buildObject requires an even number of arguments"); + static_assert((std::is_same::value && ...), "buildObject requires all arguments to be const char*"); + rapidjson::Value ret(rapidjson::kObjectType); + auto args = {kvs...}; + auto it = args.begin(); + while (it != args.end()) + { + const char *key = *it++, *value = *it++; + ret.AddMember(rapidjson::StringRef(key), rapidjson::StringRef(value), allocator); + } + return ret; +} + +inline rapidjson::Value buildBooleanValue(bool value) +{ + return value ? rapidjson::Value(rapidjson::kTrueType) : rapidjson::Value(rapidjson::kFalseType); +} + #endif // RAPIDJSON_EXTRA_H_INCLUDED diff --git a/src/utils/regexp.cpp b/src/utils/regexp.cpp index e0ddaa2d3..e18e89e91 100644 --- a/src/utils/regexp.cpp +++ b/src/utils/regexp.cpp @@ -1,5 +1,5 @@ #include -#include +#include /* #ifdef USE_STD_REGEX @@ -193,7 +193,7 @@ std::vector regGetAllMatch(const std::string &src, const std::strin reg.setPattern(match).addModifier("m").addPcre2Option(PCRE2_UTF|PCRE2_ALT_BSUX).compile(); jp::VecNum vec_num; jp::RegexMatch rm; - size_t count = rm.setRegexObject(®).setSubject(src).setNumberedSubstringVector(&vec_num).setModifier("g").match(); + size_t count = rm.setRegexObject(®).setSubject(src).setNumberedSubstringVector(&vec_num).setModifier("gm").match(); std::vector result; if(!count) return result; @@ -209,6 +209,7 @@ std::vector regGetAllMatch(const std::string &src, const std::strin { match_index++; index = begin; + continue; } result.push_back(std::move(vec_num[match_index][index])); index++; @@ -220,5 +221,5 @@ std::vector regGetAllMatch(const std::string &src, const std::strin std::string regTrim(const std::string &src) { - return regReplace(src, "^\\s*([\\s\\S]*)\\s*$", "$1", false, false); + return regReplace(src, R"(^\s*([\s\S]*)\s*$)", "$1", false, false); } diff --git a/src/utils/tribool.h b/src/utils/tribool.h index 03b3634d9..cf8bfd043 100644 --- a/src/utils/tribool.h +++ b/src/utils/tribool.h @@ -8,105 +8,102 @@ class tribool { -private: - - char _M_VALUE = 0; - public: + tribool() : value_(indeterminate) {} + tribool(bool value) : value_(value ? true_value : false_value) {} + tribool(const std::string& str) { set(str); } - tribool() { clear(); } - - template tribool(const T &value) { set(value); } - - tribool(const tribool &value) { _M_VALUE = value._M_VALUE; } - - ~tribool() = default; + tribool(const tribool& other) = default; + tribool& operator=(const tribool& other) = default; - tribool& operator=(const tribool &src) + tribool& operator=(bool value) { - _M_VALUE = src._M_VALUE; + value_ = value ? true_value : false_value; return *this; } - template tribool& operator=(const T &value) - { - set(value); - return *this; - } + bool operator==(const tribool& other) const { return value_ == other.value_; } - inline bool operator==(const tribool& rhs){ return _M_VALUE == rhs._M_VALUE; } + operator bool() const { return value_ == true_value; } - operator bool() const { return _M_VALUE == 3; } + bool is_undef() const { return value_ == indeterminate; } - bool is_undef() const { return _M_VALUE <= 1; } - - template tribool& define(const T &value) + template tribool& define(const T& value) { - if(_M_VALUE <= 1) + if (is_undef()) *this = value; return *this; } - template tribool& parse(const T &value) + template tribool& parse(const T& value) { return define(value); } tribool reverse() { - if(_M_VALUE > 1) - _M_VALUE = _M_VALUE > 2 ? 2 : 3; + if (value_ == false_value) + value_ = true_value; + else if (value_ == true_value) + value_ = false_value; return *this; } - bool get(const bool &def_value = false) const + bool get(const bool& def_value = false) const { - if(_M_VALUE <= 1) + if (is_undef()) return def_value; - return _M_VALUE == 3; + return value_ == true_value; } std::string get_str() const { - switch(_M_VALUE) + switch (value_) { - case 2: - return "false"; - case 3: - return "true"; + case indeterminate: + return "undef"; + case false_value: + return "false"; + case true_value: + return "true"; + default: + return ""; } - return "undef"; } - template bool set(const T &value) + template bool set(const T& value) { - _M_VALUE = (bool)value + 2; - return _M_VALUE > 2; + value_ = (bool)value ? true_value : false_value; + return value_; } - bool set(const std::string &str) + bool set(const std::string& str) { - switch(hash_(str)) + switch (hash_(str)) { - case "true"_hash: - case "1"_hash: - _M_VALUE = 3; - break; - case "false"_hash: - case "0"_hash: - _M_VALUE = 2; - break; - default: - if(to_int(str, 0) > 1) - _M_VALUE = 3; - else - _M_VALUE = 0; - break; + case "true"_hash: + case "1"_hash: + value_ = true_value; + break; + case "false"_hash: + case "0"_hash: + value_ = false_value; + break; + default: + if (to_int(str, 0) > 1) + value_ = true_value; + else + value_ = indeterminate; + break; } - return _M_VALUE; + return !is_undef(); } - void clear() { _M_VALUE = 0; } + void clear() { value_ = indeterminate; } + +private: + enum value_type : char { indeterminate = 0, false_value = 1, true_value = 2 }; + value_type value_; }; #endif // TRIBOOL_H_INCLUDED From 833dee1dbc0363ea21c204a7126b2e35b8082623 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Thu, 9 Nov 2023 12:01:16 +0800 Subject: [PATCH 036/120] Optimize codes --- src/generator/config/ruleconvert.cpp | 43 ++++++++--------- src/generator/config/subexport.cpp | 69 ++++++++++---------------- src/parser/subparser.cpp | 3 +- src/utils/rapidjson_extra.h | 72 ++++++++++++++++++++++------ src/utils/string.cpp | 8 ++-- 5 files changed, 109 insertions(+), 86 deletions(-) diff --git a/src/generator/config/ruleconvert.cpp b/src/generator/config/ruleconvert.cpp index a38849189..543cec488 100644 --- a/src/generator/config/ruleconvert.cpp +++ b/src/generator/config/ruleconvert.cpp @@ -457,18 +457,25 @@ void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_c } } -static rapidjson::Value transformRuleToSingBox(const std::string& rule, rapidjson::MemoryPoolAllocator<>& allocator) +static rapidjson::Value transformRuleToSingBox(const std::string& rule, const std::string &group, rapidjson::MemoryPoolAllocator<>& allocator) { - std::string type, value, group, option; + auto args = split(rule, ","); + if (args.size() < 2) return rapidjson::Value(rapidjson::kObjectType); + auto type = toLower(std::string(args[0])); + auto value = args[1]; +// std::string_view option; +// if (args.size() >= 3) option = args[2]; - regGetMatch(rule, "^(.*?),(.*?)(?:,(.*?))?(,.*)?$", 5, nullptr, &type, &value, &group, &option); rapidjson::Value rule_obj(rapidjson::kObjectType); type = replaceAllDistinct(toLower(type), "-", "_"); type = replaceAllDistinct(type, "ip_cidr6", "ip_cidr"); - if (type == "match" || type == "final") { - rule_obj.AddMember("outbound", rapidjson::Value(value.c_str(), allocator), allocator); - } else { - rule_obj.AddMember(rapidjson::Value(type.c_str(), allocator), rapidjson::Value(value.c_str(), allocator), allocator); + if (type == "match" || type == "final") + { + rule_obj.AddMember("outbound", rapidjson::Value(value.data(), value.size(), allocator), allocator); + } + else + { + rule_obj.AddMember(rapidjson::Value(type.c_str(), allocator), rapidjson::Value(value.data(), value.size(), allocator), allocator); rule_obj.AddMember("outbound", rapidjson::Value(group.c_str(), allocator), allocator); } return rule_obj; @@ -476,6 +483,7 @@ static rapidjson::Value transformRuleToSingBox(const std::string& rule, rapidjso void rulesetToSingBox(rapidjson::Document &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules) { + using namespace rapidjson_ext; std::string rule_group, retrieved_rules, strLine, final; std::stringstream strStrm; size_t total_rules = 0; @@ -507,10 +515,7 @@ void rulesetToSingBox(rapidjson::Document &base_rule, std::vector &nodes, int types, extra_settings & std::string proxyToSSSub(std::string base_conf, std::vector &nodes, extra_settings &ext) { - rapidjson::Document json, base; - std::string output_content; - - auto &alloc = json.GetAllocator(); - json.SetObject(); - json.AddMember("remarks", "", alloc); - json.AddMember("server", "", alloc); - json.AddMember("server_port", 0, alloc); - json.AddMember("method", "", alloc); - json.AddMember("password", "", alloc); - json.AddMember("plugin", "", alloc); - json.AddMember("plugin_opts", "", alloc); + using namespace rapidjson_ext; + rapidjson::Document base; + + auto &alloc = base.GetAllocator(); base_conf = trimWhitespace(base_conf); if(base_conf.empty()) base_conf = "{}"; rapidjson::ParseResult result = base.Parse(base_conf.data()); - if(result) - { - for(auto iter = base.MemberBegin(); iter != base.MemberEnd(); iter++) - json.AddMember(iter->name, iter->value, alloc); - } - else + if (!result) writeLog(0, std::string("SIP008 base loader failed with error: ") + rapidjson::GetParseError_En(result.Code()) + " (" + std::to_string(result.Offset()) + ")", LOG_LEVEL_ERROR); - rapidjson::Value jsondata; - jsondata = json.Move(); - - output_content = "["; + rapidjson::Value proxies(rapidjson::kArrayType); for(Proxy &x : nodes) { std::string &remark = x.Remark; @@ -1051,19 +1035,18 @@ std::string proxyToSSSub(std::string base_conf, std::vector &nodes, extra default: continue; } - jsondata["remarks"].SetString(rapidjson::StringRef(remark.c_str(), remark.size())); - jsondata["server"].SetString(rapidjson::StringRef(hostname.c_str(), hostname.size())); - jsondata["server_port"] = x.Port; - jsondata["password"].SetString(rapidjson::StringRef(password.c_str(), password.size())); - jsondata["method"].SetString(rapidjson::StringRef(method.c_str(), method.size())); - jsondata["plugin"].SetString(rapidjson::StringRef(plugin.c_str(), plugin.size())); - jsondata["plugin_opts"].SetString(rapidjson::StringRef(pluginopts.c_str(), pluginopts.size())); - output_content += SerializeObject(jsondata) + ","; + rapidjson::Value proxy(rapidjson::kObjectType); + proxy.CopyFrom(base, alloc) + | AddMemberOrReplace("remarks", rapidjson::Value(remark.c_str(), remark.size()), alloc) + | AddMemberOrReplace("server", rapidjson::Value(hostname.c_str(), hostname.size()), alloc) + | AddMemberOrReplace("server_port", rapidjson::Value(x.Port), alloc) + | AddMemberOrReplace("method", rapidjson::Value(method.c_str(), method.size()), alloc) + | AddMemberOrReplace("password", rapidjson::Value(password.c_str(), password.size()), alloc) + | AddMemberOrReplace("plugin", rapidjson::Value(plugin.c_str(), plugin.size()), alloc) + | AddMemberOrReplace("plugin_opts", rapidjson::Value(pluginopts.c_str(), pluginopts.size()), alloc); + proxies.PushBack(proxy, alloc); } - if(output_content.size() > 1) - output_content.erase(output_content.size() - 1); - output_content += "]"; - return output_content; + return proxies | SerializeObject(); } std::string proxyToQuan(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) @@ -2101,6 +2084,7 @@ static rapidjson::Value buildV2RayTransport(const Proxy& proxy, rapidjson::Memor } void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { + using namespace rapidjson_ext; rapidjson::Document::AllocatorType &allocator = json.GetAllocator(); rapidjson::Value outbounds(rapidjson::kArrayType), route(rapidjson::kArrayType); std::vector nodelist; @@ -2231,11 +2215,8 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); proxy.AddMember("server_port", x.Port, allocator); - if (x.TLSSecure) - { - proxy.AddMember("username", rapidjson::StringRef(x.Username.c_str()), allocator); - proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); - } + proxy.AddMember("username", rapidjson::StringRef(x.Username.c_str()), allocator); + proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); break; } case ProxyType::SOCKS5: @@ -2272,7 +2253,7 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v nodelist.push_back(x); outbounds.PushBack(proxy, allocator); } - for (const ProxyGroupConfig& x: extra_proxy_group) + for (const ProxyGroupConfig &x: extra_proxy_group) { string_array filtered_nodelist; std::string type; @@ -2318,16 +2299,14 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v if (x.Tolerance > 0) group.AddMember("tolerance", x.Tolerance, allocator); } - outbounds.PushBack(group, allocator); } - if (json.HasMember("outbounds")) - json.RemoveMember("outbounds"); - json.AddMember("outbounds", outbounds, allocator); + json | AddMemberOrReplace("outbounds", outbounds, allocator); } std::string proxyToSingBox(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { + using namespace rapidjson_ext; rapidjson::Document json; json.Parse(base_conf.data()); if(json.HasParseError()) @@ -2339,5 +2318,5 @@ std::string proxyToSingBox(std::vector &nodes, const std::string &base_co proxyToSingBox(nodes, json, ruleset_content_array, extra_proxy_group, ext); rulesetToSingBox(json, ruleset_content_array, ext.overwrite_original_rules); - return SerializeObject(json); + return json | SerializeObject(); } diff --git a/src/parser/subparser.cpp b/src/parser/subparser.cpp index 35cbf7c66..07cd39412 100644 --- a/src/parser/subparser.cpp +++ b/src/parser/subparser.cpp @@ -14,6 +14,7 @@ #include "subparser.h" using namespace rapidjson; +using namespace rapidjson_ext; using namespace YAML; string_array ss_ciphers = {"rc4-md5", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "bf-cfb", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "salsa20", "chacha20", "chacha20-ietf"}; @@ -2140,7 +2141,7 @@ void explodeNetchConf(std::string netch, std::vector &nodes) for(uint32_t i = 0; i < json["Server"].Size(); i++) { Proxy node; - explodeNetch("Netch://" + base64Encode(SerializeObject(json["Server"][i])), node); + explodeNetch("Netch://" + base64Encode(json["Server"][i] | SerializeObject()), node); node.Id = index; nodes.emplace_back(std::move(node)); diff --git a/src/utils/rapidjson_extra.h b/src/utils/rapidjson_extra.h index c3f580095..5404d822e 100644 --- a/src/utils/rapidjson_extra.h +++ b/src/utils/rapidjson_extra.h @@ -19,7 +19,7 @@ template void exception_thrower(T e, const std::string &cond, const #include #include -inline void operator >> (const rapidjson::Value& value, std::string& i) +inline void operator >> (const rapidjson::Value &value, std::string &i) { if(value.IsNull()) i = ""; @@ -35,7 +35,7 @@ inline void operator >> (const rapidjson::Value& value, std::string& i) i = ""; } -inline void operator >> (const rapidjson::Value& value, int& i) +inline void operator >> (const rapidjson::Value &value, int &i) { if(value.IsNull()) i = 0; @@ -49,7 +49,7 @@ inline void operator >> (const rapidjson::Value& value, int& i) i = 0; } -inline std::string GetMember(const rapidjson::Value& value, const std::string &member) +inline std::string GetMember(const rapidjson::Value &value, const std::string &member) { std::string retStr; if(value.IsObject() && value.HasMember(member.data())) @@ -57,23 +57,15 @@ inline std::string GetMember(const rapidjson::Value& value, const std::string &m return retStr; } -inline void GetMember(const rapidjson::Value& value, const std::string &member, std::string& target) +inline void GetMember(const rapidjson::Value &value, const std::string &member, std::string &target) { std::string retStr = GetMember(value, member); - if(retStr.size()) + if(!retStr.empty()) target.assign(retStr); } -inline std::string SerializeObject(const rapidjson::Value& value) -{ - rapidjson::StringBuffer sb; - rapidjson::Writer writer_json(sb); - value.Accept(writer_json); - return sb.GetString(); -} - template -inline rapidjson::Value buildObject(rapidjson::MemoryPoolAllocator<> & allocator, Args... kvs) +inline rapidjson::Value buildObject(rapidjson::MemoryPoolAllocator<> &allocator, Args... kvs) { static_assert(sizeof...(kvs) % 2 == 0, "buildObject requires an even number of arguments"); static_assert((std::is_same::value && ...), "buildObject requires all arguments to be const char*"); @@ -93,5 +85,57 @@ inline rapidjson::Value buildBooleanValue(bool value) return value ? rapidjson::Value(rapidjson::kTrueType) : rapidjson::Value(rapidjson::kFalseType); } +namespace rapidjson_ext { + template + struct ExtensionFunction { + virtual ReturnType operator() (rapidjson::Value &root) const = 0; + virtual ReturnType operator() (rapidjson::Value &&root) const + { + return (*this)(root); + }; + }; + + struct AddMemberOrReplace : public ExtensionFunction { + rapidjson::Value &member; + const rapidjson::Value::Ch *name; + rapidjson::MemoryPoolAllocator<> &allocator; + AddMemberOrReplace(const rapidjson::Value::Ch *name, rapidjson::Value &value, + rapidjson::MemoryPoolAllocator<> &allocator) : member(value), name(name), allocator(allocator) {} + AddMemberOrReplace(const rapidjson::Value::Ch *name, rapidjson::Value &&value, + rapidjson::MemoryPoolAllocator<> &allocator) : member(value), name(name), allocator(allocator) {} + + inline rapidjson::Value & operator() (rapidjson::Value &root) const override + { + if (root.HasMember(name)) + root[name] = member; + else + root.AddMember(rapidjson::StringRef(name), member, allocator); + return root; + } + }; + + struct SerializeObject : public ExtensionFunction { + inline std::string operator() (rapidjson::Value &root) const override + { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + root.Accept(writer); + return buffer.GetString(); + } + }; + + template + inline ReturnType operator| (rapidjson::Value &root, const ExtensionFunction &func) + { + return func(root); + } + + template + inline ReturnType operator| (rapidjson::Value &&root, const ExtensionFunction &func) + { + return func(root); + } +} + #endif // RAPIDJSON_EXTRA_H_INCLUDED diff --git a/src/utils/string.cpp b/src/utils/string.cpp index 7e2789eba..897edc83a 100644 --- a/src/utils/string.cpp +++ b/src/utils/string.cpp @@ -19,8 +19,8 @@ std::vector split(const std::string &s, const std::string &seperato while(i != s.size() && flag == 0) { flag = 1; - for(string_size x = 0; x < seperator.size(); ++x) - if(s[i] == seperator[x]) + for(char x : seperator) + if(s[i] == x) { ++i; flag = 0; @@ -32,8 +32,8 @@ std::vector split(const std::string &s, const std::string &seperato string_size j = i; while(j != s.size() && flag == 0) { - for(string_size x = 0; x < seperator.size(); ++x) - if(s[j] == seperator[x]) + for(char x : seperator) + if(s[j] == x) { flag = 1; break; From c530bc5eaa234fe3c9a64653ac7b42f44dccbbb4 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Thu, 9 Nov 2023 15:02:24 +0800 Subject: [PATCH 037/120] Fix request arguments not properly handled (#661) --- src/handler/interfaces.cpp | 179 +++++++------- src/script/cron.cpp | 2 +- src/script/script_quickjs.cpp | 7 +- src/server/webserver.cpp | 372 ------------------------------ src/server/webserver.h | 32 ++- src/server/webserver_httplib.cpp | 37 +-- src/server/webserver_libevent.cpp | 54 ++--- src/utils/ini_reader/ini_reader.h | 9 +- src/utils/string.cpp | 9 + src/utils/string.h | 2 + src/utils/urlencode.cpp | 14 ++ src/utils/urlencode.h | 3 + 12 files changed, 177 insertions(+), 543 deletions(-) delete mode 100644 src/server/webserver.cpp diff --git a/src/handler/interfaces.cpp b/src/handler/interfaces.cpp index d3e32b006..3a5c3ec7f 100644 --- a/src/handler/interfaces.cpp +++ b/src/handler/interfaces.cpp @@ -37,8 +37,6 @@ extern WebServer webServer; string_array gRegexBlacklist = {"(.*)*"}; -void refreshRulesets(RulesetConfigs &ruleset_list, std::vector &ruleset_content_array); - std::string parseProxy(const std::string &source) { std::string proxy = source; @@ -136,19 +134,18 @@ void matchUserAgent(const std::string &user_agent, std::string &target, tribool return; } } - return; } std::string getRuleset(RESPONSE_CALLBACK_ARGS) { - std::string &argument = request.argument; + auto &argument = request.argument; int *status_code = &response.status_code; /// type: 1 for Surge, 2 for Quantumult X, 3 for Clash domain rule-provider, 4 for Clash ipcidr rule-provider, 5 for Surge DOMAIN-SET, 6 for Clash classical ruleset std::string url = urlSafeBase64Decode(getUrlArg(argument, "url")), type = getUrlArg(argument, "type"), group = urlSafeBase64Decode(getUrlArg(argument, "group")); std::string output_content, dummy; int type_int = to_int(type, 0); - if(!url.size() || !type.size() || (type_int == 2 && !group.size()) || (type_int < 1 || type_int > 6)) + if(url.empty() || type.empty() || (type_int == 2 && group.empty()) || (type_int < 1 || type_int > 6)) { *status_code = 400; return "Invalid request!"; @@ -167,7 +164,7 @@ std::string getRuleset(RESPONSE_CALLBACK_ARGS) output_content += convertRuleset(content, x.rule_type); } - if(!output_content.size()) + if(output_content.empty()) { *status_code = 400; return "Invalid request!"; @@ -184,11 +181,11 @@ std::string getRuleset(RESPONSE_CALLBACK_ARGS) { posb = 0; pose = strLine.find(','); - if(pose == strLine.npos) + if(pose == std::string::npos) return 1; posb = pose + 1; pose = strLine.find(',', posb); - if(pose == strLine.npos) + if(pose == std::string::npos) { pose = strLine.size(); if(strLine[pose - 1] == '\r') @@ -215,11 +212,11 @@ std::string getRuleset(RESPONSE_CALLBACK_ARGS) switch(type_int) { case 2: - if(!std::any_of(QuanXRuleTypes.begin(), QuanXRuleTypes.end(), [&strLine](std::string type){return startsWith(strLine, type);})) + if(!std::any_of(QuanXRuleTypes.begin(), QuanXRuleTypes.end(), [&strLine](const std::string& type){return startsWith(strLine, type);})) continue; break; case 1: - if(!std::any_of(SurgeRuleTypes.begin(), SurgeRuleTypes.end(), [&strLine](std::string type){return startsWith(strLine, type);})) + if(!std::any_of(SurgeRuleTypes.begin(), SurgeRuleTypes.end(), [&strLine](const std::string& type){return startsWith(strLine, type);})) continue; break; case 3: @@ -251,7 +248,7 @@ std::string getRuleset(RESPONSE_CALLBACK_ARGS) output_content += '\n'; continue; case 6: - if(!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [&strLine](std::string type){return startsWith(strLine, type);})) + if(!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [&strLine](const std::string& type){return startsWith(strLine, type);})) continue; output_content += " - "; } @@ -303,12 +300,12 @@ void checkExternalBase(const std::string &path, std::string &dest) std::string subconverter(RESPONSE_CALLBACK_ARGS) { - std::string &argument = request.argument; + auto &argument = request.argument; int *status_code = &response.status_code; std::string argTarget = getUrlArg(argument, "target"), argSurgeVer = getUrlArg(argument, "ver"); tribool argClashNewField = getUrlArg(argument, "new_name"); - int intSurgeVer = argSurgeVer.size() ? to_int(argSurgeVer, 3) : 3; + int intSurgeVer = !argSurgeVer.empty() ? to_int(argSurgeVer, 3) : 3; if(argTarget == "auto") matchUserAgent(request.headers["User-Agent"], argTarget, argClashNewField, intSurgeVer); @@ -352,8 +349,8 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) std::vector lRulesetContent; extra_settings ext; std::string subInfo, dummy; - int interval = argUpdateInterval.size() ? to_int(argUpdateInterval, global.updateInterval) : global.updateInterval; - bool authorized = !global.APIMode || getUrlArg(argument, "token") == global.accessToken, strict = argUpdateStrict.size() ? argUpdateStrict == "true" : global.updateStrict; + int interval = !argUpdateInterval.empty() ? to_int(argUpdateInterval, global.updateInterval) : global.updateInterval; + bool authorized = !global.APIMode || getUrlArg(argument, "token") == global.accessToken, strict = !argUpdateStrict.empty() ? argUpdateStrict == "true" : global.updateStrict; if(std::find(gRegexBlacklist.cbegin(), gRegexBlacklist.cend(), argIncludeRemark) != gRegexBlacklist.cend() || std::find(gRegexBlacklist.cbegin(), gRegexBlacklist.cend(), argExcludeRemark) != gRegexBlacklist.cend()) return "Invalid request!"; @@ -365,28 +362,35 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) /// validate urls argEnableInsert.define(global.enableInsert); - if(!argUrl.size() && (!global.APIMode || authorized)) + if(argUrl.empty() && (!global.APIMode || authorized)) argUrl = global.defaultUrls; - if((!argUrl.size() && !(global.insertUrls.size() && argEnableInsert)) || !argTarget.size()) + if((argUrl.empty() && !(!global.insertUrls.empty() && argEnableInsert)) || argTarget.empty()) { *status_code = 400; return "Invalid request!"; } /// load request arguments as template variables - string_array req_args = split(argument, "&"); +// string_array req_args = split(argument, "&"); +// string_map req_arg_map; +// for(std::string &x : req_args) +// { +// string_size pos = x.find("="); +// if(pos == x.npos) +// { +// req_arg_map[x] = ""; +// continue; +// } +// if(x.substr(0, pos) == "token") +// continue; +// req_arg_map[x.substr(0, pos)] = x.substr(pos + 1); +// } string_map req_arg_map; - for(std::string &x : req_args) + for (auto &x : argument) { - string_size pos = x.find("="); - if(pos == x.npos) - { - req_arg_map[x] = ""; - continue; - } - if(x.substr(0, pos) == "token") + if(x.first == "token") continue; - req_arg_map[x.substr(0, pos)] = x.substr(pos + 1); + req_arg_map[x.first] = x.second; } req_arg_map["target"] = argTarget; req_arg_map["ver"] = std::to_string(intSurgeVer); @@ -414,7 +418,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) ext.tls13.define(argTLS13).define(global.TLS13Flag); ext.sort_flag = argSort.get(global.enableSort); - argUseSortScript.define(global.sortScript.size() != 0); + argUseSortScript.define(!global.sortScript.empty()); if(ext.sort_flag && argUseSortScript) ext.sort_script = global.sortScript; ext.filter_deprecated = argFilterDeprecated.get(global.filterDeprecated); @@ -428,7 +432,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) ext.nodelist = argGenNodeList; ext.surge_ssr_path = global.surgeSSRPath; - ext.quanx_dev_id = argDeviceID.size() ? argDeviceID : global.quanXDevID; + ext.quanx_dev_id = !argDeviceID.empty() ? argDeviceID : global.quanXDevID; ext.enable_rule_generator = global.enableRuleGen; ext.overwrite_original_rules = global.overwriteOriginalRules; if(!argExpandRulesets) @@ -437,7 +441,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) /// load external configuration if(argExternalConfig.empty()) argExternalConfig = global.defaultExtConfig; - if(argExternalConfig.size()) + if(!argExternalConfig.empty()) { //std::cerr<<"External configuration file provided. Loading...\n"; writeLog(0, "External configuration file provided. Loading...", LOG_LEVEL_INFO); @@ -459,21 +463,21 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) checkExternalBase(extconf.loon_rule_base, lLoonBase); checkExternalBase(extconf.singbox_rule_base, lSingBoxBase); - if(extconf.surge_ruleset.size()) + if(!extconf.surge_ruleset.empty()) lCustomRulesets = extconf.surge_ruleset; - if(extconf.custom_proxy_group.size()) + if(!extconf.custom_proxy_group.empty()) lCustomProxyGroups = extconf.custom_proxy_group; ext.enable_rule_generator = extconf.enable_rule_generator; ext.overwrite_original_rules = extconf.overwrite_original_rules; } } - if(extconf.rename.size()) + if(!extconf.rename.empty()) ext.rename_array = extconf.rename; - if(extconf.emoji.size()) + if(!extconf.emoji.empty()) ext.emoji_array = extconf.emoji; - if(extconf.include.size()) + if(!extconf.include.empty()) lIncludeRemarks = extconf.include; - if(extconf.exclude.size()) + if(!extconf.exclude.empty()) lExcludeRemarks = extconf.exclude; argAddEmoji.define(extconf.add_emoji); argRemoveEmoji.define(extconf.remove_old_emoji); @@ -484,14 +488,14 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) if(!lSimpleSubscription) { /// loading custom groups - if(argCustomGroups.size() && !ext.nodelist) + if(!argCustomGroups.empty() && !ext.nodelist) { string_array vArray = split(argCustomGroups, "@"); lCustomProxyGroups = INIBinding::from::from_ini(vArray); } /// loading custom rulesets - if(argCustomRulesets.size() && !ext.nodelist) + if(!argCustomRulesets.empty() && !ext.nodelist) { string_array vArray = split(argCustomRulesets, "@"); lCustomRulesets = INIBinding::from::from_ini(vArray); @@ -519,15 +523,15 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) ext.remove_emoji = argRemoveEmoji.get(global.removeEmoji); if(ext.add_emoji && ext.emoji_array.empty()) ext.emoji_array = safe_get_emojis(); - if(argRenames.size()) + if(!argRenames.empty()) ext.rename_array = INIBinding::from::from_ini(split(argRenames, "`"), "@"); else if(ext.rename_array.empty()) ext.rename_array = safe_get_renames(); /// check custom include/exclude settings - if(argIncludeRemark.size() && regValid(argIncludeRemark)) + if(!argIncludeRemark.empty() && regValid(argIncludeRemark)) lIncludeRemarks = string_array{argIncludeRemark}; - if(argExcludeRemark.size() && regValid(argExcludeRemark)) + if(!argExcludeRemark.empty() && regValid(argExcludeRemark)) lExcludeRemarks = string_array{argExcludeRemark}; /// initialize script runtime @@ -559,7 +563,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) parse_set.js_runtime = ext.js_runtime; parse_set.js_context = ext.js_context; - if(global.insertUrls.size() && argEnableInsert) + if(!global.insertUrls.empty() && argEnableInsert) { groupID = -1; urls = split(global.insertUrls, "|"); @@ -602,12 +606,12 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) groupID++; } //exit if found nothing - if(!nodes.size() && !insert_nodes.size()) + if(nodes.empty() && insert_nodes.empty()) { *status_code = 400; return "No nodes were found!"; } - if(subInfo.size() && argAppendUserinfo.get(global.appendUserinfo)) + if(!subInfo.empty() && argAppendUserinfo.get(global.appendUserinfo)) response.headers.emplace("Subscription-UserInfo", subInfo); if(request.method == "HEAD") @@ -627,7 +631,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) std::string filterScript = global.filterScript; if(authorized && !argFilterScript.empty()) filterScript = argFilterScript; - if(filterScript.size()) + if(!filterScript.empty()) { if(startsWith(filterScript, "path:")) filterScript = fileGet(filterScript.substr(5), false); @@ -670,7 +674,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) } //check custom group name - if(argGroupName.size()) + if(!argGroupName.empty()) for(Proxy &x : nodes) x.Group = argGroupName; @@ -695,7 +699,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) std::vector dummy_ruleset; std::string managed_url = base64Decode(urlDecode(getUrlArg(argument, "profile_data"))); if(managed_url.empty()) - managed_url = global.managedConfigPrefix + "/sub?" + argument; + managed_url = global.managedConfigPrefix + "/sub?" + joinArguments(argument); //std::cerr<<"Generate target: "; proxy = parseProxy(global.proxyConfig); @@ -747,7 +751,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) if(argUpload) uploadGist("surge" + argSurgeVer, argUploadPath, output_content, true); - if(global.writeManagedConfig && global.managedConfigPrefix.size()) + if(global.writeManagedConfig && !global.managedConfigPrefix.empty()) output_content = "#!MANAGED-CONFIG " + managed_url + (interval ? " interval=" + std::to_string(interval) : "") \ + " strict=" + std::string(strict ? "true" : "false") + "\n\n" + output_content; } @@ -764,7 +768,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) if(argUpload) uploadGist("surfboard", argUploadPath, output_content, true); - if(global.writeManagedConfig && global.managedConfigPrefix.size()) + if(global.writeManagedConfig && !global.managedConfigPrefix.empty()) output_content = "#!MANAGED-CONFIG " + managed_url + (interval ? " interval=" + std::to_string(interval) : "") \ + " strict=" + std::string(strict ? "true" : "false") + "\n\n" + output_content; break; @@ -899,18 +903,18 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) return "Unrecognized target"; } writeLog(0, "Generate completed.", LOG_LEVEL_INFO); - if(argFilename.size()) + if(!argFilename.empty()) response.headers.emplace("Content-Disposition", "attachment; filename=\"" + argFilename + "\"; filename*=utf-8''" + urlEncode(argFilename)); return output_content; } std::string simpleToClashR(RESPONSE_CALLBACK_ARGS) { - std::string &argument = request.argument; + auto argument = joinArguments(request.argument); int *status_code = &response.status_code; std::string url = argument.size() <= 8 ? "" : argument.substr(8); - if(!url.size() || argument.substr(0, 8) != "sublink=") + if(url.empty() || argument.substr(0, 8) != "sublink=") { *status_code = 400; return "Invalid request!"; @@ -920,13 +924,14 @@ std::string simpleToClashR(RESPONSE_CALLBACK_ARGS) *status_code = 400; return "Please insert your subscription link instead of clicking the default link."; } - request.argument = "target=clashr&url=" + urlEncode(url); + request.argument.emplace("target", "clashr"); + request.argument.emplace("url", urlEncode(url)); return subconverter(request, response); } std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) { - std::string &argument = request.argument; + auto argument = joinArguments(request.argument); int *status_code = &response.status_code; INIReader ini; @@ -937,9 +942,9 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) ini.store_any_line = true; - if(!url.size()) + if(url.empty()) url = global.defaultUrls; - if(!url.size() || argument.substr(0, 5) != "link=") + if(url.empty() || argument.substr(0, 5) != "link=") { *status_code = 400; return "Invalid request!"; @@ -1008,11 +1013,11 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) for(unsigned int i = 1; i < dummy_str_array.size(); i++) { if(startsWith(dummy_str_array[i], "url")) - singlegroup["url"] = trim(dummy_str_array[i].substr(dummy_str_array[i].find("=") + 1)); + singlegroup["url"] = trim(dummy_str_array[i].substr(dummy_str_array[i].find('=') + 1)); else if(startsWith(dummy_str_array[i], "interval")) - singlegroup["interval"] = trim(dummy_str_array[i].substr(dummy_str_array[i].find("=") + 1)); + singlegroup["interval"] = trim(dummy_str_array[i].substr(dummy_str_array[i].find('=') + 1)); else if(startsWith(dummy_str_array[i], "policy-path")) - links.emplace_back(trim(dummy_str_array[i].substr(dummy_str_array[i].find("=") + 1))); + links.emplace_back(trim(dummy_str_array[i].substr(dummy_str_array[i].find('=') + 1))); else singlegroup["proxies"].push_back(trim(dummy_str_array[i])); } @@ -1048,7 +1053,7 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) } //exit if found nothing - if(!nodes.size()) + if(nodes.empty()) { *status_code = 400; return "No nodes were found!"; @@ -1075,7 +1080,7 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) name = x.first; content = x.second; dummy_str_array = split(content, ","); - if(!dummy_str_array.size()) + if(dummy_str_array.empty()) continue; content = trim(dummy_str_array[0]); switch(hash_(content)) @@ -1112,7 +1117,7 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) if(strArray.size() != 3) continue; content = webGet(strArray[1], proxy, global.cacheRuleset); - if(!content.size()) + if(content.empty()) continue; ss << content; @@ -1125,7 +1130,7 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) strLine.erase(--lineSize); if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored continue; - else if(!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [&strLine](std::string type){return startsWith(strLine, type);})) //remove unsupported types + else if(!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [&strLine](const std::string& type){return startsWith(strLine, type);})) //remove unsupported types continue; strLine += strArray[2]; if(count_least(strLine, ',', 3)) @@ -1135,7 +1140,7 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) ss.clear(); continue; } - else if(!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [&strLine](std::string type){return startsWith(strLine, type);})) + else if(!std::any_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [&strLine](const std::string& type){return startsWith(strLine, type);})) continue; rule.push_back(x); } @@ -1148,7 +1153,7 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) std::string getProfile(RESPONSE_CALLBACK_ARGS) { - std::string &argument = request.argument; + auto &argument = request.argument; int *status_code = &response.status_code; std::string name = urlDecode(getUrlArg(argument, "name")), token = urlDecode(getUrlArg(argument, "token")); @@ -1164,7 +1169,8 @@ std::string getProfile(RESPONSE_CALLBACK_ARGS) { profile_content = vfs::vfs_get(name); } - else */if(fileExist(name)) + else */ + if(fileExist(name)) { profile_content = fileGet(name, true); } @@ -1187,7 +1193,7 @@ std::string getProfile(RESPONSE_CALLBACK_ARGS) writeLog(0, "Trying to parse profile '" + name + "'.", LOG_LEVEL_INFO); string_multimap contents; ini.get_items("Profile", contents); - if(!contents.size()) + if(contents.empty()) { //std::cerr<<"Load profile failed! Reason: Empty Profile section\n"; writeLog(0, "Load profile failed! Reason: Empty Profile section", LOG_LEVEL_ERROR); @@ -1234,7 +1240,7 @@ std::string getProfile(RESPONSE_CALLBACK_ARGS) continue; } url = ini.get("Profile", "url"); - if(url.size()) + if(!url.empty()) { all_urls += "|" + url; writeLog(0, "Profile url from '" + name + "' added.", LOG_LEVEL_INFO); @@ -1248,10 +1254,8 @@ std::string getProfile(RESPONSE_CALLBACK_ARGS) } contents.emplace("token", token); - contents.emplace("profile_data", base64Encode(global.managedConfigPrefix + "/getprofile?" + argument)); - std::string query = std::accumulate(contents.begin(), contents.end(), std::string(), [](const std::string &x, auto y){ return x + y.first + "=" + urlEncode(y.second) + "&"; }); - query += argument; - request.argument = query; + contents.emplace("profile_data", base64Encode(global.managedConfigPrefix + "/getprofile?" + joinArguments(argument))); + request.argument = argument; return subconverter(request, response); } @@ -1276,7 +1280,7 @@ inline std::string intToStream(unsigned long long stream) streamval /= 1024.0; } snprintf(chrs, 15, "%.2f %cB", streamval, units[level]); - return std::string(chrs); + return {chrs}; } std::string subInfoToMessage(std::string subinfo) @@ -1286,7 +1290,7 @@ std::string subInfoToMessage(std::string subinfo) std::string retdata, useddata = "N/A", totaldata = "N/A", expirydata = "N/A"; std::string upload = getUrlArg(subinfo, "upload"), download = getUrlArg(subinfo, "download"), total = getUrlArg(subinfo, "total"), expire = getUrlArg(subinfo, "expire"); ull used = to_number(upload, 0) + to_number(download, 0), tot = to_number(total, 0); - time_t expiry = to_number(expire, 0); + auto expiry = to_number(expire, 0); if(used != 0) useddata = intToStream(used); if(tot != 0) @@ -1309,7 +1313,7 @@ int simpleGenerator() { //std::cerr<<"\nReading generator configuration...\n"; writeLog(0, "Reading generator configuration...", LOG_LEVEL_INFO); - std::string config = fileGet("generate.ini"), path, profile, arguments, content; + std::string config = fileGet("generate.ini"), path, profile, content; if(config.empty()) { //std::cerr<<"Generator configuration not found or empty!\n"; @@ -1328,7 +1332,7 @@ int simpleGenerator() writeLog(0, "Read generator configuration completed.\n", LOG_LEVEL_INFO); string_array sections = ini.get_section_names(); - if(global.generateProfiles.size()) + if(!global.generateProfiles.empty()) { //std::cerr<<"Generating with specific artifacts: \""< writer(sb); diff --git a/src/script/script_quickjs.cpp b/src/script/script_quickjs.cpp index 438adfa7f..a6e6327ee 100644 --- a/src/script/script_quickjs.cpp +++ b/src/script/script_quickjs.cpp @@ -377,6 +377,11 @@ static qjs_fetch_Response qjs_fetch(qjs_fetch_Request request) return response; } +static std::string qjs_getUrlArg(const std::string &url, const std::string &request) +{ + return getUrlArg(url, request); +} + std::string getGeoIP(const std::string &address, const std::string &proxy) { return fetchFile("https://api.ip.sb/geoip/" + address, parseProxy(proxy), global.cacheConfig); @@ -506,7 +511,7 @@ int script_context_init(qjs::Context &context) .add<¤tTime>("time") .add<&sleepMs>("sleep") .add<&ShowMsgbox>("msgbox") - .add<&getUrlArg>("getUrlArg") + .add<&qjs_getUrlArg>("getUrlArg") .add<&fileGet>("fileGet") .add<&fileWrite>("fileWrite"); context.eval(R"( diff --git a/src/server/webserver.cpp b/src/server/webserver.cpp deleted file mode 100644 index cbba00056..000000000 --- a/src/server/webserver.cpp +++ /dev/null @@ -1,372 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "../utils/file.h" -#include "../utils/string.h" -#include "../utils/system.h" -#include "socket.h" -#include "webserver.h" - -using string_array = std::vector; -int def_timeout = 5; - -struct responseRoute -{ - std::string method; - std::string path; - std::string content_type; - response_callback rc; -}; - -std::vector responses; - -//for use of multi-thread -int max_send_failure = 10; -std::atomic_bool SERVER_EXIT_FLAG(false); -std::atomic_int working_thread(0); - -int sendall(SOCKET sock, std::string data) -{ - unsigned int total = 0, bytesleft = data.size(); - int sent = 0; - const char* datastr = data.data(); - while(total < bytesleft) - { - sent = send(sock, datastr + total, bytesleft, 0); - if(sent < 0) - { - std::cerr< 2) - { - wrong_req(client_sock); - goto end; - } - else if(vArray.size() > 1) - { - uri = vArray[0]; - args = vArray[1]; - } - else - uri = arguments; - - if(strcmp(command, "POST") == 0) - { - if(request.find("\r\n\r\n") != request.npos) - postdata = request.substr(request.find("\r\n\r\n") + 4); - } - else if(strcmp(command, "OPTIONS") == 0) - { - serve_options(client_sock); - goto end; - } - - for(std::vector::iterator iter = responses.begin(); iter != responses.end(); ++iter) - { - if(iter->method.compare(command) == 0 && iter->path == uri) - { - response_callback &rc = iter->rc; - serve_content(client_sock, iter->content_type, rc(args, postdata)); - goto end; - } - } - file_not_found(uri, client_sock); - -end: - std::cerr<<"worker stop"<max_workers = 1; - return start_web_server_multi(args); -} - -int start_web_server_multi(void *argv) -{ - //log startup - struct listener_args *args = (listener_args*)argv; - std::string listen_address = args->listen_address, request; - int port = args->port, max_conn = args->max_conn, max_workers = args->max_workers, numbytes, worker_index = 0; - socklen_t sock_size = sizeof(struct sockaddr_in); - char buf[BUFSIZ]; - struct sockaddr_in user_socket, server_addr; - SOCKET acc_socket; - int server_socket, fail_counter = 0; - server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); - std::thread workers[max_workers]; - - if (server_socket == -1) - { - //log socket error - std::cerr<<"socket build error!"< max_send_failure) - break; - continue; - } - else - { - break; - } - } - request = ""; - while(true) //receive the complete request - { - numbytes = recv(acc_socket, buf, BUFSIZ - 1, 0); - if(numbytes > 0) //received - request.append(buf); - if(numbytes < 0) - { - if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) - { - continue; - } - else - { - break; - } - } - if(numbytes == 0) - break; - } - - //handle_req(buf, acc_socket); - - while(working_thread >= max_workers) - { - sleepMs(10); - if(SERVER_EXIT_FLAG) - break; - } - while(workers[worker_index].get_id() != std::thread::id()) - { - worker_index++; - } - workers[worker_index] = std::thread(handle_req, request, acc_socket); - workers[worker_index].detach(); - worker_index++; - if(worker_index > max_workers) - worker_index = 0; - } - return 0; -} diff --git a/src/server/webserver.h b/src/server/webserver.h index 952945e6e..e36b8c420 100644 --- a/src/server/webserver.h +++ b/src/server/webserver.h @@ -14,7 +14,7 @@ struct Request { std::string method; std::string url; - std::string argument; + string_multimap argument; string_icase_map headers; std::string postdata; }; @@ -45,7 +45,7 @@ struct responseRoute std::string method; std::string path; std::string content_type; - response_callback rc; + response_callback rc {}; }; class WebServer @@ -62,13 +62,31 @@ class WebServer bool require_auth = false; std::string auth_user, auth_password, auth_realm = "Please enter username and password:"; - void append_response(const std::string &method, const std::string &uri, const std::string &content_type, response_callback response); - void append_redirect(const std::string &uri, const std::string &target); - void reset_redirect(); - int start_web_server(void *argv); - int start_web_server_multi(void *argv); void stop_web_server(); + void append_response(const std::string &method, const std::string &uri, const std::string &content_type, response_callback response) + { + responseRoute rr; + rr.method = method; + rr.path = uri; + rr.content_type = content_type; + rr.rc = response; + responses.emplace_back(std::move(rr)); + } + + void append_redirect(const std::string &uri, const std::string &target) + { + redirect_map[uri] = target; + } + + void reset_redirect() + { + std::map().swap(redirect_map); + } + + int start_web_server(listener_args *args); + int start_web_server_multi(listener_args *args); + std::vector responses; string_map redirect_map; }; diff --git a/src/server/webserver_httplib.cpp b/src/server/webserver_httplib.cpp index ce32017b6..e8450d92b 100644 --- a/src/server/webserver_httplib.cpp +++ b/src/server/webserver_httplib.cpp @@ -2,6 +2,7 @@ #ifdef MALLOC_TRIM #include #endif // MALLOC_TRIM +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 16384 #include "httplib.h" #include "../utils/base64/base64.h" @@ -11,11 +12,6 @@ #include "../utils/urlencode.h" #include "webserver.h" -int WebServer::start_web_server(void *argv) -{ - return start_web_server_multi(argv); -} - void WebServer::stop_web_server() { SERVER_EXIT_FLAG = true; @@ -33,11 +29,7 @@ static httplib::Server::Handler makeHandler(const responseRoute &rr) { req.headers[h.first] = h.second; } - for (auto &p: request.params) - { - req.argument += p.first + "=" + p.second + "&"; - } - req.argument.pop_back(); + req.argument = request.params; req.postdata = request.body; if (request.get_header_value("Content-Type") == "application/x-www-form-urlencoded") { @@ -57,10 +49,9 @@ static httplib::Server::Handler makeHandler(const responseRoute &rr) }; } -int WebServer::start_web_server_multi(void *argv) +int WebServer::start_web_server_multi(listener_args *args) { httplib::Server server; - auto *args = (listener_args *)argv; for (auto &x : responses) { switch (hash_(x.method)) @@ -104,7 +95,7 @@ int WebServer::start_web_server_multi(void *argv) std::string params; for (auto &p: req.params) { - params += p.first + "=" + p.second + "&"; + params += p.first + "=" + urlEncode(p.second) + "&"; } if (!params.empty()) { @@ -201,23 +192,7 @@ int WebServer::start_web_server_multi(void *argv) return 0; } -void WebServer::append_response(const std::string &method, const std::string &uri, const std::string &content_type, response_callback response) -{ - responseRoute rr; - rr.method = method; - rr.path = uri; - rr.content_type = content_type; - rr.rc = response; - responses.emplace_back(std::move(rr)); -} - -void WebServer::append_redirect(const std::string &uri, const std::string &target) -{ - redirect_map[uri] = target; -} - -void WebServer::reset_redirect() +int WebServer::start_web_server(listener_args *args) { - eraseElements(redirect_map); + return start_web_server_multi(args); } - diff --git a/src/server/webserver_libevent.cpp b/src/server/webserver_libevent.cpp index 8ed33158f..0d7463914 100644 --- a/src/server/webserver_libevent.cpp +++ b/src/server/webserver_libevent.cpp @@ -112,14 +112,22 @@ static inline void buffer_cleanup(struct evbuffer *eb) #endif // MALLOC_TRIM } -inline int process_request(WebServer *server, Request &request, Response &response, std::string &return_data) +static int process_request(WebServer *server, Request &request, Response &response, std::string &return_data) { writeLog(0, "handle_cmd: " + request.method + " handle_uri: " + request.url, LOG_LEVEL_VERBOSE); string_size pos = request.url.find('?'); if(pos != std::string::npos) { - request.argument = request.url.substr(pos + 1); + auto argument = split(request.url.substr(pos + 1), "&"); + for(auto &x : argument) + { + string_size pos2 = x.find('='); + if(pos2 != std::string::npos) + request.argument.emplace(x.substr(0, pos2), x.substr(pos2 + 1)); + else + request.argument.emplace(x, ""); + } request.url.erase(pos); } @@ -143,7 +151,7 @@ inline int process_request(WebServer *server, Request &request, Response &respon } catch(std::exception &e) { - return_data = "Internal server error while processing request path '" + request.url + "' with arguments '" + request.argument + "'!"; + return_data = "Internal server error while processing request path '" + request.url + "' with arguments '" + joinArguments(request.argument) + "'!"; return_data += "\n exception: "; return_data += type(e); return_data += "\n what(): "; @@ -163,9 +171,9 @@ inline int process_request(WebServer *server, Request &request, Response &respon if(!request.argument.empty()) { if(return_data.find('?') != std::string::npos) - return_data += "&" + request.argument; + return_data += "&" + joinArguments(request.argument); else - return_data += "?" + request.argument; + return_data += "?" + joinArguments(request.argument); } return 2; } @@ -179,7 +187,7 @@ inline int process_request(WebServer *server, Request &request, Response &respon return -1; } -void on_request(evhttp_request *req, void *args) +static void on_request(evhttp_request *req, void *args) { auto server = (WebServer*) args; static std::string auth_token = "Basic " + base64Encode(server->auth_user + ":" + server->auth_password); @@ -293,9 +301,8 @@ void on_request(evhttp_request *req, void *args) buffer_cleanup(output_buffer); } -int WebServer::start_web_server(void *argv) +int WebServer::start_web_server(listener_args *args) { - auto *args = reinterpret_cast(argv); std::string listen_address = args->listen_address; int port = args->port; if (!event_init()) @@ -314,10 +321,8 @@ int WebServer::start_web_server(void *argv) return -1; } - auto call_on_request = [&](evhttp_request *req, void *args) { on_request(req, args); }; - evhttp_set_allowed_methods(server.get(), EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_OPTIONS | EVHTTP_REQ_PUT | EVHTTP_REQ_PATCH | EVHTTP_REQ_DELETE | EVHTTP_REQ_HEAD); - evhttp_set_gencb(server.get(), wrap(call_on_request), this); + evhttp_set_gencb(server.get(), on_request, this); evhttp_set_timeout(server.get(), 30); if (event_dispatch() == -1) { @@ -329,14 +334,14 @@ int WebServer::start_web_server(void *argv) return 0; } -void* httpserver_dispatch(void *arg) +static void* httpserver_dispatch(void *arg) { event_base_dispatch(reinterpret_cast(arg)); event_base_free(reinterpret_cast(arg)); //free resources return nullptr; } -int httpserver_bindsocket(std::string listen_address, int listen_port, int backlog) +static int httpserver_bindsocket(std::string listen_address, int listen_port, int backlog) { SOCKET nfd; nfd = socket(AF_INET, SOCK_STREAM, 0); @@ -375,9 +380,8 @@ int httpserver_bindsocket(std::string listen_address, int listen_port, int backl return nfd; } -int WebServer::start_web_server_multi(void *argv) +int WebServer::start_web_server_multi(listener_args *args) { - auto *args = reinterpret_cast(argv); std::string listen_address = args->listen_address; int port = args->port, nthreads = args->max_workers, max_conn = args->max_conn; @@ -426,23 +430,3 @@ void WebServer::stop_web_server() { SERVER_EXIT_FLAG = true; } - -void WebServer::append_response(const std::string &method, const std::string &uri, const std::string &content_type, response_callback response) -{ - responseRoute rr; - rr.method = method; - rr.path = uri; - rr.content_type = content_type; - rr.rc = response; - responses.emplace_back(std::move(rr)); -} - -void WebServer::append_redirect(const std::string &uri, const std::string &target) -{ - redirect_map[uri] = target; -} - -void WebServer::reset_redirect() -{ - eraseElements(redirect_map); -} diff --git a/src/utils/ini_reader/ini_reader.h b/src/utils/ini_reader/ini_reader.h index bb76a1548..7f9b4922c 100644 --- a/src/utils/ini_reader/ini_reader.h +++ b/src/utils/ini_reader/ini_reader.h @@ -21,13 +21,12 @@ enum INIREADER_EXCEPTION_NONE }; -using ini_data_struct = std::map>; -using string_multimap = std::multimap; -using string_array = std::vector; -using string_size = std::string::size_type; - class INIReader { + using ini_data_struct = std::map>; + using string_multimap = std::multimap; + using string_array = std::vector; + using string_size = std::string::size_type; /** * @brief A simple INI reader which utilize map and vector * to store sections and items, allowing access in logarithmic time. diff --git a/src/utils/string.cpp b/src/utils/string.cpp index 897edc83a..667279c90 100644 --- a/src/utils/string.cpp +++ b/src/utils/string.cpp @@ -7,6 +7,7 @@ #include #include "string.h" +#include "map_extra.h" std::vector split(const std::string &s, const std::string &seperator) { @@ -298,6 +299,14 @@ std::string getUrlArg(const std::string &url, const std::string &request) return ""; } +std::string getUrlArg(const string_multimap &url, const std::string &request) +{ + auto it = url.find(request); + if(it != url.end()) + return it->second; + return ""; +} + std::string replaceAllDistinct(std::string str, const std::string &old_value, const std::string &new_value) { for(std::string::size_type pos(0); pos != std::string::npos; pos += new_value.length()) diff --git a/src/utils/string.h b/src/utils/string.h index 1a4248729..b376dcc12 100644 --- a/src/utils/string.h +++ b/src/utils/string.h @@ -11,6 +11,7 @@ using string = std::string; using string_size = std::string::size_type; using string_array = std::vector; using string_map = std::map; +using string_multimap = std::multimap; using string_pair_array = std::vector>; std::vector split(const std::string &s, const std::string &seperator); @@ -27,6 +28,7 @@ std::string join(InputIt first, InputIt last, const std::string &delimiter) } std::string getUrlArg(const std::string &url, const std::string &request); +std::string getUrlArg(const string_multimap &url, const std::string &request); std::string replaceAllDistinct(std::string str, const std::string &old_value, const std::string &new_value); std::string trimOf(const std::string& str, char target, bool before = true, bool after = true); std::string trim(const std::string& str, bool before = true, bool after = true); diff --git a/src/utils/urlencode.cpp b/src/utils/urlencode.cpp index 57c0a80f2..16e132ec6 100644 --- a/src/utils/urlencode.cpp +++ b/src/utils/urlencode.cpp @@ -69,3 +69,17 @@ std::string urlDecode(const std::string& str) } return strTemp; } + +std::string joinArguments(const string_multimap &args) +{ + std::string strTemp; + for (auto &p: args) + { + strTemp += p.first + "=" + urlEncode(p.second) + "&"; + } + if (!strTemp.empty()) + { + strTemp.pop_back(); + } + return strTemp; +} diff --git a/src/utils/urlencode.h b/src/utils/urlencode.h index 771ebae00..0a21c17ae 100644 --- a/src/utils/urlencode.h +++ b/src/utils/urlencode.h @@ -3,7 +3,10 @@ #include +#include "utils/string.h" + std::string urlEncode(const std::string& str); std::string urlDecode(const std::string& str); +std::string joinArguments(const string_multimap &args); #endif // URLENCODE_H_INCLUDED From 66edf2867176879702706b453beea12895ed7a78 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Thu, 9 Nov 2023 15:38:49 +0800 Subject: [PATCH 038/120] Fix build error --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e6977227c..5d0594d5c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,7 +71,7 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h - name: Build - run: docker run --rm -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:armv7-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" + run: docker run --rm --user $(id -u):$(id -g) -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:armv7-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" - name: Upload uses: actions/upload-artifact@v3 with: @@ -96,7 +96,7 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h - name: Build - run: docker run --rm -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:aarch64-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" + run: docker run --rm --user $(id -u):$(id -g) -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:aarch64-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" - name: Upload uses: actions/upload-artifact@v3 with: From d58a772f860cf290db50ebaaca8c768baf3811f0 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Thu, 9 Nov 2023 15:57:46 +0800 Subject: [PATCH 039/120] Revert 'Fix build error' --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5d0594d5c..e6977227c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,7 +71,7 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h - name: Build - run: docker run --rm --user $(id -u):$(id -g) -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:armv7-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" + run: docker run --rm -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:armv7-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" - name: Upload uses: actions/upload-artifact@v3 with: @@ -96,7 +96,7 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: SHA=$(git rev-parse --short HEAD) && sed -i 's/\(v[0-9]\.[0-9]\.[0-9]\)/\1-'"$SHA"'/' src/version.h - name: Build - run: docker run --rm --user $(id -u):$(id -g) -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:aarch64-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" + run: docker run --rm -v $GITHUB_WORKSPACE:/root/workdir multiarch/alpine:aarch64-latest-stable /bin/sh -c "apk add bash git nodejs npm && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh" - name: Upload uses: actions/upload-artifact@v3 with: From afd7ab71ada39ca700928152758f7fb74b85d57f Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Thu, 9 Nov 2023 16:54:52 +0800 Subject: [PATCH 040/120] Update build scripts --- .github/workflows/docker.yml | 59 +++--------------------------------- 1 file changed, 4 insertions(+), 55 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3da8bc0c9..39fa3ef8f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -30,7 +30,7 @@ jobs: - name: Get commit SHA id: vars - run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" + run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - name: Build and export id: build @@ -97,7 +97,7 @@ jobs: - name: Get commit SHA id: vars - run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" + run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - name: Build and export id: build @@ -153,9 +153,6 @@ jobs: with: fetch-depth: 0 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 @@ -167,7 +164,7 @@ jobs: - name: Get commit SHA id: vars - run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" + run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - name: Build and export id: build @@ -226,9 +223,6 @@ jobs: with: fetch-depth: 0 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 @@ -240,7 +234,7 @@ jobs: - name: Get commit SHA id: vars - run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" + run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - name: Build and export id: build @@ -317,38 +311,12 @@ jobs: with: path: /tmp/images/ -# - name: Load image -# if: github.ref == 'refs/heads/master' -# run: | -# docker load --input /tmp/image_amd64/image_amd64.tar -# docker load --input /tmp/image_386/image_386.tar -# docker load --input /tmp/image_armv7/image_armv7.tar -# docker load --input /tmp/image_arm64/image_arm64.tar -# docker image ls -a - - name: Docker login uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} -# - name: Get commit SHA -# id: vars -# run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" - -# - name: Docker buildx image and push on master branch -# if: github.ref == 'refs/heads/master' -# uses: docker/build-push-action@v3 -# with: -# platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/386 -# context: scripts/ -# tags: tindy2013/subconverter:latest -# build-args: | -# SHA=${{ steps.vars.outputs.sha_short }} -# THREADS=1 -# outputs: | -# type=image,push=true - - name: Replace tag without `v` if: startsWith(github.ref, 'refs/tags/') uses: actions/github-script@v6 @@ -358,25 +326,6 @@ jobs: return context.payload.ref.replace(/\/?refs\/tags\/v/, '') result-encoding: string -# - name: Load release image -# if: startsWith(github.ref, 'refs/tags/') -# run: | -# docker load --input /tmp/image_amd64/image_amd64_release.tar -# docker load --input /tmp/image_386/image_386_release.tar -# docker load --input /tmp/image_armv7/image_armv7_release.tar -# docker load --input /tmp/image_arm64/image_arm64_release.tar -# docker image ls -a - -# - name: Docker buildx image and push on release -# if: startsWith(github.ref, 'refs/tags/') -# uses: docker/build-push-action@v3 -# with: -# platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/386 -# context: scripts/ -# tags: tindy2013/subconverter:${{steps.version.outputs.result}} -# build-args: THREADS=1 -# outputs: type=image,push=true - - name: Merge and push manifest on master branch if: github.ref == 'refs/heads/master' run: python scripts/merge_manifest.py From 26c3c42bc9baaf75c59e48de2dcba726811666ad Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Thu, 9 Nov 2023 17:54:28 +0800 Subject: [PATCH 041/120] Fix build error --- .github/workflows/build.yml | 2 +- .github/workflows/docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e6977227c..9a4fd152a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,7 +64,7 @@ jobs: armv7_build: name: Linux armv7 Build - runs-on: [self-hosted, linux, ARM64] + runs-on: [self-hosted, linux, ARM] steps: - uses: actions/checkout@v3 - name: Add commit id into version diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 39fa3ef8f..f624e396a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -146,7 +146,7 @@ jobs: armv7_build: name: Build ARMv7 Image - runs-on: [self-hosted, linux, ARM64] + runs-on: [self-hosted, linux, ARM] steps: - name: Checkout base uses: actions/checkout@v3 From ed732a9f981ba9922039e5dd9cbb15867544b8e5 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Thu, 9 Nov 2023 21:54:40 +0800 Subject: [PATCH 042/120] Fix error when downloading subscription --- src/handler/interfaces.cpp | 2 +- src/server/webserver_httplib.cpp | 72 +++++++++++++++++++------------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/handler/interfaces.cpp b/src/handler/interfaces.cpp index 3a5c3ec7f..27be645d5 100644 --- a/src/handler/interfaces.cpp +++ b/src/handler/interfaces.cpp @@ -1003,7 +1003,7 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) name = x.first; content = x.second; dummy_str_array = split(content, ","); - if(!dummy_str_array.size()) + if(dummy_str_array.empty()) continue; type = dummy_str_array[0]; if(!(type == "select" || type == "url-test" || type == "fallback" || type == "load-balance")) //remove unsupported types diff --git a/src/server/webserver_httplib.cpp b/src/server/webserver_httplib.cpp index e8450d92b..49d36eda6 100644 --- a/src/server/webserver_httplib.cpp +++ b/src/server/webserver_httplib.cpp @@ -12,6 +12,20 @@ #include "../utils/urlencode.h" #include "webserver.h" +static const char *request_header_blacklist[] = {"host", "accept", "accept-encoding"}; + +static inline bool is_request_header_blacklisted(const std::string &header) +{ + for (auto &x : request_header_blacklist) + { + if (strcasecmp(x, header.c_str()) == 0) + { + return true; + } + } + return false; +} + void WebServer::stop_web_server() { SERVER_EXIT_FLAG = true; @@ -27,17 +41,27 @@ static httplib::Server::Handler makeHandler(const responseRoute &rr) req.url = request.path; for (auto &h: request.headers) { + if (startsWith(h.first, "LOCAL_") + || startsWith(h.first, "REMOTE_") + || is_request_header_blacklisted(h.first)) + { + continue; + } req.headers[h.first] = h.second; } req.argument = request.params; - req.postdata = request.body; if (request.get_header_value("Content-Type") == "application/x-www-form-urlencoded") { req.postdata = urlDecode(req.postdata); } + else + { + req.postdata = request.body; + } auto result = rr.rc(req, resp); response.status = resp.status_code; - for (auto &h: resp.headers) { + for (auto &h: resp.headers) + { response.set_header(h.first, h.second); } auto content_type = resp.content_type; @@ -73,10 +97,12 @@ int WebServer::start_web_server_multi(listener_args *args) break; } } - server.Options(R"(.*)", [&](const httplib::Request &req, httplib::Response &res) { + server.Options(R"(.*)", [&](const httplib::Request &req, httplib::Response &res) + { auto path = req.path; std::string allowed; - for (auto &rr : responses) { + for (auto &rr : responses) + { if (rr.path == path) { allowed += rr.method + ","; @@ -91,19 +117,10 @@ int WebServer::start_web_server_multi(listener_args *args) res.set_header("Access-Control-Allow-Headers", "Content-Type,Authorization"); } }); - server.set_pre_routing_handler([&](const httplib::Request &req, httplib::Response &res) { - std::string params; - for (auto &p: req.params) - { - params += p.first + "=" + urlEncode(p.second) + "&"; - } - if (!params.empty()) - { - params.pop_back(); - params = "?" + params; - } + server.set_pre_routing_handler([&](const httplib::Request &req, httplib::Response &res) + { writeLog(0, "Accept connection from client " + req.remote_addr + ":" + std::to_string(req.remote_port), LOG_LEVEL_DEBUG); - writeLog(0, "handle_cmd: " + req.method + " handle_uri: " + req.path + params, LOG_LEVEL_VERBOSE); + writeLog(0, "handle_cmd: " + req.method + " handle_uri: " + req.target, LOG_LEVEL_VERBOSE); if (req.has_header("SubConverter-Request")) { @@ -137,7 +154,8 @@ int WebServer::start_web_server_multi(listener_args *args) res.set_redirect(x.second); }); } - server.set_exception_handler([](const httplib::Request &req, httplib::Response &res, const std::exception_ptr &e) { + server.set_exception_handler([](const httplib::Request &req, httplib::Response &res, const std::exception_ptr &e) + { try { std::rethrow_exception(e); @@ -148,17 +166,12 @@ int WebServer::start_web_server_multi(listener_args *args) } catch (const std::exception &ex) { - std::string params; - for (auto &p: req.params) - { - params += p.first + "=" + p.second + "&"; - } - params.pop_back(); - std::string return_data = "Internal server error while processing request path '" + req.path + "' with arguments '" + params + "'!\n"; + std::string return_data = "Internal server error while processing request '" + req.target + "'!\n"; return_data += "\n exception: "; return_data += type(ex); return_data += "\n what(): "; return_data += ex.what(); + res.status = 500; res.set_content(return_data, "text/plain"); } catch (...) @@ -172,12 +185,10 @@ int WebServer::start_web_server_multi(listener_args *args) } server.bind_to_port(args->listen_address, args->port, 0); - pthread_t tid; - pthread_create(&tid, nullptr, [](void *args) -> void * { - auto *server = (httplib::Server *)args; - server->listen_after_bind(); - return nullptr; - }, &server); + std::thread thread([&]() + { + server.listen_after_bind(); + }); while (!SERVER_EXIT_FLAG) { @@ -189,6 +200,7 @@ int WebServer::start_web_server_multi(listener_args *args) } server.stop(); + thread.join(); return 0; } From 8b45e06d0d1aa004b972d06d5f2ca917cddf2000 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Fri, 10 Nov 2023 11:22:02 +0800 Subject: [PATCH 043/120] Fix bad implementation of sing-box generation --- src/generator/config/subexport.cpp | 56 +++++++++++++----------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index 6cced58ac..f47f4bf15 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -2043,7 +2043,7 @@ static std::string formatSingBoxInterval(Integer interval) return result; } -static rapidjson::Value buildV2RayTransport(const Proxy& proxy, rapidjson::MemoryPoolAllocator<>& allocator) +static rapidjson::Value buildSingBoxTransport(const Proxy& proxy, rapidjson::MemoryPoolAllocator<>& allocator) { rapidjson::Value transport(rapidjson::kObjectType); switch (hash_(proxy.TransferProtocol)) @@ -2083,6 +2083,14 @@ static rapidjson::Value buildV2RayTransport(const Proxy& proxy, rapidjson::Memor return transport; } +void addSingBoxCommonMembers(rapidjson::Value &proxy, const Proxy &x, const rapidjson::GenericStringRef &type, rapidjson::MemoryPoolAllocator<> &allocator) +{ + proxy.AddMember("type", type, allocator); + proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); + proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); + proxy.AddMember("server_port", x.Port, allocator); +} + void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { using namespace rapidjson_ext; rapidjson::Document::AllocatorType &allocator = json.GetAllocator(); @@ -2110,10 +2118,7 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v { case ProxyType::Shadowsocks: { - proxy.AddMember("type", "shadowsocks", allocator); - proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); - proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); - proxy.AddMember("server_port", x.Port, allocator); + addSingBoxCommonMembers(proxy, x, "ss", allocator); proxy.AddMember("method", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator); proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); if(!x.Plugin.empty() && !x.PluginOption.empty()) @@ -2125,10 +2130,7 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v } case ProxyType::ShadowsocksR: { - proxy.AddMember("type", "shadowsocksr", allocator); - proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); - proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); - proxy.AddMember("server_port", x.Port, allocator); + addSingBoxCommonMembers(proxy, x, "shadowsocksr", allocator); proxy.AddMember("method", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator); proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); proxy.AddMember("protocol", rapidjson::StringRef(x.Protocol.c_str()), allocator); @@ -2139,28 +2141,24 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v } case ProxyType::VMess: { - proxy.AddMember("type", "vmess", allocator); - proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); - proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); - proxy.AddMember("server_port", x.Port, allocator); + addSingBoxCommonMembers(proxy, x, "vmess", allocator); proxy.AddMember("uuid", rapidjson::StringRef(x.UserId.c_str()), allocator); proxy.AddMember("alter_id", x.AlterId, allocator); - proxy.AddMember("cipher", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator); + proxy.AddMember("security", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator); - auto transport = buildV2RayTransport(x, allocator); - proxy.AddMember("transport", transport, allocator); + auto transport = buildSingBoxTransport(x, allocator); + if (!transport.ObjectEmpty()) + proxy.AddMember("transport", transport, allocator); break; } case ProxyType::Trojan: { - proxy.AddMember("type", "trojan", allocator); - proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); - proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); - proxy.AddMember("server_port", x.Port, allocator); + addSingBoxCommonMembers(proxy, x, "trojan", allocator); proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); - auto transport = buildV2RayTransport(x, allocator); - proxy.AddMember("transport", transport, allocator); + auto transport = buildSingBoxTransport(x, allocator); + if (!transport.ObjectEmpty()) + proxy.AddMember("transport", transport, allocator); break; } case ProxyType::WireGuard: @@ -2211,21 +2209,15 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v case ProxyType::HTTP: case ProxyType::HTTPS: { - proxy.AddMember("type", "http", allocator); - proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); - proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); - proxy.AddMember("server_port", x.Port, allocator); + addSingBoxCommonMembers(proxy, x, "http", allocator); proxy.AddMember("username", rapidjson::StringRef(x.Username.c_str()), allocator); proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); break; } case ProxyType::SOCKS5: { - proxy.AddMember("type", "socks", allocator); - proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); - proxy.AddMember("server", rapidjson::StringRef(x.Hostname.c_str()), allocator); - proxy.AddMember("port", x.Port, allocator); - proxy.AddMember("version", 5, allocator); + addSingBoxCommonMembers(proxy, x, "socks", allocator); + proxy.AddMember("version", "5", allocator); proxy.AddMember("username", rapidjson::StringRef(x.Username.c_str()), allocator); proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); break; @@ -2236,7 +2228,7 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v if (x.TLSSecure) { rapidjson::Value tls(rapidjson::kObjectType); - tls.AddMember("enable", true, allocator); + tls.AddMember("enabled", true, allocator); if (!x.ServerName.empty()) tls.AddMember("server_name", rapidjson::StringRef(x.ServerName.c_str()), allocator); tls.AddMember("insecure", buildBooleanValue(scv), allocator); From 51b65ec4372dcbbd98abf660cf9fbd87f8919c88 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Fri, 10 Nov 2023 18:23:43 +0800 Subject: [PATCH 044/120] Enhancements Fix base sing-box configuration. Add support for adding Clash modes to sing-box configs. Optimize codes. --- base/base/all_base.tpl | 134 ++++++++++++++++++++++----- base/base/singbox.json | 128 ++++++++++++++++++++----- base/pref.example.ini | 5 + base/pref.example.toml | 11 +++ base/pref.example.yml | 3 + src/generator/config/ruleconvert.cpp | 10 +- src/generator/config/subexport.cpp | 106 ++++++++++++--------- src/handler/settings.cpp | 5 +- src/handler/settings.h | 2 +- 9 files changed, 306 insertions(+), 98 deletions(-) diff --git a/base/base/all_base.tpl b/base/base/all_base.tpl index e3eb53189..933e8cae2 100644 --- a/base/base/all_base.tpl +++ b/base/base/all_base.tpl @@ -269,30 +269,116 @@ enhanced-mode-by-rule = true {% if request.target == "singbox" %} { - "log": { - "disabled": false, - "level": "info", - "output": "box.log", - "timestamp": true - }, - "dns": {}, - "ntp": { - "enabled": false, - "server": "time.apple.com", - "server_port": 123, - "interval": "30m" - }, - "inbounds": [ - { - "type": "socks", - "tag": "socks-in", - "listen": "127.0.0.1", - "listen_port": 2080 - } - ], - "outbounds": [], - "route": {}, - "experimental": {} + "log": { + "disabled": false, + "level": "info", + "timestamp": true + }, + "dns": { + "servers": [ + { + "tag": "dns_proxy", + "address": "tls://1.1.1.1", + "address_resolver": "dns_resolver" + }, + { + "tag": "dns_direct", + "address": "h3://dns.alidns.com/dns-query", + "address_resolver": "dns_resolver", + "detour": "DIRECT" + }, + { + "tag": "dns_fakeip", + "address": "fakeip" + }, + { + "tag": "dns_resolver", + "address": "223.5.5.5", + "detour": "DIRECT" + }, + { + "tag": "block", + "address": "rcode://success" + } + ], + "rules": [ + { + "outbound": [ + "any" + ], + "server": "dns_resolver" + }, + { + "geosite": [ + "category-ads-all" + ], + "server": "dns_block", + "disable_cache": true + }, + { + "geosite": [ + "geolocation-!cn" + ], + "query_type": [ + "A", + "AAAA" + ], + "server": "dns_fakeip" + }, + { + "geosite": [ + "geolocation-!cn" + ], + "server": "dns_proxy" + } + ], + "final": "dns_direct", + "independent_cache": true, + "fakeip": { + "enabled": true, + {% if default(request.singbox.ipv6, "") == "1" %} + "inet6_range": "fc00::\/18", + {% endif %} + "inet4_range": "198.18.0.0\/15" + } + }, + "ntp": { + "enabled": true, + "server": "time.apple.com", + "server_port": 123, + "interval": "30m", + "detour": "DIRECT" + }, + "inbounds": [ + { + "type": "mixed", + "tag": "mixed-in", + {% if bool(default(global.singbox.allow_lan, "")) %} + "listen": "0.0.0.0", + {% else %} + "listen": "127.0.0.1", + {% endif %} + "listen_port": {{ default(global.singbox.mixed_port, "2080") }} + }, + { + "type": "tun", + "tag": "tun-in", + "inet4_address": "172.19.0.1/30", + {% if default(request.singbox.ipv6, "") == "1" %} + "inet6_address": "fdfe:dcba:9876::1/126", + {% endif %} + "auto_route": true, + "strict_route": true, + "stack": "mixed", + "sniff": true + } + ], + "outbounds": [], + "route": { + "rules": [], + "auto_detect_interface": true + }, + "experimental": {} } {% endif %} diff --git a/base/base/singbox.json b/base/base/singbox.json index 4068bfab9..4263bd55d 100644 --- a/base/base/singbox.json +++ b/base/base/singbox.json @@ -1,26 +1,104 @@ { - "log": { - "disabled": false, - "level": "info", - "output": "box.log", - "timestamp": true - }, - "dns": {}, - "ntp": { - "enabled": false, - "server": "time.apple.com", - "server_port": 123, - "interval": "30m" - }, - "inbounds": [ - { - "type": "socks", - "tag": "socks-in", - "listen": "127.0.0.1", - "listen_port": 2080 - } - ], - "outbounds": [], - "route": {}, - "experimental": {} -} \ No newline at end of file + "log": { + "disabled": false, + "level": "info", + "timestamp": true + }, + "dns": { + "servers": [ + { + "tag": "dns_proxy", + "address": "tls://1.1.1.1", + "address_resolver": "dns_resolver" + }, + { + "tag": "dns_direct", + "address": "h3://dns.alidns.com/dns-query", + "address_resolver": "dns_resolver", + "detour": "DIRECT" + }, + { + "tag": "dns_fakeip", + "address": "fakeip" + }, + { + "tag": "dns_resolver", + "address": "223.5.5.5", + "detour": "DIRECT" + }, + { + "tag": "block", + "address": "rcode://success" + } + ], + "rules": [ + { + "outbound": [ + "any" + ], + "server": "dns_resolver" + }, + { + "geosite": [ + "category-ads-all" + ], + "server": "dns_block", + "disable_cache": true + }, + { + "geosite": [ + "geolocation-!cn" + ], + "query_type": [ + "A", + "AAAA" + ], + "server": "dns_fakeip" + }, + { + "geosite": [ + "geolocation-!cn" + ], + "server": "dns_proxy" + } + ], + "final": "dns_direct", + "independent_cache": true, + "fakeip": { + "enabled": true, + "inet6_range": "fc00::\/18", + "inet4_range": "198.18.0.0\/15" + } + }, + "ntp": { + "enabled": true, + "server": "time.apple.com", + "server_port": 123, + "interval": "30m", + "detour": "DIRECT" + }, + "inbounds": [ + { + "type": "mixed", + "tag": "mixed-in", + "listen": "0.0.0.0", + "listen_port": 2080 + }, + { + "type": "tun", + "tag": "tun-in", + "inet4_address": "172.19.0.1/30", + "inet6_address": "fdfe:dcba:9876::1/126", + "auto_route": true, + "strict_route": true, + "stack": "mixed", + "sniff": true + } + ], + "outbounds": [], + "route": { + "rules": [], + "auto_detect_interface": true + }, + "experimental": {} +} diff --git a/base/pref.example.ini b/base/pref.example.ini index 7ca2a4cfb..6029e18a4 100644 --- a/base/pref.example.ini +++ b/base/pref.example.ini @@ -114,6 +114,9 @@ clash_use_new_field_name=true ; key: value clash_proxies_style=flow +;add Clash mode to sing-box rules, and add a GLOBAL group to end of outbounds +singbox_add_clash_modes=true + ;Rename remarks with the following patterns. Supports regular expression. ;Format: Search_Pattern@Replace_Pattern ;rename_node=IPLC@专线 @@ -226,6 +229,8 @@ clash.http_port=7890 clash.socks_port=7891 clash.allow_lan=true clash.log_level=info +singbox.allow_lan=true +singbox.mixed_port=2080 [aliases] ;Aliases for accessing interfaces. Can be used to shorten the URI. diff --git a/base/pref.example.toml b/base/pref.example.toml index a8bf398d6..9a486d969 100644 --- a/base/pref.example.toml +++ b/base/pref.example.toml @@ -140,6 +140,9 @@ clash_use_new_field_name = true # key: value clash_proxies_style = "flow" +# add Clash mode to sing-box rules, and add a GLOBAL group to end of outbounds +singbox_add_clash_modes = true + [[node_pref.rename_node]] match = '\(?((x|X)?(\d+)(\.?\d+)?)((\s?倍率?)|(x|X))\)?' replace = "$1x" @@ -237,6 +240,14 @@ value = "true" key = "clash.log_level" value = "info" +[[template.globals]] +key = "singbox.allow_lan" +value = "true" + +[[template.globals]] +key = "singbox.mixed_port" +value = "2080" + [[aliases]] uri = "/clash" target = "/sub?target=clash" diff --git a/base/pref.example.yml b/base/pref.example.yml index f9b6c3d51..b7df31297 100644 --- a/base/pref.example.yml +++ b/base/pref.example.yml @@ -49,6 +49,7 @@ node_pref: append_sub_userinfo: true clash_use_new_field_name: true clash_proxies_style: flow + singbox_add_clash_modes: true rename_node: # - {match: "\\(?((x|X)?(\\d+)(\\.?\\d+)?)((\\s?倍率?)|(x|X))\\)?", replace: "$1x"} # - {script: "function rename(node){}"} @@ -106,6 +107,8 @@ template: - {key: clash.socks_port, value: 7891} - {key: clash.allow_lan, value: true} - {key: clash.log_level, value: info} + - {key: singbox.allow_lan, value: true} + - {key: singbox.mixed_port, value: 2080} aliases: - {uri: /v, target: /version} diff --git a/src/generator/config/ruleconvert.cpp b/src/generator/config/ruleconvert.cpp index 543cec488..256f4dfa5 100644 --- a/src/generator/config/ruleconvert.cpp +++ b/src/generator/config/ruleconvert.cpp @@ -487,7 +487,7 @@ void rulesetToSingBox(rapidjson::Document &base_rule, std::vector& allocator = base_rule.GetAllocator(); + auto &allocator = base_rule.GetAllocator(); rapidjson::Value rules(rapidjson::kArrayType); if (!overwrite_original_rules) @@ -496,6 +496,14 @@ void rulesetToSingBox(rapidjson::Document &base_rule, std::vector global.maxAllowedRules) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index f47f4bf15..da4356132 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -155,29 +155,29 @@ bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy & return true; } -void processRemark(std::string &oldremark, std::string &newremark, string_array &remarks_list, bool proc_comma = true) +void processRemark(std::string &remark, string_array &remarks_list, bool proc_comma = true) { // Replace every '=' with '-' in the remark string to avoid parse errors from the clients. // Surge is tested to yield an error when handling '=' in the remark string, // not sure if other clients have the same problem. - std::replace(oldremark.begin(), oldremark.end(), '=', '-'); + std::replace(remark.begin(), remark.end(), '=', '-'); if(proc_comma) { - if(oldremark.find(',') != std::string::npos) + if(remark.find(',') != std::string::npos) { - oldremark.insert(0, "\""); - oldremark.append("\""); + remark.insert(0, "\""); + remark.append("\""); } } - newremark = oldremark; + std::string tempRemark = remark; int cnt = 2; - while(std::find(remarks_list.begin(), remarks_list.end(), newremark) != remarks_list.end()) + while(std::find(remarks_list.begin(), remarks_list.end(), tempRemark) != remarks_list.end()) { - newremark = oldremark + " " + std::to_string(cnt); + tempRemark = remark + " " + std::to_string(cnt); cnt++; } - oldremark = newremark; + remark = tempRemark; } void groupGenerate(const std::string &rule, std::vector &nodelist, string_array &filtered_nodelist, bool add_direct, extra_settings &ext) @@ -241,18 +241,18 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr YAML::Node singleproxy; std::string type = getProxyTypeName(x.Type); - std::string remark, pluginopts = replaceAllDistinct(x.PluginOption, ";", "&"); + std::string pluginopts = replaceAllDistinct(x.PluginOption, ";", "&"); if(ext.append_proxy_type) x.Remark = "[" + type + "] " + x.Remark; - processRemark(x.Remark, remark, remarks_list, false); + processRemark(x.Remark, remarks_list, false); tribool udp = ext.udp; tribool scv = ext.skip_cert_verify; udp.define(x.UDP); scv.define(x.AllowInsecure); - singleproxy["name"] = remark; + singleproxy["name"] = x.Remark; singleproxy["server"] = x.Hostname; singleproxy["port"] = x.Port; @@ -471,7 +471,7 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr else singleproxy.SetStyle(YAML::EmitterStyle::Flow); proxies.push_back(singleproxy); - remarks_list.emplace_back(std::move(remark)); + remarks_list.emplace_back(x.Remark); nodelist.emplace_back(x); } @@ -657,14 +657,13 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf for(Proxy &x : nodes) { - std::string remark; if(ext.append_proxy_type) { std::string type = getProxyTypeName(x.Type); x.Remark = "[" + type + "] " + x.Remark; } - processRemark(x.Remark, remark, remarks_list); + processRemark(x.Remark, remarks_list); std::string &hostname = x.Hostname, &username = x.Username, &password = x.Password, &method = x.EncryptMethod, &id = x.UserId, &transproto = x.TransferProtocol, &host = x.Host, &edge = x.Edge, &path = x.Path, &protocol = x.Protocol, &protoparam = x.ProtocolParam, &obfs = x.OBFS, &obfsparam = x.OBFSParam, &plugin = x.Plugin, &pluginopts = x.PluginOption; std::string port = std::to_string(x.Port); @@ -834,13 +833,13 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf proxy += ", udp-relay=" + udp.get_str(); if(ext.nodelist) - output_nodelist += remark + " = " + proxy + "\n"; + output_nodelist += x.Remark + " = " + proxy + "\n"; else { - ini.set("{NONAME}", remark + " = " + proxy); + ini.set("{NONAME}", x.Remark + " = " + proxy); nodelist.emplace_back(x); } - remarks_list.emplace_back(std::move(remark)); + remarks_list.emplace_back(x.Remark); } if(ext.nodelist) @@ -1083,15 +1082,13 @@ void proxyToQuan(std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_conf void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { std::string type; - std::string remark; std::string proxyStr; tribool udp, tfo, scv, tls13; std::vector nodelist; @@ -1324,7 +1320,7 @@ void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_con void proxyToMellow(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { std::string proxy; - std::string remark, username, password, method; + std::string username, password, method; std::string plugin, pluginopts; std::string id, aid, transproto, faketype, host, path, quicsecure, quicsecret, tlssecure; std::string url; @@ -1679,7 +1675,7 @@ void proxyToMellow(std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_conf, std::string type = getProxyTypeName(x.Type); x.Remark = "[" + type + "] " + x.Remark; } - std::string remark = x.Remark; - processRemark(x.Remark, remark, remarks_list); + processRemark(x.Remark, remarks_list); std::string &hostname = x.Hostname, &username = x.Username, &password = x.Password, &method = x.EncryptMethod, &plugin = x.Plugin, &pluginopts = x.PluginOption, &id = x.UserId, &transproto = x.TransferProtocol, &host = x.Host, &path = x.Path, &protocol = x.Protocol, &protoparam = x.ProtocolParam, &obfs = x.OBFS, &obfsparam = x.OBFSParam; std::string port = std::to_string(x.Port), aid = std::to_string(x.AlterId); @@ -1931,12 +1926,12 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, if(ext.nodelist) - output_nodelist += remark + " = " + proxy + "\n"; + output_nodelist += x.Remark + " = " + proxy + "\n"; else { - ini.set("{NONAME}", remark + " = " + proxy); + ini.set("{NONAME}", x.Remark + " = " + proxy); nodelist.emplace_back(x); - remarks_list.emplace_back(std::move(remark)); + remarks_list.emplace_back(x.Remark); } } @@ -2096,6 +2091,7 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v rapidjson::Document::AllocatorType &allocator = json.GetAllocator(); rapidjson::Value outbounds(rapidjson::kArrayType), route(rapidjson::kArrayType); std::vector nodelist; + string_array remarks_list; auto direct = buildObject(allocator, "type", "direct", "tag", "DIRECT"); outbounds.PushBack(direct, allocator); @@ -2108,6 +2104,8 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v if (ext.append_proxy_type) x.Remark = "[" + type + "] " + x.Remark; + processRemark(x.Remark, remarks_list, false); + tribool udp = ext.udp, tfo = ext.tfo, scv = ext.skip_cert_verify; udp.define(x.UDP); tfo.define(x.TCPFastOpen); @@ -2118,7 +2116,7 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v { case ProxyType::Shadowsocks: { - addSingBoxCommonMembers(proxy, x, "ss", allocator); + addSingBoxCommonMembers(proxy, x, "shadowsocks", allocator); proxy.AddMember("method", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator); proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); if(!x.Plugin.empty() && !x.PluginOption.empty()) @@ -2243,6 +2241,7 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v proxy.AddMember("tcp_fast_open", buildBooleanValue(tfo), allocator); } nodelist.push_back(x); + remarks_list.emplace_back(x.Remark); outbounds.PushBack(proxy, allocator); } for (const ProxyGroupConfig &x: extra_proxy_group) @@ -2293,6 +2292,21 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v } outbounds.PushBack(group, allocator); } + + if (global.singBoxAddClashModes) + { + auto global_group = rapidjson::Value(rapidjson::kObjectType); + global_group.AddMember("type", "selector", allocator); + global_group.AddMember("tag", "GLOBAL", allocator); + global_group.AddMember("outbounds", rapidjson::Value(rapidjson::kArrayType), allocator); + global_group["outbounds"].PushBack("DIRECT", allocator); + for (auto &x: remarks_list) + { + global_group["outbounds"].PushBack(rapidjson::Value(x.c_str(), allocator), allocator); + } + outbounds.PushBack(global_group, allocator); + } + json | AddMemberOrReplace("outbounds", outbounds, allocator); } diff --git a/src/handler/settings.cpp b/src/handler/settings.cpp index e0be580ae..51f4437b7 100644 --- a/src/handler/settings.cpp +++ b/src/handler/settings.cpp @@ -376,6 +376,7 @@ void readYAMLConf(YAML::Node &node) section["append_sub_userinfo"] >> global.appendUserinfo; section["clash_use_new_field_name"] >> global.clashUseNewField; section["clash_proxies_style"] >> global.clashProxiesStyle; + section["singbox_add_clash_modes"] >> global.singBoxAddClashModes; } if(section["rename_node"].IsSequence()) @@ -634,7 +635,8 @@ void readTOMLConf(toml::value &root) "filter_deprecated_nodes", global.filterDeprecated, "append_sub_userinfo", global.appendUserinfo, "clash_use_new_field_name", global.clashUseNewField, - "clash_proxies_style", global.clashProxiesStyle + "clash_proxies_style", global.clashProxiesStyle, + "singbox_add_clash_modes", global.singBoxAddClashModes ); auto renameconfs = toml::find_or>(section_node_pref, "rename_node", {}); @@ -878,6 +880,7 @@ void readConf() ini.get_bool_if_exist("append_sub_userinfo", global.appendUserinfo); ini.get_bool_if_exist("clash_use_new_field_name", global.clashUseNewField); ini.get_if_exist("clash_proxies_style", global.clashProxiesStyle); + ini.get_bool_if_exist("singbox_add_clash_modes", global.singBoxAddClashModes); if(ini.item_prefix_exist("rename_node")) { ini.get_all("rename_node", tempArray); diff --git a/src/handler/settings.h b/src/handler/settings.h index 8e48ab983..d2d6932aa 100644 --- a/src/handler/settings.h +++ b/src/handler/settings.h @@ -46,7 +46,7 @@ struct Settings bool addEmoji = false, removeEmoji = false, appendType = false, filterDeprecated = true; tribool UDPFlag, TFOFlag, skipCertVerify, TLS13Flag, enableInsert; bool enableSort = false, updateStrict = false; - bool clashUseNewField = false; + bool clashUseNewField = false, singBoxAddClashModes = true; std::string clashProxiesStyle = "flow"; std::string proxyConfig, proxyRuleset, proxySubscription; int updateInterval = 0; From 4620873220ee9a6d4dd451857585d6ad82563535 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Fri, 10 Nov 2023 20:16:50 +0800 Subject: [PATCH 045/120] Fix /getprofile not working properly --- src/handler/interfaces.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/handler/interfaces.cpp b/src/handler/interfaces.cpp index 27be645d5..faced4f90 100644 --- a/src/handler/interfaces.cpp +++ b/src/handler/interfaces.cpp @@ -1255,7 +1255,8 @@ std::string getProfile(RESPONSE_CALLBACK_ARGS) contents.emplace("token", token); contents.emplace("profile_data", base64Encode(global.managedConfigPrefix + "/getprofile?" + joinArguments(argument))); - request.argument = argument; + std::copy(argument.cbegin(), argument.cend(), std::inserter(contents, contents.end())); + request.argument = contents; return subconverter(request, response); } From d08426a53ea453b40718f933421a59dec6133827 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Fri, 10 Nov 2023 21:20:54 +0800 Subject: [PATCH 046/120] Fix some rules not working in sing-box configs --- src/generator/config/ruleconvert.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/generator/config/ruleconvert.cpp b/src/generator/config/ruleconvert.cpp index 256f4dfa5..d9d48664f 100644 --- a/src/generator/config/ruleconvert.cpp +++ b/src/generator/config/ruleconvert.cpp @@ -462,12 +462,12 @@ static rapidjson::Value transformRuleToSingBox(const std::string& rule, const st auto args = split(rule, ","); if (args.size() < 2) return rapidjson::Value(rapidjson::kObjectType); auto type = toLower(std::string(args[0])); - auto value = args[1]; + auto value = toLower(args[1]); // std::string_view option; // if (args.size() >= 3) option = args[2]; rapidjson::Value rule_obj(rapidjson::kObjectType); - type = replaceAllDistinct(toLower(type), "-", "_"); + type = replaceAllDistinct(type, "-", "_"); type = replaceAllDistinct(type, "ip_cidr6", "ip_cidr"); if (type == "match" || type == "final") { From 4e94a986b4ce8cb8cc48075f67ecd08dfe9a8edd Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Sat, 11 Nov 2023 18:24:14 +0800 Subject: [PATCH 047/120] Enhancements Rework generation of sing-box rules. Add a dns outbound to sing-box configs. --- CMakeLists.txt | 2 +- src/generator/config/ruleconvert.cpp | 27 ++++++++++++++++- src/generator/config/subexport.cpp | 2 ++ src/script/script_quickjs.cpp | 1 - src/utils/rapidjson_extra.h | 43 ++++++++++++++++++++++++---- 5 files changed, 67 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b1a6481e..22af45ca3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/include/") IF(NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE Release) ENDIF() -SET(CMAKE_CXX_STANDARD 17) +SET(CMAKE_CXX_STANDARD 20) IF(NOT MSVC) ADD_COMPILE_OPTIONS(-Wall -Wextra -Wno-unused-parameter -Wno-unused-result) diff --git a/src/generator/config/ruleconvert.cpp b/src/generator/config/ruleconvert.cpp index d9d48664f..7c5ddc2c9 100644 --- a/src/generator/config/ruleconvert.cpp +++ b/src/generator/config/ruleconvert.cpp @@ -481,6 +481,22 @@ static rapidjson::Value transformRuleToSingBox(const std::string& rule, const st return rule_obj; } +static void appendSingBoxRule(rapidjson::Value &rules, const std::string& rule, rapidjson::MemoryPoolAllocator<>& allocator) +{ + using namespace rapidjson_ext; + auto args = split(rule, ","); + if (args.size() < 2) return; + auto type = toLower(std::string(args[0])); + auto value = toLower(args[1]); +// std::string_view option; +// if (args.size() >= 3) option = args[2]; + + type = replaceAllDistinct(type, "-", "_"); + type = replaceAllDistinct(type, "ip_cidr6", "ip_cidr"); + + rules | AppendToArray(type.c_str(), rapidjson::Value(value.c_str(), allocator), allocator); +} + void rulesetToSingBox(rapidjson::Document &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules) { using namespace rapidjson_ext; @@ -504,6 +520,9 @@ void rulesetToSingBox(rapidjson::Document &base_rule, std::vector global.maxAllowedRules) @@ -532,7 +551,10 @@ void rulesetToSingBox(rapidjson::Document &base_rule, std::vector global.maxAllowedRules) @@ -548,8 +570,11 @@ void rulesetToSingBox(rapidjson::Document &base_rule, std::vector &nodes, rapidjson::Document &json, std::v outbounds.PushBack(direct, allocator); auto reject = buildObject(allocator, "type", "block", "tag", "REJECT"); outbounds.PushBack(reject, allocator); + auto dns = buildObject(allocator, "type", "dns", "tag", "dns-out"); + outbounds.PushBack(dns, allocator); for (Proxy &x : nodes) { diff --git a/src/script/script_quickjs.cpp b/src/script/script_quickjs.cpp index a6e6327ee..1bbe7e882 100644 --- a/src/script/script_quickjs.cpp +++ b/src/script/script_quickjs.cpp @@ -447,7 +447,6 @@ int script_context_init(qjs::Context &context) .fun<&qjs_fetch_Headers::parse_from_string>("parse"); module.class_("Request") .constructor<>() - .constructor("Request") .fun<&qjs_fetch_Request::method>("method") .fun<&qjs_fetch_Request::url>("url") .fun<&qjs_fetch_Request::proxy>("proxy") diff --git a/src/utils/rapidjson_extra.h b/src/utils/rapidjson_extra.h index 5404d822e..1b9d6be29 100644 --- a/src/utils/rapidjson_extra.h +++ b/src/utils/rapidjson_extra.h @@ -96,20 +96,53 @@ namespace rapidjson_ext { }; struct AddMemberOrReplace : public ExtensionFunction { - rapidjson::Value &member; + rapidjson::Value &value; const rapidjson::Value::Ch *name; rapidjson::MemoryPoolAllocator<> &allocator; AddMemberOrReplace(const rapidjson::Value::Ch *name, rapidjson::Value &value, - rapidjson::MemoryPoolAllocator<> &allocator) : member(value), name(name), allocator(allocator) {} + rapidjson::MemoryPoolAllocator<> &allocator) : value(value), name(name), allocator(allocator) {} AddMemberOrReplace(const rapidjson::Value::Ch *name, rapidjson::Value &&value, - rapidjson::MemoryPoolAllocator<> &allocator) : member(value), name(name), allocator(allocator) {} + rapidjson::MemoryPoolAllocator<> &allocator) : value(value), name(name), allocator(allocator) {} inline rapidjson::Value & operator() (rapidjson::Value &root) const override { if (root.HasMember(name)) - root[name] = member; + root[name] = value; else - root.AddMember(rapidjson::StringRef(name), member, allocator); + root.AddMember(rapidjson::Value(name, allocator), value, allocator); + return root; + } + }; + + struct AppendToArray : public ExtensionFunction + { + rapidjson::Value &value; + const rapidjson::Value::Ch *name; + rapidjson::MemoryPoolAllocator<> &allocator; + + AppendToArray(const rapidjson::Value::Ch *name, rapidjson::Value &value, + rapidjson::MemoryPoolAllocator<> &allocator): value(value), name(name), allocator(allocator) {} + + AppendToArray(const rapidjson::Value::Ch *name, rapidjson::Value &&value, + rapidjson::MemoryPoolAllocator<> &allocator): value(value), name(name), allocator(allocator) {} + + inline rapidjson::Value &operator()(rapidjson::Value &root) const override + { + if (root.HasMember(name)) + { + if (root[name].IsArray()) + { + root[name].PushBack(value, allocator); + } + else + { + root[name] = rapidjson::Value(rapidjson::kArrayType).PushBack(value, allocator); + } + } + else + { + root.AddMember(rapidjson::Value(name, allocator), rapidjson::Value(rapidjson::kArrayType).PushBack(value, allocator), allocator); + } return root; } }; From 6c7e00964518423038fb0e061f5594741f18bc60 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Sat, 11 Nov 2023 18:43:35 +0800 Subject: [PATCH 048/120] Add support for other sing-box rule types --- src/generator/config/ruleconvert.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/generator/config/ruleconvert.cpp b/src/generator/config/ruleconvert.cpp index 7c5ddc2c9..70f6c85ba 100644 --- a/src/generator/config/ruleconvert.cpp +++ b/src/generator/config/ruleconvert.cpp @@ -15,6 +15,7 @@ string_array Surge2RuleTypes = {basic_types, "IP-CIDR6", "USER-AGENT", "URL-REGE string_array SurgeRuleTypes = {basic_types, "IP-CIDR6", "USER-AGENT", "URL-REGEX", "AND", "OR", "NOT", "PROCESS-NAME", "IN-PORT", "DEST-PORT", "SRC-IP"}; string_array QuanXRuleTypes = {basic_types, "USER-AGENT", "HOST", "HOST-SUFFIX", "HOST-KEYWORD"}; string_array SurfRuleTypes = {basic_types, "IP-CIDR6", "PROCESS-NAME", "IN-PORT", "DEST-PORT", "SRC-IP"}; +string_array SingBoxRuleTypes = {basic_types, "IP-VERSION", "INBOUND", "PROTOCOL", "NETWORK", "GEOSITE", "SRC-GEOIP", "DOMAIN-REGEX", "PROCESS-NAME", "PROCESS-PATH", "PACKAGE-NAME", "PORT", "PORT-RANGE", "SRC-PORT", "SRC-PORT-RANGE", "USER", "USER-ID"}; std::string convertRuleset(const std::string &content, int type) { @@ -469,6 +470,7 @@ static rapidjson::Value transformRuleToSingBox(const std::string& rule, const st rapidjson::Value rule_obj(rapidjson::kObjectType); type = replaceAllDistinct(type, "-", "_"); type = replaceAllDistinct(type, "ip_cidr6", "ip_cidr"); + type = replaceAllDistinct(type, "src_", "source_"); if (type == "match" || type == "final") { rule_obj.AddMember("outbound", rapidjson::Value(value.data(), value.size(), allocator), allocator); @@ -563,7 +565,7 @@ void rulesetToSingBox(rapidjson::Document &base_rule, std::vector= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored continue; - if(std::none_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);})) + if(std::none_of(SingBoxRuleTypes.begin(), SingBoxRuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);})) continue; if(strFind(strLine, "//")) { From 7ea43f9c019c297187c8ff219341e0a14ccff636 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Sat, 11 Nov 2023 23:49:50 +0800 Subject: [PATCH 049/120] Fix detecting supported rule type in sing-box configs --- src/generator/config/ruleconvert.cpp | 18 ++++++----- src/utils/rapidjson_extra.h | 15 ++++++++-- src/utils/stl_extra.h | 21 +++++++++++++ src/utils/string.cpp | 45 ++++++++++++++++++++++++++-- src/utils/string.h | 3 +- 5 files changed, 87 insertions(+), 15 deletions(-) diff --git a/src/generator/config/ruleconvert.cpp b/src/generator/config/ruleconvert.cpp index 70f6c85ba..37935c4ea 100644 --- a/src/generator/config/ruleconvert.cpp +++ b/src/generator/config/ruleconvert.cpp @@ -486,17 +486,21 @@ static rapidjson::Value transformRuleToSingBox(const std::string& rule, const st static void appendSingBoxRule(rapidjson::Value &rules, const std::string& rule, rapidjson::MemoryPoolAllocator<>& allocator) { using namespace rapidjson_ext; - auto args = split(rule, ","); + auto args = split(rule, ','); if (args.size() < 2) return; - auto type = toLower(std::string(args[0])); - auto value = toLower(args[1]); + auto type = args[0]; // std::string_view option; // if (args.size() >= 3) option = args[2]; - type = replaceAllDistinct(type, "-", "_"); - type = replaceAllDistinct(type, "ip_cidr6", "ip_cidr"); + if (none_of(SingBoxRuleTypes, [&](const std::string& t){ return type == t; })) + return; + + auto realType = toLower(std::string(type)); + auto value = toLower(std::string(args[1])); + realType = replaceAllDistinct(realType, "-", "_"); + realType = replaceAllDistinct(realType, "ip_cidr6", "ip_cidr"); - rules | AppendToArray(type.c_str(), rapidjson::Value(value.c_str(), allocator), allocator); + rules | AppendToArray(realType.c_str(), rapidjson::Value(value.c_str(), value.size(), allocator), allocator); } void rulesetToSingBox(rapidjson::Document &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules) @@ -565,8 +569,6 @@ void rulesetToSingBox(rapidjson::Document &base_rule, std::vector= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored continue; - if(std::none_of(SingBoxRuleTypes.begin(), SingBoxRuleTypes.end(), [strLine](const std::string& type){return startsWith(strLine, type);})) - continue; if(strFind(strLine, "//")) { strLine.erase(strLine.find("//")); diff --git a/src/utils/rapidjson_extra.h b/src/utils/rapidjson_extra.h index 1b9d6be29..c5fd5c2fe 100644 --- a/src/utils/rapidjson_extra.h +++ b/src/utils/rapidjson_extra.h @@ -117,14 +117,23 @@ namespace rapidjson_ext { struct AppendToArray : public ExtensionFunction { rapidjson::Value &value; - const rapidjson::Value::Ch *name; + rapidjson::GenericValue> name; rapidjson::MemoryPoolAllocator<> &allocator; AppendToArray(const rapidjson::Value::Ch *name, rapidjson::Value &value, - rapidjson::MemoryPoolAllocator<> &allocator): value(value), name(name), allocator(allocator) {} + rapidjson::MemoryPoolAllocator<> &allocator): value(value), name(rapidjson::Value(name, allocator)), allocator(allocator) {} AppendToArray(const rapidjson::Value::Ch *name, rapidjson::Value &&value, - rapidjson::MemoryPoolAllocator<> &allocator): value(value), name(name), allocator(allocator) {} + rapidjson::MemoryPoolAllocator<> &allocator): value(value), name(rapidjson::Value(name, allocator)), allocator(allocator) {} + + AppendToArray(const rapidjson::Value::Ch *name, std::size_t length, rapidjson::Value &value, + rapidjson::MemoryPoolAllocator<> &allocator): value(value), name(rapidjson::Value(name, length, allocator)), allocator(allocator) {} + + AppendToArray(const rapidjson::Value::Ch *name, std::size_t length, rapidjson::Value &&value, + rapidjson::MemoryPoolAllocator<> &allocator): value(value), name(rapidjson::Value(name, length, allocator)), allocator(allocator) {} + + AppendToArray(rapidjson::Value &&name, rapidjson::Value &value, + rapidjson::MemoryPoolAllocator<> &allocator): value(value), allocator(allocator) { this->name.Swap(name); } inline rapidjson::Value &operator()(rapidjson::Value &root) const override { diff --git a/src/utils/stl_extra.h b/src/utils/stl_extra.h index 46c3723a3..6e14390cd 100644 --- a/src/utils/stl_extra.h +++ b/src/utils/stl_extra.h @@ -14,4 +14,25 @@ template inline void eraseElements(T &target) T().swap(target); } +template +concept ConstIterable = requires(Container a, Element b) { + { a.cbegin() } -> std::same_as; + { a.cend() } -> std::same_as; + typename Container::const_reference; +}; + +template +concept Iterable = requires(Container a, Element b) { + { a.begin() } -> std::same_as; + { a.end() } -> std::same_as; + typename Container::reference; +}; + +template +requires ConstIterable +inline bool none_of(ConstIterableContainer &container, std::function func) +{ + return std::none_of(container.cbegin(), container.cend(), func); +} + #endif // STL_EXTRA_H_INCLUDED diff --git a/src/utils/string.cpp b/src/utils/string.cpp index 667279c90..86b1ca881 100644 --- a/src/utils/string.cpp +++ b/src/utils/string.cpp @@ -9,7 +9,7 @@ #include "string.h" #include "map_extra.h" -std::vector split(const std::string &s, const std::string &seperator) +std::vector split(const std::string &s, const std::string &separator) { std::vector result; string_size i = 0; @@ -20,7 +20,7 @@ std::vector split(const std::string &s, const std::string &seperato while(i != s.size() && flag == 0) { flag = 1; - for(char x : seperator) + for(char x : separator) if(s[i] == x) { ++i; @@ -33,7 +33,7 @@ std::vector split(const std::string &s, const std::string &seperato string_size j = i; while(j != s.size() && flag == 0) { - for(char x : seperator) + for(char x : separator) if(s[j] == x) { flag = 1; @@ -51,6 +51,45 @@ std::vector split(const std::string &s, const std::string &seperato return result; } +std::vector split(std::string_view s, char separator) +{ + std::vector result; + string_size i = 0; + + while (i != s.size()) + { + int flag = 0; + while(i != s.size() && flag == 0) + { + flag = 1; + if(s[i] == separator) + { + ++i; + flag = 0; + break; + } + } + + flag = 0; + string_size j = i; + while(j != s.size() && flag == 0) + { + if(s[j] == separator) + { + flag = 1; + break; + } + ++j; + } + if (i != j) + { + result.push_back(s.substr(i, j-i)); + i = j; + } + } + return result; +} + std::string UTF8ToCodePoint(const std::string &data) { std::stringstream ss; diff --git a/src/utils/string.h b/src/utils/string.h index b376dcc12..9d1571fa2 100644 --- a/src/utils/string.h +++ b/src/utils/string.h @@ -14,7 +14,8 @@ using string_map = std::map; using string_multimap = std::multimap; using string_pair_array = std::vector>; -std::vector split(const std::string &s, const std::string &seperator); +std::vector split(const std::string &s, const std::string &separator); +std::vector split(std::string_view s, char separator); std::string join(const string_array &arr, const std::string &delimiter); template From b71cd1e668032ddf8c4ce17cb2429d7782dd36b7 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:07:59 +0800 Subject: [PATCH 050/120] Enhancements Fix crash when using WireGuard as type filter rule. Fix support for compiler older than gcc-10 or clang-10. Optimize codes. --- src/generator/config/subexport.cpp | 6 +++--- src/utils/rapidjson_extra.h | 22 ++++++++++------------ src/utils/stl_extra.h | 24 ++++++++++++++++++------ 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index 66260c95a..8dcb27bc8 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -116,7 +116,7 @@ bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy & std::string target, ret_real_rule; static const std::string groupid_regex = R"(^!!(?:GROUPID|INSERT)=([\d\-+!,]+)(?:!!(.*))?$)", group_regex = R"(^!!(?:GROUP)=(.+?)(?:!!(.*))?$)"; static const std::string type_regex = R"(^!!(?:TYPE)=(.+?)(?:!!(.*))?$)", port_regex = R"(^!!(?:PORT)=(.+?)(?:!!(.*))?$)", server_regex = R"(^!!(?:SERVER)=(.+?)(?:!!(.*))?$)"; - static const string_array types = {"", "SS", "SSR", "VMESS", "TROJAN", "SNELL", "HTTP", "HTTPS", "SOCKS5"}; + static const string_array types = {"", "SS", "SSR", "VMESS", "TROJAN", "SNELL", "HTTP", "HTTPS", "SOCKS5", "WIREGUARD"}; if(startsWith(rule, "!!GROUP=")) { regGetMatch(rule, group_regex, 3, 0, &target, &ret_real_rule); @@ -155,7 +155,7 @@ bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy & return true; } -void processRemark(std::string &remark, string_array &remarks_list, bool proc_comma = true) +void processRemark(std::string &remark, const string_array &remarks_list, bool proc_comma = true) { // Replace every '=' with '-' in the remark string to avoid parse errors from the clients. // Surge is tested to yield an error when handling '=' in the remark string, @@ -172,7 +172,7 @@ void processRemark(std::string &remark, string_array &remarks_list, bool proc_co } std::string tempRemark = remark; int cnt = 2; - while(std::find(remarks_list.begin(), remarks_list.end(), tempRemark) != remarks_list.end()) + while(std::find(remarks_list.cbegin(), remarks_list.cend(), tempRemark) != remarks_list.cbegin()) { tempRemark = remark + " " + std::to_string(cnt); cnt++; diff --git a/src/utils/rapidjson_extra.h b/src/utils/rapidjson_extra.h index c5fd5c2fe..67ee63719 100644 --- a/src/utils/rapidjson_extra.h +++ b/src/utils/rapidjson_extra.h @@ -93,6 +93,16 @@ namespace rapidjson_ext { { return (*this)(root); }; + + friend ReturnType operator| (rapidjson::Value &root, const ExtensionFunction &func) + { + return func(root); + } + + friend ReturnType operator| (rapidjson::Value &&root, const ExtensionFunction &func) + { + return func(root); + } }; struct AddMemberOrReplace : public ExtensionFunction { @@ -165,18 +175,6 @@ namespace rapidjson_ext { return buffer.GetString(); } }; - - template - inline ReturnType operator| (rapidjson::Value &root, const ExtensionFunction &func) - { - return func(root); - } - - template - inline ReturnType operator| (rapidjson::Value &&root, const ExtensionFunction &func) - { - return func(root); - } } diff --git a/src/utils/stl_extra.h b/src/utils/stl_extra.h index 6e14390cd..49c84c0ae 100644 --- a/src/utils/stl_extra.h +++ b/src/utils/stl_extra.h @@ -14,25 +14,37 @@ template inline void eraseElements(T &target) T().swap(target); } -template -concept ConstIterable = requires(Container a, Element b) { +#if __cpp_concepts >= 201907L // C++20 concepts supported (g++-10 or clang++-10) + +template +concept ConstIterable = requires(Container a) { { a.cbegin() } -> std::same_as; { a.cend() } -> std::same_as; typename Container::const_reference; }; -template -concept Iterable = requires(Container a, Element b) { +template +concept Iterable = requires(Container a) { { a.begin() } -> std::same_as; { a.end() } -> std::same_as; typename Container::reference; }; template -requires ConstIterable -inline bool none_of(ConstIterableContainer &container, std::function func) +requires ConstIterable +inline bool none_of(const ConstIterableContainer &container, std::function func) { return std::none_of(container.cbegin(), container.cend(), func); } +#else // __cpp_concepts >= 201907L + +template +inline bool none_of(const Container &container, std::function func) +{ + return std::none_of(container.cbegin(), container.cend(), func); +} + +#endif // __cpp_concepts >= 201907L + #endif // STL_EXTRA_H_INCLUDED From 885a63b83778fbcf85765d10ec94ea06b9ca72da Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Tue, 14 Nov 2023 16:42:22 +0800 Subject: [PATCH 051/120] Enhancements Fix a typo which cause the server to hang. Add option to enable reload pref config on request. Optimize codes. --- base/pref.example.ini | 3 +++ base/pref.example.toml | 3 +++ base/pref.example.yml | 1 + src/generator/config/subexport.cpp | 26 +++++++++++++------------- src/handler/interfaces.cpp | 2 +- src/handler/settings.cpp | 5 ++++- src/handler/settings.h | 1 + 7 files changed, 26 insertions(+), 15 deletions(-) diff --git a/base/pref.example.ini b/base/pref.example.ini index 6029e18a4..fe1688fa8 100644 --- a/base/pref.example.ini +++ b/base/pref.example.ini @@ -73,6 +73,9 @@ proxy_subscription=NONE ;Append a proxy type string ([SS] [SSR] [VMess]) to node remark. append_proxy_type=false +;When requesting /sub, reload this config file first. +reload_conf_on_request=false + [userinfo] ;Rules to extract stream data from node ;Format: full_match_regex|new_format_regex diff --git a/base/pref.example.toml b/base/pref.example.toml index 9a486d969..b78a007e5 100644 --- a/base/pref.example.toml +++ b/base/pref.example.toml @@ -79,6 +79,9 @@ proxy_subscription = "NONE" # Append a proxy type string ([SS] [SSR] [VMess]) to node remark. append_proxy_type = false +# When requesting /sub, reload this config file first. +reload_conf_on_request = false + [[userinfo.stream_rule]] # Rules to extract stream data from node # Format: full_match_regex|new_format_regex diff --git a/base/pref.example.yml b/base/pref.example.yml index b7df31297..f532994cc 100644 --- a/base/pref.example.yml +++ b/base/pref.example.yml @@ -24,6 +24,7 @@ common: proxy_ruleset: SYSTEM proxy_subscription: NONE append_proxy_type: false + reload_conf_on_request: false userinfo: stream_rule: diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index 8dcb27bc8..941eabc51 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -172,7 +172,7 @@ void processRemark(std::string &remark, const string_array &remarks_list, bool p } std::string tempRemark = remark; int cnt = 2; - while(std::find(remarks_list.cbegin(), remarks_list.cend(), tempRemark) != remarks_list.cbegin()) + while(std::find(remarks_list.cbegin(), remarks_list.cend(), tempRemark) != remarks_list.cend()) { tempRemark = remark + " " + std::to_string(cnt); cnt++; @@ -2078,7 +2078,7 @@ static rapidjson::Value buildSingBoxTransport(const Proxy& proxy, rapidjson::Mem return transport; } -void addSingBoxCommonMembers(rapidjson::Value &proxy, const Proxy &x, const rapidjson::GenericStringRef &type, rapidjson::MemoryPoolAllocator<> &allocator) +static void addSingBoxCommonMembers(rapidjson::Value &proxy, const Proxy &x, const rapidjson::GenericStringRef &type, rapidjson::MemoryPoolAllocator<> &allocator) { proxy.AddMember("type", type, allocator); proxy.AddMember("tag", rapidjson::StringRef(x.Remark.c_str()), allocator); @@ -2086,6 +2086,15 @@ void addSingBoxCommonMembers(rapidjson::Value &proxy, const Proxy &x, const rapi proxy.AddMember("server_port", x.Port, allocator); } +static rapidjson::Value stringArrayToJsonArray(const std::string &array, const std::string &delimiter, rapidjson::MemoryPoolAllocator<> &allocator) +{ + rapidjson::Value result(rapidjson::kArrayType); + string_array vArray = split(array, delimiter); + for (const auto &x : vArray) + result.PushBack(rapidjson::Value(trim(x).c_str(), allocator), allocator); + return result; +} + void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { using namespace rapidjson_ext; rapidjson::Document::AllocatorType &allocator = json.GetAllocator(); @@ -2181,22 +2190,13 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v if (!x.AllowedIPs.empty()) { - auto allowed = split(x.AllowedIPs, ","); - rapidjson::Value allowed_ips(rapidjson::kArrayType); - for (const auto &ip: allowed) { - allowed_ips.PushBack(rapidjson::Value(trim(ip).c_str(), allocator), allocator); - } + auto allowed_ips = stringArrayToJsonArray(x.AllowedIPs, ",", allocator); peer.AddMember("allowed_ips", allowed_ips, allocator); } if (!x.ClientId.empty()) { - auto client_id = split(x.ClientId, ","); - rapidjson::Value reserved(rapidjson::kArrayType); - for (const auto &id : client_id) - { - reserved.PushBack(to_int(trim(id)), allocator); - } + auto reserved = stringArrayToJsonArray(x.ClientId, ",", allocator); peer.AddMember("reserved", reserved, allocator); } diff --git a/src/handler/interfaces.cpp b/src/handler/interfaces.cpp index faced4f90..44e7c1773 100644 --- a/src/handler/interfaces.cpp +++ b/src/handler/interfaces.cpp @@ -323,7 +323,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) return "Invalid target!"; } //check if we need to read configuration - if((!global.APIMode || global.CFWChildProcess) && !global.generatorMode) + if(global.reloadConfOnRequest && (!global.APIMode || global.CFWChildProcess) && !global.generatorMode) readConf(); /// string values diff --git a/src/handler/settings.cpp b/src/handler/settings.cpp index 51f4437b7..678da3368 100644 --- a/src/handler/settings.cpp +++ b/src/handler/settings.cpp @@ -338,6 +338,7 @@ void readYAMLConf(YAML::Node &node) section["proxy_config"] >> global.proxyConfig; section["proxy_ruleset"] >> global.proxyRuleset; section["proxy_subscription"] >> global.proxySubscription; + section["reload_conf_on_request"] >> global.reloadConfOnRequest; if(node["userinfo"].IsDefined()) { @@ -612,7 +613,8 @@ void readTOMLConf(toml::value &root) "proxy_config", global.proxyConfig, "proxy_ruleset", global.proxyRuleset, "proxy_subscription", global.proxySubscription, - "append_proxy_type", global.appendType + "append_proxy_type", global.appendType, + "reload_conf_on_request", global.reloadConfOnRequest ); if(filter) @@ -854,6 +856,7 @@ void readConf() ini.get_if_exist("proxy_config", global.proxyConfig); ini.get_if_exist("proxy_ruleset", global.proxyRuleset); ini.get_if_exist("proxy_subscription", global.proxySubscription); + ini.get_bool_if_exist("reload_conf_on_request", global.reloadConfOnRequest); if(ini.section_exist("surge_external_proxy")) { diff --git a/src/handler/settings.h b/src/handler/settings.h index d2d6932aa..9705145a5 100644 --- a/src/handler/settings.h +++ b/src/handler/settings.h @@ -42,6 +42,7 @@ struct Settings std::string generateProfiles; //preferences + bool reloadConfOnRequest = false; RegexMatchConfigs renames, emojis; bool addEmoji = false, removeEmoji = false, appendType = false, filterDeprecated = true; tribool UDPFlag, TFOFlag, skipCertVerify, TLS13Flag, enableInsert; From 434a775202e6f3605f923d2f0a28a9ade0408a4d Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Wed, 15 Nov 2023 17:59:09 +0800 Subject: [PATCH 052/120] Update local dependencies --- include/inja.hpp | 2687 ++---- include/jpcre2.hpp | 98 +- include/nlohmann/json.hpp | 11600 ++++++++++--------------- src/generator/template/templates.cpp | 4 +- 4 files changed, 5424 insertions(+), 8965 deletions(-) diff --git a/include/inja.hpp b/include/inja.hpp index c8ee88893..d8a9744b0 100644 --- a/include/inja.hpp +++ b/include/inja.hpp @@ -1,10 +1,10 @@ /* - ___ _ Version 3.3 + ___ _ Version 3.4.0 |_ _|_ __ (_) __ _ https://github.com/pantor/inja | || '_ \ | |/ _` | Licensed under the MIT License . | || | | || | (_| | - |___|_| |_|/ |\__,_| Copyright (c) 2018-2021 Lars Berscheid - |__/ + |___|_| |_|/ |\__,_| Copyright (c) 2018-2023 Lars Berscheid + |__/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -25,1464 +25,49 @@ SOFTWARE. #ifndef INCLUDE_INJA_INJA_HPP_ #define INCLUDE_INJA_INJA_HPP_ -#include +#include "nlohmann/json.hpp" namespace inja { #ifndef INJA_DATA_TYPE - using json = nlohmann::json; +using json = nlohmann::json; #else - using json = INJA_DATA_TYPE; +using json = INJA_DATA_TYPE; #endif -} - -#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(INJA_NOEXCEPTION) - #ifndef INJA_THROW - #define INJA_THROW(exception) throw exception - #endif -#else - #include - #ifndef INJA_THROW - #define INJA_THROW(exception) std::abort(); std::ignore = exception - #endif - #ifndef INJA_NOEXCEPTION - #define INJA_NOEXCEPTION - #endif -#endif - -// #include "environment.hpp" -#ifndef INCLUDE_INJA_ENVIRONMENT_HPP_ -#define INCLUDE_INJA_ENVIRONMENT_HPP_ - -#include -#include -#include -#include -#include - -// #include "config.hpp" -#ifndef INCLUDE_INJA_CONFIG_HPP_ -#define INCLUDE_INJA_CONFIG_HPP_ - -#include -#include - -// #include "string_view.hpp" -// Copyright 2017-2019 by Martin Moene -// -// string-view lite, a C++17-like string_view for C++98 and later. -// For more information see https://github.com/martinmoene/string-view-lite -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - - - -#ifndef NONSTD_SV_LITE_H_INCLUDED -#define NONSTD_SV_LITE_H_INCLUDED - -#define string_view_lite_MAJOR 1 -#define string_view_lite_MINOR 4 -#define string_view_lite_PATCH 0 - -#define string_view_lite_VERSION \ - nssv_STRINGIFY(string_view_lite_MAJOR) "." nssv_STRINGIFY(string_view_lite_MINOR) "." nssv_STRINGIFY( \ - string_view_lite_PATCH) - -#define nssv_STRINGIFY(x) nssv_STRINGIFY_(x) -#define nssv_STRINGIFY_(x) #x - -// string-view lite configuration: - -#define nssv_STRING_VIEW_DEFAULT 0 -#define nssv_STRING_VIEW_NONSTD 1 -#define nssv_STRING_VIEW_STD 2 - -#if !defined(nssv_CONFIG_SELECT_STRING_VIEW) -#define nssv_CONFIG_SELECT_STRING_VIEW (nssv_HAVE_STD_STRING_VIEW ? nssv_STRING_VIEW_STD : nssv_STRING_VIEW_NONSTD) -#endif - -#if defined(nssv_CONFIG_SELECT_STD_STRING_VIEW) || defined(nssv_CONFIG_SELECT_NONSTD_STRING_VIEW) -#error nssv_CONFIG_SELECT_STD_STRING_VIEW and nssv_CONFIG_SELECT_NONSTD_STRING_VIEW are deprecated and removed, please use nssv_CONFIG_SELECT_STRING_VIEW=nssv_STRING_VIEW_... -#endif - -#ifndef nssv_CONFIG_STD_SV_OPERATOR -#define nssv_CONFIG_STD_SV_OPERATOR 0 -#endif - -#ifndef nssv_CONFIG_USR_SV_OPERATOR -#define nssv_CONFIG_USR_SV_OPERATOR 1 -#endif - -#ifdef nssv_CONFIG_CONVERSION_STD_STRING -#define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS nssv_CONFIG_CONVERSION_STD_STRING -#define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS nssv_CONFIG_CONVERSION_STD_STRING -#endif - -#ifndef nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS -#define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS 1 -#endif - -#ifndef nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS -#define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS 1 -#endif - -// Control presence of exception handling (try and auto discover): - -#ifndef nssv_CONFIG_NO_EXCEPTIONS -#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) -#define nssv_CONFIG_NO_EXCEPTIONS 0 -#else -#define nssv_CONFIG_NO_EXCEPTIONS 1 -#endif -#endif - -// C++ language version detection (C++20 is speculative): -// Note: VC14.0/1900 (VS2015) lacks too much from C++14. - -#ifndef nssv_CPLUSPLUS -#if defined(_MSVC_LANG) && !defined(__clang__) -#define nssv_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG) -#else -#define nssv_CPLUSPLUS __cplusplus -#endif -#endif - -#define nssv_CPP98_OR_GREATER (nssv_CPLUSPLUS >= 199711L) -#define nssv_CPP11_OR_GREATER (nssv_CPLUSPLUS >= 201103L) -#define nssv_CPP11_OR_GREATER_ (nssv_CPLUSPLUS >= 201103L) -#define nssv_CPP14_OR_GREATER (nssv_CPLUSPLUS >= 201402L) -#define nssv_CPP17_OR_GREATER (nssv_CPLUSPLUS >= 201703L) -#define nssv_CPP20_OR_GREATER (nssv_CPLUSPLUS >= 202000L) - -// use C++17 std::string_view if available and requested: - -#if nssv_CPP17_OR_GREATER && defined(__has_include) -#if __has_include( ) -#define nssv_HAVE_STD_STRING_VIEW 1 -#else -#define nssv_HAVE_STD_STRING_VIEW 0 -#endif -#else -#define nssv_HAVE_STD_STRING_VIEW 0 -#endif - -#define nssv_USES_STD_STRING_VIEW \ - ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_STD) || \ - ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_DEFAULT) && nssv_HAVE_STD_STRING_VIEW)) - -#define nssv_HAVE_STARTS_WITH (nssv_CPP20_OR_GREATER || !nssv_USES_STD_STRING_VIEW) -#define nssv_HAVE_ENDS_WITH nssv_HAVE_STARTS_WITH - -// -// Use C++17 std::string_view: -// - -#if nssv_USES_STD_STRING_VIEW - -#include - -// Extensions for std::string: - -#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS - -namespace nonstd { - -template > -std::basic_string to_string(std::basic_string_view v, - Allocator const &a = Allocator()) { - return std::basic_string(v.begin(), v.end(), a); -} - -template -std::basic_string_view to_string_view(std::basic_string const &s) { - return std::basic_string_view(s.data(), s.size()); -} - -// Literal operators sv and _sv: - -#if nssv_CONFIG_STD_SV_OPERATOR - -using namespace std::literals::string_view_literals; - -#endif - -#if nssv_CONFIG_USR_SV_OPERATOR - -inline namespace literals { -inline namespace string_view_literals { - -constexpr std::string_view operator"" _sv(const char *str, size_t len) noexcept // (1) -{ - return std::string_view {str, len}; -} - -constexpr std::u16string_view operator"" _sv(const char16_t *str, size_t len) noexcept // (2) -{ - return std::u16string_view {str, len}; -} - -constexpr std::u32string_view operator"" _sv(const char32_t *str, size_t len) noexcept // (3) -{ - return std::u32string_view {str, len}; -} - -constexpr std::wstring_view operator"" _sv(const wchar_t *str, size_t len) noexcept // (4) -{ - return std::wstring_view {str, len}; -} - -} // namespace string_view_literals -} // namespace literals - -#endif // nssv_CONFIG_USR_SV_OPERATOR - -} // namespace nonstd - -#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS - -namespace nonstd { - -using std::basic_string_view; -using std::string_view; -using std::u16string_view; -using std::u32string_view; -using std::wstring_view; - -// literal "sv" and "_sv", see above - -using std::operator==; -using std::operator!=; -using std::operator<; -using std::operator<=; -using std::operator>; -using std::operator>=; - -using std::operator<<; - -} // namespace nonstd - -#else // nssv_HAVE_STD_STRING_VIEW - -// -// Before C++17: use string_view lite: -// - -// Compiler versions: -// -// MSVC++ 6.0 _MSC_VER == 1200 (Visual Studio 6.0) -// MSVC++ 7.0 _MSC_VER == 1300 (Visual Studio .NET 2002) -// MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio .NET 2003) -// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) -// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) -// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) -// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) -// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) -// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) -// MSVC++ 14.1 _MSC_VER >= 1910 (Visual Studio 2017) - -#if defined(_MSC_VER) && !defined(__clang__) -#define nssv_COMPILER_MSVC_VER (_MSC_VER) -#define nssv_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * (5 + (_MSC_VER < 1900))) -#else -#define nssv_COMPILER_MSVC_VER 0 -#define nssv_COMPILER_MSVC_VERSION 0 -#endif - -#define nssv_COMPILER_VERSION(major, minor, patch) (10 * (10 * (major) + (minor)) + (patch)) - -#if defined(__clang__) -#define nssv_COMPILER_CLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) -#else -#define nssv_COMPILER_CLANG_VERSION 0 -#endif - -#if defined(__GNUC__) && !defined(__clang__) -#define nssv_COMPILER_GNUC_VERSION nssv_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) -#else -#define nssv_COMPILER_GNUC_VERSION 0 -#endif - -// half-open range [lo..hi): -#define nssv_BETWEEN(v, lo, hi) ((lo) <= (v) && (v) < (hi)) - -// Presence of language and library features: - -#ifdef _HAS_CPP0X -#define nssv_HAS_CPP0X _HAS_CPP0X -#else -#define nssv_HAS_CPP0X 0 -#endif - -// Unless defined otherwise below, consider VC14 as C++11 for variant-lite: - -#if nssv_COMPILER_MSVC_VER >= 1900 -#undef nssv_CPP11_OR_GREATER -#define nssv_CPP11_OR_GREATER 1 -#endif - -#define nssv_CPP11_90 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1500) -#define nssv_CPP11_100 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1600) -#define nssv_CPP11_110 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1700) -#define nssv_CPP11_120 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1800) -#define nssv_CPP11_140 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1900) -#define nssv_CPP11_141 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1910) - -#define nssv_CPP14_000 (nssv_CPP14_OR_GREATER) -#define nssv_CPP17_000 (nssv_CPP17_OR_GREATER) - -// Presence of C++11 language features: - -#define nssv_HAVE_CONSTEXPR_11 nssv_CPP11_140 -#define nssv_HAVE_EXPLICIT_CONVERSION nssv_CPP11_140 -#define nssv_HAVE_INLINE_NAMESPACE nssv_CPP11_140 -#define nssv_HAVE_NOEXCEPT nssv_CPP11_140 -#define nssv_HAVE_NULLPTR nssv_CPP11_100 -#define nssv_HAVE_REF_QUALIFIER nssv_CPP11_140 -#define nssv_HAVE_UNICODE_LITERALS nssv_CPP11_140 -#define nssv_HAVE_USER_DEFINED_LITERALS nssv_CPP11_140 -#define nssv_HAVE_WCHAR16_T nssv_CPP11_100 -#define nssv_HAVE_WCHAR32_T nssv_CPP11_100 - -#if !((nssv_CPP11_OR_GREATER && nssv_COMPILER_CLANG_VERSION) || nssv_BETWEEN(nssv_COMPILER_CLANG_VERSION, 300, 400)) -#define nssv_HAVE_STD_DEFINED_LITERALS nssv_CPP11_140 -#else -#define nssv_HAVE_STD_DEFINED_LITERALS 0 -#endif - -// Presence of C++14 language features: - -#define nssv_HAVE_CONSTEXPR_14 nssv_CPP14_000 - -// Presence of C++17 language features: - -#define nssv_HAVE_NODISCARD nssv_CPP17_000 - -// Presence of C++ library features: - -#define nssv_HAVE_STD_HASH nssv_CPP11_120 - -// C++ feature usage: - -#if nssv_HAVE_CONSTEXPR_11 -#define nssv_constexpr constexpr -#else -#define nssv_constexpr /*constexpr*/ -#endif - -#if nssv_HAVE_CONSTEXPR_14 -#define nssv_constexpr14 constexpr -#else -#define nssv_constexpr14 /*constexpr*/ -#endif - -#if nssv_HAVE_EXPLICIT_CONVERSION -#define nssv_explicit explicit -#else -#define nssv_explicit /*explicit*/ -#endif - -#if nssv_HAVE_INLINE_NAMESPACE -#define nssv_inline_ns inline -#else -#define nssv_inline_ns /*inline*/ -#endif - -#if nssv_HAVE_NOEXCEPT -#define nssv_noexcept noexcept -#else -#define nssv_noexcept /*noexcept*/ -#endif - -//#if nssv_HAVE_REF_QUALIFIER -//# define nssv_ref_qual & -//# define nssv_refref_qual && -//#else -//# define nssv_ref_qual /*&*/ -//# define nssv_refref_qual /*&&*/ -//#endif - -#if nssv_HAVE_NULLPTR -#define nssv_nullptr nullptr -#else -#define nssv_nullptr NULL -#endif - -#if nssv_HAVE_NODISCARD -#define nssv_nodiscard [[nodiscard]] -#else -#define nssv_nodiscard /*[[nodiscard]]*/ -#endif - -// Additional includes: - -#include -#include -#include -#include -#include -#include // std::char_traits<> - -#if !nssv_CONFIG_NO_EXCEPTIONS -#include -#endif - -#if nssv_CPP11_OR_GREATER -#include -#endif - -// Clang, GNUC, MSVC warning suppression macros: - -#if defined(__clang__) -#pragma clang diagnostic ignored "-Wreserved-user-defined-literal" -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wuser-defined-literals" -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wliteral-suffix" -#endif // __clang__ - -#if nssv_COMPILER_MSVC_VERSION >= 140 -#define nssv_SUPPRESS_MSGSL_WARNING(expr) [[gsl::suppress(expr)]] -#define nssv_SUPPRESS_MSVC_WARNING(code, descr) __pragma(warning(suppress : code)) -#define nssv_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable : codes)) -#else -#define nssv_SUPPRESS_MSGSL_WARNING(expr) -#define nssv_SUPPRESS_MSVC_WARNING(code, descr) -#define nssv_DISABLE_MSVC_WARNINGS(codes) -#endif - -#if defined(__clang__) -#define nssv_RESTORE_WARNINGS() _Pragma("clang diagnostic pop") -#elif defined(__GNUC__) -#define nssv_RESTORE_WARNINGS() _Pragma("GCC diagnostic pop") -#elif nssv_COMPILER_MSVC_VERSION >= 140 -#define nssv_RESTORE_WARNINGS() __pragma(warning(pop)) -#else -#define nssv_RESTORE_WARNINGS() -#endif - -// Suppress the following MSVC (GSL) warnings: -// - C4455, non-gsl : 'operator ""sv': literal suffix identifiers that do not -// start with an underscore are reserved -// - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions; -// use brace initialization, gsl::narrow_cast or gsl::narow -// - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead - -nssv_DISABLE_MSVC_WARNINGS(4455 26481 26472) - // nssv_DISABLE_CLANG_WARNINGS( "-Wuser-defined-literals" ) - // nssv_DISABLE_GNUC_WARNINGS( -Wliteral-suffix ) - - namespace nonstd { - namespace sv_lite { - -#if nssv_CPP11_OR_GREATER - - namespace detail { - - // Expect tail call optimization to make length() non-recursive: - - template inline constexpr std::size_t length(CharT *s, std::size_t result = 0) { - return *s == '\0' ? result : length(s + 1, result + 1); - } - - } // namespace detail - -#endif // nssv_CPP11_OR_GREATER - - template > class basic_string_view; - - // - // basic_string_view: - // - - template */ - > - class basic_string_view { - public: - // Member types: - - typedef Traits traits_type; - typedef CharT value_type; - - typedef CharT *pointer; - typedef CharT const *const_pointer; - typedef CharT &reference; - typedef CharT const &const_reference; - - typedef const_pointer iterator; - typedef const_pointer const_iterator; - typedef std::reverse_iterator reverse_iterator; - typedef std::reverse_iterator const_reverse_iterator; - - typedef std::size_t size_type; - typedef std::ptrdiff_t difference_type; - - // 24.4.2.1 Construction and assignment: - - nssv_constexpr basic_string_view() nssv_noexcept : data_(nssv_nullptr), size_(0) {} - -#if nssv_CPP11_OR_GREATER - nssv_constexpr basic_string_view(basic_string_view const &other) nssv_noexcept = default; -#else - nssv_constexpr basic_string_view(basic_string_view const &other) nssv_noexcept : data_(other.data_), - size_(other.size_) {} -#endif - - nssv_constexpr basic_string_view(CharT const *s, size_type count) nssv_noexcept // non-standard noexcept - : data_(s), - size_(count) {} - - nssv_constexpr basic_string_view(CharT const *s) nssv_noexcept // non-standard noexcept - : data_(s) -#if nssv_CPP17_OR_GREATER - , - size_(Traits::length(s)) -#elif nssv_CPP11_OR_GREATER - , - size_(detail::length(s)) -#else - , - size_(Traits::length(s)) -#endif - { - } - - // Assignment: - -#if nssv_CPP11_OR_GREATER - nssv_constexpr14 basic_string_view &operator=(basic_string_view const &other) nssv_noexcept = default; -#else - nssv_constexpr14 basic_string_view &operator=(basic_string_view const &other) nssv_noexcept { - data_ = other.data_; - size_ = other.size_; - return *this; - } -#endif - - // 24.4.2.2 Iterator support: - - nssv_constexpr const_iterator begin() const nssv_noexcept { return data_; } - nssv_constexpr const_iterator end() const nssv_noexcept { return data_ + size_; } - - nssv_constexpr const_iterator cbegin() const nssv_noexcept { return begin(); } - nssv_constexpr const_iterator cend() const nssv_noexcept { return end(); } - - nssv_constexpr const_reverse_iterator rbegin() const nssv_noexcept { return const_reverse_iterator(end()); } - nssv_constexpr const_reverse_iterator rend() const nssv_noexcept { return const_reverse_iterator(begin()); } - - nssv_constexpr const_reverse_iterator crbegin() const nssv_noexcept { return rbegin(); } - nssv_constexpr const_reverse_iterator crend() const nssv_noexcept { return rend(); } - - // 24.4.2.3 Capacity: - - nssv_constexpr size_type size() const nssv_noexcept { return size_; } - nssv_constexpr size_type length() const nssv_noexcept { return size_; } - nssv_constexpr size_type max_size() const nssv_noexcept { return (std::numeric_limits::max)(); } - - // since C++20 - nssv_nodiscard nssv_constexpr bool empty() const nssv_noexcept { return 0 == size_; } - - // 24.4.2.4 Element access: - - nssv_constexpr const_reference operator[](size_type pos) const { return data_at(pos); } - - nssv_constexpr14 const_reference at(size_type pos) const { -#if nssv_CONFIG_NO_EXCEPTIONS - assert(pos < size()); -#else - if (pos >= size()) { - throw std::out_of_range("nonstd::string_view::at()"); - } -#endif - return data_at(pos); - } - - nssv_constexpr const_reference front() const { return data_at(0); } - nssv_constexpr const_reference back() const { return data_at(size() - 1); } - - nssv_constexpr const_pointer data() const nssv_noexcept { return data_; } - - // 24.4.2.5 Modifiers: - - nssv_constexpr14 void remove_prefix(size_type n) { - assert(n <= size()); - data_ += n; - size_ -= n; - } - - nssv_constexpr14 void remove_suffix(size_type n) { - assert(n <= size()); - size_ -= n; - } - - nssv_constexpr14 void swap(basic_string_view &other) nssv_noexcept { - using std::swap; - swap(data_, other.data_); - swap(size_, other.size_); - } - - // 24.4.2.6 String operations: - - size_type copy(CharT *dest, size_type n, size_type pos = 0) const { -#if nssv_CONFIG_NO_EXCEPTIONS - assert(pos <= size()); -#else - if (pos > size()) { - throw std::out_of_range("nonstd::string_view::copy()"); - } -#endif - const size_type rlen = (std::min)(n, size() - pos); - - (void)Traits::copy(dest, data() + pos, rlen); - - return rlen; - } - - nssv_constexpr14 basic_string_view substr(size_type pos = 0, size_type n = npos) const { -#if nssv_CONFIG_NO_EXCEPTIONS - assert(pos <= size()); -#else - if (pos > size()) { - throw std::out_of_range("nonstd::string_view::substr()"); - } -#endif - return basic_string_view(data() + pos, (std::min)(n, size() - pos)); - } - - // compare(), 6x: - - nssv_constexpr14 int compare(basic_string_view other) const nssv_noexcept // (1) - { - if (const int result = Traits::compare(data(), other.data(), (std::min)(size(), other.size()))) { - return result; - } - - return size() == other.size() ? 0 : size() < other.size() ? -1 : 1; - } - - nssv_constexpr int compare(size_type pos1, size_type n1, basic_string_view other) const // (2) - { - return substr(pos1, n1).compare(other); - } - - nssv_constexpr int compare(size_type pos1, size_type n1, basic_string_view other, size_type pos2, - size_type n2) const // (3) - { - return substr(pos1, n1).compare(other.substr(pos2, n2)); - } - - nssv_constexpr int compare(CharT const *s) const // (4) - { - return compare(basic_string_view(s)); - } - - nssv_constexpr int compare(size_type pos1, size_type n1, CharT const *s) const // (5) - { - return substr(pos1, n1).compare(basic_string_view(s)); - } - - nssv_constexpr int compare(size_type pos1, size_type n1, CharT const *s, size_type n2) const // (6) - { - return substr(pos1, n1).compare(basic_string_view(s, n2)); - } - - // 24.4.2.7 Searching: - - // starts_with(), 3x, since C++20: - - nssv_constexpr bool starts_with(basic_string_view v) const nssv_noexcept // (1) - { - return size() >= v.size() && compare(0, v.size(), v) == 0; - } - - nssv_constexpr bool starts_with(CharT c) const nssv_noexcept // (2) - { - return starts_with(basic_string_view(&c, 1)); - } - - nssv_constexpr bool starts_with(CharT const *s) const // (3) - { - return starts_with(basic_string_view(s)); - } - - // ends_with(), 3x, since C++20: - - nssv_constexpr bool ends_with(basic_string_view v) const nssv_noexcept // (1) - { - return size() >= v.size() && compare(size() - v.size(), npos, v) == 0; - } - - nssv_constexpr bool ends_with(CharT c) const nssv_noexcept // (2) - { - return ends_with(basic_string_view(&c, 1)); - } - - nssv_constexpr bool ends_with(CharT const *s) const // (3) - { - return ends_with(basic_string_view(s)); - } - - // find(), 4x: - - nssv_constexpr14 size_type find(basic_string_view v, size_type pos = 0) const nssv_noexcept // (1) - { - return assert(v.size() == 0 || v.data() != nssv_nullptr), - pos >= size() ? npos : to_pos(std::search(cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq)); - } - - nssv_constexpr14 size_type find(CharT c, size_type pos = 0) const nssv_noexcept // (2) - { - return find(basic_string_view(&c, 1), pos); - } - - nssv_constexpr14 size_type find(CharT const *s, size_type pos, size_type n) const // (3) - { - return find(basic_string_view(s, n), pos); - } - - nssv_constexpr14 size_type find(CharT const *s, size_type pos = 0) const // (4) - { - return find(basic_string_view(s), pos); - } - - // rfind(), 4x: - - nssv_constexpr14 size_type rfind(basic_string_view v, size_type pos = npos) const nssv_noexcept // (1) - { - if (size() < v.size()) { - return npos; - } - - if (v.empty()) { - return (std::min)(size(), pos); - } - - const_iterator last = cbegin() + (std::min)(size() - v.size(), pos) + v.size(); - const_iterator result = std::find_end(cbegin(), last, v.cbegin(), v.cend(), Traits::eq); - - return result != last ? size_type(result - cbegin()) : npos; - } - - nssv_constexpr14 size_type rfind(CharT c, size_type pos = npos) const nssv_noexcept // (2) - { - return rfind(basic_string_view(&c, 1), pos); - } - - nssv_constexpr14 size_type rfind(CharT const *s, size_type pos, size_type n) const // (3) - { - return rfind(basic_string_view(s, n), pos); - } - - nssv_constexpr14 size_type rfind(CharT const *s, size_type pos = npos) const // (4) - { - return rfind(basic_string_view(s), pos); - } - - // find_first_of(), 4x: - - nssv_constexpr size_type find_first_of(basic_string_view v, size_type pos = 0) const nssv_noexcept // (1) - { - return pos >= size() ? npos - : to_pos(std::find_first_of(cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq)); - } - - nssv_constexpr size_type find_first_of(CharT c, size_type pos = 0) const nssv_noexcept // (2) - { - return find_first_of(basic_string_view(&c, 1), pos); - } - - nssv_constexpr size_type find_first_of(CharT const *s, size_type pos, size_type n) const // (3) - { - return find_first_of(basic_string_view(s, n), pos); - } - - nssv_constexpr size_type find_first_of(CharT const *s, size_type pos = 0) const // (4) - { - return find_first_of(basic_string_view(s), pos); - } - - // find_last_of(), 4x: - - nssv_constexpr size_type find_last_of(basic_string_view v, size_type pos = npos) const nssv_noexcept // (1) - { - return empty() ? npos - : pos >= size() ? find_last_of(v, size() - 1) - : to_pos(std::find_first_of(const_reverse_iterator(cbegin() + pos + 1), crend(), - v.cbegin(), v.cend(), Traits::eq)); - } - - nssv_constexpr size_type find_last_of(CharT c, size_type pos = npos) const nssv_noexcept // (2) - { - return find_last_of(basic_string_view(&c, 1), pos); - } - - nssv_constexpr size_type find_last_of(CharT const *s, size_type pos, size_type count) const // (3) - { - return find_last_of(basic_string_view(s, count), pos); - } - - nssv_constexpr size_type find_last_of(CharT const *s, size_type pos = npos) const // (4) - { - return find_last_of(basic_string_view(s), pos); - } - - // find_first_not_of(), 4x: - - nssv_constexpr size_type find_first_not_of(basic_string_view v, size_type pos = 0) const nssv_noexcept // (1) - { - return pos >= size() ? npos : to_pos(std::find_if(cbegin() + pos, cend(), not_in_view(v))); - } - - nssv_constexpr size_type find_first_not_of(CharT c, size_type pos = 0) const nssv_noexcept // (2) - { - return find_first_not_of(basic_string_view(&c, 1), pos); - } - - nssv_constexpr size_type find_first_not_of(CharT const *s, size_type pos, size_type count) const // (3) - { - return find_first_not_of(basic_string_view(s, count), pos); - } - - nssv_constexpr size_type find_first_not_of(CharT const *s, size_type pos = 0) const // (4) - { - return find_first_not_of(basic_string_view(s), pos); - } - - // find_last_not_of(), 4x: - - nssv_constexpr size_type find_last_not_of(basic_string_view v, size_type pos = npos) const nssv_noexcept // (1) - { - return empty() ? npos - : pos >= size() - ? find_last_not_of(v, size() - 1) - : to_pos(std::find_if(const_reverse_iterator(cbegin() + pos + 1), crend(), not_in_view(v))); - } - - nssv_constexpr size_type find_last_not_of(CharT c, size_type pos = npos) const nssv_noexcept // (2) - { - return find_last_not_of(basic_string_view(&c, 1), pos); - } - - nssv_constexpr size_type find_last_not_of(CharT const *s, size_type pos, size_type count) const // (3) - { - return find_last_not_of(basic_string_view(s, count), pos); - } - - nssv_constexpr size_type find_last_not_of(CharT const *s, size_type pos = npos) const // (4) - { - return find_last_not_of(basic_string_view(s), pos); - } - - // Constants: - -#if nssv_CPP17_OR_GREATER - static nssv_constexpr size_type npos = size_type(-1); -#elif nssv_CPP11_OR_GREATER - enum : size_type { npos = size_type(-1) }; -#else - enum { npos = size_type(-1) }; -#endif - - private: - struct not_in_view { - const basic_string_view v; - - nssv_constexpr explicit not_in_view(basic_string_view v) : v(v) {} - - nssv_constexpr bool operator()(CharT c) const { return npos == v.find_first_of(c); } - }; - - nssv_constexpr size_type to_pos(const_iterator it) const { return it == cend() ? npos : size_type(it - cbegin()); } - - nssv_constexpr size_type to_pos(const_reverse_iterator it) const { - return it == crend() ? npos : size_type(crend() - it - 1); - } - - nssv_constexpr const_reference data_at(size_type pos) const { -#if nssv_BETWEEN(nssv_COMPILER_GNUC_VERSION, 1, 500) - return data_[pos]; -#else - return assert(pos < size()), data_[pos]; -#endif - } - - private: - const_pointer data_; - size_type size_; - - public: -#if nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS - - template - basic_string_view(std::basic_string const &s) nssv_noexcept : data_(s.data()), - size_(s.size()) {} - -#if nssv_HAVE_EXPLICIT_CONVERSION - - template explicit operator std::basic_string() const { - return to_string(Allocator()); - } - -#endif // nssv_HAVE_EXPLICIT_CONVERSION - -#if nssv_CPP11_OR_GREATER - - template > - std::basic_string to_string(Allocator const &a = Allocator()) const { - return std::basic_string(begin(), end(), a); - } - -#else - - std::basic_string to_string() const { return std::basic_string(begin(), end()); } - - template std::basic_string to_string(Allocator const &a) const { - return std::basic_string(begin(), end(), a); - } - -#endif // nssv_CPP11_OR_GREATER - -#endif // nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS - }; - - // - // Non-member functions: - // - - // 24.4.3 Non-member comparison functions: - // lexicographically compare two string views (function template): - - template - nssv_constexpr bool operator==(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) == 0; - } - - template - nssv_constexpr bool operator!=(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) != 0; - } - - template - nssv_constexpr bool operator<(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - template - nssv_constexpr bool operator<=(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - template - nssv_constexpr bool operator>(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - template - nssv_constexpr bool operator>=(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - - // Let S be basic_string_view, and sv be an instance of S. - // Implementations shall provide sufficient additional overloads marked - // constexpr and noexcept so that an object t with an implicit conversion - // to S can be compared according to Table 67. - -#if !nssv_CPP11_OR_GREATER || nssv_BETWEEN(nssv_COMPILER_MSVC_VERSION, 100, 141) - - // accomodate for older compilers: - - // == - - template - nssv_constexpr bool operator==(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) == 0; - } - - template - nssv_constexpr bool operator==(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) == 0; - } - - template - nssv_constexpr bool operator==(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.size() == rhs.size() && lhs.compare(rhs) == 0; - } - - template - nssv_constexpr bool operator==(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return lhs.size() == rhs.size() && lhs.compare(rhs) == 0; - } - - // != - - template - nssv_constexpr bool operator!=(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) != 0; - } - - template - nssv_constexpr bool operator!=(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) != 0; - } - - template - nssv_constexpr bool operator!=(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.size() != rhs.size() && lhs.compare(rhs) != 0; - } - - template - nssv_constexpr bool operator!=(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return lhs.size() != rhs.size() || rhs.compare(lhs) != 0; - } - - // < - - template - nssv_constexpr bool operator<(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - template - nssv_constexpr bool operator<(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) > 0; - } - - template - nssv_constexpr bool operator<(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - template - nssv_constexpr bool operator<(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return rhs.compare(lhs) > 0; - } - - // <= - - template - nssv_constexpr bool operator<=(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - template - nssv_constexpr bool operator<=(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) >= 0; - } - - template - nssv_constexpr bool operator<=(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - template - nssv_constexpr bool operator<=(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return rhs.compare(lhs) >= 0; - } - - // > - - template - nssv_constexpr bool operator>(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - template - nssv_constexpr bool operator>(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) < 0; - } - - template - nssv_constexpr bool operator>(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - template - nssv_constexpr bool operator>(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return rhs.compare(lhs) < 0; - } - - // >= - - template - nssv_constexpr bool operator>=(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - - template - nssv_constexpr bool operator>=(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) <= 0; - } - - template - nssv_constexpr bool operator>=(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - - template - nssv_constexpr bool operator>=(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return rhs.compare(lhs) <= 0; - } - -#else // newer compilers: - -#define nssv_BASIC_STRING_VIEW_I(T, U) typename std::decay>::type - -#if nssv_BETWEEN(nssv_COMPILER_MSVC_VERSION, 140, 150) -#define nssv_MSVC_ORDER(x) , int = x -#else -#define nssv_MSVC_ORDER(x) /*, int=x*/ -#endif - - // == - - template - nssv_constexpr bool operator==(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) == 0; - } - - template - nssv_constexpr bool operator==(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.size() == rhs.size() && lhs.compare(rhs) == 0; - } - - // != - - template - nssv_constexpr bool operator!=(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.size() != rhs.size() || lhs.compare(rhs) != 0; - } - - template - nssv_constexpr bool operator!=(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) != 0; - } - - // < - - template - nssv_constexpr bool operator<(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - template - nssv_constexpr bool operator<(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - // <= - - template - nssv_constexpr bool operator<=(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - template - nssv_constexpr bool operator<=(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - // > - - template - nssv_constexpr bool operator>(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - template - nssv_constexpr bool operator>(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - // >= - - template - nssv_constexpr bool operator>=(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - - template - nssv_constexpr bool operator>=(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - -#undef nssv_MSVC_ORDER -#undef nssv_BASIC_STRING_VIEW_I - -#endif // compiler-dependent approach to comparisons - - // 24.4.4 Inserters and extractors: - - namespace detail { - - template void write_padding(Stream &os, std::streamsize n) { - for (std::streamsize i = 0; i < n; ++i) - os.rdbuf()->sputc(os.fill()); - } - - template Stream &write_to_stream(Stream &os, View const &sv) { - typename Stream::sentry sentry(os); - - if (!os) - return os; - - const std::streamsize length = static_cast(sv.length()); - - // Whether, and how, to pad: - const bool pad = (length < os.width()); - const bool left_pad = pad && (os.flags() & std::ios_base::adjustfield) == std::ios_base::right; - - if (left_pad) - write_padding(os, os.width() - length); - - // Write span characters: - os.rdbuf()->sputn(sv.begin(), length); - - if (pad && !left_pad) - write_padding(os, os.width() - length); - - // Reset output stream width: - os.width(0); - - return os; - } - - } // namespace detail - - template - std::basic_ostream &operator<<(std::basic_ostream &os, - basic_string_view sv) { - return detail::write_to_stream(os, sv); - } - - // Several typedefs for common character types are provided: - - typedef basic_string_view string_view; - typedef basic_string_view wstring_view; -#if nssv_HAVE_WCHAR16_T - typedef basic_string_view u16string_view; - typedef basic_string_view u32string_view; -#endif - - } // namespace sv_lite -} // namespace nonstd::sv_lite - -// -// 24.4.6 Suffix for basic_string_view literals: -// - -#if nssv_HAVE_USER_DEFINED_LITERALS - -namespace nonstd { -nssv_inline_ns namespace literals { - nssv_inline_ns namespace string_view_literals { - -#if nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS - - nssv_constexpr nonstd::sv_lite::string_view operator"" sv(const char *str, size_t len) nssv_noexcept // (1) - { - return nonstd::sv_lite::string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::u16string_view operator"" sv(const char16_t *str, size_t len) nssv_noexcept // (2) - { - return nonstd::sv_lite::u16string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::u32string_view operator"" sv(const char32_t *str, size_t len) nssv_noexcept // (3) - { - return nonstd::sv_lite::u32string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::wstring_view operator"" sv(const wchar_t *str, size_t len) nssv_noexcept // (4) - { - return nonstd::sv_lite::wstring_view {str, len}; - } - -#endif // nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS - -#if nssv_CONFIG_USR_SV_OPERATOR - - nssv_constexpr nonstd::sv_lite::string_view operator"" _sv(const char *str, size_t len) nssv_noexcept // (1) - { - return nonstd::sv_lite::string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::u16string_view operator"" _sv(const char16_t *str, size_t len) nssv_noexcept // (2) - { - return nonstd::sv_lite::u16string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::u32string_view operator"" _sv(const char32_t *str, size_t len) nssv_noexcept // (3) - { - return nonstd::sv_lite::u32string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::wstring_view operator"" _sv(const wchar_t *str, size_t len) nssv_noexcept // (4) - { - return nonstd::sv_lite::wstring_view {str, len}; - } - -#endif // nssv_CONFIG_USR_SV_OPERATOR - } -} -} // namespace nonstd +} // namespace inja +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(INJA_NOEXCEPTION) +#ifndef INJA_THROW +#define INJA_THROW(exception) throw exception #endif - -// -// Extensions for std::string: -// - -#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS - -namespace nonstd { -namespace sv_lite { - -// Exclude MSVC 14 (19.00): it yields ambiguous to_string(): - -#if nssv_CPP11_OR_GREATER && nssv_COMPILER_MSVC_VERSION != 140 - -template > -std::basic_string to_string(basic_string_view v, - Allocator const &a = Allocator()) { - return std::basic_string(v.begin(), v.end(), a); -} - #else - -template std::basic_string to_string(basic_string_view v) { - return std::basic_string(v.begin(), v.end()); -} - -template -std::basic_string to_string(basic_string_view v, Allocator const &a) { - return std::basic_string(v.begin(), v.end(), a); -} - -#endif // nssv_CPP11_OR_GREATER - -template -basic_string_view to_string_view(std::basic_string const &s) { - return basic_string_view(s.data(), s.size()); -} - -} // namespace sv_lite -} // namespace nonstd - -#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS - -// -// make types and algorithms available in namespace nonstd: -// - -namespace nonstd { - -using sv_lite::basic_string_view; -using sv_lite::string_view; -using sv_lite::wstring_view; - -#if nssv_HAVE_WCHAR16_T -using sv_lite::u16string_view; +#include +#ifndef INJA_THROW +#define INJA_THROW(exception) \ + std::abort(); \ + std::ignore = exception #endif -#if nssv_HAVE_WCHAR32_T -using sv_lite::u32string_view; +#ifndef INJA_NOEXCEPTION +#define INJA_NOEXCEPTION #endif - -// literal "sv" - -using sv_lite::operator==; -using sv_lite::operator!=; -using sv_lite::operator<; -using sv_lite::operator<=; -using sv_lite::operator>; -using sv_lite::operator>=; - -using sv_lite::operator<<; - -#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS -using sv_lite::to_string; -using sv_lite::to_string_view; #endif -} // namespace nonstd - -// 24.4.5 Hash support (C++11): +// #include "environment.hpp" +#ifndef INCLUDE_INJA_ENVIRONMENT_HPP_ +#define INCLUDE_INJA_ENVIRONMENT_HPP_ -// Note: The hash value of a string view object is equal to the hash value of -// the corresponding string object. +#include +#include +#include +#include +#include +#include -#if nssv_HAVE_STD_HASH +// #include "config.hpp" +#ifndef INCLUDE_INJA_CONFIG_HPP_ +#define INCLUDE_INJA_CONFIG_HPP_ #include - -namespace std { - -template <> struct hash { -public: - std::size_t operator()(nonstd::string_view v) const nssv_noexcept { - return std::hash()(std::string(v.data(), v.size())); - } -}; - -template <> struct hash { -public: - std::size_t operator()(nonstd::wstring_view v) const nssv_noexcept { - return std::hash()(std::wstring(v.data(), v.size())); - } -}; - -template <> struct hash { -public: - std::size_t operator()(nonstd::u16string_view v) const nssv_noexcept { - return std::hash()(std::u16string(v.data(), v.size())); - } -}; - -template <> struct hash { -public: - std::size_t operator()(nonstd::u32string_view v) const nssv_noexcept { - return std::hash()(std::u32string(v.data(), v.size())); - } -}; - -} // namespace std - -#endif // nssv_HAVE_STD_HASH - -nssv_RESTORE_WARNINGS() - -#endif // nssv_HAVE_STD_STRING_VIEW -#endif // NONSTD_SV_LITE_H_INCLUDED +#include // #include "template.hpp" #ifndef INCLUDE_INJA_TEMPLATE_HPP_ @@ -1498,22 +83,21 @@ nssv_RESTORE_WARNINGS() #define INCLUDE_INJA_NODE_HPP_ #include +#include #include // #include "function_storage.hpp" #ifndef INCLUDE_INJA_FUNCTION_STORAGE_HPP_ #define INCLUDE_INJA_FUNCTION_STORAGE_HPP_ +#include #include -// #include "string_view.hpp" - - namespace inja { -using Arguments = std::vector; -using CallbackFunction = std::function; -using VoidCallbackFunction = std::function; +using Arguments = std::vector; +using CallbackFunction = std::function; +using VoidCallbackFunction = std::function; /*! * \brief Class for builtin functions and user-defined callbacks. @@ -1567,13 +151,11 @@ class FunctionStorage { Super, Join, Callback, - ParenLeft, - ParenRight, None, }; struct FunctionData { - explicit FunctionData(const Operation &op, const CallbackFunction &cb = CallbackFunction{}) : operation(op), callback(cb) {} + explicit FunctionData(const Operation& op, const CallbackFunction& cb = CallbackFunction {}): operation(op), callback(cb) {} const Operation operation; const CallbackFunction callback; }; @@ -1582,52 +164,52 @@ class FunctionStorage { const int VARIADIC {-1}; std::map, FunctionData> function_storage = { - {std::make_pair("at", 2), FunctionData { Operation::At }}, - {std::make_pair("default", 2), FunctionData { Operation::Default }}, - {std::make_pair("divisibleBy", 2), FunctionData { Operation::DivisibleBy }}, - {std::make_pair("even", 1), FunctionData { Operation::Even }}, - {std::make_pair("exists", 1), FunctionData { Operation::Exists }}, - {std::make_pair("existsIn", 2), FunctionData { Operation::ExistsInObject }}, - {std::make_pair("first", 1), FunctionData { Operation::First }}, - {std::make_pair("float", 1), FunctionData { Operation::Float }}, - {std::make_pair("int", 1), FunctionData { Operation::Int }}, - {std::make_pair("isArray", 1), FunctionData { Operation::IsArray }}, - {std::make_pair("isBoolean", 1), FunctionData { Operation::IsBoolean }}, - {std::make_pair("isFloat", 1), FunctionData { Operation::IsFloat }}, - {std::make_pair("isInteger", 1), FunctionData { Operation::IsInteger }}, - {std::make_pair("isNumber", 1), FunctionData { Operation::IsNumber }}, - {std::make_pair("isObject", 1), FunctionData { Operation::IsObject }}, - {std::make_pair("isString", 1), FunctionData { Operation::IsString }}, - {std::make_pair("last", 1), FunctionData { Operation::Last }}, - {std::make_pair("length", 1), FunctionData { Operation::Length }}, - {std::make_pair("lower", 1), FunctionData { Operation::Lower }}, - {std::make_pair("max", 1), FunctionData { Operation::Max }}, - {std::make_pair("min", 1), FunctionData { Operation::Min }}, - {std::make_pair("odd", 1), FunctionData { Operation::Odd }}, - {std::make_pair("range", 1), FunctionData { Operation::Range }}, - {std::make_pair("round", 2), FunctionData { Operation::Round }}, - {std::make_pair("sort", 1), FunctionData { Operation::Sort }}, - {std::make_pair("upper", 1), FunctionData { Operation::Upper }}, - {std::make_pair("super", 0), FunctionData { Operation::Super }}, - {std::make_pair("super", 1), FunctionData { Operation::Super }}, - {std::make_pair("join", 2), FunctionData { Operation::Join }}, + {std::make_pair("at", 2), FunctionData {Operation::At}}, + {std::make_pair("default", 2), FunctionData {Operation::Default}}, + {std::make_pair("divisibleBy", 2), FunctionData {Operation::DivisibleBy}}, + {std::make_pair("even", 1), FunctionData {Operation::Even}}, + {std::make_pair("exists", 1), FunctionData {Operation::Exists}}, + {std::make_pair("existsIn", 2), FunctionData {Operation::ExistsInObject}}, + {std::make_pair("first", 1), FunctionData {Operation::First}}, + {std::make_pair("float", 1), FunctionData {Operation::Float}}, + {std::make_pair("int", 1), FunctionData {Operation::Int}}, + {std::make_pair("isArray", 1), FunctionData {Operation::IsArray}}, + {std::make_pair("isBoolean", 1), FunctionData {Operation::IsBoolean}}, + {std::make_pair("isFloat", 1), FunctionData {Operation::IsFloat}}, + {std::make_pair("isInteger", 1), FunctionData {Operation::IsInteger}}, + {std::make_pair("isNumber", 1), FunctionData {Operation::IsNumber}}, + {std::make_pair("isObject", 1), FunctionData {Operation::IsObject}}, + {std::make_pair("isString", 1), FunctionData {Operation::IsString}}, + {std::make_pair("last", 1), FunctionData {Operation::Last}}, + {std::make_pair("length", 1), FunctionData {Operation::Length}}, + {std::make_pair("lower", 1), FunctionData {Operation::Lower}}, + {std::make_pair("max", 1), FunctionData {Operation::Max}}, + {std::make_pair("min", 1), FunctionData {Operation::Min}}, + {std::make_pair("odd", 1), FunctionData {Operation::Odd}}, + {std::make_pair("range", 1), FunctionData {Operation::Range}}, + {std::make_pair("round", 2), FunctionData {Operation::Round}}, + {std::make_pair("sort", 1), FunctionData {Operation::Sort}}, + {std::make_pair("upper", 1), FunctionData {Operation::Upper}}, + {std::make_pair("super", 0), FunctionData {Operation::Super}}, + {std::make_pair("super", 1), FunctionData {Operation::Super}}, + {std::make_pair("join", 2), FunctionData {Operation::Join}}, }; public: - void add_builtin(nonstd::string_view name, int num_args, Operation op) { - function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData { op }); + void add_builtin(std::string_view name, int num_args, Operation op) { + function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData {op}); } - void add_callback(nonstd::string_view name, int num_args, const CallbackFunction &callback) { - function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData { Operation::Callback, callback }); + void add_callback(std::string_view name, int num_args, const CallbackFunction& callback) { + function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData {Operation::Callback, callback}); } - FunctionData find_function(nonstd::string_view name, int num_args) const { + FunctionData find_function(std::string_view name, int num_args) const { auto it = function_storage.find(std::make_pair(static_cast(name), num_args)); if (it != function_storage.end()) { return it->second; - // Find variadic function + // Find variadic function } else if (num_args > 0) { it = function_storage.find(std::make_pair(static_cast(name), VARIADIC)); if (it != function_storage.end()) { @@ -1635,7 +217,7 @@ class FunctionStorage { } } - return FunctionData { Operation::None }; + return FunctionData {Operation::None}; } }; @@ -1643,8 +225,6 @@ class FunctionStorage { #endif // INCLUDE_INJA_FUNCTION_STORAGE_HPP_ -// #include "string_view.hpp" - // #include "utils.hpp" #ifndef INCLUDE_INJA_UTILS_HPP_ #define INCLUDE_INJA_UTILS_HPP_ @@ -1652,6 +232,7 @@ class FunctionStorage { #include #include #include +#include #include // #include "exceptions.hpp" @@ -1674,67 +255,64 @@ struct InjaError : public std::runtime_error { const SourceLocation location; - explicit InjaError(const std::string &type, const std::string &message) + explicit InjaError(const std::string& type, const std::string& message) : std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message), location({0, 0}) {} - explicit InjaError(const std::string &type, const std::string &message, SourceLocation location) - : std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" + - std::to_string(location.column) + ") " + message), + explicit InjaError(const std::string& type, const std::string& message, SourceLocation location) + : std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" + std::to_string(location.column) + ") " + message), type(type), message(message), location(location) {} }; struct ParserError : public InjaError { - explicit ParserError(const std::string &message, SourceLocation location) : InjaError("parser_error", message, location) {} + explicit ParserError(const std::string& message, SourceLocation location): InjaError("parser_error", message, location) {} }; struct RenderError : public InjaError { - explicit RenderError(const std::string &message, SourceLocation location) : InjaError("render_error", message, location) {} + explicit RenderError(const std::string& message, SourceLocation location): InjaError("render_error", message, location) {} }; struct FileError : public InjaError { - explicit FileError(const std::string &message) : InjaError("file_error", message) {} - explicit FileError(const std::string &message, SourceLocation location) : InjaError("file_error", message, location) {} + explicit FileError(const std::string& message): InjaError("file_error", message) {} + explicit FileError(const std::string& message, SourceLocation location): InjaError("file_error", message, location) {} }; -struct JsonError : public InjaError { - explicit JsonError(const std::string &message, SourceLocation location) : InjaError("json_error", message, location) {} +struct DataError : public InjaError { + explicit DataError(const std::string& message, SourceLocation location): InjaError("data_error", message, location) {} }; } // namespace inja #endif // INCLUDE_INJA_EXCEPTIONS_HPP_ -// #include "string_view.hpp" - namespace inja { namespace string_view { -inline nonstd::string_view slice(nonstd::string_view view, size_t start, size_t end) { +inline std::string_view slice(std::string_view view, size_t start, size_t end) { start = std::min(start, view.size()); end = std::min(std::max(start, end), view.size()); return view.substr(start, end - start); } -inline std::pair split(nonstd::string_view view, char Separator) { +inline std::pair split(std::string_view view, char Separator) { size_t idx = view.find(Separator); - if (idx == nonstd::string_view::npos) { - return std::make_pair(view, nonstd::string_view()); + if (idx == std::string_view::npos) { + return std::make_pair(view, std::string_view()); } - return std::make_pair(slice(view, 0, idx), slice(view, idx + 1, nonstd::string_view::npos)); + return std::make_pair(slice(view, 0, idx), slice(view, idx + 1, std::string_view::npos)); } -inline bool starts_with(nonstd::string_view view, nonstd::string_view prefix) { +inline bool starts_with(std::string_view view, std::string_view prefix) { return (view.size() >= prefix.size() && view.compare(0, prefix.size(), prefix) == 0); } } // namespace string_view -inline SourceLocation get_source_location(nonstd::string_view content, size_t pos) { +inline SourceLocation get_source_location(std::string_view content, size_t pos) { // Get line and offset position (starts at 1:1) auto sliced = string_view::slice(content, 0, pos); std::size_t last_newline = sliced.rfind("\n"); - if (last_newline == nonstd::string_view::npos) { + if (last_newline == std::string_view::npos) { return {1, sliced.length() + 1}; } @@ -1752,14 +330,14 @@ inline SourceLocation get_source_location(nonstd::string_view content, size_t po return {count_lines + 1, sliced.length() - last_newline}; } -inline void replace_substring(std::string& s, const std::string& f, - const std::string& t) -{ - if (f.empty()) return; - for (auto pos = s.find(f); // find first occurrence of f - pos != std::string::npos; // make sure f was found - s.replace(pos, f.size(), t), // replace with t, and - pos = s.find(f, pos + t.size())) // find next occurrence of f +inline void replace_substring(std::string& s, const std::string& f, const std::string& t) { + if (f.empty()) { + return; + } + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f {} } @@ -1768,7 +346,6 @@ inline void replace_substring(std::string& s, const std::string& f, #endif // INCLUDE_INJA_UTILS_HPP_ - namespace inja { class NodeVisitor; @@ -1776,7 +353,7 @@ class BlockNode; class TextNode; class ExpressionNode; class LiteralNode; -class JsonNode; +class DataNode; class FunctionNode; class ExpressionListNode; class StatementNode; @@ -1789,7 +366,6 @@ class ExtendsStatementNode; class BlockStatementNode; class SetStatementNode; - class NodeVisitor { public: virtual ~NodeVisitor() = default; @@ -1798,7 +374,7 @@ class NodeVisitor { virtual void visit(const TextNode& node) = 0; virtual void visit(const ExpressionNode& node) = 0; virtual void visit(const LiteralNode& node) = 0; - virtual void visit(const JsonNode& node) = 0; + virtual void visit(const DataNode& node) = 0; virtual void visit(const FunctionNode& node) = 0; virtual void visit(const ExpressionListNode& node) = 0; virtual void visit(const StatementNode& node) = 0; @@ -1821,16 +397,15 @@ class AstNode { size_t pos; - AstNode(size_t pos) : pos(pos) { } - virtual ~AstNode() { } + AstNode(size_t pos): pos(pos) {} + virtual ~AstNode() {} }; - class BlockNode : public AstNode { public: std::vector> nodes; - explicit BlockNode() : AstNode(0) {} + explicit BlockNode(): AstNode(0) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -1841,7 +416,7 @@ class TextNode : public AstNode { public: const size_t length; - explicit TextNode(size_t pos, size_t length): AstNode(pos), length(length) { } + explicit TextNode(size_t pos, size_t length): AstNode(pos), length(length) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -1850,7 +425,7 @@ class TextNode : public AstNode { class ExpressionNode : public AstNode { public: - explicit ExpressionNode(size_t pos) : AstNode(pos) {} + explicit ExpressionNode(size_t pos): AstNode(pos) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -1861,22 +436,22 @@ class LiteralNode : public ExpressionNode { public: const json value; - explicit LiteralNode(const json& value, size_t pos) : ExpressionNode(pos), value(value) { } + explicit LiteralNode(std::string_view data_text, size_t pos): ExpressionNode(pos), value(json::parse(data_text)) {} void accept(NodeVisitor& v) const { v.visit(*this); } }; -class JsonNode : public ExpressionNode { +class DataNode : public ExpressionNode { public: const std::string name; const json::json_pointer ptr; - static std::string convert_dot_to_json_ptr(nonstd::string_view ptr_name) { + static std::string convert_dot_to_ptr(std::string_view ptr_name) { std::string result; do { - nonstd::string_view part; + std::string_view part; std::tie(part, ptr_name) = string_view::split(ptr_name, '.'); result.push_back('/'); result.append(part.begin(), part.end()); @@ -1884,7 +459,7 @@ class JsonNode : public ExpressionNode { return result; } - explicit JsonNode(nonstd::string_view ptr_name, size_t pos) : ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_json_ptr(ptr_name))) { } + explicit DataNode(std::string_view ptr_name, size_t pos): ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_ptr(ptr_name))) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -1906,102 +481,103 @@ class FunctionNode : public ExpressionNode { Op operation; std::string name; - int number_args; // Should also be negative -> -1 for unknown number + int number_args; // Can also be negative -> -1 for unknown number std::vector> arguments; CallbackFunction callback; - explicit FunctionNode(nonstd::string_view name, size_t pos) : ExpressionNode(pos), precedence(8), associativity(Associativity::Left), operation(Op::Callback), name(name), number_args(1) { } - explicit FunctionNode(Op operation, size_t pos) : ExpressionNode(pos), operation(operation), number_args(1) { + explicit FunctionNode(std::string_view name, size_t pos) + : ExpressionNode(pos), precedence(8), associativity(Associativity::Left), operation(Op::Callback), name(name), number_args(0) {} + explicit FunctionNode(Op operation, size_t pos): ExpressionNode(pos), operation(operation), number_args(1) { switch (operation) { - case Op::Not: { - number_args = 1; - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::And: { - number_args = 2; - precedence = 1; - associativity = Associativity::Left; - } break; - case Op::Or: { - number_args = 2; - precedence = 1; - associativity = Associativity::Left; - } break; - case Op::In: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Equal: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::NotEqual: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Greater: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::GreaterEqual: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Less: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::LessEqual: { - number_args = 2; - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Add: { - number_args = 2; - precedence = 3; - associativity = Associativity::Left; - } break; - case Op::Subtract: { - number_args = 2; - precedence = 3; - associativity = Associativity::Left; - } break; - case Op::Multiplication: { - number_args = 2; - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::Division: { - number_args = 2; - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::Power: { - number_args = 2; - precedence = 5; - associativity = Associativity::Right; - } break; - case Op::Modulo: { - number_args = 2; - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::AtId: { - number_args = 2; - precedence = 8; - associativity = Associativity::Left; - } break; - default: { - precedence = 1; - associativity = Associativity::Left; - } + case Op::Not: { + number_args = 1; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::And: { + number_args = 2; + precedence = 1; + associativity = Associativity::Left; + } break; + case Op::Or: { + number_args = 2; + precedence = 1; + associativity = Associativity::Left; + } break; + case Op::In: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Equal: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::NotEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Greater: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::GreaterEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Less: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::LessEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Add: { + number_args = 2; + precedence = 3; + associativity = Associativity::Left; + } break; + case Op::Subtract: { + number_args = 2; + precedence = 3; + associativity = Associativity::Left; + } break; + case Op::Multiplication: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::Division: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::Power: { + number_args = 2; + precedence = 5; + associativity = Associativity::Right; + } break; + case Op::Modulo: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::AtId: { + number_args = 2; + precedence = 8; + associativity = Associativity::Left; + } break; + default: { + precedence = 1; + associativity = Associativity::Left; + } } } @@ -2014,8 +590,8 @@ class ExpressionListNode : public AstNode { public: std::shared_ptr root; - explicit ExpressionListNode() : AstNode(0) { } - explicit ExpressionListNode(size_t pos) : AstNode(pos) { } + explicit ExpressionListNode(): AstNode(0) {} + explicit ExpressionListNode(size_t pos): AstNode(pos) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -2024,7 +600,7 @@ class ExpressionListNode : public AstNode { class StatementNode : public AstNode { public: - StatementNode(size_t pos) : AstNode(pos) { } + StatementNode(size_t pos): AstNode(pos) {} virtual void accept(NodeVisitor& v) const = 0; }; @@ -2033,9 +609,9 @@ class ForStatementNode : public StatementNode { public: ExpressionListNode condition; BlockNode body; - BlockNode *const parent; + BlockNode* const parent; - ForStatementNode(BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent) { } + ForStatementNode(BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent) {} virtual void accept(NodeVisitor& v) const = 0; }; @@ -2044,7 +620,7 @@ class ForArrayStatementNode : public ForStatementNode { public: const std::string value; - explicit ForArrayStatementNode(const std::string& value, BlockNode *const parent, size_t pos) : ForStatementNode(parent, pos), value(value) { } + explicit ForArrayStatementNode(const std::string& value, BlockNode* const parent, size_t pos): ForStatementNode(parent, pos), value(value) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -2056,7 +632,8 @@ class ForObjectStatementNode : public ForStatementNode { const std::string key; const std::string value; - explicit ForObjectStatementNode(const std::string& key, const std::string& value, BlockNode *const parent, size_t pos) : ForStatementNode(parent, pos), key(key), value(value) { } + explicit ForObjectStatementNode(const std::string& key, const std::string& value, BlockNode* const parent, size_t pos) + : ForStatementNode(parent, pos), key(key), value(value) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -2068,13 +645,13 @@ class IfStatementNode : public StatementNode { ExpressionListNode condition; BlockNode true_statement; BlockNode false_statement; - BlockNode *const parent; + BlockNode* const parent; const bool is_nested; bool has_false_statement {false}; - explicit IfStatementNode(BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent), is_nested(false) { } - explicit IfStatementNode(bool is_nested, BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent), is_nested(is_nested) { } + explicit IfStatementNode(BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent), is_nested(false) {} + explicit IfStatementNode(bool is_nested, BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent), is_nested(is_nested) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -2085,7 +662,7 @@ class IncludeStatementNode : public StatementNode { public: const std::string file; - explicit IncludeStatementNode(const std::string& file, size_t pos) : StatementNode(pos), file(file) { } + explicit IncludeStatementNode(const std::string& file, size_t pos): StatementNode(pos), file(file) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -2096,24 +673,24 @@ class ExtendsStatementNode : public StatementNode { public: const std::string file; - explicit ExtendsStatementNode(const std::string& file, size_t pos) : StatementNode(pos), file(file) { } + explicit ExtendsStatementNode(const std::string& file, size_t pos): StatementNode(pos), file(file) {} void accept(NodeVisitor& v) const { v.visit(*this); - }; + } }; class BlockStatementNode : public StatementNode { public: const std::string name; BlockNode block; - BlockNode *const parent; + BlockNode* const parent; - explicit BlockStatementNode(BlockNode *const parent, const std::string& name, size_t pos) : StatementNode(pos), name(name), parent(parent) { } + explicit BlockStatementNode(BlockNode* const parent, const std::string& name, size_t pos): StatementNode(pos), name(name), parent(parent) {} void accept(NodeVisitor& v) const { v.visit(*this); - }; + } }; class SetStatementNode : public StatementNode { @@ -2121,7 +698,7 @@ class SetStatementNode : public StatementNode { const std::string key; ExpressionListNode expression; - explicit SetStatementNode(const std::string& key, size_t pos) : StatementNode(pos), key(key) { } + explicit SetStatementNode(const std::string& key, size_t pos): StatementNode(pos), key(key) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -2139,7 +716,6 @@ class SetStatementNode : public StatementNode { // #include "node.hpp" - namespace inja { /*! @@ -2152,11 +728,11 @@ class StatisticsVisitor : public NodeVisitor { } } - void visit(const TextNode&) { } - void visit(const ExpressionNode&) { } - void visit(const LiteralNode&) { } + void visit(const TextNode&) {} + void visit(const ExpressionNode&) {} + void visit(const LiteralNode&) {} - void visit(const JsonNode&) { + void visit(const DataNode&) { variable_counter += 1; } @@ -2170,8 +746,8 @@ class StatisticsVisitor : public NodeVisitor { node.root->accept(*this); } - void visit(const StatementNode&) { } - void visit(const ForStatementNode&) { } + void visit(const StatementNode&) {} + void visit(const ForStatementNode&) {} void visit(const ForArrayStatementNode& node) { node.condition.accept(*this); @@ -2189,20 +765,20 @@ class StatisticsVisitor : public NodeVisitor { node.false_statement.accept(*this); } - void visit(const IncludeStatementNode&) { } + void visit(const IncludeStatementNode&) {} - void visit(const ExtendsStatementNode&) { } + void visit(const ExtendsStatementNode&) {} void visit(const BlockStatementNode& node) { node.block.accept(*this); } - void visit(const SetStatementNode&) { } + void visit(const SetStatementNode&) {} public: unsigned int variable_counter; - explicit StatisticsVisitor() : variable_counter(0) { } + explicit StatisticsVisitor(): variable_counter(0) {} }; } // namespace inja @@ -2210,7 +786,6 @@ class StatisticsVisitor : public NodeVisitor { #endif // INCLUDE_INJA_STATISTICS_HPP_ - namespace inja { /*! @@ -2221,8 +796,8 @@ struct Template { std::string content; std::map> block_storage; - explicit Template() { } - explicit Template(const std::string& content): content(content) { } + explicit Template() {} + explicit Template(const std::string& content): content(content) {} /// Return number of variables (total number, not distinct ones) in the template int count_variables() { @@ -2323,7 +898,6 @@ struct RenderConfig { #include #include #include -#include #include // #include "config.hpp" @@ -2346,9 +920,7 @@ struct RenderConfig { #define INCLUDE_INJA_TOKEN_HPP_ #include - -// #include "string_view.hpp" - +#include namespace inja { @@ -2393,12 +965,12 @@ struct Token { Unknown, Eof, }; - + Kind kind {Kind::Unknown}; - nonstd::string_view text; + std::string_view text; explicit constexpr Token() = default; - explicit constexpr Token(Kind kind, nonstd::string_view text) : kind(kind), text(text) {} + explicit constexpr Token(Kind kind, std::string_view text): kind(kind), text(text) {} std::string describe() const { switch (kind) { @@ -2448,16 +1020,15 @@ class Lexer { Number, }; - const LexerConfig &config; + const LexerConfig& config; State state; MinusState minus_state; - nonstd::string_view m_in; + std::string_view m_in; size_t tok_start; size_t pos; - - Token scan_body(nonstd::string_view close, Token::Kind closeKind, nonstd::string_view close_trim = nonstd::string_view(), bool trim = false) { + Token scan_body(std::string_view close, Token::Kind closeKind, std::string_view close_trim = std::string_view(), bool trim = false) { again: // skip whitespace (except for \n as it might be a close) if (tok_start >= m_in.size()) { @@ -2611,7 +1182,7 @@ class Lexer { } const char ch = m_in[pos]; // be very permissive in lexer (we'll catch errors when conversion happens) - if (!std::isdigit(ch) && ch != '.' && ch != 'e' && ch != 'E' && ch != '+' && ch != '-') { + if (!(std::isdigit(ch) || ch == '.' || ch == 'e' || ch == 'E' || (ch == '+' && (pos == 0 || m_in[pos-1] == 'e' || m_in[pos-1] == 'E')) || (ch == '-' && (pos == 0 || m_in[pos-1] == 'e' || m_in[pos-1] == 'E')))) { break; } pos += 1; @@ -2627,7 +1198,7 @@ class Lexer { } const char ch = m_in[pos++]; if (ch == '\\') { - escape = true; + escape = !escape; } else if (!escape && ch == m_in[tok_start]) { break; } else { @@ -2637,7 +1208,9 @@ class Lexer { return make_token(Token::Kind::String); } - Token make_token(Token::Kind kind) const { return Token(kind, string_view::slice(m_in, tok_start, pos)); } + Token make_token(Token::Kind kind) const { + return Token(kind, string_view::slice(m_in, tok_start, pos)); + } void skip_whitespaces_and_newlines() { if (pos < m_in.size()) { @@ -2667,8 +1240,8 @@ class Lexer { } } - static nonstd::string_view clear_final_line_if_whitespace(nonstd::string_view text) { - nonstd::string_view result = text; + static std::string_view clear_final_line_if_whitespace(std::string_view text) { + std::string_view result = text; while (!result.empty()) { const char ch = result.back(); if (ch == ' ' || ch == '\t') { @@ -2683,13 +1256,13 @@ class Lexer { } public: - explicit Lexer(const LexerConfig &config) : config(config), state(State::Text), minus_state(MinusState::Number) {} + explicit Lexer(const LexerConfig& config): config(config), state(State::Text), minus_state(MinusState::Number) {} SourceLocation current_position() const { return get_source_location(m_in, tok_start); } - void start(nonstd::string_view input) { + void start(std::string_view input) { m_in = input; tok_start = 0; pos = 0; @@ -2715,7 +1288,7 @@ class Lexer { case State::Text: { // fast-scan to first open character const size_t open_start = m_in.substr(pos).find_first_of(config.open_chars); - if (open_start == nonstd::string_view::npos) { + if (open_start == std::string_view::npos) { // didn't find open, return remaining text as text token pos = m_in.size(); return make_token(Token::Kind::Text); @@ -2723,7 +1296,7 @@ class Lexer { pos += open_start; // try to match one of the opening sequences, and get the close - nonstd::string_view open_str = m_in.substr(pos); + std::string_view open_str = m_in.substr(pos); bool must_lstrip = false; if (inja::string_view::starts_with(open_str, config.expression_open)) { if (inja::string_view::starts_with(open_str, config.expression_open_force_lstrip)) { @@ -2735,7 +1308,7 @@ class Lexer { } else if (inja::string_view::starts_with(open_str, config.statement_open)) { if (inja::string_view::starts_with(open_str, config.statement_open_no_lstrip)) { state = State::StatementStartNoLstrip; - } else if (inja::string_view::starts_with(open_str, config.statement_open_force_lstrip )) { + } else if (inja::string_view::starts_with(open_str, config.statement_open_force_lstrip)) { state = State::StatementStartForceLstrip; must_lstrip = true; } else { @@ -2757,7 +1330,7 @@ class Lexer { goto again; } - nonstd::string_view text = string_view::slice(m_in, tok_start, pos); + std::string_view text = string_view::slice(m_in, tok_start, pos); if (must_lstrip) { text = clear_final_line_if_whitespace(text); } @@ -2816,7 +1389,7 @@ class Lexer { case State::CommentBody: { // fast-scan to comment close const size_t end = m_in.substr(pos).find(config.comment_close); - if (end == nonstd::string_view::npos) { + if (end == std::string_view::npos) { pos = m_in.size(); return make_token(Token::Kind::Eof); } @@ -2837,7 +1410,7 @@ class Lexer { } } - const LexerConfig &get_config() const { + const LexerConfig& get_config() const { return config; } }; @@ -2855,39 +1428,34 @@ class Lexer { // #include "utils.hpp" - namespace inja { /*! * \brief Class for parsing an inja Template. */ class Parser { - const ParserConfig &config; + using Arguments = std::vector>; + using OperatorStack = std::stack>; + + const ParserConfig& config; Lexer lexer; - TemplateStorage &template_storage; - const FunctionStorage &function_storage; + TemplateStorage& template_storage; + const FunctionStorage& function_storage; Token tok, peek_tok; bool have_peek_tok {false}; - size_t current_paren_level {0}; - size_t current_bracket_level {0}; - size_t current_brace_level {0}; + std::string_view literal_start; - nonstd::string_view json_literal_start; + BlockNode* current_block {nullptr}; + ExpressionListNode* current_expression_list {nullptr}; - BlockNode *current_block {nullptr}; - ExpressionListNode *current_expression_list {nullptr}; - std::stack> function_stack; - std::vector> arguments; - - std::stack> operator_stack; std::stack if_statement_stack; std::stack for_statement_stack; std::stack block_statement_stack; - inline void throw_parser_error(const std::string &message) { + inline void throw_parser_error(const std::string& message) const { INJA_THROW(ParserError(message, lexer.current_position())); } @@ -2907,15 +1475,19 @@ class Parser { } } - inline void add_json_literal(const char* content_ptr) { - nonstd::string_view json_text(json_literal_start.data(), tok.text.data() - json_literal_start.data() + tok.text.size()); - arguments.emplace_back(std::make_shared(json::parse(json_text), json_text.data() - content_ptr)); + inline void add_literal(Arguments &arguments, const char* content_ptr) { + std::string_view data_text(literal_start.data(), tok.text.data() - literal_start.data() + tok.text.size()); + arguments.emplace_back(std::make_shared(data_text, data_text.data() - content_ptr)); } - inline void add_operator() { + inline void add_operator(Arguments &arguments, OperatorStack &operator_stack) { auto function = operator_stack.top(); operator_stack.pop(); + if (static_cast(arguments.size()) < function->number_args) { + throw_parser_error("too few arguments"); + } + for (int i = 0; i < function->number_args; ++i) { function->arguments.insert(function->arguments.begin(), arguments.back()); arguments.pop_back(); @@ -2923,7 +1495,7 @@ class Parser { arguments.emplace_back(function); } - void add_to_template_storage(nonstd::string_view path, std::string& template_name) { + void add_to_template_storage(std::string_view path, std::string& template_name) { if (template_storage.find(template_name) != template_storage.end()) { return; } @@ -2949,7 +1521,6 @@ class Parser { template_storage.emplace(template_name, include_template); parse_into_template(template_storage[template_name], template_name); return; - } else if (!config.include_callback) { INJA_THROW(FileError("failed accessing file at '" + template_name + "'")); } @@ -2963,37 +1534,56 @@ class Parser { } } - bool parse_expression(Template &tmpl, Token::Kind closing) { - while (tok.kind != closing && tok.kind != Token::Kind::Eof) { + std::string parse_filename() const { + if (tok.kind != Token::Kind::String) { + throw_parser_error("expected string, got '" + tok.describe() + "'"); + } + + if (tok.text.length() < 2) { + throw_parser_error("expected filename, got '" + static_cast(tok.text) + "'"); + } + + // Remove first and last character "" + return std::string {tok.text.substr(1, tok.text.length() - 2)}; + } + + bool parse_expression(Template& tmpl, Token::Kind closing) { + current_expression_list->root = parse_expression(tmpl); + return tok.kind == closing; + } + + std::shared_ptr parse_expression(Template& tmpl) { + size_t current_bracket_level {0}; + size_t current_brace_level {0}; + Arguments arguments; + OperatorStack operator_stack; + + while (tok.kind != Token::Kind::Eof) { // Literals switch (tok.kind) { case Token::Kind::String: { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; - add_json_literal(tmpl.content.c_str()); + literal_start = tok.text; + add_literal(arguments, tmpl.content.c_str()); } - } break; case Token::Kind::Number: { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; - add_json_literal(tmpl.content.c_str()); + literal_start = tok.text; + add_literal(arguments, tmpl.content.c_str()); } - } break; case Token::Kind::LeftBracket: { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; + literal_start = tok.text; } current_bracket_level += 1; - } break; case Token::Kind::LeftBrace: { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; + literal_start = tok.text; } current_brace_level += 1; - } break; case Token::Kind::RightBracket: { if (current_bracket_level == 0) { @@ -3002,9 +1592,8 @@ class Parser { current_bracket_level -= 1; if (current_brace_level == 0 && current_bracket_level == 0) { - add_json_literal(tmpl.content.c_str()); + add_literal(arguments, tmpl.content.c_str()); } - } break; case Token::Kind::RightBrace: { if (current_brace_level == 0) { @@ -3013,35 +1602,57 @@ class Parser { current_brace_level -= 1; if (current_brace_level == 0 && current_bracket_level == 0) { - add_json_literal(tmpl.content.c_str()); + add_literal(arguments, tmpl.content.c_str()); } - } break; case Token::Kind::Id: { get_peek_token(); - // Json Literal - if (tok.text == static_cast("true") || tok.text == static_cast("false") || tok.text == static_cast("null")) { + // Data Literal + if (tok.text == static_cast("true") || tok.text == static_cast("false") || + tok.text == static_cast("null")) { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; - add_json_literal(tmpl.content.c_str()); + literal_start = tok.text; + add_literal(arguments, tmpl.content.c_str()); } - // Operator + // Operator } else if (tok.text == "and" || tok.text == "or" || tok.text == "in" || tok.text == "not") { goto parse_operator; - // Functions + // Functions } else if (peek_tok.kind == Token::Kind::LeftParen) { - operator_stack.emplace(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); - function_stack.emplace(operator_stack.top().get(), current_paren_level); + auto func = std::make_shared(tok.text, tok.text.data() - tmpl.content.c_str()); + get_next_token(); + do { + get_next_token(); + auto expr = parse_expression(tmpl); + if (!expr) { + break; + } + func->number_args += 1; + func->arguments.emplace_back(expr); + } while (tok.kind == Token::Kind::Comma); + if (tok.kind != Token::Kind::RightParen) { + throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'"); + } + + auto function_data = function_storage.find_function(func->name, func->number_args); + if (function_data.operation == FunctionStorage::Operation::None) { + throw_parser_error("unknown function " + func->name); + } + func->operation = function_data.operation; + if (function_data.operation == FunctionStorage::Operation::Callback) { + func->callback = function_data.callback; + } + arguments.emplace_back(func); - // Variables + // Variables } else { - arguments.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); + arguments.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); } - // Operators + // Operators } break; case Token::Kind::Equal: case Token::Kind::NotEqual: @@ -3057,7 +1668,7 @@ class Parser { case Token::Kind::Percent: case Token::Kind::Dot: { - parse_operator: + parse_operator: FunctionStorage::Operation operation; switch (tok.kind) { case Token::Kind::Id: { @@ -3118,93 +1729,58 @@ class Parser { } auto function_node = std::make_shared(operation, tok.text.data() - tmpl.content.c_str()); - while (!operator_stack.empty() && ((operator_stack.top()->precedence > function_node->precedence) || (operator_stack.top()->precedence == function_node->precedence && function_node->associativity == FunctionNode::Associativity::Left)) && (operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft)) { - add_operator(); + while (!operator_stack.empty() && + ((operator_stack.top()->precedence > function_node->precedence) || + (operator_stack.top()->precedence == function_node->precedence && function_node->associativity == FunctionNode::Associativity::Left))) { + add_operator(arguments, operator_stack); } operator_stack.emplace(function_node); - } break; case Token::Kind::Comma: { if (current_brace_level == 0 && current_bracket_level == 0) { - if (function_stack.empty()) { - throw_parser_error("unexpected ','"); - } - - function_stack.top().first->number_args += 1; + goto break_loop; } - } break; case Token::Kind::Colon: { if (current_brace_level == 0 && current_bracket_level == 0) { throw_parser_error("unexpected ':'"); } - } break; case Token::Kind::LeftParen: { - current_paren_level += 1; - operator_stack.emplace(std::make_shared(FunctionStorage::Operation::ParenLeft, tok.text.data() - tmpl.content.c_str())); - - get_peek_token(); - if (peek_tok.kind == Token::Kind::RightParen) { - if (!function_stack.empty() && function_stack.top().second == current_paren_level - 1) { - function_stack.top().first->number_args = 0; - } - } - - } break; - case Token::Kind::RightParen: { - current_paren_level -= 1; - while (!operator_stack.empty() && operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft) { - add_operator(); - } - - if (!operator_stack.empty() && operator_stack.top()->operation == FunctionStorage::Operation::ParenLeft) { - operator_stack.pop(); + get_next_token(); + auto expr = parse_expression(tmpl); + if (tok.kind != Token::Kind::RightParen) { + throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'"); } - - if (!function_stack.empty() && function_stack.top().second == current_paren_level) { - auto func = function_stack.top().first; - auto function_data = function_storage.find_function(func->name, func->number_args); - if (function_data.operation == FunctionStorage::Operation::None) { - throw_parser_error("unknown function " + func->name); - } - func->operation = function_data.operation; - if (function_data.operation == FunctionStorage::Operation::Callback) { - func->callback = function_data.callback; - } - - if (operator_stack.empty()) { - throw_parser_error("internal error at function " + func->name); - } - - add_operator(); - function_stack.pop(); + if (!expr) { + throw_parser_error("empty expression in parentheses"); } - } + arguments.emplace_back(expr); + } break; default: - break; + goto break_loop; } get_next_token(); } + break_loop: while (!operator_stack.empty()) { - add_operator(); + add_operator(arguments, operator_stack); } + std::shared_ptr expr; if (arguments.size() == 1) { - current_expression_list->root = arguments[0]; + expr = arguments[0]; arguments = {}; - } else if (arguments.size() > 1) { throw_parser_error("malformed expression"); } - - return true; + return expr; } - bool parse_statement(Template &tmpl, Token::Kind closing, nonstd::string_view path) { + bool parse_statement(Template& tmpl, Token::Kind closing, std::string_view path) { if (tok.kind != Token::Kind::Id) { return false; } @@ -3221,12 +1797,11 @@ class Parser { if (!parse_expression(tmpl, closing)) { return false; } - } else if (tok.text == static_cast("else")) { if (if_statement_stack.empty()) { throw_parser_error("else without matching if"); } - auto &if_statement_data = if_statement_stack.top(); + auto& if_statement_data = if_statement_stack.top(); get_next_token(); if_statement_data->has_false_statement = true; @@ -3246,7 +1821,6 @@ class Parser { return false; } } - } else if (tok.text == static_cast("endif")) { if (if_statement_stack.empty()) { throw_parser_error("endif without matching if"); @@ -3257,12 +1831,11 @@ class Parser { if_statement_stack.pop(); } - auto &if_statement_data = if_statement_stack.top(); + auto& if_statement_data = if_statement_stack.top(); get_next_token(); current_block = if_statement_data->parent; if_statement_stack.pop(); - } else if (tok.text == static_cast("block")) { get_next_token(); @@ -3282,18 +1855,16 @@ class Parser { } get_next_token(); - } else if (tok.text == static_cast("endblock")) { if (block_statement_stack.empty()) { throw_parser_error("endblock without matching block"); } - auto &block_statement_data = block_statement_stack.top(); + auto& block_statement_data = block_statement_stack.top(); get_next_token(); current_block = block_statement_data->parent; block_statement_stack.pop(); - } else if (tok.text == static_cast("for")) { get_next_token(); @@ -3317,11 +1888,13 @@ class Parser { value_token = tok; get_next_token(); - for_statement_node = std::make_shared(static_cast(key_token.text), static_cast(value_token.text), current_block, tok.text.data() - tmpl.content.c_str()); + for_statement_node = std::make_shared(static_cast(key_token.text), static_cast(value_token.text), + current_block, tok.text.data() - tmpl.content.c_str()); - // Array type + // Array type } else { - for_statement_node = std::make_shared(static_cast(value_token.text), current_block, tok.text.data() - tmpl.content.c_str()); + for_statement_node = + std::make_shared(static_cast(value_token.text), current_block, tok.text.data() - tmpl.content.c_str()); } current_block->nodes.emplace_back(for_statement_node); @@ -3337,46 +1910,34 @@ class Parser { if (!parse_expression(tmpl, closing)) { return false; } - } else if (tok.text == static_cast("endfor")) { if (for_statement_stack.empty()) { throw_parser_error("endfor without matching for"); } - auto &for_statement_data = for_statement_stack.top(); + auto& for_statement_data = for_statement_stack.top(); get_next_token(); current_block = for_statement_data->parent; for_statement_stack.pop(); - } else if (tok.text == static_cast("include")) { get_next_token(); - if (tok.kind != Token::Kind::String) { - throw_parser_error("expected string, got '" + tok.describe() + "'"); - } - - std::string template_name = json::parse(tok.text).get_ref(); + std::string template_name = parse_filename(); add_to_template_storage(path, template_name); current_block->nodes.emplace_back(std::make_shared(template_name, tok.text.data() - tmpl.content.c_str())); get_next_token(); - } else if (tok.text == static_cast("extends")) { get_next_token(); - if (tok.kind != Token::Kind::String) { - throw_parser_error("expected string, got '" + tok.describe() + "'"); - } - - std::string template_name = json::parse(tok.text).get_ref(); + std::string template_name = parse_filename(); add_to_template_storage(path, template_name); current_block->nodes.emplace_back(std::make_shared(template_name, tok.text.data() - tmpl.content.c_str())); get_next_token(); - } else if (tok.text == static_cast("set")) { get_next_token(); @@ -3399,14 +1960,13 @@ class Parser { if (!parse_expression(tmpl, closing)) { return false; } - } else { return false; } return true; } - void parse_into(Template &tmpl, nonstd::string_view path) { + void parse_into(Template& tmpl, std::string_view path) { lexer.start(tmpl.content); current_block = &tmpl.root; @@ -3420,7 +1980,8 @@ class Parser { if (!for_statement_stack.empty()) { throw_parser_error("unmatched for"); } - } return; + } + return; case Token::Kind::Text: { current_block->nodes.emplace_back(std::make_shared(tok.text.data() - tmpl.content.c_str(), tok.text.size())); } break; @@ -3450,10 +2011,6 @@ class Parser { current_expression_list = expression_list_node.get(); if (!parse_expression(tmpl, Token::Kind::ExpressionClose)) { - throw_parser_error("expected expression, got '" + tok.describe() + "'"); - } - - if (tok.kind != Token::Kind::ExpressionClose) { throw_parser_error("expected expression close, got '" + tok.describe() + "'"); } } break; @@ -3470,31 +2027,26 @@ class Parser { } } - public: - explicit Parser(const ParserConfig &parser_config, const LexerConfig &lexer_config, - TemplateStorage &template_storage, const FunctionStorage &function_storage) - : config(parser_config), lexer(lexer_config), template_storage(template_storage), function_storage(function_storage) { } + explicit Parser(const ParserConfig& parser_config, const LexerConfig& lexer_config, TemplateStorage& template_storage, + const FunctionStorage& function_storage) + : config(parser_config), lexer(lexer_config), template_storage(template_storage), function_storage(function_storage) {} - Template parse(nonstd::string_view input, nonstd::string_view path) { + Template parse(std::string_view input, std::string_view path) { auto result = Template(static_cast(input)); parse_into(result, path); return result; } - Template parse(nonstd::string_view input) { - return parse(input, "./"); - } - - void parse_into_template(Template& tmpl, nonstd::string_view filename) { - nonstd::string_view path = filename.substr(0, filename.find_last_of("/\\") + 1); + void parse_into_template(Template& tmpl, std::string_view filename) { + std::string_view path = filename.substr(0, filename.find_last_of("/\\") + 1); // StringRef path = sys::path::parent_path(filename); auto sub_parser = Parser(config, lexer.get_config(), template_storage, function_storage); sub_parser.parse_into(tmpl, path); } - std::string load_file(const std::string& filename) { + static std::string load_file(const std::string& filename) { std::ifstream file; file.open(filename); if (file.fail()) { @@ -3535,31 +2087,31 @@ namespace inja { /*! * \brief Class for rendering a Template with data. */ -class Renderer : public NodeVisitor { +class Renderer : public NodeVisitor { using Op = FunctionStorage::Operation; const RenderConfig config; - const TemplateStorage &template_storage; - const FunctionStorage &function_storage; + const TemplateStorage& template_storage; + const FunctionStorage& function_storage; - const Template *current_template; + const Template* current_template; size_t current_level {0}; std::vector template_stack; std::vector block_statement_stack; - const json *json_input; - std::ostream *output_stream; + const json* data_input; + std::ostream* output_stream; - json json_additional_data; - json* current_loop_data = &json_additional_data["loop"]; + json additional_data; + json* current_loop_data = &additional_data["loop"]; - std::vector> json_tmp_stack; - std::stack json_eval_stack; - std::stack not_found_stack; + std::vector> data_tmp_stack; + std::stack data_eval_stack; + std::stack not_found_stack; bool break_rendering {false}; - bool truthy(const json* data) const { + static bool truthy(const json* data) { if (data->is_boolean()) { return data->get(); } else if (data->is_number()) { @@ -3570,9 +2122,11 @@ class Renderer : public NodeVisitor { return !data->empty(); } - void print_json(const std::shared_ptr value) { + void print_data(const std::shared_ptr value) { if (value->is_string()) { *output_stream << value->get_ref(); + } else if (value->is_number_unsigned()) { + *output_stream << value->get(); } else if (value->is_number_integer()) { *output_stream << value->get(); } else if (value->is_null()) { @@ -3588,14 +2142,14 @@ class Renderer : public NodeVisitor { expression_list.root->accept(*this); - if (json_eval_stack.empty()) { + if (data_eval_stack.empty()) { throw_renderer_error("empty expression", expression_list); - } else if (json_eval_stack.size() != 1) { + } else if (data_eval_stack.size() != 1) { throw_renderer_error("malformed expression", expression_list); } - const auto result = json_eval_stack.top(); - json_eval_stack.pop(); + const auto result = data_eval_stack.top(); + data_eval_stack.pop(); if (!result) { if (not_found_stack.empty()) { @@ -3610,13 +2164,18 @@ class Renderer : public NodeVisitor { return std::make_shared(*result); } - void throw_renderer_error(const std::string &message, const AstNode& node) { + void throw_renderer_error(const std::string& message, const AstNode& node) { SourceLocation loc = get_source_location(current_template->content, node.pos); INJA_THROW(RenderError(message, loc)); } - template - std::array get_arguments(const FunctionNode& node) { + void make_result(const json&& result) { + auto result_ptr = std::make_shared(result); + data_tmp_stack.push_back(result_ptr); + data_eval_stack.push(result_ptr.get()); + } + + template std::array get_arguments(const FunctionNode& node) { if (node.arguments.size() < N_start + N) { throw_renderer_error("function needs " + std::to_string(N_start + N) + " variables, but has only found " + std::to_string(node.arguments.size()), node); } @@ -3625,49 +2184,48 @@ class Renderer : public NodeVisitor { node.arguments[i]->accept(*this); } - if (json_eval_stack.size() < N) { - throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node); + if (data_eval_stack.size() < N) { + throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node); } std::array result; for (size_t i = 0; i < N; i += 1) { - result[N - i - 1] = json_eval_stack.top(); - json_eval_stack.pop(); + result[N - i - 1] = data_eval_stack.top(); + data_eval_stack.pop(); if (!result[N - i - 1]) { - const auto json_node = not_found_stack.top(); + const auto data_node = not_found_stack.top(); not_found_stack.pop(); if (throw_not_found) { - throw_renderer_error("variable '" + static_cast(json_node->name) + "' not found", *json_node); + throw_renderer_error("variable '" + static_cast(data_node->name) + "' not found", *data_node); } } } return result; } - template - Arguments get_argument_vector(const FunctionNode& node) { + template Arguments get_argument_vector(const FunctionNode& node) { const size_t N = node.arguments.size(); - for (auto a: node.arguments) { + for (auto a : node.arguments) { a->accept(*this); } - if (json_eval_stack.size() < N) { - throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node); + if (data_eval_stack.size() < N) { + throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node); } Arguments result {N}; for (size_t i = 0; i < N; i += 1) { - result[N - i - 1] = json_eval_stack.top(); - json_eval_stack.pop(); + result[N - i - 1] = data_eval_stack.top(); + data_eval_stack.pop(); if (!result[N - i - 1]) { - const auto json_node = not_found_stack.top(); + const auto data_node = not_found_stack.top(); not_found_stack.pop(); if (throw_not_found) { - throw_renderer_error("variable '" + static_cast(json_node->name) + "' not found", *json_node); + throw_renderer_error("variable '" + static_cast(data_node->name) + "' not found", *data_node); } } } @@ -3688,160 +2246,118 @@ class Renderer : public NodeVisitor { output_stream->write(current_template->content.c_str() + node.pos, node.length); } - void visit(const ExpressionNode&) { } + void visit(const ExpressionNode&) {} void visit(const LiteralNode& node) { - json_eval_stack.push(&node.value); + data_eval_stack.push(&node.value); } - void visit(const JsonNode& node) { - if (json_additional_data.contains(node.ptr)) { - json_eval_stack.push(&(json_additional_data[node.ptr])); - - } else if (json_input->contains(node.ptr)) { - json_eval_stack.push(&(*json_input)[node.ptr]); - + void visit(const DataNode& node) { + if (additional_data.contains(node.ptr)) { + data_eval_stack.push(&(additional_data[node.ptr])); + } else if (data_input->contains(node.ptr)) { + data_eval_stack.push(&(*data_input)[node.ptr]); } else { // Try to evaluate as a no-argument callback const auto function_data = function_storage.find_function(node.name, 0); if (function_data.operation == FunctionStorage::Operation::Callback) { Arguments empty_args {}; const auto value = std::make_shared(function_data.callback(empty_args)); - json_tmp_stack.push_back(value); - json_eval_stack.push(value.get()); - + data_tmp_stack.push_back(value); + data_eval_stack.push(value.get()); } else { - json_eval_stack.push(nullptr); + data_eval_stack.push(nullptr); not_found_stack.emplace(&node); } } } void visit(const FunctionNode& node) { - std::shared_ptr result_ptr; - switch (node.operation) { case Op::Not: { const auto args = get_arguments<1>(node); - result_ptr = std::make_shared(!truthy(args[0])); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(!truthy(args[0])); } break; case Op::And: { - result_ptr = std::make_shared(truthy(get_arguments<1, 0>(node)[0]) && truthy(get_arguments<1, 1>(node)[0])); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(truthy(get_arguments<1, 0>(node)[0]) && truthy(get_arguments<1, 1>(node)[0])); } break; case Op::Or: { - result_ptr = std::make_shared(truthy(get_arguments<1, 0>(node)[0]) || truthy(get_arguments<1, 1>(node)[0])); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(truthy(get_arguments<1, 0>(node)[0]) || truthy(get_arguments<1, 1>(node)[0])); } break; case Op::In: { const auto args = get_arguments<2>(node); - result_ptr = std::make_shared(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end()); } break; case Op::Equal: { const auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] == *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(*args[0] == *args[1]); } break; case Op::NotEqual: { const auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] != *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(*args[0] != *args[1]); } break; case Op::Greater: { const auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] > *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(*args[0] > *args[1]); } break; case Op::GreaterEqual: { const auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] >= *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(*args[0] >= *args[1]); } break; case Op::Less: { const auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] < *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(*args[0] < *args[1]); } break; case Op::LessEqual: { const auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] <= *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(*args[0] <= *args[1]); } break; case Op::Add: { const auto args = get_arguments<2>(node); if (args[0]->is_string() && args[1]->is_string()) { - result_ptr = std::make_shared(args[0]->get_ref() + args[1]->get_ref()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get_ref() + args[1]->get_ref()); } else if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - result_ptr = std::make_shared(args[0]->get() + args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() + args[1]->get()); } else { - result_ptr = std::make_shared(args[0]->get() + args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() + args[1]->get()); } - json_eval_stack.push(result_ptr.get()); } break; case Op::Subtract: { const auto args = get_arguments<2>(node); if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - result_ptr = std::make_shared(args[0]->get() - args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() - args[1]->get()); } else { - result_ptr = std::make_shared(args[0]->get() - args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() - args[1]->get()); } - json_eval_stack.push(result_ptr.get()); } break; case Op::Multiplication: { const auto args = get_arguments<2>(node); if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - result_ptr = std::make_shared(args[0]->get() * args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() * args[1]->get()); } else { - result_ptr = std::make_shared(args[0]->get() * args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() * args[1]->get()); } - json_eval_stack.push(result_ptr.get()); } break; case Op::Division: { const auto args = get_arguments<2>(node); - if (args[1]->get() == 0) { + if (args[1]->get() == 0) { throw_renderer_error("division by zero", node); } - result_ptr = std::make_shared(args[0]->get() / args[1]->get()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(args[0]->get() / args[1]->get()); } break; case Op::Power: { const auto args = get_arguments<2>(node); - if (args[0]->is_number_integer() && args[1]->get() >= 0) { - int result = static_cast(std::pow(args[0]->get(), args[1]->get())); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); + if (args[0]->is_number_integer() && args[1]->get() >= 0) { + const auto result = static_cast(std::pow(args[0]->get(), args[1]->get())); + make_result(result); } else { - double result = std::pow(args[0]->get(), args[1]->get()); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); + const auto result = std::pow(args[0]->get(), args[1]->get()); + make_result(result); } - json_eval_stack.push(result_ptr.get()); } break; case Op::Modulo: { const auto args = get_arguments<2>(node); - result_ptr = std::make_shared(args[0]->get() % args[1]->get()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(args[0]->get() % args[1]->get()); } break; case Op::AtId: { const auto container = get_arguments<1, 0, false>(node)[0]; @@ -3851,168 +2367,128 @@ class Renderer : public NodeVisitor { } const auto id_node = not_found_stack.top(); not_found_stack.pop(); - json_eval_stack.pop(); - json_eval_stack.push(&container->at(id_node->name)); + data_eval_stack.pop(); + data_eval_stack.push(&container->at(id_node->name)); } break; case Op::At: { const auto args = get_arguments<2>(node); if (args[0]->is_object()) { - json_eval_stack.push(&args[0]->at(args[1]->get())); + data_eval_stack.push(&args[0]->at(args[1]->get())); } else { - json_eval_stack.push(&args[0]->at(args[1]->get())); + data_eval_stack.push(&args[0]->at(args[1]->get())); } } break; case Op::Default: { const auto test_arg = get_arguments<1, 0, false>(node)[0]; - json_eval_stack.push(test_arg ? test_arg : get_arguments<1, 1>(node)[0]); + data_eval_stack.push(test_arg ? test_arg : get_arguments<1, 1>(node)[0]); } break; case Op::DivisibleBy: { const auto args = get_arguments<2>(node); - const int divisor = args[1]->get(); - result_ptr = std::make_shared((divisor != 0) && (args[0]->get() % divisor == 0)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto divisor = args[1]->get(); + make_result((divisor != 0) && (args[0]->get() % divisor == 0)); } break; case Op::Even: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->get() % 2 == 0); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->get() % 2 == 0); } break; case Op::Exists: { - auto &&name = get_arguments<1>(node)[0]->get_ref(); - result_ptr = std::make_shared(json_input->contains(json::json_pointer(JsonNode::convert_dot_to_json_ptr(name)))); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + auto&& name = get_arguments<1>(node)[0]->get_ref(); + make_result(data_input->contains(json::json_pointer(DataNode::convert_dot_to_ptr(name)))); } break; case Op::ExistsInObject: { const auto args = get_arguments<2>(node); - auto &&name = args[1]->get_ref(); - result_ptr = std::make_shared(args[0]->find(name) != args[0]->end()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + auto&& name = args[1]->get_ref(); + make_result(args[0]->find(name) != args[0]->end()); } break; case Op::First: { const auto result = &get_arguments<1>(node)[0]->front(); - json_eval_stack.push(result); + data_eval_stack.push(result); } break; case Op::Float: { - result_ptr = std::make_shared(std::stod(get_arguments<1>(node)[0]->get_ref())); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::stod(get_arguments<1>(node)[0]->get_ref())); } break; case Op::Int: { - result_ptr = std::make_shared(std::stoi(get_arguments<1>(node)[0]->get_ref())); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::stoi(get_arguments<1>(node)[0]->get_ref())); } break; case Op::Last: { const auto result = &get_arguments<1>(node)[0]->back(); - json_eval_stack.push(result); + data_eval_stack.push(result); } break; case Op::Length: { const auto val = get_arguments<1>(node)[0]; if (val->is_string()) { - result_ptr = std::make_shared(val->get_ref().length()); + make_result(val->get_ref().length()); } else { - result_ptr = std::make_shared(val->size()); + make_result(val->size()); } - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); } break; case Op::Lower: { - std::string result = get_arguments<1>(node)[0]->get(); - std::transform(result.begin(), result.end(), result.begin(), ::tolower); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + auto result = get_arguments<1>(node)[0]->get(); + std::transform(result.begin(), result.end(), result.begin(), [](char c) { return static_cast(::tolower(c)); }); + make_result(std::move(result)); } break; case Op::Max: { const auto args = get_arguments<1>(node); const auto result = std::max_element(args[0]->begin(), args[0]->end()); - json_eval_stack.push(&(*result)); + data_eval_stack.push(&(*result)); } break; case Op::Min: { const auto args = get_arguments<1>(node); const auto result = std::min_element(args[0]->begin(), args[0]->end()); - json_eval_stack.push(&(*result)); + data_eval_stack.push(&(*result)); } break; case Op::Odd: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->get() % 2 != 0); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->get() % 2 != 0); } break; case Op::Range: { - std::vector result(get_arguments<1>(node)[0]->get()); + std::vector result(get_arguments<1>(node)[0]->get()); std::iota(result.begin(), result.end(), 0); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::move(result)); } break; case Op::Round: { const auto args = get_arguments<2>(node); - const int precision = args[1]->get(); - const double result = std::round(args[0]->get() * std::pow(10.0, precision)) / std::pow(10.0, precision); - if(0==precision){ - result_ptr = std::make_shared(int(result)); - }else{ - result_ptr = std::make_shared(std::move(result)); + const auto precision = args[1]->get(); + const double result = std::round(args[0]->get() * std::pow(10.0, precision)) / std::pow(10.0, precision); + if (precision == 0) { + make_result(int(result)); + } else { + make_result(result); } - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); } break; case Op::Sort: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->get>()); + auto result_ptr = std::make_shared(get_arguments<1>(node)[0]->get>()); std::sort(result_ptr->begin(), result_ptr->end()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + data_tmp_stack.push_back(result_ptr); + data_eval_stack.push(result_ptr.get()); } break; case Op::Upper: { - std::string result = get_arguments<1>(node)[0]->get(); - std::transform(result.begin(), result.end(), result.begin(), ::toupper); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + auto result = get_arguments<1>(node)[0]->get(); + std::transform(result.begin(), result.end(), result.begin(), [](char c) { return static_cast(::toupper(c)); }); + make_result(std::move(result)); } break; case Op::IsBoolean: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_boolean()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_boolean()); } break; case Op::IsNumber: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_number()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_number()); } break; case Op::IsInteger: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_number_integer()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_number_integer()); } break; case Op::IsFloat: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_number_float()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_number_float()); } break; case Op::IsObject: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_object()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_object()); } break; case Op::IsArray: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_array()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_array()); } break; case Op::IsString: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_string()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_string()); } break; case Op::Callback: { auto args = get_argument_vector(node); - result_ptr = std::make_shared(node.callback(args)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(node.callback(args)); } break; case Op::Super: { const auto args = get_argument_vector(node); @@ -4029,8 +2505,8 @@ class Renderer : public NodeVisitor { } const auto current_block_statement = block_statement_stack.back(); - const Template *new_template = template_stack.at(level); - const Template *old_template = current_template; + const Template* new_template = template_stack.at(level); + const Template* old_template = current_template; const auto block_it = new_template->block_storage.find(current_block_statement->name); if (block_it != new_template->block_storage.end()) { current_template = new_template; @@ -4041,13 +2517,11 @@ class Renderer : public NodeVisitor { } else { throw_renderer_error("could not find block with name '" + current_block_statement->name + "'", node); } - result_ptr = std::make_shared(nullptr); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(nullptr); } break; case Op::Join: { const auto args = get_arguments<2>(node); - const auto separator = args[1]->get(); + const auto separator = args[1]->get(); std::ostringstream os; std::string sep; for (const auto& value : *args[0]) { @@ -4055,28 +2529,24 @@ class Renderer : public NodeVisitor { if (value.is_string()) { os << value.get(); // otherwise the value is surrounded with "" } else { - os << value; + os << value.dump(); } sep = separator; } - result_ptr = std::make_shared(os.str()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(os.str()); } break; - case Op::ParenLeft: - case Op::ParenRight: case Op::None: break; } } void visit(const ExpressionListNode& node) { - print_json(eval_expression_list(node)); + print_data(eval_expression_list(node)); } - void visit(const StatementNode&) { } + void visit(const StatementNode&) {} - void visit(const ForStatementNode&) { } + void visit(const ForStatementNode&) {} void visit(const ForArrayStatementNode& node) { const auto result = eval_expression_list(node.condition); @@ -4093,7 +2563,7 @@ class Renderer : public NodeVisitor { (*current_loop_data)["is_first"] = true; (*current_loop_data)["is_last"] = (result->size() <= 1); for (auto it = result->begin(); it != result->end(); ++it) { - json_additional_data[static_cast(node.value)] = *it; + additional_data[static_cast(node.value)] = *it; (*current_loop_data)["index"] = index; (*current_loop_data)["index1"] = index + 1; @@ -4108,12 +2578,12 @@ class Renderer : public NodeVisitor { ++index; } - json_additional_data[static_cast(node.value)].clear(); + additional_data[static_cast(node.value)].clear(); if (!(*current_loop_data)["parent"].empty()) { const auto tmp = (*current_loop_data)["parent"]; *current_loop_data = std::move(tmp); } else { - current_loop_data = &json_additional_data["loop"]; + current_loop_data = &additional_data["loop"]; } } @@ -4131,8 +2601,8 @@ class Renderer : public NodeVisitor { (*current_loop_data)["is_first"] = true; (*current_loop_data)["is_last"] = (result->size() <= 1); for (auto it = result->begin(); it != result->end(); ++it) { - json_additional_data[static_cast(node.key)] = it.key(); - json_additional_data[static_cast(node.value)] = it.value(); + additional_data[static_cast(node.key)] = it.key(); + additional_data[static_cast(node.value)] = it.value(); (*current_loop_data)["index"] = index; (*current_loop_data)["index1"] = index + 1; @@ -4147,12 +2617,12 @@ class Renderer : public NodeVisitor { ++index; } - json_additional_data[static_cast(node.key)].clear(); - json_additional_data[static_cast(node.value)].clear(); + additional_data[static_cast(node.key)].clear(); + additional_data[static_cast(node.value)].clear(); if (!(*current_loop_data)["parent"].empty()) { *current_loop_data = std::move((*current_loop_data)["parent"]); } else { - current_loop_data = &json_additional_data["loop"]; + current_loop_data = &additional_data["loop"]; } } @@ -4169,7 +2639,7 @@ class Renderer : public NodeVisitor { auto sub_renderer = Renderer(config, template_storage, function_storage); const auto included_template_it = template_storage.find(node.file); if (included_template_it != template_storage.end()) { - sub_renderer.render_to(*output_stream, included_template_it->second, *json_input, &json_additional_data); + sub_renderer.render_to(*output_stream, included_template_it->second, *data_input, &additional_data); } else if (config.throw_at_missing_includes) { throw_renderer_error("include '" + node.file + "' not found", node); } @@ -4178,8 +2648,8 @@ class Renderer : public NodeVisitor { void visit(const ExtendsStatementNode& node) { const auto included_template_it = template_storage.find(node.file); if (included_template_it != template_storage.end()) { - const Template *parent_template = &included_template_it->second; - render_to(*output_stream, *parent_template, *json_input, &json_additional_data); + const Template* parent_template = &included_template_it->second; + render_to(*output_stream, *parent_template, *data_input, &additional_data); break_rendering = true; } else if (config.throw_at_missing_includes) { throw_renderer_error("extends '" + node.file + "' not found", node); @@ -4194,7 +2664,7 @@ class Renderer : public NodeVisitor { if (block_it != current_template->block_storage.end()) { block_statement_stack.emplace_back(&node); block_it->second->block.accept(*this); - block_statement_stack.pop_back(); + block_statement_stack.pop_back(); } current_level = old_level; current_template = template_stack.back(); @@ -4204,26 +2674,26 @@ class Renderer : public NodeVisitor { std::string ptr = node.key; replace_substring(ptr, ".", "/"); ptr = "/" + ptr; - json_additional_data[json::json_pointer(ptr)] = *eval_expression_list(node.expression); + additional_data[json::json_pointer(ptr)] = *eval_expression_list(node.expression); } public: - Renderer(const RenderConfig& config, const TemplateStorage &template_storage, const FunctionStorage &function_storage) - : config(config), template_storage(template_storage), function_storage(function_storage) { } + Renderer(const RenderConfig& config, const TemplateStorage& template_storage, const FunctionStorage& function_storage) + : config(config), template_storage(template_storage), function_storage(function_storage) {} - void render_to(std::ostream &os, const Template &tmpl, const json &data, json *loop_data = nullptr) { + void render_to(std::ostream& os, const Template& tmpl, const json& data, json* loop_data = nullptr) { output_stream = &os; current_template = &tmpl; - json_input = &data; + data_input = &data; if (loop_data) { - json_additional_data = *loop_data; - current_loop_data = &json_additional_data["loop"]; + additional_data = *loop_data; + current_loop_data = &additional_data["loop"]; } template_stack.emplace_back(current_template); current_template->root.accept(*this); - json_tmp_stack.clear(); + data_tmp_stack.clear(); } }; @@ -4231,8 +2701,6 @@ class Renderer : public NodeVisitor { #endif // INCLUDE_INJA_RENDERER_HPP_ -// #include "string_view.hpp" - // #include "template.hpp" // #include "utils.hpp" @@ -4244,9 +2712,6 @@ namespace inja { * \brief Class for changing the configuration. */ class Environment { - std::string input_path; - std::string output_path; - LexerConfig lexer_config; ParserConfig parser_config; RenderConfig render_config; @@ -4254,16 +2719,19 @@ class Environment { FunctionStorage function_storage; TemplateStorage template_storage; +protected: + std::string input_path; + std::string output_path; + public: - Environment() : Environment("") {} + Environment(): Environment("") {} - explicit Environment(const std::string &global_path) : input_path(global_path), output_path(global_path) {} + explicit Environment(const std::string& global_path): input_path(global_path), output_path(global_path) {} - Environment(const std::string &input_path, const std::string &output_path) - : input_path(input_path), output_path(output_path) {} + Environment(const std::string& input_path, const std::string& output_path): input_path(input_path), output_path(output_path) {} /// Sets the opener and closer for template statements - void set_statement(const std::string &open, const std::string &close) { + void set_statement(const std::string& open, const std::string& close) { lexer_config.statement_open = open; lexer_config.statement_open_no_lstrip = open + "+"; lexer_config.statement_open_force_lstrip = open + "-"; @@ -4273,13 +2741,13 @@ class Environment { } /// Sets the opener for template line statements - void set_line_statement(const std::string &open) { + void set_line_statement(const std::string& open) { lexer_config.line_statement = open; lexer_config.update_open_chars(); } /// Sets the opener and closer for template expressions - void set_expression(const std::string &open, const std::string &close) { + void set_expression(const std::string& open, const std::string& close) { lexer_config.expression_open = open; lexer_config.expression_open_force_lstrip = open + "-"; lexer_config.expression_close = close; @@ -4288,7 +2756,7 @@ class Environment { } /// Sets the opener and closer for template comments - void set_comment(const std::string &open, const std::string &close) { + void set_comment(const std::string& open, const std::string& close) { lexer_config.comment_open = open; lexer_config.comment_open_force_lstrip = open + "-"; lexer_config.comment_close = close; @@ -4316,116 +2784,119 @@ class Environment { render_config.throw_at_missing_includes = will_throw; } - Template parse(nonstd::string_view input) { + Template parse(std::string_view input) { Parser parser(parser_config, lexer_config, template_storage, function_storage); - return parser.parse(input); + return parser.parse(input, input_path); } - Template parse_template(const std::string &filename) { + Template parse_template(const std::string& filename) { Parser parser(parser_config, lexer_config, template_storage, function_storage); - auto result = Template(parser.load_file(input_path + static_cast(filename))); + auto result = Template(Parser::load_file(input_path + static_cast(filename))); parser.parse_into_template(result, input_path + static_cast(filename)); return result; } - Template parse_file(const std::string &filename) { + Template parse_file(const std::string& filename) { return parse_template(filename); } - std::string render(nonstd::string_view input, const json &data) { return render(parse(input), data); } + std::string render(std::string_view input, const json& data) { + return render(parse(input), data); + } - std::string render(const Template &tmpl, const json &data) { + std::string render(const Template& tmpl, const json& data) { std::stringstream os; render_to(os, tmpl, data); return os.str(); } - std::string render_file(const std::string &filename, const json &data) { + std::string render_file(const std::string& filename, const json& data) { return render(parse_template(filename), data); } - std::string render_file_with_json_file(const std::string &filename, const std::string &filename_data) { + std::string render_file_with_json_file(const std::string& filename, const std::string& filename_data) { const json data = load_json(filename_data); return render_file(filename, data); } - void write(const std::string &filename, const json &data, const std::string &filename_out) { + void write(const std::string& filename, const json& data, const std::string& filename_out) { std::ofstream file(output_path + filename_out); file << render_file(filename, data); file.close(); } - void write(const Template &temp, const json &data, const std::string &filename_out) { + void write(const Template& temp, const json& data, const std::string& filename_out) { std::ofstream file(output_path + filename_out); file << render(temp, data); file.close(); } - void write_with_json_file(const std::string &filename, const std::string &filename_data, - const std::string &filename_out) { + void write_with_json_file(const std::string& filename, const std::string& filename_data, const std::string& filename_out) { const json data = load_json(filename_data); write(filename, data, filename_out); } - void write_with_json_file(const Template &temp, const std::string &filename_data, const std::string &filename_out) { + void write_with_json_file(const Template& temp, const std::string& filename_data, const std::string& filename_out) { const json data = load_json(filename_data); write(temp, data, filename_out); } - std::ostream &render_to(std::ostream &os, const Template &tmpl, const json &data) { + std::ostream& render_to(std::ostream& os, const Template& tmpl, const json& data) { Renderer(render_config, template_storage, function_storage).render_to(os, tmpl, data); return os; } - std::string load_file(const std::string &filename) { + std::string load_file(const std::string& filename) { Parser parser(parser_config, lexer_config, template_storage, function_storage); - return parser.load_file(input_path + filename); + return Parser::load_file(input_path + filename); } - json load_json(const std::string &filename) { + json load_json(const std::string& filename) { std::ifstream file; file.open(input_path + filename); if (file.fail()) { INJA_THROW(FileError("failed accessing file at '" + input_path + filename + "'")); } - json j; - file >> j; - return j; + + return json::parse(std::istreambuf_iterator(file), std::istreambuf_iterator()); } /*! @brief Adds a variadic callback */ - void add_callback(const std::string &name, const CallbackFunction &callback) { + void add_callback(const std::string& name, const CallbackFunction& callback) { add_callback(name, -1, callback); } /*! @brief Adds a variadic void callback */ - void add_void_callback(const std::string &name, const VoidCallbackFunction &callback) { + void add_void_callback(const std::string& name, const VoidCallbackFunction& callback) { add_void_callback(name, -1, callback); } /*! @brief Adds a callback with given number or arguments */ - void add_callback(const std::string &name, int num_args, const CallbackFunction &callback) { + void add_callback(const std::string& name, int num_args, const CallbackFunction& callback) { function_storage.add_callback(name, num_args, callback); } /*! @brief Adds a void callback with given number or arguments */ - void add_void_callback(const std::string &name, int num_args, const VoidCallbackFunction &callback) { - function_storage.add_callback(name, num_args, [callback](Arguments& args) { callback(args); return json(); }); + void add_void_callback(const std::string& name, int num_args, const VoidCallbackFunction& callback) { + function_storage.add_callback(name, num_args, [callback](Arguments& args) { + callback(args); + return json(); + }); } /** Includes a template with a given name into the environment. * Then, a template can be rendered in another template using the * include "" syntax. */ - void include_template(const std::string &name, const Template &tmpl) { + void include_template(const std::string& name, const Template& tmpl) { template_storage[name] = tmpl; } @@ -4440,14 +2911,14 @@ class Environment { /*! @brief render with default settings to a string */ -inline std::string render(nonstd::string_view input, const json &data) { +inline std::string render(std::string_view input, const json& data) { return Environment().render(input, data); } /*! @brief render with default settings to the given output stream */ -inline void render_to(std::ostream &os, nonstd::string_view input, const json &data) { +inline void render_to(std::ostream& os, std::string_view input, const json& data) { Environment env; env.render_to(os, env.parse(input), data); } @@ -4462,9 +2933,7 @@ inline void render_to(std::ostream &os, nonstd::string_view input, const json &d // #include "renderer.hpp" -// #include "string_view.hpp" - // #include "template.hpp" -#endif // INCLUDE_INJA_INJA_HPP_ \ No newline at end of file +#endif // INCLUDE_INJA_INJA_HPP_ diff --git a/include/jpcre2.hpp b/include/jpcre2.hpp index 073627562..5adaa9024 100644 --- a/include/jpcre2.hpp +++ b/include/jpcre2.hpp @@ -76,8 +76,14 @@ #ifndef JPCRE2_USE_FUNCTION_POINTER_CALLBACK #include // std::function #endif +#endif +#if __cplusplus >= 201703L || _MSVC_LANG >= 201703L + #define JPCRE2_USE_MINIMUM_CXX_17 1 + #include #else - #define JPCRE2_USE_MINIMUM_CXX_11 0 + #ifdef JPCRE2_UNSET_CAPTURES_NULL + #error JPCRE2_UNSET_CAPTURES_NULL requires C++17 + #endif #endif #define JPCRE2_UNUSED(x) ((void)(x)) @@ -194,7 +200,7 @@ static inline void _jvassert(bool cond, char const * name, const char* f, size_t static inline std::string _tostdstring(unsigned x){ char buf[128]; - int written = std::sprintf(buf, "%u", x); + int written = std::snprintf(buf, 128, "%u", x); return (written > 0) ? std::string(buf, buf + written) : std::string(); } @@ -1197,7 +1203,7 @@ template<> inline std::basic_string MSG::INVALID_MODIFIER(){ return template<> inline std::basic_string MSG::INVALID_MODIFIER(){ return L"Invalid modifier: "; } template<> inline std::basic_string MSG::INSUFFICIENT_OVECTOR(){ return "ovector wasn't big enough"; } template<> inline std::basic_string MSG::INSUFFICIENT_OVECTOR(){ return L"ovector wasn't big enough"; } -#if JPCRE2_USE_MINIMUM_CXX_11 +#ifdef JPCRE2_USE_MINIMUM_CXX_11 template<> inline std::basic_string MSG::INVALID_MODIFIER(){ return u"Invalid modifier: "; } template<> inline std::basic_string MSG::INVALID_MODIFIER(){ return U"Invalid modifier: "; } template<> inline std::basic_string MSG::INSUFFICIENT_OVECTOR(){ return u"ovector wasn't big enough"; } @@ -1230,7 +1236,7 @@ template<> inline std::basic_string MSG::INSUFFICIENT_OVECTO ///```cpp ///typedef jpcre2::select jp; ///``` -#if JPCRE2_USE_MINIMUM_CXX_11 +#ifdef JPCRE2_USE_MINIMUM_CXX_11 template class Map=std::map> #else template @@ -1252,7 +1258,7 @@ struct select{ ///char32_t | std::u32string (>=C++11) typedef typename std::basic_string String; - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 ///Map for Named substrings. typedef class Map MapNas; ///Substring name to Substring number map. @@ -1268,7 +1274,11 @@ struct select{ typedef MapNtN MapNtn; ///Vector for Numbered substrings (Sub container). + #ifdef JPCRE2_UNSET_CAPTURES_NULL + typedef typename std::vector> NumSub; + #else typedef typename std::vector NumSub; + #endif ///Vector of matches with named substrings. typedef typename std::vector VecNas; ///Vector of substring name to substring number map. @@ -1449,7 +1459,7 @@ struct select{ onlyCopy(rm); } - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 void deepMove(RegexMatch& rm){ m_subject = std::move_if_noexcept(rm.m_subject); onlyCopy(rm); @@ -1498,7 +1508,7 @@ struct select{ return *this; } - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 ///@overload ///... ///Move constructor. @@ -1579,7 +1589,7 @@ struct select{ /// Returns the last error message ///@return Last error message virtual String getErrorMessage() const { - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 return select::getErrorMessage(error_number, error_offset); #else return select::getErrorMessage(error_number, error_offset); @@ -2044,14 +2054,18 @@ struct select{ ///@param ntn jp::MapNtN map. ///@return total match (group 0) of current match. static String fill(NumSub const &num, MapNas const &nas, MapNtn const &ntn){ + #ifdef JPCRE2_UNSET_CAPTURES_NULL + return *num[0]; + #else return num[0]; + #endif } private: //prevent object instantiation. callback(); callback(callback const &); - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 callback(callback&&); #endif ~callback(); @@ -2219,7 +2233,7 @@ struct select{ onlyCopy(me); } - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 void deepMove(MatchEvaluator& me){ vec_num = std::move_if_noexcept(me.vec_num); vec_nas = std::move_if_noexcept(me.vec_nas); @@ -2391,7 +2405,7 @@ struct select{ return *this; } - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 ///@overload /// ... @@ -2957,7 +2971,7 @@ struct select{ onlyCopy(rr); } - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 void deepMove(RegexReplace& rr){ r_subject = std::move_if_noexcept(rr.r_subject); onlyCopy(rr); @@ -3005,7 +3019,7 @@ struct select{ return *this; } - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 ///@overload ///... @@ -3080,7 +3094,7 @@ struct select{ /// Returns the last error message ///@return Last error message String getErrorMessage() const { - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 return select::getErrorMessage(error_number, error_offset); #else return select::getErrorMessage(error_number, error_offset); @@ -3619,7 +3633,7 @@ struct select{ : freeRegexMemory(); } - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 void deepMove(Regex& r) { pat_str = std::move_if_noexcept(r.pat_str); @@ -3740,7 +3754,7 @@ struct select{ } - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 /// @overload @@ -3895,6 +3909,15 @@ struct select{ return pat_str_ptr; } + ///Get number of captures from compiled code. + ///@return New line option value or 0. + Uint getNumCaptures() { + if(!code) return 0; + Uint numCaptures = 0; + int ret = Pcre2Func::pattern_info(code, PCRE2_INFO_CAPTURECOUNT, &numCaptures); + if(ret < 0) error_number = ret; + return numCaptures; + } /// Calculate modifier string from PCRE2 and JPCRE2 options and return it. /// @@ -3944,7 +3967,7 @@ struct select{ /// Returns the last error message ///@return Last error message String getErrorMessage() const { - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 return select::getErrorMessage(error_number, error_offset); #else return select::getErrorMessage(error_number, error_offset); @@ -4388,7 +4411,7 @@ struct select{ //prevent object instantiation of select class select(); select(select const &); - #if JPCRE2_USE_MINIMUM_CXX_11 + #ifdef JPCRE2_USE_MINIMUM_CXX_11 select(select&&); #endif ~select(); @@ -4417,7 +4440,7 @@ inline void jpcre2::ModifierTable::parseModifierTable(std::string& tabjs, VecOpt } -#if JPCRE2_USE_MINIMUM_CXX_11 +#ifdef JPCRE2_USE_MINIMUM_CXX_11 template class Map> void jpcre2::select::Regex::compile() { #else @@ -4458,7 +4481,7 @@ void jpcre2::select::Regex::compile() { } -#if JPCRE2_USE_MINIMUM_CXX_11 +#ifdef JPCRE2_USE_MINIMUM_CXX_11 template class Map> typename jpcre2::select::String jpcre2::select::MatchEvaluator::replace(bool do_match, Uint replace_opts, SIZE_T * counter) { #else @@ -4488,7 +4511,7 @@ typename jpcre2::select::String jpcre2::select::MatchEvaluator:: SIZE_T last = vec_eoff.size(); last = (last>0)?last-1:0; JPCRE2_ASSERT(vec_eoff[last] <= RegexMatch::getSubject().size(), "ValueError: subject string is not of the required size, may be it's changed!!!\ - If you are using esisting match data, try a new match."); + If you are using existing match data, try a new match."); //loop through the matches for(SIZE_T i=0;i::String jpcre2::select::MatchEvaluator:: //second part ///the matched part is the subject //~ Pcre2Sptr subject = (Pcre2Sptr) RegexMatch::getSubjectPointer()->c_str(); - //substr(vec_soff[i], vec_eoff[i] - vec_soff[i]).c_str();//->substr(vec_soff[i], vec_eoff[i]-vec_soff[i]); + //substr(vec_soff[i], vec_eoff[i] - vec_soff[i]).c_str(); Pcre2Sptr subject = r_subject_ptr + vec_soff[i]; PCRE2_SIZE subject_length = vec_eoff[i] - vec_soff[i]; @@ -4586,7 +4609,7 @@ typename jpcre2::select::String jpcre2::select::MatchEvaluator:: } -#if JPCRE2_USE_MINIMUM_CXX_11 +#ifdef JPCRE2_USE_MINIMUM_CXX_11 template class Map> typename jpcre2::select::String jpcre2::select::MatchEvaluator::nreplace(bool do_match, Uint jo, SIZE_T* counter){ #else @@ -4605,7 +4628,7 @@ typename jpcre2::select::String jpcre2::select::MatchEvaluator:: SIZE_T last = vec_eoff.size(); last = (last>0)?last-1:0; JPCRE2_ASSERT(vec_eoff[last] <= RegexMatch::getSubject().size(), "ValueError: subject string is not of the required size, may be it's changed!!!\ - If you are using esisting match data, try a new match."); + If you are using existing match data, try a new match."); //loop through the matches for(SIZE_T i=0;i::String jpcre2::select::MatchEvaluator:: } -#if JPCRE2_USE_MINIMUM_CXX_11 +#ifdef JPCRE2_USE_MINIMUM_CXX_11 template class Map> typename jpcre2::select::String jpcre2::select::RegexReplace::replace() { #else @@ -4713,7 +4736,7 @@ typename jpcre2::select::String jpcre2::select::RegexReplace::re } -#if JPCRE2_USE_MINIMUM_CXX_11 +#ifdef JPCRE2_USE_MINIMUM_CXX_11 template class Map> bool jpcre2::select::RegexMatch::getNumberedSubstrings(int rc, Pcre2Sptr subject, PCRE2_SIZE* ovector, uint32_t ovector_count) { #else @@ -4724,16 +4747,22 @@ bool jpcre2::select::RegexMatch::getNumberedSubstrings(int rc, Pcre2Sptr uint32_t rcu = rc; num_sub.reserve(rcu); //we know exactly how many elements it will have. uint32_t i; - for (i = 0u; i < rcu; i++) - num_sub.push_back(String((Char*)(subject + ovector[2*i]), ovector[2*i+1] - ovector[2*i])); - for (uint32_t j = i; j < ovector_count; j++) - num_sub.push_back(String()); + for (i = 0u; i < ovector_count; i++) { + if (ovector[2*i] != PCRE2_UNSET) + num_sub.push_back(String((Char*)(subject + ovector[2*i]), ovector[2*i+1] - ovector[2*i])); + else + #ifdef JPCRE2_UNSET_CAPTURES_NULL + num_sub.push_back(std::nullopt); + #else + num_sub.push_back(String()); + #endif + } vec_num->push_back(num_sub); //this function shouldn't be called if this vector is null return true; } -#if JPCRE2_USE_MINIMUM_CXX_11 +#ifdef JPCRE2_USE_MINIMUM_CXX_11 template class Map> bool jpcre2::select::RegexMatch::getNamedSubstrings(int namecount, int name_entry_size, Pcre2Sptr name_table, @@ -4771,7 +4800,7 @@ bool jpcre2::select::RegexMatch::getNamedSubstrings(int namecount, int n } -#if JPCRE2_USE_MINIMUM_CXX_11 +#ifdef JPCRE2_USE_MINIMUM_CXX_11 template class Map> jpcre2::SIZE_T jpcre2::select::RegexMatch::match() { #else @@ -5113,6 +5142,11 @@ jpcre2::SIZE_T jpcre2::select::RegexMatch::match() { ///Using the standard `NDEBUG` macro will have the same effect, ///but it is recommended that you use `JPCRE2_NDEBUG` to strip out debug codes specifically for this library. + +///@def JPCRE2_UNSET_CAPTURES_NULL +///Define to change the type of NumSub so that captures are recorded +///with std::optional. It is undefined by default. This feature requires C++17. + #endif diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 8959265da..4d1a37ad7 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1,39 +1,23 @@ -/* - __ _____ _____ _____ - __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 3.10.2 -|_____|_____|_____|_|___| https://github.com/nlohmann/json - -Licensed under the MIT License . -SPDX-License-Identifier: MIT -Copyright (c) 2013-2019 Niels Lohmann . - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + +/****************************************************************************\ + * Note on documentation: The source files contain links to the online * + * documentation of the public API at https://json.nlohmann.me. This URL * + * contains the most recent documentation and should also be applicable to * + * previous versions; documentation for deprecated functions is not * + * removed, but marked deprecated. See "Generate documentation" section in * + * file docs/README.md. * +\****************************************************************************/ #ifndef INCLUDE_NLOHMANN_JSON_HPP_ #define INCLUDE_NLOHMANN_JSON_HPP_ -#define NLOHMANN_JSON_VERSION_MAJOR 3 -#define NLOHMANN_JSON_VERSION_MINOR 10 -#define NLOHMANN_JSON_VERSION_PATCH 2 - #include // all_of, find, for_each #include // nullptr_t, ptrdiff_t, size_t #include // hash, less @@ -49,12 +33,129 @@ SOFTWARE. #include // vector // #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + -#include #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// This file contains all macro definitions affecting or depending on the ABI + +#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK + #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 2 + #warning "Already included a different version of the library!" + #endif + #endif +#endif + +#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 2 // NOLINT(modernize-macro-to-enum) + +#ifndef JSON_DIAGNOSTICS + #define JSON_DIAGNOSTICS 0 +#endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_DIAGNOSTICS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp +#else + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION + #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 +#endif + +// Construct the namespace ABI tags component +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) + +#define NLOHMANN_JSON_ABI_TAGS \ + NLOHMANN_JSON_ABI_TAGS_CONCAT( \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + +// Construct the namespace version component +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ + _v ## major ## _ ## minor ## _ ## patch +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) + +#if NLOHMANN_JSON_NAMESPACE_NO_VERSION +#define NLOHMANN_JSON_NAMESPACE_VERSION +#else +#define NLOHMANN_JSON_NAMESPACE_VERSION \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ + NLOHMANN_JSON_VERSION_MINOR, \ + NLOHMANN_JSON_VERSION_PATCH) +#endif + +// Combine namespace components +#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b +#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ + NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) + +#ifndef NLOHMANN_JSON_NAMESPACE +#define NLOHMANN_JSON_NAMESPACE \ + nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN +#define NLOHMANN_JSON_NAMESPACE_BEGIN \ + namespace nlohmann \ + { \ + inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) \ + { +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_END +#define NLOHMANN_JSON_NAMESPACE_END \ + } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ + } // namespace nlohmann +#endif + // #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + #include // transform @@ -70,14 +171,31 @@ SOFTWARE. #include // valarray // #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include // nullptr_t #include // exception #include // runtime_error #include // to_string #include // vector // #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + #include // array @@ -85,102 +203,130 @@ SOFTWARE. #include // uint8_t #include // string -namespace nlohmann -{ +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // declval, pair +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { -/////////////////////////// -// JSON type enumeration // -/////////////////////////// -/*! -@brief the JSON type enumeration +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; -This enumeration collects the different JSON types. It is internally used to -distinguish the stored values, and the functions @ref basic_json::is_null(), -@ref basic_json::is_object(), @ref basic_json::is_array(), -@ref basic_json::is_string(), @ref basic_json::is_boolean(), -@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), -@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), -@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and -@ref basic_json::is_structured() rely on it. +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END -@note There are three enumeration entries (number_integer, number_unsigned, and -number_float), because the library distinguishes these three types for numbers: -@ref basic_json::number_unsigned_t is used for unsigned integers, -@ref basic_json::number_integer_t is used for signed integers, and -@ref basic_json::number_float_t is used for floating-point numbers or to -approximate integers which do not fit in the limits of their respective type. -@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON -value with the default value for a given type +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ -@since version 1.0.0 -*/ -enum class value_t : std::uint8_t +// https://en.cppreference.com/w/cpp/experimental/is_detected +struct nonesuch { - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - number_integer, ///< number value (signed integer) - number_unsigned, ///< number value (unsigned integer) - number_float, ///< number value (floating-point) - binary, ///< binary array (ordered collection of bytes) - discarded ///< discarded by the parser callback function + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; }; -/*! -@brief comparison operator for JSON types - -Returns an ordering that is similar to Python: -- order: null < boolean < number < object < array < string < binary -- furthermore, each type is not smaller than itself -- discarded values are not comparable -- binary is represented as a b"" string in python and directly comparable to a - string; however, making a binary array directly comparable with a string would - be surprising behavior in a JSON file. +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; -@since version 1.0.0 -*/ -inline bool operator<(const value_t lhs, const value_t rhs) noexcept +template class Op, class... Args> +struct detector>, Op, Args...> { - static constexpr std::array order = {{ - 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, - 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, - 6 /* binary */ - } - }; + using value_t = std::true_type; + using type = Op; +}; - const auto l_index = static_cast(lhs); - const auto r_index = static_cast(rhs); - return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; -} -} // namespace detail -} // namespace nlohmann +template class Op, class... Args> +using is_detected = typename detector::value_t; -// #include +template class Op, class... Args> +struct is_detected_lazy : is_detected { }; +template class Op, class... Args> +using detected_t = typename detector::type; -#include -// #include +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END -#include // pair // #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-License-Identifier: MIT + /* Hedley - https://nemequ.github.io/hedley * Created by Evan Nemerson - * - * To the extent possible under law, the author(s) have dedicated all - * copyright and related and neighboring rights to this software to - * the public domain worldwide. This software is distributed without - * any warranty. - * - * For details, see . - * SPDX-License-Identifier: CC0-1.0 */ #if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) @@ -2215,9 +2361,12 @@ JSON_HEDLEY_DIAGNOSTIC_POP #endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ -// This file contains all internal macro definitions +// This file contains all internal macro definitions (except those affecting ABI) // You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them +// #include + + // exclude unsupported compilers #if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) #if defined(__clang__) @@ -2248,6 +2397,104 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define JSON_HAS_CPP_11 #endif +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + +#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) + #ifdef JSON_HAS_CPP_17 + #if defined(__cpp_lib_filesystem) + #define JSON_HAS_FILESYSTEM 1 + #elif defined(__cpp_lib_experimental_filesystem) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif !defined(__has_include) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #endif + + // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ + #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__clang_major__) && __clang_major__ < 7 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support + #if defined(_MSC_VER) && _MSC_VER < 1914 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before iOS 13 + #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before macOS Catalina + #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + #endif +#endif + +#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_FILESYSTEM + #define JSON_HAS_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_THREE_WAY_COMPARISON + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L + #define JSON_HAS_THREE_WAY_COMPARISON 1 + #else + #define JSON_HAS_THREE_WAY_COMPARISON 0 + #endif +#endif + +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + +#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) + #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else + #define JSON_NO_UNIQUE_ADDRESS +#endif + // disable documentation warnings on clang #if defined(__clang__) #pragma clang diagnostic push @@ -2255,7 +2502,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" #endif -// allow to disable exceptions +// allow disabling exceptions #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) #define JSON_THROW(exception) throw exception #define JSON_TRY try @@ -2289,7 +2536,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER #endif -// allow to override assert +// allow overriding assert #if !defined(JSON_ASSERT) #include // assert #define JSON_ASSERT(x) assert(x) @@ -2485,6 +2732,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; #define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); /*! @brief macro @@ -2495,6 +2743,10 @@ JSON_HEDLEY_DIAGNOSTIC_POP friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { Type nlohmann_json_default_obj; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + /*! @brief macro @def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE @@ -2504,6 +2756,49 @@ JSON_HEDLEY_DIAGNOSTIC_POP inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { Type nlohmann_json_default_obj; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + + +// inspired from https://stackoverflow.com/a/26745591 +// allows to call any std function as if (e.g. with begin): +// using std::begin; begin(x); +// +// it allows using the detected idiom to retrieve the return type +// of such an expression +#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ + namespace detail { \ + using std::std_name; \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + } \ + \ + namespace detail2 { \ + struct std_name##_tag \ + { \ + }; \ + \ + template \ + std_name##_tag std_name(T&&...); \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + \ + template \ + struct would_call_std_##std_name \ + { \ + static constexpr auto const value = ::nlohmann::detail:: \ + is_detected_exact::value; \ + }; \ + } /* namespace detail2 */ \ + \ + template \ + struct would_call_std_##std_name : detail2::would_call_std_##std_name \ + { \ + } + #ifndef JSON_USE_IMPLICIT_CONVERSIONS #define JSON_USE_IMPLICIT_CONVERSIONS 1 #endif @@ -2514,35 +2809,155 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define JSON_EXPLICIT explicit #endif -#ifndef JSON_DIAGNOSTICS - #define JSON_DIAGNOSTICS 0 +#ifndef JSON_DISABLE_ENUM_SERIALIZATION + #define JSON_DISABLE_ENUM_SERIALIZATION 0 +#endif + +#ifndef JSON_USE_GLOBAL_UDLS + #define JSON_USE_GLOBAL_UDLS 1 #endif +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif -namespace nlohmann -{ +NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { -/*! -@brief replace all occurrences of a substring by another string - -@param[in,out] s the string to manipulate; changed so that all - occurrences of @a f are replaced with @a t -@param[in] f the substring to replace with @a t -@param[in] t the string to replace @a f +/////////////////////////// +// JSON type enumeration // +/////////////////////////// -@pre The search string @a f must not be empty. **This precondition is -enforced with an assertion.** +/*! +@brief the JSON type enumeration -@since version 2.0.0 -*/ -inline void replace_substring(std::string& s, const std::string& f, - const std::string& t) -{ +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief replace all occurrences of a substring by another string + +@param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t +@param[in] f the substring to replace with @a t +@param[in] t the string to replace @a f + +@pre The search string @a f must not be empty. **This precondition is +enforced with an assertion.** + +@since version 2.0.0 +*/ +template +inline void replace_substring(StringType& s, const StringType& f, + const StringType& t) +{ JSON_ASSERT(!f.empty()); for (auto pos = s.find(f); // find first occurrence of f - pos != std::string::npos; // make sure f was found + pos != StringType::npos; // make sure f was found s.replace(pos, f.size(), t), // replace with t, and pos = s.find(f, pos + t.size())) // find next occurrence of f {} @@ -2555,10 +2970,11 @@ inline void replace_substring(std::string& s, const std::string& f, * * Note the order of escaping "~" to "~0" and "/" to "~1" is important. */ -inline std::string escape(std::string s) +template +inline StringType escape(StringType s) { - replace_substring(s, "~", "~0"); - replace_substring(s, "/", "~1"); + replace_substring(s, StringType{"~"}, StringType{"~0"}); + replace_substring(s, StringType{"/"}, StringType{"~1"}); return s; } @@ -2569,24 +2985,36 @@ inline std::string escape(std::string s) * * Note the order of escaping "~1" to "/" and "~0" to "~" is important. */ -static void unescape(std::string& s) +template +static void unescape(StringType& s) { - replace_substring(s, "~1", "/"); - replace_substring(s, "~0", "~"); + replace_substring(s, StringType{"~1"}, StringType{"/"}); + replace_substring(s, StringType{"~0"}, StringType{"~"}); } -} // namespace detail -} // namespace nlohmann +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END // #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + #include // size_t -namespace nlohmann -{ +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { + /// struct to capture the start position of the current token struct position_t { @@ -2604,497 +3032,85 @@ struct position_t } }; -} // namespace detail -} // namespace nlohmann +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END // #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-FileCopyrightText: 2018 The Abseil Authors +// SPDX-License-Identifier: MIT -namespace nlohmann -{ -namespace detail -{ -//////////////// -// exceptions // -//////////////// - -/*! -@brief general exception of the @ref basic_json class -This class is an extension of `std::exception` objects with a member @a id for -exception ids. It is used as the base class for all exceptions thrown by the -@ref basic_json class. This class can hence be used as "wildcard" to catch -exceptions. -Subclasses: -- @ref parse_error for exceptions indicating a parse error -- @ref invalid_iterator for exceptions indicating errors with iterators -- @ref type_error for exceptions indicating executing a member function with - a wrong type -- @ref out_of_range for exceptions indicating access out of the defined range -- @ref other_error for exceptions indicating other library errors +#include // array +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // index_sequence, make_index_sequence, index_sequence_for -@internal -@note To have nothrow-copy-constructible exceptions, we internally use - `std::runtime_error` which can cope with arbitrary-length error messages. - Intermediate strings are built with static functions and then passed to - the actual constructor. -@endinternal +// #include -@liveexample{The following code shows how arbitrary library exceptions can be -caught.,exception} -@since version 3.0.0 -*/ -class exception : public std::exception +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail { - public: - /// returns the explanatory string - const char* what() const noexcept override - { - return m.what(); - } - - /// the id of the exception - const int id; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes) - - protected: - JSON_HEDLEY_NON_NULL(3) - exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} - static std::string name(const std::string& ename, int id_) - { - return "[json.exception." + ename + "." + std::to_string(id_) + "] "; - } +template +using uncvref_t = typename std::remove_cv::type>::type; - template - static std::string diagnostics(const BasicJsonType& leaf_element) - { -#if JSON_DIAGNOSTICS - std::vector tokens; - for (const auto* current = &leaf_element; current->m_parent != nullptr; current = current->m_parent) - { - switch (current->m_parent->type()) - { - case value_t::array: - { - for (std::size_t i = 0; i < current->m_parent->m_value.array->size(); ++i) - { - if (¤t->m_parent->m_value.array->operator[](i) == current) - { - tokens.emplace_back(std::to_string(i)); - break; - } - } - break; - } +#ifdef JSON_HAS_CPP_14 - case value_t::object: - { - for (const auto& element : *current->m_parent->m_value.object) - { - if (&element.second == current) - { - tokens.emplace_back(element.first.c_str()); - break; - } - } - break; - } +// the following utilities are natively available in C++14 +using std::enable_if_t; +using std::index_sequence; +using std::make_index_sequence; +using std::index_sequence_for; - case value_t::null: // LCOV_EXCL_LINE - case value_t::string: // LCOV_EXCL_LINE - case value_t::boolean: // LCOV_EXCL_LINE - case value_t::number_integer: // LCOV_EXCL_LINE - case value_t::number_unsigned: // LCOV_EXCL_LINE - case value_t::number_float: // LCOV_EXCL_LINE - case value_t::binary: // LCOV_EXCL_LINE - case value_t::discarded: // LCOV_EXCL_LINE - default: // LCOV_EXCL_LINE - break; // LCOV_EXCL_LINE - } - } +#else - if (tokens.empty()) - { - return ""; - } +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; - return "(" + std::accumulate(tokens.rbegin(), tokens.rend(), std::string{}, - [](const std::string & a, const std::string & b) - { - return a + "/" + detail::escape(b); - }) + ") "; -#else - static_cast(leaf_element); - return ""; -#endif - } +// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h +// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. - private: - /// an exception object as storage for error messages - std::runtime_error m; -}; +//// START OF CODE FROM GOOGLE ABSEIL -/*! -@brief exception indicating a parse error - -This exception is thrown by the library when a parse error occurs. Parse errors -can occur during the deserialization of JSON text, CBOR, MessagePack, as well -as when using JSON Patch. - -Member @a byte holds the byte index of the last read character in the input -file. - -Exceptions have ids 1xx. - -name / id | example message | description ------------------------------- | --------------- | ------------------------- -json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. -json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. -json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. -json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. -json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. -json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. -json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. -json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. -json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. -json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. -json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. -json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. -json.exception.parse_error.114 | parse error: Unsupported BSON record type 0x0F | The parsing of the corresponding BSON record type is not implemented (yet). -json.exception.parse_error.115 | parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A | A UBJSON high-precision number could not be parsed. - -@note For an input with n bytes, 1 is the index of the first character and n+1 - is the index of the terminating null byte or the end of file. This also - holds true when reading a byte vector (CBOR or MessagePack). - -@liveexample{The following code shows how a `parse_error` exception can be -caught.,parse_error} - -@sa - @ref exception for the base class of the library exceptions -@sa - @ref invalid_iterator for exceptions indicating errors with iterators -@sa - @ref type_error for exceptions indicating executing a member function with - a wrong type -@sa - @ref out_of_range for exceptions indicating access out of the defined range -@sa - @ref other_error for exceptions indicating other library errors - -@since version 3.0.0 -*/ -class parse_error : public exception +// integer_sequence +// +// Class template representing a compile-time integer sequence. An instantiation +// of `integer_sequence` has a sequence of integers encoded in its +// type through its template arguments (which is a common need when +// working with C++11 variadic templates). `absl::integer_sequence` is designed +// to be a drop-in replacement for C++14's `std::integer_sequence`. +// +// Example: +// +// template< class T, T... Ints > +// void user_function(integer_sequence); +// +// int main() +// { +// // user_function's `T` will be deduced to `int` and `Ints...` +// // will be deduced to `0, 1, 2, 3, 4`. +// user_function(make_integer_sequence()); +// } +template +struct integer_sequence { - public: - /*! - @brief create a parse error exception - @param[in] id_ the id of the exception - @param[in] pos the position where the error occurred (or with - chars_read_total=0 if the position cannot be - determined) - @param[in] what_arg the explanatory string - @return parse_error object - */ - template - static parse_error create(int id_, const position_t& pos, const std::string& what_arg, const BasicJsonType& context) - { - std::string w = exception::name("parse_error", id_) + "parse error" + - position_string(pos) + ": " + exception::diagnostics(context) + what_arg; - return parse_error(id_, pos.chars_read_total, w.c_str()); - } - - template - static parse_error create(int id_, std::size_t byte_, const std::string& what_arg, const BasicJsonType& context) + using value_type = T; + static constexpr std::size_t size() noexcept { - std::string w = exception::name("parse_error", id_) + "parse error" + - (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") + - ": " + exception::diagnostics(context) + what_arg; - return parse_error(id_, byte_, w.c_str()); + return sizeof...(Ints); } - - /*! - @brief byte index of the parse error - - The byte index of the last read character in the input file. - - @note For an input with n bytes, 1 is the index of the first character and - n+1 is the index of the terminating null byte or the end of file. - This also holds true when reading a byte vector (CBOR or MessagePack). - */ - const std::size_t byte; - - private: - parse_error(int id_, std::size_t byte_, const char* what_arg) - : exception(id_, what_arg), byte(byte_) {} - - static std::string position_string(const position_t& pos) - { - return " at line " + std::to_string(pos.lines_read + 1) + - ", column " + std::to_string(pos.chars_read_current_line); - } -}; - -/*! -@brief exception indicating errors with iterators - -This exception is thrown if iterators passed to a library function do not match -the expected semantics. - -Exceptions have ids 2xx. - -name / id | example message | description ------------------------------------ | --------------- | ------------------------- -json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. -json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. -json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. -json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. -json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. -json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. -json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. -json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. -json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. -json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. -json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. -json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. -json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. -json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). - -@liveexample{The following code shows how an `invalid_iterator` exception can be -caught.,invalid_iterator} - -@sa - @ref exception for the base class of the library exceptions -@sa - @ref parse_error for exceptions indicating a parse error -@sa - @ref type_error for exceptions indicating executing a member function with - a wrong type -@sa - @ref out_of_range for exceptions indicating access out of the defined range -@sa - @ref other_error for exceptions indicating other library errors - -@since version 3.0.0 -*/ -class invalid_iterator : public exception -{ - public: - template - static invalid_iterator create(int id_, const std::string& what_arg, const BasicJsonType& context) - { - std::string w = exception::name("invalid_iterator", id_) + exception::diagnostics(context) + what_arg; - return invalid_iterator(id_, w.c_str()); - } - - private: - JSON_HEDLEY_NON_NULL(3) - invalid_iterator(int id_, const char* what_arg) - : exception(id_, what_arg) {} -}; - -/*! -@brief exception indicating executing a member function with a wrong type - -This exception is thrown in case of a type error; that is, a library function is -executed on a JSON value whose type does not match the expected semantics. - -Exceptions have ids 3xx. - -name / id | example message | description ------------------------------ | --------------- | ------------------------- -json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. -json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. -json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t &. -json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. -json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. -json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. -json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. -json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. -json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. -json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. -json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. -json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. -json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. -json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. -json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. -json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | -json.exception.type_error.317 | JSON value cannot be serialized to requested format | The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) | - -@liveexample{The following code shows how a `type_error` exception can be -caught.,type_error} - -@sa - @ref exception for the base class of the library exceptions -@sa - @ref parse_error for exceptions indicating a parse error -@sa - @ref invalid_iterator for exceptions indicating errors with iterators -@sa - @ref out_of_range for exceptions indicating access out of the defined range -@sa - @ref other_error for exceptions indicating other library errors - -@since version 3.0.0 -*/ -class type_error : public exception -{ - public: - template - static type_error create(int id_, const std::string& what_arg, const BasicJsonType& context) - { - std::string w = exception::name("type_error", id_) + exception::diagnostics(context) + what_arg; - return type_error(id_, w.c_str()); - } - - private: - JSON_HEDLEY_NON_NULL(3) - type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} -}; - -/*! -@brief exception indicating access out of the defined range - -This exception is thrown in case a library function is called on an input -parameter that exceeds the expected range, for instance in case of array -indices or nonexisting object keys. - -Exceptions have ids 4xx. - -name / id | example message | description -------------------------------- | --------------- | ------------------------- -json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. -json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. -json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. -json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. -json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. -json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. -json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. (until version 3.8.0) | -json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | -json.exception.out_of_range.409 | BSON key cannot contain code point U+0000 (at byte 2) | Key identifiers to be serialized to BSON cannot contain code point U+0000, since the key is stored as zero-terminated c-string | - -@liveexample{The following code shows how an `out_of_range` exception can be -caught.,out_of_range} - -@sa - @ref exception for the base class of the library exceptions -@sa - @ref parse_error for exceptions indicating a parse error -@sa - @ref invalid_iterator for exceptions indicating errors with iterators -@sa - @ref type_error for exceptions indicating executing a member function with - a wrong type -@sa - @ref other_error for exceptions indicating other library errors - -@since version 3.0.0 -*/ -class out_of_range : public exception -{ - public: - template - static out_of_range create(int id_, const std::string& what_arg, const BasicJsonType& context) - { - std::string w = exception::name("out_of_range", id_) + exception::diagnostics(context) + what_arg; - return out_of_range(id_, w.c_str()); - } - - private: - JSON_HEDLEY_NON_NULL(3) - out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} -}; - -/*! -@brief exception indicating other library errors - -This exception is thrown in case of errors that cannot be classified with the -other exception types. - -Exceptions have ids 5xx. - -name / id | example message | description ------------------------------- | --------------- | ------------------------- -json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. - -@sa - @ref exception for the base class of the library exceptions -@sa - @ref parse_error for exceptions indicating a parse error -@sa - @ref invalid_iterator for exceptions indicating errors with iterators -@sa - @ref type_error for exceptions indicating executing a member function with - a wrong type -@sa - @ref out_of_range for exceptions indicating access out of the defined range - -@liveexample{The following code shows how an `other_error` exception can be -caught.,other_error} - -@since version 3.0.0 -*/ -class other_error : public exception -{ - public: - template - static other_error create(int id_, const std::string& what_arg, const BasicJsonType& context) - { - std::string w = exception::name("other_error", id_) + exception::diagnostics(context) + what_arg; - return other_error(id_, w.c_str()); - } - - private: - JSON_HEDLEY_NON_NULL(3) - other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} -}; -} // namespace detail -} // namespace nlohmann - -// #include - -// #include - - -#include // size_t -#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type -#include // index_sequence, make_index_sequence, index_sequence_for - -// #include - - -namespace nlohmann -{ -namespace detail -{ - -template -using uncvref_t = typename std::remove_cv::type>::type; - -#ifdef JSON_HAS_CPP_14 - -// the following utilities are natively available in C++14 -using std::enable_if_t; -using std::index_sequence; -using std::make_index_sequence; -using std::index_sequence_for; - -#else - -// alias templates to reduce boilerplate -template -using enable_if_t = typename std::enable_if::type; - -// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h -// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. - -//// START OF CODE FROM GOOGLE ABSEIL - -// integer_sequence -// -// Class template representing a compile-time integer sequence. An instantiation -// of `integer_sequence` has a sequence of integers encoded in its -// type through its template arguments (which is a common need when -// working with C++11 variadic templates). `absl::integer_sequence` is designed -// to be a drop-in replacement for C++14's `std::integer_sequence`. -// -// Example: -// -// template< class T, T... Ints > -// void user_function(integer_sequence); -// -// int main() -// { -// // user_function's `T` will be deduced to `int` and `Ints...` -// // will be deduced to `0, 1, 2, 3, 4`. -// user_function(make_integer_sequence()); -// } -template -struct integer_sequence -{ - using value_type = T; - static constexpr std::size_t size() noexcept - { - return sizeof...(Ints); - } -}; +}; // index_sequence // @@ -3178,28 +3194,32 @@ template<> struct priority_tag<0> {}; template struct static_const { - static constexpr T value{}; + static JSON_INLINE_VARIABLE constexpr T value{}; }; -template -constexpr T static_const::value; - -} // namespace detail -} // namespace nlohmann - -// #include - +#ifndef JSON_HAS_CPP_17 + template + constexpr T static_const::value; +#endif -namespace nlohmann -{ -namespace detail +template +inline constexpr std::array make_array(Args&& ... args) { -// dispatching helper struct -template struct identity_tag {}; + return std::array {{static_cast(std::forward(args))...}}; +} + } // namespace detail -} // namespace nlohmann +NLOHMANN_JSON_NAMESPACE_END // #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + #include // numeric_limits @@ -3208,32 +3228,29 @@ template struct identity_tag {}; #include // tuple // #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT -#include // random_access_iterator_tag -// #include +#include // random_access_iterator_tag +// #include -namespace nlohmann -{ -namespace detail -{ -template struct make_void -{ - using type = void; -}; -template using void_t = typename make_void::type; -} // namespace detail -} // namespace nlohmann +// #include // #include -namespace nlohmann -{ +NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { + template struct iterator_types {}; @@ -3272,160 +3289,135 @@ struct iterator_traits::value>> using pointer = T*; using reference = T&; }; -} // namespace detail -} // namespace nlohmann + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END // #include -// #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT -// #include -#include +// #include -// #include +NLOHMANN_JSON_NAMESPACE_BEGIN -// https://en.cppreference.com/w/cpp/experimental/is_detected -namespace nlohmann -{ -namespace detail -{ -struct nonesuch -{ - nonesuch() = delete; - ~nonesuch() = delete; - nonesuch(nonesuch const&) = delete; - nonesuch(nonesuch const&&) = delete; - void operator=(nonesuch const&) = delete; - void operator=(nonesuch&&) = delete; -}; +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); -template class Op, - class... Args> -struct detector -{ - using value_t = std::false_type; - using type = Default; -}; +NLOHMANN_JSON_NAMESPACE_END -template class Op, class... Args> -struct detector>, Op, Args...> -{ - using value_t = std::true_type; - using type = Op; -}; +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT -template class Op, class... Args> -using is_detected = typename detector::value_t; -template class Op, class... Args> -struct is_detected_lazy : is_detected { }; -template class Op, class... Args> -using detected_t = typename detector::type; +// #include -template class Op, class... Args> -using detected_or = detector; -template class Op, class... Args> -using detected_or_t = typename detected_or::type; +NLOHMANN_JSON_NAMESPACE_BEGIN -template class Op, class... Args> -using is_detected_exact = std::is_same>; +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); -template class Op, class... Args> -using is_detected_convertible = - std::is_convertible, To>; -} // namespace detail -} // namespace nlohmann +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include // #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + #ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ -#define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ -#include // int64_t, uint64_t -#include // map -#include // allocator -#include // string -#include // vector + #include // int64_t, uint64_t + #include // map + #include // allocator + #include // string + #include // vector -/*! -@brief namespace for Niels Lohmann -@see https://github.com/nlohmann -@since version 1.0.0 -*/ -namespace nlohmann -{ -/*! -@brief default JSONSerializer template argument + // #include -This serializer ignores the template arguments and uses ADL -([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) -for serialization. -*/ -template -struct adl_serializer; - -template class ObjectType = - std::map, - template class ArrayType = std::vector, - class StringType = std::string, class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template class AllocatorType = std::allocator, - template class JSONSerializer = - adl_serializer, - class BinaryType = std::vector> -class basic_json; -/*! -@brief JSON Pointer - -A JSON pointer defines a string syntax for identifying a specific value -within a JSON document. It can be used with functions `at` and -`operator[]`. Furthermore, JSON pointers are the base for JSON patches. - -@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) - -@since version 2.0.0 -*/ -template -class json_pointer; + /*! + @brief namespace for Niels Lohmann + @see https://github.com/nlohmann + @since version 1.0.0 + */ + NLOHMANN_JSON_NAMESPACE_BEGIN -/*! -@brief default JSON class + /*! + @brief default JSONSerializer template argument -This type is the default specialization of the @ref basic_json class which -uses the standard template types. + This serializer ignores the template arguments and uses ADL + ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) + for serialization. + */ + template + struct adl_serializer; -@since version 1.0.0 -*/ -using json = basic_json<>; + /// a class to store JSON values + /// @sa https://json.nlohmann.me/api/basic_json/ + template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector> + class basic_json; -template -struct ordered_map; + /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document + /// @sa https://json.nlohmann.me/api/json_pointer/ + template + class json_pointer; -/*! -@brief ordered JSON class + /*! + @brief default specialization + @sa https://json.nlohmann.me/api/json/ + */ + using json = basic_json<>; -This type preserves the insertion order of object keys. + /// @brief a minimal map-like container that preserves insertion order + /// @sa https://json.nlohmann.me/api/ordered_map/ + template + struct ordered_map; -@since version 3.9.0 -*/ -using ordered_json = basic_json; + /// @brief specialization that maintains the insertion order of object keys + /// @sa https://json.nlohmann.me/api/ordered_json/ + using ordered_json = basic_json; -} // namespace nlohmann + NLOHMANN_JSON_NAMESPACE_END #endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ -namespace nlohmann -{ +NLOHMANN_JSON_NAMESPACE_BEGIN /*! @brief detail namespace with internal helper functions @@ -3436,6 +3428,7 @@ implementations of some @ref basic_json methods, and meta-programming helpers. */ namespace detail { + ///////////// // helpers // ///////////// @@ -3454,6 +3447,16 @@ template struct is_basic_json : std::false_type {}; NLOHMANN_BASIC_JSON_TPL_DECLARATION struct is_basic_json : std::true_type {}; +// used by exceptions create() member functions +// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t +// false_type otherwise +template +struct is_basic_json_context : + std::integral_constant < bool, + is_basic_json::type>::type>::value + || std::is_same::value > +{}; + ////////////////////// // json_ref helpers // ////////////////////// @@ -3492,9 +3495,6 @@ using reference_t = typename T::reference; template using iterator_category_t = typename T::iterator_category; -template -using iterator_t = typename T::iterator; - template using to_json_function = decltype(T::to_json(std::declval()...)); @@ -3558,6 +3558,24 @@ struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> T>::value; }; +template +using detect_key_compare = typename T::key_compare; + +template +struct has_key_compare : std::integral_constant::value> {}; + +// obtains the actual object key comparator +template +struct actual_object_comparator +{ + using object_t = typename BasicJsonType::object_t; + using object_comparator_t = typename BasicJsonType::default_object_comparator_t; + using type = typename std::conditional < has_key_compare::value, + typename object_t::key_compare, object_comparator_t>::type; +}; + +template +using actual_object_comparator_t = typename actual_object_comparator::type; /////////////////// // is_ functions // @@ -3565,10 +3583,10 @@ struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> // https://en.cppreference.com/w/cpp/types/conjunction template struct conjunction : std::true_type { }; -template struct conjunction : B1 { }; -template -struct conjunction -: std::conditional, B1>::type {}; +template struct conjunction : B { }; +template +struct conjunction +: std::conditional(B::value), conjunction, B>::type {}; // https://en.cppreference.com/w/cpp/types/negation template struct negation : std::integral_constant < bool, !B::value > { }; @@ -3630,6 +3648,31 @@ struct is_iterator_traits> is_detected::value; }; +template +struct is_range +{ + private: + using t_ref = typename std::add_lvalue_reference::type; + + using iterator = detected_t; + using sentinel = detected_t; + + // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator + // and https://en.cppreference.com/w/cpp/iterator/sentinel_for + // but reimplementing these would be too much work, as a lot of other concepts are used underneath + static constexpr auto is_iterator_begin = + is_iterator_traits>::value; + + public: + static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; +}; + +template +using iterator_t = enable_if_t::value, result_of_begin())>>; + +template +using range_value_t = value_type_t>>; + // The following implementation of is_complete_type is taken from // https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ // and is written by Xiang Fan who agreed to using it in this library. @@ -3697,60 +3740,46 @@ struct is_constructible_object_type : is_constructible_object_type_impl {}; -template -struct is_compatible_string_type_impl : std::false_type {}; - template -struct is_compatible_string_type_impl < - BasicJsonType, CompatibleStringType, - enable_if_t::value >> +struct is_compatible_string_type { static constexpr auto value = is_constructible::value; }; template -struct is_compatible_string_type - : is_compatible_string_type_impl {}; - -template -struct is_constructible_string_type_impl : std::false_type {}; - -template -struct is_constructible_string_type_impl < - BasicJsonType, ConstructibleStringType, - enable_if_t::value >> +struct is_constructible_string_type { + // launder type through decltype() to fix compilation failure on ICPC +#ifdef __INTEL_COMPILER + using laundered_type = decltype(std::declval()); +#else + using laundered_type = ConstructibleStringType; +#endif + static constexpr auto value = - is_constructible::value; + conjunction < + is_constructible, + is_detected_exact>::value; }; -template -struct is_constructible_string_type - : is_constructible_string_type_impl {}; - template struct is_compatible_array_type_impl : std::false_type {}; template struct is_compatible_array_type_impl < BasicJsonType, CompatibleArrayType, - enable_if_t < is_detected::value&& + enable_if_t < is_detected::value&& -// This is needed because json_reverse_iterator has a ::iterator type... -// Therefore it is detected as a CompatibleArrayType. -// The real fix would be to have an Iterable concept. - !is_iterator_traits < - iterator_traits>::value >> + is_iterator_traits>>::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 + !std::is_same>::value >> { static constexpr bool value = is_constructible::value; + range_value_t>::value; }; template @@ -3772,130 +3801,800 @@ struct is_constructible_array_type_impl < BasicJsonType, ConstructibleArrayType, enable_if_t < !std::is_same::value&& + !is_compatible_string_type::value&& is_default_constructible::value&& (std::is_move_assignable::value || std::is_copy_assignable::value)&& -is_detected::value&& is_detected::value&& -is_complete_type < -detected_t>::value >> +is_iterator_traits>>::value&& +is_detected::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 +!std::is_same>::value&& + is_complete_type < + detected_t>::value >> { + using value_type = range_value_t; + static constexpr bool value = - // This is needed because json_reverse_iterator has a ::iterator type, - // furthermore, std::back_insert_iterator (and other iterators) have a - // base class `iterator`... Therefore it is detected as a - // ConstructibleArrayType. The real fix would be to have an Iterable - // concept. - !is_iterator_traits>::value && - - (std::is_same::value || - has_from_json::value || - has_non_default_from_json < - BasicJsonType, typename ConstructibleArrayType::value_type >::value); + std::is_same::value || + has_from_json::value || + has_non_default_from_json < + BasicJsonType, + value_type >::value; +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral::value&& + std::is_integral::value&& + !std::is_same::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + is_constructible::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type + : is_compatible_integer_type_impl {}; + +template +struct is_compatible_type_impl: std::false_type {}; + +template +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t::value >> +{ + static constexpr bool value = + has_to_json::value; +}; + +template +struct is_compatible_type + : is_compatible_type_impl {}; + +template +struct is_constructible_tuple : std::false_type {}; + +template +struct is_constructible_tuple> : conjunction...> {}; + +template +struct is_json_iterator_of : std::false_type {}; + +template +struct is_json_iterator_of : std::true_type {}; + +template +struct is_json_iterator_of : std::true_type +{}; + +// checks if a given type T is a template specialization of Primary +template