diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..b8da84c97 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,78 @@ +name: Build + +'on': + push: + branches: + - master + pull_request: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +permissions: {} + +jobs: + Mainline: + name: 'Mainline' + strategy: + matrix: + sanitizer: + - address + - memory + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - name: Install Dependencies + run: | + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get -o Dpkg::Use-Pty=0 update + sudo rm -f /var/lib/man-db/auto-update + sudo apt-get -o Dpkg::Use-Pty=0 install -y cmake clang ninja-build + + - name: Compile mainline + env: + SANITIZER: '${{ matrix.sanitizer }}' # test with different "sanitizers" + run: ./mainline.sh + + just_dependencies: + name: 'Just dependencies' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - name: Install Dependencies + run: | + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get -o Dpkg::Use-Pty=0 update + sudo rm -f /var/lib/man-db/auto-update + sudo apt-get -o Dpkg::Use-Pty=0 install -y cmake clang ninja-build + + - name: Compile deps target + run: ./scripts/compile_target.sh deps + + PythonTests: + name: 'Python tests' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + with: + python-version: '3.12' + + - name: Install test dependencies + run: pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary '.[python-tests]' + + - name: Run TLV constants sync test + run: pytest tests/test_tlv_constants_sync.py diff --git a/.github/workflows/checksrc.yml b/.github/workflows/checksrc.yml new file mode 100644 index 000000000..09e53c232 --- /dev/null +++ b/.github/workflows/checksrc.yml @@ -0,0 +1,50 @@ +# Copyright (C) Daniel Stenberg, , et al. +# +# SPDX-License-Identifier: curl + +name: 'Source' + +'on': + push: + branches: + - master + pull_request: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +permissions: {} + +jobs: + linters: + name: 'linters' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + with: + python-version: '3.12' + + - name: 'install prereqs' + run: | + /home/linuxbrew/.linuxbrew/bin/brew install zizmor + pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary '.[ci-tests]' + + - name: 'zizmor GHA' + env: + GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + run: | + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + zizmor --pedantic .github/workflows/*.yml + + - name: 'ruff' + run: | + ruff --version + # shellcheck disable=SC2046 + ruff check $(git ls-files '*.py') diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43da0775e..c6d6b863e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,7 @@ +# Workflow used by curl/curl + name: CI + 'on': push: branches: @@ -20,14 +23,16 @@ permissions: {} jobs: DetermineMatrix: + name: 'Determine matrix' runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: + persist-credentials: false repository: curl/curl-fuzzer + - name: Set matrix id: set-matrix run: | @@ -36,29 +41,31 @@ jobs: python3 -m generate_matrix | tee $GITHUB_OUTPUT BuildFuzzers: + name: 'Build fuzzers' runs-on: ubuntu-latest steps: - # Use the CIFuzz job to test the repository. - - name: Build Fuzzers - uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master - with: - oss-fuzz-project-name: 'curl' - dry-run: false - keep-unaffected-fuzz-targets: true + # Use the CIFuzz job to test the repository. + - name: Build Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master # zizmor: ignore[unpinned-uses] + with: + oss-fuzz-project-name: 'curl' + dry-run: false + keep-unaffected-fuzz-targets: true - # Archive the fuzzer output (which maintains permissions) - - name: Create fuzz tar - run: tar cvf fuzz.tar build-out/ + # Archive the fuzzer output (which maintains permissions) + - name: Create fuzz tar + run: tar cvf fuzz.tar build-out/ - # Upload the fuzzer output - - name: Archive fuzz tar - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - with: - name: fuzz_tar - path: fuzz.tar + # Upload the fuzzer output + - name: Archive fuzz tar + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: fuzz_tar + path: fuzz.tar RunFuzzers: - needs: [ BuildFuzzers, DetermineMatrix ] + name: 'Run fuzzers' + needs: [BuildFuzzers, DetermineMatrix] runs-on: ubuntu-latest strategy: matrix: ${{ fromJSON(needs.DetermineMatrix.outputs.matrix) }} @@ -67,89 +74,33 @@ jobs: with: name: fuzz_tar - name: Unpack fuzzer ${{ matrix.fuzzer }} - run: tar xvf fuzz.tar build-out/${{ matrix.fuzzer }} build-out/${{ matrix.fuzzer }}_seed_corpus.zip + env: + MATRIX_FUZZER: '${{ matrix.fuzzer }}' + run: tar xvf fuzz.tar build-out/"${MATRIX_FUZZER}" build-out/"${MATRIX_FUZZER}"_seed_corpus.zip - name: Display extracted files run: ls -laR build-out/ - name: Run Fuzzer ${{ matrix.fuzzer }} - uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master # zizmor: ignore[unpinned-uses] with: oss-fuzz-project-name: 'curl' fuzz-seconds: 120 dry-run: false - name: Upload Crash uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - if: failure() + if: ${{ failure() }} with: name: artifacts path: ./out/artifacts - Mainline: - strategy: - matrix: - sanitizer: - - address - - memory - - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - repository: curl/curl-fuzzer - - name: Install Dependencies - run: | - sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo rm -f /var/lib/man-db/auto-update - sudo apt-get -o Dpkg::Use-Pty=0 install -y cmake clang ninja-build - - name: Compile mainline - env: - # test with different "sanitizers" - SANITIZER: ${{ matrix.sanitizer }} - run: ./mainline.sh - - just_dependencies: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - repository: curl/curl-fuzzer - - name: Install Dependencies - run: | - sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo rm -f /var/lib/man-db/auto-update - sudo apt-get -o Dpkg::Use-Pty=0 install -y cmake clang ninja-build - - name: Compile deps target - run: ./scripts/compile_target.sh deps - - PythonTests: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - repository: curl/curl-fuzzer - - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 - with: - python-version: '3.12' - - name: Install test dependencies - run: | - python -m pip install --upgrade pip - pip install pytest - - name: Run TLV constants sync test - run: pytest tests/test_tlv_constants_sync.py - # Ensure that the repository can be built for i386 Testi386: + name: 'Test i386' runs-on: ubuntu-latest steps: - - name: Build Fuzzers - uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master - with: - oss-fuzz-project-name: 'curl' - dry-run: false - keep-unaffected-fuzz-targets: true - architecture: 'i386' + - name: Build Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master # zizmor: ignore[unpinned-uses] + with: + oss-fuzz-project-name: 'curl' + dry-run: false + keep-unaffected-fuzz-targets: true + architecture: 'i386' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c7c2e70cd..ea70a9c46 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -11,7 +11,8 @@ name: 'CodeQL' - cron: '0 0 * * 4' concurrency: - group: ${{ github.workflow }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true permissions: {} diff --git a/.github/workflows/pages-ci.yml b/.github/workflows/pages-ci.yml index 226e3a30f..576487717 100644 --- a/.github/workflows/pages-ci.yml +++ b/.github/workflows/pages-ci.yml @@ -1,36 +1,42 @@ name: Playwright browser test for corpus decoder -on: +'on': push: - branches: [master] + branches: + - master pull_request: - branches: [master] + branches: + - master workflow_dispatch: -permissions: - contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +permissions: {} jobs: test: + name: 'Test pages' runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false - - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: - python-version: "3.12" + python-version: '3.12' - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install .[browser-tests] - pip install pytest pytest-playwright + pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary '.[page-gen,browser-tests]' python -m playwright install - name: Install Playwright system dependencies run: | + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo rm -f /var/lib/man-db/auto-update playwright install-deps - name: Run Playwright browser test diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 9d224c3e1..d4e9b67ab 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,51 +1,52 @@ name: Deploy decoder to GitHub Pages -on: +'on': push: - branches: [master] + branches: + - master workflow_dispatch: -permissions: - contents: read - pages: write - id-token: write - concurrency: - group: "pages" - cancel-in-progress: false + group: ${{ github.workflow }} + cancel-in-progress: false # zizmor: ignore[concurrency-limits] + +permissions: {} jobs: build: + name: 'Build pages' runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false - - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: - python-version: "3.12" + python-version: '3.12' - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install . + run: pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary '.[page-gen]' - name: Generate decoder HTML run: python -m curl_fuzzer_tools.generate_decoder_html - name: Upload artifact - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 with: path: docs deploy: + name: 'Deploy pages' needs: build runs-on: ubuntu-latest + permissions: + pages: write # To deploy to Pages + id-token: write # To verify the deployment originates from an appropriate source environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/.vscode/settings.json b/.vscode/settings.json index d0b582f29..1a5da3e8e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,4 +2,4 @@ "[python]": { "editor.defaultFormatter": "charliermarsh.ruff" }, -} \ No newline at end of file +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b557df04..03e239ff8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 3.11) project(curl_fuzzer_deps) if(NOT "$ENV{MAKE}" STREQUAL "") - set(MAKE "$ENV{MAKE}") + set(MAKE "$ENV{MAKE}") else() - set(MAKE "make") + set(MAKE "make") endif() include(ExternalProject) @@ -18,11 +18,10 @@ set(ZLIB_URL https://zlib.net/zlib-${ZLIB_VERSION}.tar.xz set(ZLIB_INSTALL_DIR ${CMAKE_BINARY_DIR}/zlib-install) set(ZLIB_STATIC_LIB ${ZLIB_INSTALL_DIR}/lib/libz.a) -ExternalProject_Add( - zlib_external +ExternalProject_Add(zlib_external URL ${ZLIB_URL} PREFIX ${CMAKE_BINARY_DIR}/zlib - CONFIGURE_COMMAND /configure --static --prefix=${ZLIB_INSTALL_DIR} + CONFIGURE_COMMAND ./configure --static --prefix=${ZLIB_INSTALL_DIR} BUILD_IN_SOURCE 1 BUILD_BYPRODUCTS ${ZLIB_STATIC_LIB} DOWNLOAD_EXTRACT_TIMESTAMP TRUE @@ -37,12 +36,12 @@ set(ZSTD_URL https://github.com/facebook/zstd/releases/download/v${ZSTD_VERSION} set(ZSTD_INSTALL_DIR ${CMAKE_BINARY_DIR}/zstd-install) set(ZSTD_STATIC_LIB ${ZSTD_INSTALL_DIR}/lib/libzstd.a) -ExternalProject_Add( - zstd_external +ExternalProject_Add(zstd_external URL ${ZSTD_URL} PREFIX ${CMAKE_BINARY_DIR}/zstd SOURCE_SUBDIR build/cmake - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${ZSTD_INSTALL_DIR} -DZSTD_BUILD_PROGRAMS=OFF -DZSTD_BUILD_SHARED=OFF -DZSTD_BUILD_STATIC=ON -DZSTD_BUILD_CONTRIB=OFF -DZSTD_BUILD_TESTS=OFF + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${ZSTD_INSTALL_DIR} + -DZSTD_BUILD_SHARED=OFF -DZSTD_BUILD_STATIC=ON -DZSTD_BUILD_PROGRAMS=OFF -DZSTD_BUILD_CONTRIB=OFF -DZSTD_BUILD_TESTS=OFF BUILD_BYPRODUCTS ${ZSTD_STATIC_LIB} DOWNLOAD_EXTRACT_TIMESTAMP TRUE DOWNLOAD_NO_PROGRESS 1 @@ -50,13 +49,14 @@ ExternalProject_Add( # For the memory sanitizer build, turn off OpenSSL as it causes bugs we can't # affect (see 16697, 17624) -if(NOT (DEFINED ENV{SANITIZER} AND "$ENV{SANITIZER}" STREQUAL "memory")) +if(NOT "$ENV{SANITIZER}" STREQUAL "memory") message(STATUS "Building OpenSSL as a dependency") # Install openssl # # renovate: datasource=github-tags depName=openssl/openssl set(OPENSSL_VERSION 3.6.0) - set(OPENSSL_URL https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz) + set(OPENSSL_URL + https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz) set(OPENSSL_INSTALL_DIR ${CMAKE_BINARY_DIR}/openssl-install) set(OPENSSL_SRC_DIR ${CMAKE_BINARY_DIR}/openssl/src/openssl_external) set(OPENSSL_STATIC_LIB ${OPENSSL_INSTALL_DIR}/lib/libssl.a ${OPENSSL_INSTALL_DIR}/lib/libcrypto.a) @@ -65,14 +65,14 @@ if(NOT (DEFINED ENV{SANITIZER} AND "$ENV{SANITIZER}" STREQUAL "memory")) set(OPENSSL_ARCH_TARGET "") set(OPENSSL_ARCH_FLAG "") set(OPENSSL_EC_FLAG "enable-ec_nistp_64_gcc_128") - if(DEFINED ENV{ARCHITECTURE} AND "$ENV{ARCHITECTURE}" STREQUAL "i386") + if("$ENV{ARCHITECTURE}" STREQUAL "i386") set(OPENSSL_ARCH_TARGET "linux-generic32") set(OPENSSL_ARCH_FLAG "386") set(OPENSSL_EC_FLAG "no-threads") endif() set(OPENSSL_ASM_FLAG "") - if(DEFINED ENV{SANITIZER} AND "$ENV{SANITIZER}" STREQUAL "memory") + if("$ENV{SANITIZER}" STREQUAL "memory") set(OPENSSL_ASM_FLAG "no-asm") endif() @@ -104,8 +104,7 @@ if(NOT (DEFINED ENV{SANITIZER} AND "$ENV{SANITIZER}" STREQUAL "memory")) $ENV{OPENSSLFLAGS} ) - ExternalProject_Add( - openssl_external + ExternalProject_Add(openssl_external URL ${OPENSSL_URL} PREFIX ${CMAKE_BINARY_DIR}/openssl CONFIGURE_COMMAND ${OPENSSL_CONFIGURE_COMMAND} @@ -139,12 +138,11 @@ set(NGHTTP2_URL https://github.com/nghttp2/nghttp2/releases/download/v${NGHTTP2_ set(NGHTTP2_INSTALL_DIR ${CMAKE_BINARY_DIR}/nghttp2-install) set(NGHTTP2_STATIC_LIB ${NGHTTP2_INSTALL_DIR}/lib/libnghttp2.a) -ExternalProject_Add( - nghttp2_external +ExternalProject_Add(nghttp2_external URL ${NGHTTP2_URL} PREFIX ${CMAKE_BINARY_DIR}/nghttp2 - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${NGHTTP2_INSTALL_DIR} -DENABLE_LIB_ONLY=ON -DENABLE_THREADS=OFF -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF - -DBUILD_TESTING=OFF -DENABLE_DOC=OFF ${NGHTTP2_OPENSSL_OPTION} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${NGHTTP2_INSTALL_DIR} -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF + -DENABLE_LIB_ONLY=ON -DENABLE_THREADS=OFF -DBUILD_TESTING=OFF -DENABLE_DOC=OFF ${NGHTTP2_OPENSSL_OPTION} BUILD_BYPRODUCTS ${NGHTTP2_STATIC_LIB} DOWNLOAD_EXTRACT_TIMESTAMP TRUE DOWNLOAD_NO_PROGRESS 1 @@ -162,11 +160,11 @@ set(LIBIDN2_URL https://ftp.gnu.org/gnu/libidn/libidn2-${LIBIDN2_VERSION}.tar.gz set(LIBIDN2_INSTALL_DIR ${CMAKE_BINARY_DIR}/libidn2-install) set(LIBIDN2_STATIC_LIB ${LIBIDN2_INSTALL_DIR}/lib/libidn2.a) -ExternalProject_Add( - libidn2_external +ExternalProject_Add(libidn2_external URL ${LIBIDN2_URL} PREFIX ${CMAKE_BINARY_DIR}/libidn2 - CONFIGURE_COMMAND ./configure --disable-dependency-tracking --prefix=${LIBIDN2_INSTALL_DIR} --disable-shared --enable-static --disable-doc + CONFIGURE_COMMAND ./configure --disable-dependency-tracking --prefix=${LIBIDN2_INSTALL_DIR} --disable-shared --enable-static + --disable-doc BUILD_IN_SOURCE 1 BUILD_BYPRODUCTS ${LIBIDN2_STATIC_LIB} DOWNLOAD_EXTRACT_TIMESTAMP TRUE @@ -181,8 +179,7 @@ set(GDB_INSTALL_DIR ${CMAKE_BINARY_DIR}/gdb-install) option(BUILD_GDB "Build GDB as an external project" OFF) if(BUILD_GDB) - ExternalProject_Add( - gdb_external + ExternalProject_Add(gdb_external URL ${GDB_URL} PREFIX ${CMAKE_BINARY_DIR}/gdb CONFIGURE_COMMAND ./configure --disable-dependency-tracking --prefix=${GDB_INSTALL_DIR} @@ -201,8 +198,7 @@ set(OPENLDAP_INSTALL_DIR ${CMAKE_BINARY_DIR}/openldap-install) set(OPENLDAP_STATIC_LIB_LDAP ${OPENLDAP_INSTALL_DIR}/lib/libldap.a) set(OPENLDAP_STATIC_LIB_LBER ${OPENLDAP_INSTALL_DIR}/lib/liblber.a) -ExternalProject_Add( - openldap_external +ExternalProject_Add(openldap_external URL ${OPENLDAP_URL} PREFIX ${CMAKE_BINARY_DIR}/openldap CONFIGURE_COMMAND ./configure --prefix=${OPENLDAP_INSTALL_DIR} --disable-shared --enable-static --without-tls --disable-slapd @@ -212,7 +208,7 @@ ExternalProject_Add( DOWNLOAD_NO_PROGRESS 1 ) -if (TARGET openssl_external) +if(TARGET openssl_external) add_dependencies(openldap_external openssl_external) else() message(STATUS "Not building OpenLDAP with OpenSSL") @@ -273,10 +269,9 @@ set(CURL_POST_INSTALL_COMMAND # Conditionally check to see if there's a source directory or not. # If there is, use it. Otherwise, download the latest version. # -if (DEFINED ENV{CURL_SOURCE_DIR}) +if(NOT "$ENV{CURL_SOURCE_DIR}" STREQUAL "") message(STATUS "Building curl from source directory: $ENV{CURL_SOURCE_DIR}") - ExternalProject_Add( - curl_external + ExternalProject_Add(curl_external SOURCE_DIR $ENV{CURL_SOURCE_DIR} PATCH_COMMAND ${CMAKE_COMMAND} -E echo "pre-build commands" ${CURL_POST_INSTALL_COMMAND} @@ -286,8 +281,7 @@ if (DEFINED ENV{CURL_SOURCE_DIR}) else() message(STATUS "Building curl from git master") set(CURL_URL "https://github.com/curl/curl") - ExternalProject_Add( - curl_external + ExternalProject_Add(curl_external GIT_REPOSITORY ${CURL_URL} GIT_SHALLOW 1 PREFIX ${CMAKE_BINARY_DIR}/curl @@ -312,10 +306,6 @@ set(CURL_DEPS add_dependencies(curl_external ${CURL_DEPS}) # Now it's time for the main targets! -# -# Read environment variables for compiler flags -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} $ENV{CFLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} $ENV{CXXFLAGS}") # Paths to curl install (adjust as needed) set(CURL_INCLUDE_DIRS @@ -325,10 +315,10 @@ set(CURL_INCLUDE_DIRS set(CURL_LIB_DIR ${CURL_INSTALL_DIR}/lib) # Fuzzing engine -if (DEFINED ENV{LIB_FUZZING_ENGINE}) +if(NOT "$ENV{LIB_FUZZING_ENGINE}" STREQUAL "") # Check to see if ENV{LIB_FUZZING_ENGINE} is a file. If so, use it directly. # Otherwise, assume it's a flag to the compiler. - if (EXISTS $ENV{LIB_FUZZING_ENGINE}) + if(EXISTS "$ENV{LIB_FUZZING_ENGINE}") message(STATUS "Using LIB_FUZZING_ENGINE file: $ENV{LIB_FUZZING_ENGINE}") set(LIB_FUZZING_ENGINE $ENV{LIB_FUZZING_ENGINE}) set(LIB_FUZZING_ENGINE_FLAG "") @@ -367,32 +357,32 @@ set(COMMON_LINK_OPTIONS ${LIB_FUZZING_ENGINE_FLAG}) set(FUZZ_DEPS curl_external ${CURL_DEPS} ${LIB_FUZZING_ENGINE_DEP}) # Helper macro to define a fuzzer target -macro(add_curl_fuzzer name proto) - add_executable(${name} ${COMMON_SOURCES}) - target_compile_options(${name} PRIVATE ${COMMON_FLAGS} -DFUZZ_PROTOCOLS_${proto}) - target_include_directories(${name} PRIVATE ${CURL_INCLUDE_DIRS}) - target_link_libraries(${name} PRIVATE ${COMMON_LINK_LIBS}) - target_link_options(${name} PRIVATE ${COMMON_LINK_OPTIONS}) - add_dependencies(${name} ${FUZZ_DEPS}) +macro(curl_add_fuzzer _name _proto) + add_executable(${_name} ${COMMON_SOURCES}) + target_compile_options(${_name} PRIVATE ${COMMON_FLAGS} -DFUZZ_PROTOCOLS_${_proto}) + target_include_directories(${_name} PRIVATE ${CURL_INCLUDE_DIRS}) + target_link_libraries(${_name} PRIVATE ${COMMON_LINK_LIBS}) + target_link_options(${_name} PRIVATE ${COMMON_LINK_OPTIONS}) + add_dependencies(${_name} ${FUZZ_DEPS}) endmacro() # Main fuzzer and protocol-specific fuzzers -add_curl_fuzzer(curl_fuzzer ALL) -add_curl_fuzzer(curl_fuzzer_dict DICT) -add_curl_fuzzer(curl_fuzzer_file FILE) -add_curl_fuzzer(curl_fuzzer_ftp FTP) -add_curl_fuzzer(curl_fuzzer_gopher GOPHER) -add_curl_fuzzer(curl_fuzzer_http HTTP) -add_curl_fuzzer(curl_fuzzer_https HTTPS) -add_curl_fuzzer(curl_fuzzer_imap IMAP) -add_curl_fuzzer(curl_fuzzer_ldap LDAP) -add_curl_fuzzer(curl_fuzzer_mqtt MQTT) -add_curl_fuzzer(curl_fuzzer_pop3 POP3) -add_curl_fuzzer(curl_fuzzer_rtsp RTSP) -add_curl_fuzzer(curl_fuzzer_smb SMB) -add_curl_fuzzer(curl_fuzzer_smtp SMTP) -add_curl_fuzzer(curl_fuzzer_tftp TFTP) -add_curl_fuzzer(curl_fuzzer_ws WS) +curl_add_fuzzer(curl_fuzzer ALL) +curl_add_fuzzer(curl_fuzzer_dict DICT) +curl_add_fuzzer(curl_fuzzer_file FILE) +curl_add_fuzzer(curl_fuzzer_ftp FTP) +curl_add_fuzzer(curl_fuzzer_gopher GOPHER) +curl_add_fuzzer(curl_fuzzer_http HTTP) +curl_add_fuzzer(curl_fuzzer_https HTTPS) +curl_add_fuzzer(curl_fuzzer_imap IMAP) +curl_add_fuzzer(curl_fuzzer_ldap LDAP) +curl_add_fuzzer(curl_fuzzer_mqtt MQTT) +curl_add_fuzzer(curl_fuzzer_pop3 POP3) +curl_add_fuzzer(curl_fuzzer_rtsp RTSP) +curl_add_fuzzer(curl_fuzzer_smb SMB) +curl_add_fuzzer(curl_fuzzer_smtp SMTP) +curl_add_fuzzer(curl_fuzzer_tftp TFTP) +curl_add_fuzzer(curl_fuzzer_ws WS) # BUFQ fuzzer add_executable(curl_fuzzer_bufq fuzz_bufq.cc) diff --git a/LICENSE b/LICENSE index 613203047..e611d51ff 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2017 curl-fuzzer authors +Copyright (c) curl-fuzzer authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/REPRODUCING.md b/REPRODUCING.md index 57c364394..8d2ba09a2 100644 --- a/REPRODUCING.md +++ b/REPRODUCING.md @@ -1,5 +1,5 @@ # Reproducing OSS-Fuzz issues -## Reproducible vs non-reproducible +## Reproducible vs non-reproducible OSS-Fuzz generates two kinds of issues; reproducible and non-reproducible. It _generally_ only raises issues for reproducible problems; that is, a testcase that can be passed to the relevant fuzzer which causes a crash. They are marked as such in the OSS-Fuzz dashboard. These instructions are for diagnosing reproducible problems. diff --git a/codecoverage.sh.off b/codecoverage.sh.off index ccb352d3b..2ff63b4fe 100755 --- a/codecoverage.sh.off +++ b/codecoverage.sh.off @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex @@ -23,7 +23,7 @@ INSTALLDIR=/tmp/curlcov_install # if there have been earlier mainline runs locally if [[ -d .deps/ && -f Makefile ]] then - make distclean + make distclean fi # Install openssl diff --git a/codeprofile.sh.off b/codeprofile.sh.off index 01f6d7876..185dddc27 100755 --- a/codeprofile.sh.off +++ b/codeprofile.sh.off @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex diff --git a/curl_fuzzer.cc b/curl_fuzzer.cc index b66130f58..0eccea806 100644 --- a/curl_fuzzer.cc +++ b/curl_fuzzer.cc @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2017 - 2022, Max Dymond, , et al. + * Copyright (C) Max Dymond, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/curl_fuzzer.h b/curl_fuzzer.h index bb2beae0b..1e13d710b 100644 --- a/curl_fuzzer.h +++ b/curl_fuzzer.h @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2017, Max Dymond, , et al. + * Copyright (C) Max Dymond, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -26,60 +26,60 @@ /** * TLV types. */ -#define TLV_TYPE_URL 1 -#define TLV_TYPE_RESPONSE0 2 -#define TLV_TYPE_USERNAME 3 -#define TLV_TYPE_PASSWORD 4 -#define TLV_TYPE_POSTFIELDS 5 -#define TLV_TYPE_HEADER 6 -#define TLV_TYPE_COOKIE 7 -#define TLV_TYPE_UPLOAD1 8 -#define TLV_TYPE_RANGE 9 -#define TLV_TYPE_CUSTOMREQUEST 10 -#define TLV_TYPE_MAIL_RECIPIENT 11 -#define TLV_TYPE_MAIL_FROM 12 -#define TLV_TYPE_MIME_PART 13 -#define TLV_TYPE_MIME_PART_NAME 14 -#define TLV_TYPE_MIME_PART_DATA 15 -#define TLV_TYPE_HTTPAUTH 16 -#define TLV_TYPE_RESPONSE1 17 -#define TLV_TYPE_RESPONSE2 18 -#define TLV_TYPE_RESPONSE3 19 -#define TLV_TYPE_RESPONSE4 20 -#define TLV_TYPE_RESPONSE5 21 -#define TLV_TYPE_RESPONSE6 22 -#define TLV_TYPE_RESPONSE7 23 -#define TLV_TYPE_RESPONSE8 24 -#define TLV_TYPE_RESPONSE9 25 -#define TLV_TYPE_RESPONSE10 26 -#define TLV_TYPE_OPTHEADER 27 -#define TLV_TYPE_NOBODY 28 -#define TLV_TYPE_FOLLOWLOCATION 29 -#define TLV_TYPE_ACCEPTENCODING 30 -#define TLV_TYPE_SECOND_RESPONSE0 31 -#define TLV_TYPE_SECOND_RESPONSE1 32 -#define TLV_TYPE_WILDCARDMATCH 33 -#define TLV_TYPE_RTSP_REQUEST 34 -#define TLV_TYPE_RTSP_SESSION_ID 35 -#define TLV_TYPE_RTSP_STREAM_URI 36 -#define TLV_TYPE_RTSP_TRANSPORT 37 -#define TLV_TYPE_RTSP_CLIENT_CSEQ 38 -#define TLV_TYPE_MAIL_AUTH 39 -#define TLV_TYPE_HTTP_VERSION 40 -#define TLV_TYPE_DOH_URL 41 -#define TLV_TYPE_LOGIN_OPTIONS 42 -#define TLV_TYPE_XOAUTH2_BEARER 43 -#define TLV_TYPE_USERPWD 44 -#define TLV_TYPE_USERAGENT 45 -#define TLV_TYPE_NETRC 46 -#define TLV_TYPE_SSH_HOST_PUBLIC_KEY_SHA256 47 -#define TLV_TYPE_POST 48 -#define TLV_TYPE_WS_OPTIONS 49 -#define TLV_TYPE_CONNECT_ONLY 50 -#define TLV_TYPE_HSTS 51 -#define TLV_TYPE_HTTPPOSTBODY 52 -#define TLV_TYPE_PROXY 53 -#define TLV_TYPE_PROXYTYPE 54 +#define TLV_TYPE_URL 1 +#define TLV_TYPE_RESPONSE0 2 +#define TLV_TYPE_USERNAME 3 +#define TLV_TYPE_PASSWORD 4 +#define TLV_TYPE_POSTFIELDS 5 +#define TLV_TYPE_HEADER 6 +#define TLV_TYPE_COOKIE 7 +#define TLV_TYPE_UPLOAD1 8 +#define TLV_TYPE_RANGE 9 +#define TLV_TYPE_CUSTOMREQUEST 10 +#define TLV_TYPE_MAIL_RECIPIENT 11 +#define TLV_TYPE_MAIL_FROM 12 +#define TLV_TYPE_MIME_PART 13 +#define TLV_TYPE_MIME_PART_NAME 14 +#define TLV_TYPE_MIME_PART_DATA 15 +#define TLV_TYPE_HTTPAUTH 16 +#define TLV_TYPE_RESPONSE1 17 +#define TLV_TYPE_RESPONSE2 18 +#define TLV_TYPE_RESPONSE3 19 +#define TLV_TYPE_RESPONSE4 20 +#define TLV_TYPE_RESPONSE5 21 +#define TLV_TYPE_RESPONSE6 22 +#define TLV_TYPE_RESPONSE7 23 +#define TLV_TYPE_RESPONSE8 24 +#define TLV_TYPE_RESPONSE9 25 +#define TLV_TYPE_RESPONSE10 26 +#define TLV_TYPE_OPTHEADER 27 +#define TLV_TYPE_NOBODY 28 +#define TLV_TYPE_FOLLOWLOCATION 29 +#define TLV_TYPE_ACCEPTENCODING 30 +#define TLV_TYPE_SECOND_RESPONSE0 31 +#define TLV_TYPE_SECOND_RESPONSE1 32 +#define TLV_TYPE_WILDCARDMATCH 33 +#define TLV_TYPE_RTSP_REQUEST 34 +#define TLV_TYPE_RTSP_SESSION_ID 35 +#define TLV_TYPE_RTSP_STREAM_URI 36 +#define TLV_TYPE_RTSP_TRANSPORT 37 +#define TLV_TYPE_RTSP_CLIENT_CSEQ 38 +#define TLV_TYPE_MAIL_AUTH 39 +#define TLV_TYPE_HTTP_VERSION 40 +#define TLV_TYPE_DOH_URL 41 +#define TLV_TYPE_LOGIN_OPTIONS 42 +#define TLV_TYPE_XOAUTH2_BEARER 43 +#define TLV_TYPE_USERPWD 44 +#define TLV_TYPE_USERAGENT 45 +#define TLV_TYPE_NETRC 46 +#define TLV_TYPE_SSH_HOST_PUBLIC_KEY_SHA256 47 +#define TLV_TYPE_POST 48 +#define TLV_TYPE_WS_OPTIONS 49 +#define TLV_TYPE_CONNECT_ONLY 50 +#define TLV_TYPE_HSTS 51 +#define TLV_TYPE_HTTPPOSTBODY 52 +#define TLV_TYPE_PROXY 53 +#define TLV_TYPE_PROXYTYPE 54 #define TLV_TYPE_PROXYUSERPWD 100 #define TLV_TYPE_REFERER 101 @@ -274,25 +274,25 @@ #define MAXIMUM_WRITE_LENGTH 52428800 /* convenience string for HTTPPOST body name */ -#define FUZZ_HTTPPOST_NAME "test" +#define FUZZ_HTTPPOST_NAME "test" /* Cookie-jar WRITE (CURLOPT_COOKIEJAR) path. */ #define FUZZ_COOKIE_JAR_PATH "/dev/null" /* Cookie-jar READ (CURLOPT_COOKIEFILE) path. */ -#define FUZZ_RO_COOKIE_FILE_PATH "/dev/null" +#define FUZZ_RO_COOKIE_FILE_PATH "/dev/null" /* Alt-Svc header cache path */ -#define FUZZ_ALT_SVC_HEADER_CACHE_PATH "/dev/null" +#define FUZZ_ALT_SVC_HEADER_CACHE_PATH "/dev/null" /* HSTS header cache path */ -#define FUZZ_HSTS_HEADER_CACHE_PATH "/dev/null" +#define FUZZ_HSTS_HEADER_CACHE_PATH "/dev/null" /* Certificate Revocation List file path */ -#define FUZZ_CRL_FILE_PATH "/dev/null" +#define FUZZ_CRL_FILE_PATH "/dev/null" /* .netrc file path */ -#define FUZZ_NETRC_FILE_PATH "/dev/null" +#define FUZZ_NETRC_FILE_PATH "/dev/null" /* Number of supported responses */ #define TLV_MAX_NUM_RESPONSES 11 diff --git a/curl_fuzzer_callback.cc b/curl_fuzzer_callback.cc index 86f9d304b..f34d8b1b2 100644 --- a/curl_fuzzer_callback.cc +++ b/curl_fuzzer_callback.cc @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2017, Max Dymond, , et al. + * Copyright (C) Max Dymond, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/curl_fuzzer_tlv.cc b/curl_fuzzer_tlv.cc index 07de82e26..5aae1aa8d 100644 --- a/curl_fuzzer_tlv.cc +++ b/curl_fuzzer_tlv.cc @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2017, Max Dymond, , et al. + * Copyright (C) Max Dymond, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -462,10 +462,10 @@ void fuzz_setup_http_post(FUZZ_DATA *fuzz, TLV *tlv) * for lots of others which could be added here. */ curl_formadd(&post, &last, - CURLFORM_COPYNAME, FUZZ_HTTPPOST_NAME, - CURLFORM_PTRCONTENTS, fuzz->post_body, - CURLFORM_CONTENTLEN, (curl_off_t) strlen(fuzz->post_body), - CURLFORM_END); + CURLFORM_COPYNAME, FUZZ_HTTPPOST_NAME, + CURLFORM_PTRCONTENTS, fuzz->post_body, + CURLFORM_CONTENTLEN, (curl_off_t) strlen(fuzz->post_body), + CURLFORM_END); fuzz->last_post_part = last; fuzz->httppost = post; diff --git a/fuzz_bufq.cc b/fuzz_bufq.cc index 995b62d7b..b63e59065 100644 --- a/fuzz_bufq.cc +++ b/fuzz_bufq.cc @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2017 - 2022, Max Dymond, , et al. + * Copyright (C) Max Dymond, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/fuzz_bufq.h b/fuzz_bufq.h index f3b103d0f..e1adbe20d 100644 --- a/fuzz_bufq.h +++ b/fuzz_bufq.h @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2017, Max Dymond, , et al. + * Copyright (C) Max Dymond, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -49,4 +49,4 @@ #define FV_PRINTF(verbose, ...) \ if(!!(verbose)) { \ printf(__VA_ARGS__); \ - } \ No newline at end of file + } diff --git a/fuzz_fnmatch.cc b/fuzz_fnmatch.cc index 1706037fd..a0e340d5e 100644 --- a/fuzz_fnmatch.cc +++ b/fuzz_fnmatch.cc @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2017, Max Dymond, , et al. + * Copyright (C) Max Dymond, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/fuzz_url.cc b/fuzz_url.cc index 9cefdcfe7..7a0214d04 100644 --- a/fuzz_url.cc +++ b/fuzz_url.cc @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2017 - 2022, Max Dymond, , et al. + * Copyright (C) Max Dymond, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -53,4 +53,3 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) /* This function must always return 0. Non-zero codes are reserved. */ return 0; } - diff --git a/generate_fnmatch.sh b/generate_fnmatch.sh index df85f854f..5785cdc09 100755 --- a/generate_fnmatch.sh +++ b/generate_fnmatch.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Redirect the output of this script to a test file. -printf "$1\0$2\0" +printf '%s\0%s\0' "$1" "$2" diff --git a/mainline.sh b/mainline.sh index 0cb1eae74..21fa55476 100755 --- a/mainline.sh +++ b/mainline.sh @@ -1,9 +1,9 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex # Save off the current folder as the build root. -export BUILD_ROOT=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +export BUILD_ROOT; BUILD_ROOT=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) SCRIPTDIR=${BUILD_ROOT}/scripts # Parse the options. @@ -30,4 +30,4 @@ export CXXFLAGS="-fsanitize=address,fuzzer-no-link -stdlib=libstdc++ $FUZZ_FLAG" export CPPFLAGS="$FUZZ_FLAG" export OPENSSLFLAGS="-fno-sanitize=alignment -lstdc++" -${SCRIPTDIR}/compile_target.sh ${TARGET} +"${SCRIPTDIR}"/compile_target.sh "${TARGET}" diff --git a/ossconfig/http.dict b/ossconfig/http.dict index 57b7b4372..0afa0b4e1 100644 --- a/ossconfig/http.dict +++ b/ossconfig/http.dict @@ -38,4 +38,3 @@ "Pragma:" "no-cache" "Host:" - diff --git a/ossfuzz.sh b/ossfuzz.sh index 895e3d2da..26f672156 100755 --- a/ossfuzz.sh +++ b/ossfuzz.sh @@ -1,4 +1,4 @@ -#!/bin/bash -eu +#!/usr/bin/env bash #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -6,7 +6,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 2018-2021, Max Dymond, , et al. +# Copyright (C) Max Dymond, , et al. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms @@ -21,13 +21,13 @@ # ########################################################################### +set -eu + # Save off the current folder as the build root. export BUILD_ROOT=$PWD SCRIPTDIR=${BUILD_ROOT}/scripts -. ${SCRIPTDIR}/fuzz_targets - -GDBDIR=/src/gdb +. "${SCRIPTDIR}"/fuzz_targets echo "BUILD_ROOT: $BUILD_ROOT" echo "FUZZ_TARGETS: $FUZZ_TARGETS" @@ -36,7 +36,7 @@ echo "FUZZ_TARGETS: $FUZZ_TARGETS" export CURL_SOURCE_DIR=/src/curl # Compile the fuzzers. -${SCRIPTDIR}/compile_target.sh fuzz +"${SCRIPTDIR}"/compile_target.sh fuzz # Zip up the seed corpus. scripts/create_zip.sh @@ -44,8 +44,8 @@ scripts/create_zip.sh # Copy the fuzzers over. for TARGET in $FUZZ_TARGETS do - cp -v build/${TARGET} ${TARGET}_seed_corpus.zip $OUT/ + cp -v build/"${TARGET}" "${TARGET}_seed_corpus.zip" "$OUT"/ done # Copy dictionary and options file to $OUT. -cp -v ossconfig/*.dict ossconfig/*.options $OUT/ +cp -v ossconfig/*.dict ossconfig/*.options "$OUT"/ diff --git a/pyproject.toml b/pyproject.toml index c734808b8..6bd33d647 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,15 +22,23 @@ classifiers = [ "Topic :: Software Development :: Testing", "Typing :: Typed", ] -dependencies = [ - "scapy (>=2.6.1,<3.0.0)", - "jinja2 (>=3.1.0,<4.0.0)", -] [project.optional-dependencies] +ci-tests = [ + "ruff==0.14.2", +] +page-gen = [ + "scapy>=2.6.1,<3.0.0", + "jinja2>=3.1.0,<4.0.0", +] browser-tests = [ + "jinja2>=3.1.0,<4.0.0", "pytest>=8.3,<9", "playwright>=1.55,<1.56", + "pytest_playwright>=0.7.1,<0.8", +] +python-tests = [ + "pytest>=8.3,<9", ] [project.scripts] @@ -52,10 +60,16 @@ build-backend = "setuptools.build_meta" dev = [ "mypy==1.18.2", "ruff==0.14.2", + "scapy>=2.6.1,<3.0.0", +] +page-gen = [ + "jinja2>=3.1.0,<4.0.0", ] browser-tests = [ + "jinja2>=3.1.0,<4.0.0", "pytest>=8.3,<9", "playwright>=1.55,<1.56", + "pytest_playwright>=0.7.1,<0.8", ] [tool.mypy] @@ -80,13 +94,10 @@ target-version = "py39" [tool.ruff.lint] extend-select = [ - "D" # pydocstyle -] - -ignore = [ - "D400", - "D401", - "D415" + "B007","B016","C405","C416","COM818", + "D200","D213","D204","D401","D415", + "FURB129","N818","PERF401","PERF403","PIE790","PIE808","PLW0127", + "Q004","RUF010","SIM101","SIM117","SIM118","TRY400","TRY401" ] [tool.ruff.format] diff --git a/scripts/check_data.sh b/scripts/check_data.sh index 99002dcaf..9fa83595d 100755 --- a/scripts/check_data.sh +++ b/scripts/check_data.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash SCRIPTDIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) BUILD_ROOT=$(readlink -f "${SCRIPTDIR}/..") @@ -11,7 +11,7 @@ then fi # Exit if the build root has not been defined. -. ${SCRIPTDIR}/fuzz_targets +. "${SCRIPTDIR}"/fuzz_targets if [[ ${DEBUG} == 1 ]] then @@ -44,6 +44,7 @@ do PERCALL=100 fi - find ${BUILD_ROOT}/corpora/${TARGET}/ ${EXTRA_CORPUS} -type f -print0 | xargs -0 -L${PERCALL} ${BUILD_ROOT}/build/${TARGET} + # shellcheck disable=SC2248 + find "${BUILD_ROOT}/corpora/${TARGET}/" ${EXTRA_CORPUS} -type f -print0 | xargs -0 -L${PERCALL} "${BUILD_ROOT}/build/${TARGET}" fi done diff --git a/scripts/compile_target.sh b/scripts/compile_target.sh index f422bfde6..7cd27ba54 100755 --- a/scripts/compile_target.sh +++ b/scripts/compile_target.sh @@ -1,4 +1,4 @@ -#!/bin/bash -eu +#!/usr/bin/env bash #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | @@ -21,20 +21,22 @@ # ########################################################################### +set -eu + TARGET=${1:-fuzz} SCRIPTDIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -export BUILD_ROOT=$(readlink -f "${SCRIPTDIR}/..") +export BUILD_ROOT; BUILD_ROOT=$(readlink -f "${SCRIPTDIR}/..") # Check for GDB-specific behaviour by checking for the GDBMODE flag. # - Compile with -O0 so that DEBUGASSERTs can be debugged in gdb. if [[ -n ${GDBMODE:-} ]] then - [[ -n ${CFLAGS:-} ]] && export CFLAGS="${CFLAGS} -O0" || export CFLAGS="-O0" - [[ -n ${CXXFLAGS:-} ]] && export CXXFLAGS="${CXXFLAGS} -O0" || export CXXFLAGS="-O0" - CMAKE_GDB_FLAG="-DBUILD_GDB=ON" + export CFLAGS="${CFLAGS:-} -O0" + export CXXFLAGS="${CXXFLAGS:-} -O0" + CMAKE_GDB_FLAG="-DBUILD_GDB=ON" else - CMAKE_GDB_FLAG="-DBUILD_GDB=OFF" + CMAKE_GDB_FLAG="-DBUILD_GDB=OFF" fi echo "BUILD_ROOT: $BUILD_ROOT" @@ -48,9 +50,9 @@ echo "ARCHITECTURE: ${ARCHITECTURE:-undefined}" if [[ "${ARCHITECTURE:-}" == "i386" ]] then - CMAKE_VERBOSE_FLAG="-v" + CMAKE_VERBOSE_FLAG="-v" else - CMAKE_VERBOSE_FLAG="" + CMAKE_VERBOSE_FLAG="" fi export MAKEFLAGS; MAKEFLAGS+=" -s -j$(($(nproc) + 0))" @@ -58,13 +60,14 @@ echo "MAKEFLAGS: ${MAKEFLAGS}" # Create a build directory for the dependencies. BUILD_DIR=${BUILD_ROOT}/build -mkdir -p ${BUILD_DIR} +mkdir -p "${BUILD_DIR}" options='' command -v ninja >/dev/null 2>&1 && options+=' -G Ninja' # Compile the dependencies. -pushd ${BUILD_DIR} -cmake ${CMAKE_GDB_FLAG} .. ${options} -cmake --build . --target ${TARGET} ${CMAKE_VERBOSE_FLAG} +pushd "${BUILD_DIR}" +# shellcheck disable=SC2086 +cmake "${CMAKE_GDB_FLAG}" .. ${options} +cmake --build . --target "${TARGET}" ${CMAKE_VERBOSE_FLAG} popd diff --git a/scripts/create_zip.sh b/scripts/create_zip.sh index 87c010b38..f4645b5cf 100755 --- a/scripts/create_zip.sh +++ b/scripts/create_zip.sh @@ -1,15 +1,15 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex SCRIPTDIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) BUILD_ROOT=$(readlink -f "${SCRIPTDIR}/..") -. ${SCRIPTDIR}/fuzz_targets +. "${SCRIPTDIR}"/fuzz_targets for TARGET in ${FUZZ_TARGETS} do - pushd ${BUILD_ROOT}/corpora/${TARGET} - zip ../../${TARGET}_seed_corpus.zip * - popd + pushd "${BUILD_ROOT}/corpora/${TARGET}" + zip ../../"${TARGET}_seed_corpus.zip" ./* + popd done diff --git a/scripts/fuzz_targets b/scripts/fuzz_targets index 4d02af65f..cba08fb2c 100644 --- a/scripts/fuzz_targets +++ b/scripts/fuzz_targets @@ -1,3 +1,3 @@ -#!/bin/bash +#!/usr/bin/env bash export FUZZ_TARGETS="curl_fuzzer_dict curl_fuzzer_file curl_fuzzer_ftp curl_fuzzer_gopher curl_fuzzer_http curl_fuzzer_https curl_fuzzer_imap curl_fuzzer_ldap curl_fuzzer_mqtt curl_fuzzer_pop3 curl_fuzzer_rtsp curl_fuzzer_smb curl_fuzzer_smtp curl_fuzzer_tftp curl_fuzzer_ws curl_fuzzer fuzz_url curl_fuzzer_bufq" diff --git a/scripts/ossfuzzdeps.sh b/scripts/ossfuzzdeps.sh index 2e836762b..30eb07d49 100755 --- a/scripts/ossfuzzdeps.sh +++ b/scripts/ossfuzzdeps.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # This script is called from google/oss-fuzz:projects/curl/Dockerfile to install necessary # dependencies for building curl fuzz targets. @@ -6,32 +6,31 @@ # Use it to compile and install all the dependencies set -ex -SCRIPTDIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) # Work out if we need to install with sudo or not. if [[ $(id -u) -eq 0 ]] then - # We are root, so we can install without sudo. - echo "Running as root, no sudo required." - export SUDO="" + # We are root, so we can install without sudo. + echo "Running as root, no sudo required." + export SUDO="" else - # We are not root, so we need to use sudo. - echo "Running as non-root, using sudo." - export SUDO="sudo" + # We are not root, so we need to use sudo. + echo "Running as non-root, using sudo." + export SUDO="sudo" fi # Download dependencies for oss-fuzz $SUDO apt-get -o Dpkg::Use-Pty=0 update $SUDO apt-get -o Dpkg::Use-Pty=0 install -y \ - make \ - autoconf \ - automake \ - libtool \ - libgmp-dev \ - libssl-dev \ - zlib1g-dev \ - pkg-config \ - wget \ - cmake \ - ninja-build \ - groff-base + make \ + autoconf \ + automake \ + libtool \ + libgmp-dev \ + libssl-dev \ + zlib1g-dev \ + pkg-config \ + wget \ + cmake \ + ninja-build \ + groff-base diff --git a/src/curl_fuzzer_tools/corpus.py b/src/curl_fuzzer_tools/corpus.py index 0a411bbde..5b0a6d90b 100644 --- a/src/curl_fuzzer_tools/corpus.py +++ b/src/curl_fuzzer_tools/corpus.py @@ -1,4 +1,4 @@ -"""Common corpus functions""" +"""Common corpus functions.""" import logging import struct @@ -11,7 +11,7 @@ class BaseType(object): - """Known TLV types""" + """Known TLV types.""" TYPE_URL = 1 TYPE_RSP0 = 2 @@ -484,41 +484,41 @@ class BaseType(object): class TLVEncoder(BaseType): - """Class for encoding TLVs""" + """Class for encoding TLVs.""" def __init__(self, output: BinaryIO, test_data: TestData) -> None: - """Create a TLVEncoder object""" + """Create a TLVEncoder object.""" self.output = output self.test_data = test_data def write_string(self, tlv_type: int, wstring: str) -> None: - """Write a string TLV to the output""" + """Write a string TLV to the output.""" data = wstring.encode("utf-8") self.write_tlv(tlv_type, len(data), data) def write_u32(self, tlv_type: int, num: int) -> None: - """Write an unsigned 32-bit integer TLV to the output""" + """Write an unsigned 32-bit integer TLV to the output.""" data = struct.pack("!L", num) self.write_tlv(tlv_type, len(data), data) def write_bytes(self, tlv_type: int, bytedata: bytes) -> None: - """Write a bytes TLV to the output""" + """Write a bytes TLV to the output.""" self.write_tlv(tlv_type, len(bytedata), bytedata) def maybe_write_string(self, tlv_type: int, wstring: Optional[str]) -> None: - """Write a string TLV to the output if specified""" + """Write a string TLV to the output if specified.""" if wstring is not None: self.write_string(tlv_type, wstring) def maybe_write_u32(self, tlv_type: int, num: Optional[int]) -> None: - """Write an unsigned 32-bit integer TLV to the output if specified""" + """Write an unsigned 32-bit integer TLV to the output if specified.""" if num is not None: self.write_u32(tlv_type, num) def maybe_write_response( self, rsp_type: int, rsp: Optional[str], rsp_file: Optional[Path], rsp_test: int ) -> None: - """Write a response TLV to the output if specified""" + """Write a response TLV to the output if specified.""" if rsp: self.write_bytes(rsp_type, rsp.encode("utf-8")) elif rsp_file: @@ -529,7 +529,7 @@ def maybe_write_response( self.write_bytes(rsp_type, wstring.encode("utf-8")) def write_mimepart(self, namevalue: str) -> None: - """Write a MIME part TLV to the output""" + """Write a MIME part TLV to the output.""" (name, value) = namevalue.split(":", 1) # Create some mimepart TLVs for the name and value @@ -550,7 +550,7 @@ def write_mimepart(self, namevalue: str) -> None: def encode_tlv( self, tlv_type: int, tlv_length: int, tlv_data: Optional[bytes] = None ) -> bytes: - """Encodes the Type, Length, and Value into a bytes array""" + """Encode the Type, Length, and Value into a bytes array.""" log.debug( "Encoding TLV %r, length %d, data %r", self.TYPEMAP.get(tlv_type, ""), @@ -568,7 +568,7 @@ def encode_tlv( def write_tlv( self, tlv_type: int, tlv_length: int, tlv_data: Optional[bytes] = None ) -> None: - """Writes an encoded TLV to the output as bytes""" + """Write an encoded TLV to the output as bytes.""" log.debug( "Writing TLV %r, length %d, data %r", self.TYPEMAP.get(tlv_type, ""), @@ -581,13 +581,13 @@ def write_tlv( class TLVContents(BaseType): - """Class for TLV contents""" + """Class for TLV contents.""" TLV_DECODE_FMT = "!HL" TLV_DECODE_FMT_LEN = struct.calcsize(TLV_DECODE_FMT) def __init__(self, data: bytes) -> None: - """Create a TLVContents object""" + """Create a TLVContents object.""" # Parse the data to populate the TLV fields (stype, slen) = struct.unpack( self.TLV_DECODE_FMT, data[0 : self.TLV_DECODE_FMT_LEN] @@ -601,7 +601,7 @@ def __init__(self, data: bytes) -> None: ] def __repr__(self) -> str: - """Return a string representation of the TLVContents object""" + """Return a string representation of the TLVContents object.""" stype = self.TYPEMAP.get(self.type, "") return ( f"{self.__class__.__name__}(type={stype!r} ({self.type!r}), " @@ -609,27 +609,27 @@ def __repr__(self) -> str: ) def total_length(self) -> int: - """Return the total length of the TLV, including the header""" + """Return the total length of the TLV, including the header.""" return self.TLV_DECODE_FMT_LEN + self.length class TLVDecoder(BaseType): - """Class for decoding TLVs""" + """Class for decoding TLVs.""" def __init__(self, inputdata: bytes) -> None: - """Create a TLVDecoder object""" + """Create a TLVDecoder object.""" self.inputdata = inputdata self.pos = 0 self.tlv: Optional["TLVContents"] = None def __iter__(self) -> "TLVDecoder": - """Return an iterator for the TLVs""" + """Return an iterator for the TLVs.""" self.pos = 0 self.tlv = None return self def __next__(self) -> "TLVContents": - """Return the next TLV in the input data""" + """Return the next TLV in the input data.""" if self.tlv: self.pos += self.tlv.total_length() diff --git a/src/curl_fuzzer_tools/corpus_to_pcap.py b/src/curl_fuzzer_tools/corpus_to_pcap.py old mode 100644 new mode 100755 index 08af5b49f..b8ec2e289 --- a/src/curl_fuzzer_tools/corpus_to_pcap.py +++ b/src/curl_fuzzer_tools/corpus_to_pcap.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # """Script which converts corpus files to pcap files.""" @@ -74,7 +74,7 @@ def corpus_to_pcap(args: argparse.Namespace) -> None: def main() -> None: - """Main function""" + """Begin main function.""" parser = argparse.ArgumentParser() parser.add_argument("--input", required=True) parser.add_argument("--output", required=True) diff --git a/src/curl_fuzzer_tools/curl_test_data.py b/src/curl_fuzzer_tools/curl_test_data.py index f4cbfc4d7..85ec5da8a 100755 --- a/src/curl_fuzzer_tools/curl_test_data.py +++ b/src/curl_fuzzer_tools/curl_test_data.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Project ___| | | | _ \| | @@ -6,7 +6,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 2017, Daniel Stenberg, , et al. +# Copyright (C) Daniel Stenberg, , et al. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms @@ -19,7 +19,7 @@ # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY # KIND, either express or implied. # -"""Module for extracting test data from the test data folder""" +"""Module for extracting test data from the test data folder.""" from __future__ import absolute_import, division, print_function, unicode_literals @@ -34,14 +34,14 @@ class TestData(object): - """Class for extracting test data from the curl test data folder""" + """Class for extracting test data from the curl test data folder.""" def __init__(self, data_folder: Path) -> None: - """Create a TestData object""" + """Create a TestData object.""" self.data_folder = data_folder def get_test_data(self, test_number: int) -> str: - """Get the test data for a given test number""" + """Get the test data for a given test number.""" # Create the test file name filename = self.data_folder / f"test{test_number}" diff --git a/src/curl_fuzzer_tools/generate_corpus.py b/src/curl_fuzzer_tools/generate_corpus.py index 47185c293..9d712cc58 100755 --- a/src/curl_fuzzer_tools/generate_corpus.py +++ b/src/curl_fuzzer_tools/generate_corpus.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # """Simple script which generates corpus files.""" @@ -131,7 +131,7 @@ def generate_corpus(args: argparse.Namespace) -> None: def main() -> None: - """Main function""" + """Begin main function.""" parser = argparse.ArgumentParser() parser.add_argument("--output", required=True) parser.add_argument("--url", required=True) @@ -175,13 +175,13 @@ def main() -> None: upload1.add_argument("--upload1") upload1.add_argument("--upload1file") - for ii in range(0, 11): + for ii in range(11): group = parser.add_mutually_exclusive_group() group.add_argument("--rsp{0}".format(ii)) group.add_argument("--rsp{0}file".format(ii)) group.add_argument("--rsp{0}test".format(ii), type=int) - for ii in range(0, 2): + for ii in range(2): group = parser.add_mutually_exclusive_group() group.add_argument("--secrsp{0}".format(ii)) group.add_argument("--secrsp{0}file".format(ii)) diff --git a/src/curl_fuzzer_tools/generate_decoder_html.py b/src/curl_fuzzer_tools/generate_decoder_html.py old mode 100644 new mode 100755 index 36ff5fc2b..a45046234 --- a/src/curl_fuzzer_tools/generate_decoder_html.py +++ b/src/curl_fuzzer_tools/generate_decoder_html.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """Generate an interactive HTML page for decoding curl corpus files.""" from __future__ import annotations @@ -65,7 +66,7 @@ def main() -> Path: def run() -> None: - """Wrapper to set up logging before running the tool.""" + """Set up logging before running the tool.""" common_logging(__name__, __file__) main() diff --git a/src/curl_fuzzer_tools/generate_matrix.py b/src/curl_fuzzer_tools/generate_matrix.py old mode 100644 new mode 100755 index 5a199624d..a549a27f8 --- a/src/curl_fuzzer_tools/generate_matrix.py +++ b/src/curl_fuzzer_tools/generate_matrix.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Generate a matrix of fuzzers for Github Actions""" +"""Generate a matrix of fuzzers for Github Actions.""" import json import logging @@ -9,7 +9,7 @@ log = logging.getLogger(__name__) def main() -> None: - """Main function""" + """Begin main function.""" # Get FUZZ_TARGETS from the environment fuzz_targets = os.getenv("FUZZ_TARGETS", "") log.info("Fuzz targets: %s", fuzz_targets) @@ -27,7 +27,7 @@ def main() -> None: print(f"matrix={json.dumps(output_data)}") def run() -> None: - """Run the main function""" + """Run the main function.""" logging.basicConfig(level=logging.INFO, stream=sys.stderr) main() diff --git a/src/curl_fuzzer_tools/logger.py b/src/curl_fuzzer_tools/logger.py index edf08c1ee..a67c2fe56 100644 --- a/src/curl_fuzzer_tools/logger.py +++ b/src/curl_fuzzer_tools/logger.py @@ -1,4 +1,4 @@ -"""Common logging functionality""" +"""Common logging functionality.""" import logging import sys diff --git a/src/curl_fuzzer_tools/py.typed b/src/curl_fuzzer_tools/py.typed deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/curl_fuzzer_tools/read_corpus.py b/src/curl_fuzzer_tools/read_corpus.py index 045859713..9554cd9f2 100755 --- a/src/curl_fuzzer_tools/read_corpus.py +++ b/src/curl_fuzzer_tools/read_corpus.py @@ -21,7 +21,7 @@ def read_corpus(corpus_file: Path) -> None: def main() -> None: - """Main function""" + """Begin main function.""" parser = argparse.ArgumentParser() parser.add_argument( "input", diff --git a/standalone_fuzz_target_runner.cc b/standalone_fuzz_target_runner.cc index dc0b874c1..2cdd4ed60 100644 --- a/standalone_fuzz_target_runner.cc +++ b/standalone_fuzz_target_runner.cc @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2017, Max Dymond, , et al. + * Copyright (C) Max Dymond, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/testinput.h b/testinput.h index cfa4b964e..2820e417d 100644 --- a/testinput.h +++ b/testinput.h @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2017, Max Dymond, , et al. + * Copyright (C) Max Dymond, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -21,4 +21,4 @@ ***************************************************************************/ #include -extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); \ No newline at end of file +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); diff --git a/tests/browser/test_corpus_decoder.py b/tests/browser/test_corpus_decoder.py index 6ed7118ef..ca93a48e8 100644 --- a/tests/browser/test_corpus_decoder.py +++ b/tests/browser/test_corpus_decoder.py @@ -63,110 +63,111 @@ def test_upload_repository_corpus(tmp_path: Path) -> None: @pytest.mark.skipif(sync_playwright is None, reason="Playwright not installed") @pytest.mark.parametrize("scheme", ["light", "dark"]) def test_accessibility_after_upload_in_light_and_dark(tmp_path: Path, scheme: Literal["light", "dark"]) -> None: - """Basic accessibility smoke: after upload, key elements are visible in both schemes. - - This test toggles prefers-color-scheme and checks that: - - The dark/light CSS actually applies (by inspecting body background color in dark) - - Headings and summary items remain present - - A coarse contrast check (>= 3.0) passes between body background and heading text - to catch regressions where text becomes unreadable. - """ - html_path = tmp_path / "index.html" - generate_html(html_path) - - corpus_path = _example_corpus() - expected_tlvs = _expected_tlvs(corpus_path) - - file_url = html_path.resolve().as_uri() - - if sync_playwright is None: - pytest.skip("Playwright not installed") - - with sync_playwright() as playwright: - browser = playwright.chromium.launch() - page = browser.new_page() - page.emulate_media(color_scheme=scheme) # Apply requested color scheme - page.goto(file_url) - - # Upload corpus and wait for summary - page.set_input_files("#corpus-input", str(corpus_path)) - page.wait_for_selector(f"text=Decoded {expected_tlvs} TLVs successfully.") - - # Verify headings and summary exist - assert page.locator("header h1").count() == 1 - assert page.locator("#summary-count").inner_text().strip() == str(expected_tlvs) - - # Page-wide contrast sweep over visible text nodes; collect failures (< 3.0) - results = page.evaluate( - r""" - () => { - function parseColor(c) { - const m = c.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([0-9.]+))?\)/); - if (!m) return {r:0,g:0,b:0,a:1}; - return { r: +m[1], g: +m[2], b: +m[3], a: m[4] === undefined ? 1 : +m[4] }; - } - function blend(top, bottom) { - // Alpha composite 'top' over 'bottom'; both are {r,g,b,a} with a in [0,1] - const a = top.a + bottom.a * (1 - top.a); - const r = Math.round((top.r * top.a + bottom.r * bottom.a * (1 - top.a)) / (a || 1)); - const g = Math.round((top.g * top.a + bottom.g * bottom.a * (1 - top.a)) / (a || 1)); - const b = Math.round((top.b * top.a + bottom.b * bottom.a * (1 - top.a)) / (a || 1)); - return { r, g, b, a: 1 }; - } - function srgbToLin(v) { - v /= 255; - return v <= 0.04045 ? v/12.92 : Math.pow((v + 0.055)/1.055, 2.4); - } - function relLuma({r,g,b}) { - const R = srgbToLin(r), G = srgbToLin(g), B = srgbToLin(b); - return 0.2126*R + 0.7152*G + 0.0722*B; - } - function isVisible(el) { - const cs = getComputedStyle(el); - const rect = el.getBoundingClientRect(); - return rect.width > 0 && rect.height > 0 && cs.visibility !== 'hidden' && cs.display !== 'none' && parseFloat(cs.opacity) > 0.05; - } - function bodyBg() { - let b = parseColor(getComputedStyle(document.body).backgroundColor); - if (b.a === 0) b = { r: 255, g: 255, b: 255, a: 1 }; - return b; - } - function effectiveBackground(el) { - if (!el) return bodyBg(); - const cs = getComputedStyle(el); - const bg = parseColor(cs.backgroundColor); - if (bg.a === 0) return effectiveBackground(el.parentElement); - const parentBg = effectiveBackground(el.parentElement); - if (bg.a >= 1) return bg; - return blend(bg, parentBg); - } - const nodes = Array.from(document.querySelectorAll('*')); - const failures = []; - let scanned = 0; - for (const el of nodes) { - if (!isVisible(el)) continue; - const text = (el.textContent || '').trim(); - if (!text) continue; - const cs = getComputedStyle(el); - let fg = parseColor(cs.color); - const bg = effectiveBackground(el); - if (fg.a === 0) continue; // fully transparent text - if (fg.a < 1) fg = blend(fg, bg); - const L1 = relLuma(fg); - const L2 = relLuma(bg); - const contrast = (Math.max(L1,L2)+0.05) / (Math.min(L1,L2)+0.05); - scanned += 1; - if (contrast < 3.0) { - failures.push({ tag: el.tagName.toLowerCase(), text: text.slice(0, 60), contrast: Math.round(contrast*100)/100 }); - } - } - return { scanned, failures, minContrast: failures.length ? Math.min(...failures.map(f=>f.contrast)) : null }; - } - """ - ) - assert results and isinstance(results, dict) - assert results.get("scanned", 0) > 0 - failed = results.get("failures", []) - assert not failed, f"Low contrast elements in {scheme} mode: {failed[:3]}{(' …' if len(failed) > 3 else '')}" - - browser.close() + """ + Basic accessibility smoke: after upload, key elements are visible in both schemes. + + This test toggles prefers-color-scheme and checks that: + - The dark/light CSS actually applies (by inspecting body background color in dark) + - Headings and summary items remain present + - A coarse contrast check (>= 3.0) passes between body background and heading text + to catch regressions where text becomes unreadable. + """ + html_path = tmp_path / "index.html" + generate_html(html_path) + + corpus_path = _example_corpus() + expected_tlvs = _expected_tlvs(corpus_path) + + file_url = html_path.resolve().as_uri() + + if sync_playwright is None: + pytest.skip("Playwright not installed") + + with sync_playwright() as playwright: + browser = playwright.chromium.launch() + page = browser.new_page() + page.emulate_media(color_scheme=scheme) # Apply requested color scheme + page.goto(file_url) + + # Upload corpus and wait for summary + page.set_input_files("#corpus-input", str(corpus_path)) + page.wait_for_selector(f"text=Decoded {expected_tlvs} TLVs successfully.") + + # Verify headings and summary exist + assert page.locator("header h1").count() == 1 + assert page.locator("#summary-count").inner_text().strip() == str(expected_tlvs) + + # Page-wide contrast sweep over visible text nodes; collect failures (< 3.0) + results = page.evaluate( + r""" + () => { + function parseColor(c) { + const m = c.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([0-9.]+))?\)/); + if (!m) return {r:0,g:0,b:0,a:1}; + return { r: +m[1], g: +m[2], b: +m[3], a: m[4] === undefined ? 1 : +m[4] }; + } + function blend(top, bottom) { + // Alpha composite 'top' over 'bottom'; both are {r,g,b,a} with a in [0,1] + const a = top.a + bottom.a * (1 - top.a); + const r = Math.round((top.r * top.a + bottom.r * bottom.a * (1 - top.a)) / (a || 1)); + const g = Math.round((top.g * top.a + bottom.g * bottom.a * (1 - top.a)) / (a || 1)); + const b = Math.round((top.b * top.a + bottom.b * bottom.a * (1 - top.a)) / (a || 1)); + return { r, g, b, a: 1 }; + } + function srgbToLin(v) { + v /= 255; + return v <= 0.04045 ? v/12.92 : Math.pow((v + 0.055)/1.055, 2.4); + } + function relLuma({r,g,b}) { + const R = srgbToLin(r), G = srgbToLin(g), B = srgbToLin(b); + return 0.2126*R + 0.7152*G + 0.0722*B; + } + function isVisible(el) { + const cs = getComputedStyle(el); + const rect = el.getBoundingClientRect(); + return rect.width > 0 && rect.height > 0 && cs.visibility !== 'hidden' && cs.display !== 'none' && parseFloat(cs.opacity) > 0.05; + } + function bodyBg() { + let b = parseColor(getComputedStyle(document.body).backgroundColor); + if (b.a === 0) b = { r: 255, g: 255, b: 255, a: 1 }; + return b; + } + function effectiveBackground(el) { + if (!el) return bodyBg(); + const cs = getComputedStyle(el); + const bg = parseColor(cs.backgroundColor); + if (bg.a === 0) return effectiveBackground(el.parentElement); + const parentBg = effectiveBackground(el.parentElement); + if (bg.a >= 1) return bg; + return blend(bg, parentBg); + } + const nodes = Array.from(document.querySelectorAll('*')); + const failures = []; + let scanned = 0; + for (const el of nodes) { + if (!isVisible(el)) continue; + const text = (el.textContent || '').trim(); + if (!text) continue; + const cs = getComputedStyle(el); + let fg = parseColor(cs.color); + const bg = effectiveBackground(el); + if (fg.a === 0) continue; // fully transparent text + if (fg.a < 1) fg = blend(fg, bg); + const L1 = relLuma(fg); + const L2 = relLuma(bg); + const contrast = (Math.max(L1,L2)+0.05) / (Math.min(L1,L2)+0.05); + scanned += 1; + if (contrast < 3.0) { + failures.push({ tag: el.tagName.toLowerCase(), text: text.slice(0, 60), contrast: Math.round(contrast*100)/100 }); + } + } + return { scanned, failures, minContrast: failures.length ? Math.min(...failures.map(f=>f.contrast)) : null }; + } + """ + ) + assert results and isinstance(results, dict) + assert results.get("scanned", 0) > 0 + failed = results.get("failures", []) + assert not failed, f"Low contrast elements in {scheme} mode: {failed[:3]}{(' …' if len(failed) > 3 else '')}" + + browser.close() diff --git a/tests/test_tlv_constants_sync.py b/tests/test_tlv_constants_sync.py index ae68e42a9..a092d33e8 100644 --- a/tests/test_tlv_constants_sync.py +++ b/tests/test_tlv_constants_sync.py @@ -103,4 +103,4 @@ def test_tlv_constants_are_in_sync() -> None: + ", ".join( f"{python_by_value[value]} ({value})" for value in extra_value_ids ) - ) \ No newline at end of file + )