├── .python-version ├── tools ├── release │ ├── __init__.py │ ├── lib │ │ ├── __init__.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── samples │ │ │ │ ├── cmakelists.txt │ │ │ │ ├── cmakelists_expected.txt │ │ │ │ ├── ada_version_h.txt │ │ │ │ ├── ada_version_h_expected.txt │ │ │ │ ├── doxygen.txt │ │ │ │ └── doxygen_expected.txt │ │ │ └── test_update_versions.py │ │ ├── release.py │ │ └── versions.py │ ├── requirements.txt │ ├── update_versions.py │ └── create_release.py ├── CMakeLists.txt ├── prepare-doxygen.sh ├── cli │ ├── line_iterator.h │ ├── benchmark_adaparse.sh │ ├── benchmark_write_to_file.sh │ └── CMakeLists.txt ├── run-clangcldocker.sh └── update-wpt.sh ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 2-feature-request.yml │ └── 1-bug-report.yml ├── dependabot.yml └── workflows │ ├── dependency-review.yml │ ├── pkg.yml │ ├── documentation.yml │ ├── release-script-tests.yml │ ├── codspeed.yml │ ├── ubuntu-release.yml │ ├── ubuntu-sanitized.yml │ ├── ubuntu-undef.yml │ ├── emscripten.yml │ ├── ubuntu_pedantic.yml │ ├── lint_and_format_check.yml │ ├── cifuzz.yml │ ├── ubuntu-s390x.yml │ ├── macos_install.yml │ ├── alpine.yml │ ├── ubuntu.yml │ ├── visual_studio_clang.yml │ ├── codeql.yml │ ├── ubuntu_install.yml │ ├── wpt-updater.yml │ ├── visual_studio.yml │ ├── ubuntu-riscv64-rvv.yml │ ├── ubuntu-loongarch64.yml │ ├── release_create.yml │ ├── release_prepare.yml │ └── scorecard.yml ├── .clang-format ├── fuzz ├── ada_c.options ├── parse.options ├── url_pattern.options ├── url.dict ├── idna.cc ├── can_parse.cc ├── url_search_params.cc ├── ada_c.c ├── build.sh └── url_pattern.cc ├── cmake ├── ada-config.cmake.in ├── JoinPaths.cmake ├── toolchains-dev │ ├── riscv64-rvv.cmake │ ├── loongarch64.cmake │ └── README.md ├── ada-flags.cmake └── add-cpp-test.cmake ├── .editorconfig ├── tests ├── wpt │ ├── CMakeLists.txt │ ├── urltestdata-javascript-only.json │ ├── percent-encoding.json │ ├── ada_long_urltestdata.json │ ├── IdnaTestV2-removed.json │ ├── urlpattern-compare-test-data.json │ ├── ada_extra_setters_tests.json │ └── verifydnslength_tests.json ├── wasm │ ├── CMakeLists.txt │ ├── test.js.in │ └── wasm.cpp ├── from_file_tests.cpp ├── installation │ └── CMakeLists.txt └── CMakeLists.txt ├── docs ├── doxygen │ ├── footer.html │ └── header.html └── RELEASE.md ├── SECURITY.md ├── include ├── ada │ ├── errors.h │ ├── ada_version.h │ ├── character_sets.h │ ├── encoding_type.h │ ├── url_base-inl.h │ ├── implementation-inl.h │ ├── log.h │ ├── serializers.h │ ├── unicode-inl.h │ ├── checkers-inl.h │ ├── url_pattern_regex.h │ ├── scheme.h │ ├── url_components-inl.h │ ├── parser.h │ ├── url_components.h │ ├── state.h │ ├── implementation.h │ ├── scheme-inl.h │ ├── checkers.h │ └── url_base.h └── ada.h ├── benchmarks ├── competitors │ └── servo-url │ │ ├── Cargo.toml │ │ ├── cbindgen.toml │ │ ├── README.md │ │ ├── servo_url.h │ │ └── lib.rs ├── bbc_bench.cpp ├── benchmark_header.h ├── bench.cpp └── performancecounters │ ├── linux-perf-events.h │ └── event_counter.h ├── ada.pc.in ├── singleheader ├── demo.cpp ├── README.md ├── demo.c ├── CMakeLists.txt └── amalgamate.py ├── src ├── ada.cpp ├── url_components.cpp ├── url_pattern_regex.cpp ├── serializers.cpp ├── implementation.cpp ├── CMakeLists.txt └── checkers.cpp ├── .gitignore ├── .clang-tidy ├── LICENSE-MIT └── pyproject.toml /.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /tools/release/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/release/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/release/lib/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(cli) -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [anonrig, lemire] 2 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | SortIncludes: Never 3 | -------------------------------------------------------------------------------- /tools/release/requirements.txt: -------------------------------------------------------------------------------- 1 | PyGithub==2.8.1 2 | pytest==9.0.1 3 | -------------------------------------------------------------------------------- /fuzz/ada_c.options: -------------------------------------------------------------------------------- 1 | [libfuzzer] 2 | dict = url.dict 3 | max_len = 1024 4 | -------------------------------------------------------------------------------- /fuzz/parse.options: -------------------------------------------------------------------------------- 1 | [libfuzzer] 2 | dict = url.dict 3 | max_len = 1024 4 | -------------------------------------------------------------------------------- /cmake/ada-config.cmake.in: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/ada_targets.cmake") 2 | -------------------------------------------------------------------------------- /fuzz/url_pattern.options: -------------------------------------------------------------------------------- 1 | [libfuzzer] 2 | dict = url.dict 3 | max_len = 100 4 | rss_limit_mb = 16000 5 | timeout = 60 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_size = 2 7 | indent_style = space 8 | -------------------------------------------------------------------------------- /tests/wpt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | file(GLOB_RECURSE wpt_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.json) 3 | foreach(wpt_file ${wpt_files}) 4 | configure_file(${wpt_file} ${wpt_file} COPYONLY) 5 | endforeach(wpt_file) 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Looking for documentation? 4 | url: https://ada-url.github.io/ada 5 | about: Please navigate to our documentation website. 6 | -------------------------------------------------------------------------------- /docs/doxygen/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Please use the following contact information for reporting a vulnerability: 6 | 7 | - [Daniel Lemire](https://github.com/lemire) - daniel@lemire.me 8 | - [Yagiz Nizipli](https://github.com/anonrig) - yagiz@nizipli.com 9 | -------------------------------------------------------------------------------- /include/ada/errors.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file errors.h 3 | * @brief Definitions for the errors. 4 | */ 5 | #ifndef ADA_ERRORS_H 6 | #define ADA_ERRORS_H 7 | 8 | #include 9 | namespace ada { 10 | enum class errors : uint8_t { type_error }; 11 | } // namespace ada 12 | #endif // ADA_ERRORS_H -------------------------------------------------------------------------------- /benchmarks/competitors/servo-url/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "servo-url" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | path = "lib.rs" 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | url = "2.5.7" 12 | libc = "0.2" 13 | 14 | [profile.release] 15 | opt-level = 3 16 | debug = false 17 | lto = true 18 | -------------------------------------------------------------------------------- /fuzz/url.dict: -------------------------------------------------------------------------------- 1 | # Protocols 2 | "ftp:" 3 | "file:///" 4 | "file:" 5 | "http:" 6 | "https:" 7 | "ws:" 8 | "wss:" 9 | 10 | # Suffixes 11 | ".com" 12 | 13 | # Full URLs 14 | "https://www.ada-url.com" 15 | 16 | # Encoded characters 17 | "%2f" 18 | "%40" 19 | "%26" 20 | 21 | # Misc 22 | "://" 23 | "//" 24 | "\\" 25 | "../" 26 | ";type=a" 27 | "xn--" 28 | -------------------------------------------------------------------------------- /tools/release/lib/tests/samples/cmakelists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(ada 4 | DESCRIPTION "Fast spec-compliant URL parser" 5 | LANGUAGES C CXX 6 | VERSION 1.0.0 7 | ) 8 | 9 | set(ADA_LIB_VERSION "1.0.0" CACHE STRING "ada library version") 10 | set(ADA_LIB_SOVERSION "1" CACHE STRING "ada library soversion") 11 | -------------------------------------------------------------------------------- /tools/release/lib/tests/samples/cmakelists_expected.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(ada 4 | DESCRIPTION "Fast spec-compliant URL parser" 5 | LANGUAGES C CXX 6 | VERSION 2.0.0 7 | ) 8 | 9 | set(ADA_LIB_VERSION "2.0.0" CACHE STRING "ada library version") 10 | set(ADA_LIB_SOVERSION "2" CACHE STRING "ada library soversion") 11 | -------------------------------------------------------------------------------- /ada.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | includedir=@PKGCONFIG_INCLUDEDIR@ 3 | libdir=@PKGCONFIG_LIBDIR@ 4 | 5 | Name: @PROJECT_NAME@ 6 | Description: @PROJECT_DESCRIPTION@ 7 | URL: @PROJECT_HOMEPAGE_URL@ 8 | Version: @PROJECT_VERSION@ 9 | Cflags: -I${includedir} @PKGCONFIG_CFLAGS@ 10 | Libs: -L${libdir} -l@PROJECT_NAME@ 11 | @PKGCONFIG_LIBS_PRIVATE@ 12 | -------------------------------------------------------------------------------- /benchmarks/competitors/servo-url/cbindgen.toml: -------------------------------------------------------------------------------- 1 | autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" 2 | include_version = true 3 | braces = "SameLine" 4 | line_length = 100 5 | tab_width = 2 6 | language = "C++" 7 | namespaces = ["servo_url"] 8 | include_guard = "servo_url_ffi_h" 9 | 10 | [parse] 11 | parse_deps = true 12 | include = ["url"] 13 | -------------------------------------------------------------------------------- /include/ada/ada_version.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ada_version.h 3 | * @brief Definitions for Ada's version number. 4 | */ 5 | #ifndef ADA_ADA_VERSION_H 6 | #define ADA_ADA_VERSION_H 7 | 8 | #define ADA_VERSION "3.3.0" 9 | 10 | namespace ada { 11 | 12 | enum { 13 | ADA_VERSION_MAJOR = 3, 14 | ADA_VERSION_MINOR = 3, 15 | ADA_VERSION_REVISION = 0, 16 | }; 17 | 18 | } // namespace ada 19 | 20 | #endif // ADA_ADA_VERSION_H 21 | -------------------------------------------------------------------------------- /tools/release/lib/tests/samples/ada_version_h.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ada_version.h 3 | * @brief Definitions for Ada's version number. 4 | */ 5 | #ifndef ADA_ADA_VERSION_H 6 | #define ADA_ADA_VERSION_H 7 | 8 | #define ADA_VERSION "1.0.0" 9 | 10 | namespace ada { 11 | 12 | enum { 13 | ADA_VERSION_MAJOR = 1, 14 | ADA_VERSION_MINOR = 0, 15 | ADA_VERSION_REVISION = 0, 16 | }; 17 | 18 | } // namespace ada 19 | 20 | #endif // ADA_ADA_VERSION_H 21 | -------------------------------------------------------------------------------- /singleheader/demo.cpp: -------------------------------------------------------------------------------- 1 | #include "ada.cpp" 2 | #include "ada.h" 3 | #include 4 | 5 | int main(int, char *[]) { 6 | auto url = ada::parse("https://www.google.com"); 7 | if (!url) { 8 | std::cout << "failure" << std::endl; 9 | return EXIT_FAILURE; 10 | } 11 | url->set_protocol("http"); 12 | std::cout << url->get_protocol() << std::endl; 13 | std::cout << url->get_host() << std::endl; 14 | return EXIT_SUCCESS; 15 | } 16 | -------------------------------------------------------------------------------- /tools/release/lib/tests/samples/ada_version_h_expected.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ada_version.h 3 | * @brief Definitions for Ada's version number. 4 | */ 5 | #ifndef ADA_ADA_VERSION_H 6 | #define ADA_ADA_VERSION_H 7 | 8 | #define ADA_VERSION "2.0.0" 9 | 10 | namespace ada { 11 | 12 | enum { 13 | ADA_VERSION_MAJOR = 2, 14 | ADA_VERSION_MINOR = 0, 15 | ADA_VERSION_REVISION = 0, 16 | }; 17 | 18 | } // namespace ada 19 | 20 | #endif // ADA_ADA_VERSION_H 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Set update schedule for GitHub Actions 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: github-actions 6 | directory: / 7 | schedule: 8 | interval: monthly 9 | 10 | - package-ecosystem: cargo 11 | directory: /benchmarks/competitors/servo-url 12 | schedule: 13 | interval: monthly 14 | 15 | - package-ecosystem: pip 16 | directory: /tools/release 17 | schedule: 18 | interval: monthly 19 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | name: 'Dependency Review' 2 | 3 | on: [pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | dependency-review: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: 'Checkout Repository' 13 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 14 | - name: 'Dependency Review' 15 | uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 16 | -------------------------------------------------------------------------------- /fuzz/idna.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "ada.cpp" 7 | #include "ada.h" 8 | 9 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 10 | FuzzedDataProvider fdp(data, size); 11 | std::string source = fdp.ConsumeRandomLengthString(256); 12 | 13 | /** 14 | * ada::idna 15 | */ 16 | ada::idna::to_ascii(source); 17 | ada::idna::to_unicode(source); 18 | 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /tests/wasm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | link_libraries(ada) 2 | # Node 3 | add_executable(wasm-node wasm.cpp) 4 | set_target_properties(wasm-node PROPERTIES LINK_FLAGS "-Os -s ENVIRONMENT=node -s EXPORT_NAME=loadWASM -s MODULARIZE=1 --bind") 5 | 6 | configure_file(test.js.in test.js) 7 | 8 | find_program(NODEJS_BINARY NAMES node nodejs) 9 | if(NODEJS_BINARY) 10 | add_test(NAME wasmtest 11 | COMMAND "${NODEJS_BINARY}" "${CMAKE_CURRENT_BINARY_DIR}/test.js") 12 | endif(NODEJS_BINARY) 13 | 14 | -------------------------------------------------------------------------------- /src/ada.cpp: -------------------------------------------------------------------------------- 1 | #include "ada.h" 2 | #include "checkers.cpp" 3 | #include "unicode.cpp" 4 | #include "serializers.cpp" 5 | #include "implementation.cpp" 6 | #include "helpers.cpp" 7 | #include "url.cpp" 8 | #include "parser.cpp" 9 | #include "url_components.cpp" 10 | #include "url_aggregator.cpp" 11 | 12 | #if ADA_INCLUDE_URL_PATTERN 13 | #include "url_pattern.cpp" 14 | #include "url_pattern_helpers.cpp" 15 | #include "url_pattern_regex.cpp" 16 | #endif // ADA_INCLUDE_URL_PATTERN 17 | 18 | #include "ada_c.cpp" 19 | -------------------------------------------------------------------------------- /cmake/JoinPaths.cmake: -------------------------------------------------------------------------------- 1 | function(join_paths joined_path first_path_segment) 2 | set(temp_path "${first_path_segment}") 3 | foreach(current_segment IN LISTS ARGN) 4 | if(NOT ("${current_segment}" STREQUAL "")) 5 | if(IS_ABSOLUTE "${current_segment}") 6 | set(temp_path "${current_segment}") 7 | else() 8 | set(temp_path "${temp_path}/${current_segment}") 9 | endif() 10 | endif() 11 | endforeach() 12 | set(${joined_path} "${temp_path}" PARENT_SCOPE) 13 | endfunction() 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # common build directory 2 | build 3 | *-build-* 4 | 5 | # Python cache 6 | __pycache__ 7 | venv 8 | 9 | cmake-build-debug 10 | 11 | .cache 12 | docs/html 13 | docs/theme 14 | 15 | # Generated using only the Github workflow 16 | benchmark_result.json 17 | 18 | singleheader/ada.h 19 | singleheader/ada_c.h 20 | singleheader/ada.cpp 21 | singleheader/singleheader.zip 22 | 23 | benchmarks/competitors/servo-url/debug 24 | benchmarks/competitors/servo-url/target 25 | 26 | #ignore VScode 27 | .vscode/ 28 | .idea 29 | 30 | # bazel output 31 | bazel-* 32 | -------------------------------------------------------------------------------- /docs/RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Document 2 | 3 | ## Preparation 4 | 5 | In order to release a new version of Ada, please update the 6 | following documents: 7 | 8 | - [CMakeLists.txt](../CMakeLists.txt) 9 | - [Doxygen](../doxygen) 10 | - [ada_version.h](../include/ada/ada_version.h) 11 | 12 | ## Release 13 | 14 | - Run amalgation script using `./singleheader/amalgamate.py` 15 | - Create a Github release with following format: `v1.0.0` 16 | - Upload the following documents to the release 17 | - `./singleheader/ada.h` 18 | - `./singleheader/ada.cpp` 19 | - `./singleheader/singleheader.zip` 20 | -------------------------------------------------------------------------------- /tools/prepare-doxygen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | PACKAGE_URL="https://github.com/jothepro/doxygen-awesome-css.git" 5 | PACKAGE_VERSION="v2.3.3" 6 | 7 | BASE_DIR=$(pwd) 8 | THEME_DIR="$BASE_DIR/docs/theme" 9 | WORKSPACE=$(mktemp -d 2> /dev/null || mktemp -d -t 'tmp') 10 | 11 | cleanup () { 12 | EXIT_CODE=$? 13 | [ -d "$WORKSPACE" ] && rm -rf "$WORKSPACE" 14 | exit $EXIT_CODE 15 | } 16 | 17 | trap cleanup INT TERM EXIT 18 | 19 | cd "$WORKSPACE" 20 | git clone --depth=1 --branch "$PACKAGE_VERSION" "$PACKAGE_URL" theme 21 | rm -rf "$THEME_DIR" 22 | mv "$WORKSPACE/theme" "$THEME_DIR" 23 | -------------------------------------------------------------------------------- /tests/from_file_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "ada.h" 2 | #include 3 | #include 4 | #include "gtest/gtest.h" 5 | 6 | std::string long_way(std::string path) { 7 | ada::result base = ada::parse("file://"); 8 | base->set_pathname(path); 9 | return base->get_href(); 10 | } 11 | 12 | TEST(from_file_tests, basics) { 13 | for (std::string path : 14 | {"", "fsfds", "C:\\\\blabala\\fdfds\\back.txt", "/home/user/txt.txt", 15 | "/%2e.bar", "/foo/%2e%2", "/foo/..bar", "foo\t%91"}) { 16 | ASSERT_TRUE(long_way(path) == ada::href_from_file(path)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /benchmarks/competitors/servo-url/README.md: -------------------------------------------------------------------------------- 1 | ## Servo URL FFI 2 | 3 | This folder includes FFI bindings for servo/url. 4 | 5 | ### Links 6 | 7 | - https://github.com/eqrion/cbindgen/blob/master/docs.md 8 | - https://gist.github.com/zbraniecki/b251714d77ffebbc73c03447f2b2c69f 9 | - https://github.com/Michael-F-Bryan/rust-ffi-guide/blob/master/book/setting_up.md 10 | 11 | ### Building 12 | 13 | - Generating cbindgen output 14 | - Install dependencies with `brew install cbindgen` 15 | - Generate with `cbindgen --config cbindgen.toml --crate servo-url --output servo_url.h` 16 | - Building 17 | - Run with `cargo build --release` 18 | -------------------------------------------------------------------------------- /cmake/toolchains-dev/riscv64-rvv.cmake: -------------------------------------------------------------------------------- 1 | # Usage: 2 | # $ cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains-dev/riscv64-rvv.cmake 3 | set(CMAKE_SYSTEM_NAME Generic) 4 | 5 | set(target riscv64-linux-gnu) 6 | set(c_compiler gcc) 7 | set(cxx_compiler g++) 8 | 9 | set(CMAKE_C_COMPILER "${target}-${c_compiler}") 10 | set(CMAKE_CXX_COMPILER "${target}-${cxx_compiler}") 11 | 12 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 13 | 14 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 15 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 16 | 17 | set(CMAKE_CROSSCOMPILING_EMULATOR "qemu-riscv64") 18 | 19 | set(CMAKE_CXX_FLAGS "-march=rv64gcv") 20 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: > 2 | bugprone-*, 3 | -bugprone-easily-swappable-parameters, 4 | -bugprone-exception-escape, 5 | -bugprone-implicit-widening-of-multiplication-result, 6 | -bugprone-narrowing-conversions, 7 | -bugprone-suspicious-include, 8 | -bugprone-unhandled-exception-at-new, 9 | clang-analyzer-*, 10 | -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, 11 | 12 | # Turn all the warnings from the checks above into errors. 13 | WarningsAsErrors: '*' 14 | # Check first-party (non-system, non-vendored) headers. 15 | HeaderFilterRegex: '.*' 16 | ExcludeHeaderFilterRegex: 'build/_deps/' 17 | SystemHeaders: false 18 | -------------------------------------------------------------------------------- /fuzz/can_parse.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "ada.cpp" 7 | #include "ada.h" 8 | 9 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 10 | FuzzedDataProvider fdp(data, size); 11 | std::string source = fdp.ConsumeRandomLengthString(256); 12 | std::string base_source = fdp.ConsumeRandomLengthString(256); 13 | 14 | /** 15 | * ada::can_parse 16 | */ 17 | auto base_source_view = 18 | std::string_view(base_source.data(), base_source.length()); 19 | ada::can_parse(source); 20 | ada::can_parse(source, &base_source_view); 21 | 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/pkg.yml: -------------------------------------------------------------------------------- 1 | name: Debian pkg-config 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | pkg-config: 10 | runs-on: ubuntu-latest 11 | container: 12 | image: debian:12 13 | 14 | steps: 15 | - uses: actions/checkout@v6.0.0 16 | 17 | - name: Install dependencies 18 | run: | 19 | apt -y update 20 | apt -y --no-install-recommends install g++ cmake make pkg-config 21 | 22 | - name: Build and install 23 | run: | 24 | cmake -B build 25 | cmake --build build 26 | cmake --install build 27 | 28 | - name: Test pkg-config 29 | run: pkg-config --cflags --libs ada 30 | -------------------------------------------------------------------------------- /benchmarks/competitors/servo-url/servo_url.h: -------------------------------------------------------------------------------- 1 | #ifndef servo_url_ffi_h 2 | #define servo_url_ffi_h 3 | 4 | /* This file was modified manually. */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace servo_url { 13 | 14 | /// A parsed URL record. 15 | struct Url; 16 | 17 | extern "C" { 18 | 19 | Url *parse_url(const char *raw_input, size_t raw_input_length); 20 | 21 | void free_url(Url *raw); 22 | 23 | const char *parse_url_to_href(const char *raw_input, size_t raw_input_length); 24 | 25 | void free_string(const char *); 26 | } // extern "C" 27 | 28 | } // namespace servo_url 29 | 30 | #endif // servo_url_ffi_h 31 | -------------------------------------------------------------------------------- /tools/release/lib/release.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re 4 | from github.Repository import Repository 5 | from github.GitRelease import GitRelease 6 | 7 | 8 | def is_valid_tag(tag: str) -> bool: 9 | tag_regex = r'^v\d+\.\d+\.\d+$' 10 | return bool(re.match(tag_regex, tag)) 11 | 12 | 13 | def create_release(repository: Repository, tag: str) -> GitRelease: 14 | if not is_valid_tag(tag): 15 | raise Exception(f'Invalid tag: {tag}') 16 | 17 | try: 18 | return repository.create_git_release( 19 | tag=tag, name=tag, draft=True, prerelease=False, generate_release_notes=True 20 | ) 21 | except Exception as exp: 22 | raise Exception(f'create_release: Error creating release/tag {tag}: {exp!s}') from exp 23 | -------------------------------------------------------------------------------- /cmake/toolchains-dev/loongarch64.cmake: -------------------------------------------------------------------------------- 1 | # Usage: 2 | # $ cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains-dev/loongarch64.cmake 3 | set(CMAKE_SYSTEM_NAME Generic) 4 | 5 | #set(target loongarch64-unknown-linux-gnu) 6 | set(target loongarch64-linux-gnu) 7 | set(version 14) 8 | set(c_compiler gcc) 9 | set(cxx_compiler g++) 10 | 11 | set(CMAKE_C_COMPILER "${target}-${c_compiler}-${version}") 12 | set(CMAKE_CXX_COMPILER "${target}-${cxx_compiler}-${version}") 13 | 14 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 15 | 16 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 17 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 18 | 19 | set(LOONGARCH64_ISA "loongarch64") 20 | set(CMAKE_CROSSCOMPILING_EMULATOR "qemu-${LOONGARCH64_ISA}") 21 | 22 | set(CMAKE_CXX_FLAGS "-mlsx") 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-feature-request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature request 2 | description: Suggest an idea for this project 3 | labels: [feature request] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for suggesting an idea to make Node.js better. 9 | 10 | Please fill in as much of the following form as you're able. 11 | - type: textarea 12 | attributes: 13 | label: What is the problem this feature will solve? 14 | validations: 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: What is the feature you are proposing to solve the problem? 19 | validations: 20 | required: true 21 | - type: textarea 22 | attributes: 23 | label: What alternatives have you considered? 24 | -------------------------------------------------------------------------------- /singleheader/README.md: -------------------------------------------------------------------------------- 1 | ## Amalgamation demo 2 | 3 | While in the ada main directory, using Python 3, type: 4 | 5 | ``` 6 | python singleheader/amalgamate.py 7 | ``` 8 | 9 | This will create two new files (ada.h and ada.cpp). 10 | 11 | You can then compile the demo file as follows: 12 | 13 | ``` 14 | c++ -std=c++20 -c demo.cpp 15 | ``` 16 | 17 | It will produce a binary file (e.g., demo.o) which contains ada.cpp. 18 | 19 | ``` 20 | c++ -std=c++20 -o demo demo.cpp 21 | ./demo 22 | ``` 23 | 24 | You may build and link using CMake (--target demo), because CMake can configure all the necessary flags. 25 | 26 | 27 | ### C Demo 28 | 29 | You may also build a C executable. 30 | 31 | ``` 32 | c++ -c ada.cpp -std=c++20 33 | cc -c demo.c 34 | c++ demo.o ada.o -o cdemo 35 | ./cdemo 36 | ``` 37 | -------------------------------------------------------------------------------- /tests/installation/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | project(test_ada_install VERSION 0.1.0 LANGUAGES CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | find_package(ada REQUIRED) 9 | 10 | # You can provide your own code, this is just an example: 11 | file(WRITE main.cpp " 12 | #include \"ada.h\" 13 | #include 14 | 15 | int main(int , char *[]) { 16 | ada::result url = ada::parse(\"https://www.google.com\"); 17 | url->set_protocol(\"http\"); 18 | std::cout << url->get_protocol() << std::endl; 19 | std::cout << url->get_host() << std::endl; 20 | return EXIT_SUCCESS; 21 | }") 22 | 23 | add_executable(main main.cpp) 24 | target_link_libraries(main PUBLIC ada::ada) 25 | -------------------------------------------------------------------------------- /include/ada/character_sets.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file character_sets.h 3 | * @brief Declaration of the character sets used by unicode functions. 4 | * @author Node.js 5 | * @see https://github.com/nodejs/node/blob/main/src/node_url_tables.cc 6 | */ 7 | #ifndef ADA_CHARACTER_SETS_H 8 | #define ADA_CHARACTER_SETS_H 9 | 10 | #include "ada/common_defs.h" 11 | #include 12 | 13 | /** 14 | * These functions are not part of our public API and may 15 | * change at any time. 16 | * @private 17 | * @namespace ada::character_sets 18 | * @brief Includes the definitions for unicode character sets. 19 | */ 20 | namespace ada::character_sets { 21 | ada_really_inline constexpr bool bit_at(const uint8_t a[], uint8_t i); 22 | } // namespace ada::character_sets 23 | 24 | #endif // ADA_CHARACTER_SETS_H 25 | -------------------------------------------------------------------------------- /include/ada/encoding_type.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file encoding_type.h 3 | * @brief Definition for supported encoding types. 4 | */ 5 | #ifndef ADA_ENCODING_TYPE_H 6 | #define ADA_ENCODING_TYPE_H 7 | 8 | #include "ada/common_defs.h" 9 | #include 10 | 11 | namespace ada { 12 | 13 | /** 14 | * This specification defines three encodings with the same names as encoding 15 | * schemes defined in the Unicode standard: UTF-8, UTF-16LE, and UTF-16BE. 16 | * 17 | * @see https://encoding.spec.whatwg.org/#encodings 18 | */ 19 | enum class encoding_type { 20 | UTF8, 21 | UTF_16LE, 22 | UTF_16BE, 23 | }; 24 | 25 | /** 26 | * Convert a encoding_type to string. 27 | */ 28 | ada_warn_unused std::string_view to_string(encoding_type type); 29 | 30 | } // namespace ada 31 | 32 | #endif // ADA_ENCODING_TYPE_H 33 | -------------------------------------------------------------------------------- /tools/release/update_versions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import lib.versions as update_versions 5 | from lib.release import is_valid_tag 6 | 7 | WORK_DIR = os.path.dirname(os.path.abspath(__file__)).replace('/tools/release', '') 8 | 9 | ADA_VERSION_H = f'{WORK_DIR}/include/ada/ada_version.h' 10 | DOXYGEN = f'{WORK_DIR}/doxygen' 11 | CMAKE_LISTS = f'{WORK_DIR}/CMakeLists.txt' 12 | 13 | NEXT_TAG = os.environ['NEXT_RELEASE_TAG'] 14 | if not NEXT_TAG or not is_valid_tag(NEXT_TAG): 15 | raise Exception(f'Bad environment variables. Invalid NEXT_RELEASE_TAG {NEXT_TAG}.') 16 | 17 | NEXT_TAG = NEXT_TAG[1:] # from v1.0.0 to 1.0.0 18 | 19 | update_versions.update_ada_version_h(NEXT_TAG, ADA_VERSION_H) 20 | update_versions.update_doxygen_version(NEXT_TAG, DOXYGEN) 21 | update_versions.update_cmakelists_version(NEXT_TAG, CMAKE_LISTS) 22 | -------------------------------------------------------------------------------- /include/ada/url_base-inl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file url_base-inl.h 3 | * @brief Inline functions for url base 4 | */ 5 | #ifndef ADA_URL_BASE_INL_H 6 | #define ADA_URL_BASE_INL_H 7 | 8 | #include "ada/scheme.h" 9 | #include "ada/checkers.h" 10 | #include "ada/url.h" 11 | 12 | #include 13 | #if ADA_REGULAR_VISUAL_STUDIO 14 | #include 15 | #endif // ADA_REGULAR_VISUAL_STUDIO 16 | 17 | namespace ada { 18 | 19 | [[nodiscard]] ada_really_inline constexpr bool url_base::is_special() 20 | const noexcept { 21 | return type != ada::scheme::NOT_SPECIAL; 22 | } 23 | 24 | [[nodiscard]] inline uint16_t url_base::get_special_port() const noexcept { 25 | return ada::scheme::get_special_port(type); 26 | } 27 | 28 | [[nodiscard]] ada_really_inline uint16_t 29 | url_base::scheme_default_port() const noexcept { 30 | return scheme::get_special_port(type); 31 | } 32 | 33 | } // namespace ada 34 | 35 | #endif // ADA_URL_BASE_INL_H 36 | -------------------------------------------------------------------------------- /tests/wasm/test.js.in: -------------------------------------------------------------------------------- 1 | const assert = require('node:assert'); 2 | const test = require('node:test'); 3 | const wasm = require('${CMAKE_CURRENT_BINARY_DIR}/wasm-node'); 4 | 5 | function toJS(obj) { 6 | const result = {}; 7 | for (const key of Object.keys(obj.__proto__)) { 8 | result[key] = typeof obj[key] === "object" ? toJS(obj[key]) : obj[key]; 9 | } 10 | return result; 11 | } 12 | 13 | const expected = { 14 | "result": "success", 15 | "href": "https://google.com/?q=Yagiz#Nizipli", 16 | "type": 2, 17 | "components": { 18 | "protocol_end": 6, 19 | "username_end": 8, 20 | "host_start": 8, 21 | "host_end": 18, 22 | "port": 4294967295, 23 | "pathname_start": 18, 24 | "search_start": 19, 25 | "hash_start": 27 26 | } 27 | }; 28 | 29 | test('wasm', async () => { 30 | const { parse } = await wasm(); 31 | assert.deepStrictEqual(toJS(parse('https://google.com/?q=Yagiz#Nizipli')), expected); 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /tools/cli/line_iterator.h: -------------------------------------------------------------------------------- 1 | #ifndef LINE_ITERATOR_H 2 | #define LINE_ITERATOR_H 3 | 4 | #include 5 | 6 | struct line_iterator { 7 | std::string_view all_text{}; 8 | size_t next_end_of_line{0}; 9 | line_iterator(const char *_buffer, size_t _len) : all_text(_buffer, _len) {} 10 | 11 | inline bool find_another_complete_line() noexcept { 12 | next_end_of_line = all_text.find('\n'); 13 | return next_end_of_line != std::string_view::npos; 14 | } 15 | 16 | inline operator bool() const noexcept { 17 | return next_end_of_line != std::string_view::npos; 18 | } 19 | 20 | inline std::string_view grab_line() noexcept { 21 | auto line = all_text.substr(0, next_end_of_line); // advance to next EOL 22 | // remove anything prior to said EOL 23 | all_text.remove_prefix(next_end_of_line + 1); 24 | return line; 25 | } 26 | 27 | inline size_t tail() const noexcept { return all_text.size(); } 28 | }; 29 | 30 | #endif -------------------------------------------------------------------------------- /include/ada/implementation-inl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file implementation-inl.h 3 | */ 4 | #ifndef ADA_IMPLEMENTATION_INL_H 5 | #define ADA_IMPLEMENTATION_INL_H 6 | 7 | #include "ada/url_pattern_regex.h" 8 | 9 | #include "ada/expected.h" 10 | #include "ada/implementation.h" 11 | 12 | #include 13 | #include 14 | 15 | namespace ada { 16 | 17 | #if ADA_INCLUDE_URL_PATTERN 18 | template 19 | ada_warn_unused tl::expected, errors> 20 | parse_url_pattern(std::variant&& input, 21 | const std::string_view* base_url, 22 | const url_pattern_options* options) { 23 | return parser::parse_url_pattern_impl(std::move(input), 24 | base_url, options); 25 | } 26 | #endif // ADA_INCLUDE_URL_PATTERN 27 | 28 | } // namespace ada 29 | 30 | #endif // ADA_IMPLEMENTATION_INL_H 31 | -------------------------------------------------------------------------------- /tools/release/create_release.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from os import environ, path 4 | from github import Github 5 | from lib.release import create_release 6 | 7 | WORK_DIR = path.dirname(path.abspath(__file__)).replace('/tools/release', '') 8 | 9 | NEXT_TAG = environ.get('NEXT_RELEASE_TAG', None) 10 | REPO_NAME = environ.get('GITHUB_REPOSITORY', None) 11 | TOKEN = environ.get('GITHUB_TOKEN', None) 12 | if not NEXT_TAG or not REPO_NAME or not TOKEN: 13 | raise Exception('Bad environment variables. Invalid GITHUB_REPOSITORY, GITHUB_TOKEN or NEXT_RELEASE_TAG') 14 | 15 | g = Github(TOKEN) 16 | repository = g.get_repo(REPO_NAME) 17 | 18 | release = create_release(repository, NEXT_TAG) 19 | release.upload_asset('singleheader/ada.cpp') 20 | release.upload_asset('singleheader/ada.h') 21 | release.upload_asset('singleheader/ada_c.h') 22 | release.upload_asset('singleheader/singleheader.zip') 23 | release.update_release(name=release.name, message=release.body, draft=False, make_latest="true") 24 | -------------------------------------------------------------------------------- /tests/wpt/urltestdata-javascript-only.json: -------------------------------------------------------------------------------- 1 | [ 2 | "See ../README.md for a description of the format.", 3 | { 4 | "input": "http://example.com/\uD800\uD801\uDFFE\uDFFF\uFDD0\uFDCF\uFDEF\uFDF0\uFFFE\uFFFF?\uD800\uD801\uDFFE\uDFFF\uFDD0\uFDCF\uFDEF\uFDF0\uFFFE\uFFFF", 5 | "base": null, 6 | "href": "http://example.com/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF", 7 | "origin": "http://example.com", 8 | "protocol": "http:", 9 | "username": "", 10 | "password": "", 11 | "host": "example.com", 12 | "hostname": "example.com", 13 | "port": "", 14 | "pathname": "/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF", 15 | "search": "?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF", 16 | "hash": "" 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /tests/wpt/percent-encoding.json: -------------------------------------------------------------------------------- 1 | [ 2 | "Tests for percent-encoding.", 3 | { 4 | "input": "\u2020", 5 | "output": { 6 | "big5": "%26%238224%3B", 7 | "euc-kr": "%A2%D3", 8 | "utf-8": "%E2%80%A0", 9 | "windows-1252": "%86" 10 | } 11 | }, 12 | "This uses a trailing A to prevent the URL parser from trimming the C0 control.", 13 | { 14 | "input": "\u000EA", 15 | "output": { 16 | "big5": "%0EA", 17 | "iso-2022-jp": "%26%2365533%3BA", 18 | "utf-8": "%0EA" 19 | } 20 | }, 21 | { 22 | "input": "\u203E\u005C", 23 | "output": { 24 | "iso-2022-jp": "%1B(J~%1B(B\\", 25 | "utf-8": "%E2%80%BE\\" 26 | } 27 | }, 28 | { 29 | "input": "\uE5E5", 30 | "output": { 31 | "gb18030": "%26%2358853%3B", 32 | "utf-8": "%EE%97%A5" 33 | } 34 | }, 35 | { 36 | "input": "\u2212", 37 | "output": { 38 | "shift_jis": "%81|", 39 | "utf-8": "%E2%88%92" 40 | } 41 | }, 42 | { 43 | "input": "á|", 44 | "output": { 45 | "utf-8": "%C3%A1|" 46 | } 47 | } 48 | ] 49 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Doxygen GitHub Pages 2 | 3 | on: 4 | release: 5 | types: [created] 6 | # Allows you to run this workflow manually from the Actions tab 7 | workflow_dispatch: 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | deploy: 18 | permissions: 19 | contents: write 20 | pages: write 21 | id-token: write 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 25 | - name: Install theme 26 | run: ./tools/prepare-doxygen.sh 27 | - uses: mattnotmitt/doxygen-action@ded75d963c260fd8489801611a5079d149ebcc07 # edge 28 | with: 29 | doxyfile-path: './doxygen' 30 | - name: Deploy to GitHub Pages 31 | uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 32 | with: 33 | github_token: ${{ secrets.GITHUB_TOKEN }} 34 | publish_dir: docs/html 35 | -------------------------------------------------------------------------------- /.github/workflows/release-script-tests.yml: -------------------------------------------------------------------------------- 1 | name: Release Script Tests 2 | 3 | on: 4 | # workflow_call is used to indicate that a workflow can be called by another workflow. 5 | workflow_call: 6 | pull_request: 7 | types: [opened, synchronize, reopened, ready_for_review] 8 | paths-ignore: 9 | - '**.md' 10 | - 'docs/**' 11 | push: 12 | branches: 13 | - main 14 | paths-ignore: 15 | - '**.md' 16 | - 'docs/**' 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | release-script-test: 23 | runs-on: ubuntu-latest 24 | defaults: 25 | run: 26 | working-directory: ./tools/release 27 | 28 | steps: 29 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 30 | 31 | - name: Prepare Python 32 | uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 33 | with: 34 | cache: 'pip' # caching pip dependencies 35 | 36 | - name: Install dependencies 37 | run: pip install -r requirements.txt 38 | 39 | - name: Run tests 40 | run: pytest -v 41 | -------------------------------------------------------------------------------- /tests/wpt/ada_long_urltestdata.json: -------------------------------------------------------------------------------- 1 | [ 2 | "# Windows' codebase will not allow this case for now.", 3 | "# Host", 4 | { 5 | "input": "http://ğığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığı.com", 6 | "base": "about:blank", 7 | "href": "http://xn--teaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa78hbabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.com/", 8 | "origin": "http://xn--teaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa78hbabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.com", 9 | "protocol": "http:", 10 | "username": "", 11 | "password": "", 12 | "host": "xn--teaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa78hbabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.com", 13 | "hostname": "xn--teaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa78hbabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.com", 14 | "port": "", 15 | "pathname": "/", 16 | "search": "", 17 | "hash": "" 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /.github/workflows/codspeed.yml: -------------------------------------------------------------------------------- 1 | name: CodSpeed Benchmarks 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | paths-ignore: 8 | - '**.md' 9 | - 'docs/**' 10 | pull_request: 11 | # `workflow_dispatch` allows CodSpeed to trigger backtest 12 | # performance analysis in order to generate initial data. 13 | workflow_dispatch: 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | benchmarks: 20 | name: Run benchmarks 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 24 | - name: Build the benchmark target(s) 25 | run: | 26 | cmake -DCMAKE_BUILD_TYPE=Release -DADA_TESTING=OFF -DADA_DEVELOPMENT_CHECKS=OFF -DADA_USE_UNSAFE_STD_REGEX_PROVIDER=ON -DADA_BENCHMARKS=ON -DCODSPEED_MODE=simulation -G Ninja -B build 27 | cmake --build build -j 28 | env: 29 | CXX: g++-12 30 | - name: Run the benchmarks 31 | uses: CodSpeedHQ/action@v4 32 | with: 33 | mode: simulation 34 | run: cmake --build build --target run_all_benchmarks 35 | 36 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2023 Yagiz Nizipli and Daniel Lemire 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /include/ada/log.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file log.h 3 | * @brief Includes the definitions for logging. 4 | * @private Excluded from docs through the doxygen file. 5 | */ 6 | #ifndef ADA_LOG_H 7 | #define ADA_LOG_H 8 | #include "ada/common_defs.h" 9 | 10 | // To enable logging, set ADA_LOGGING to 1: 11 | #ifndef ADA_LOGGING 12 | #define ADA_LOGGING 0 13 | #endif 14 | 15 | #if ADA_LOGGING 16 | #include 17 | #endif // ADA_LOGGING 18 | 19 | namespace ada { 20 | 21 | /** 22 | * Log a message. If you want to have no overhead when logging is disabled, use 23 | * the ada_log macro. 24 | * @private 25 | */ 26 | template 27 | constexpr ada_really_inline void log([[maybe_unused]] Args... args) { 28 | #if ADA_LOGGING 29 | ((std::cout << "ADA_LOG: ") << ... << args) << std::endl; 30 | #endif // ADA_LOGGING 31 | } 32 | } // namespace ada 33 | 34 | #if ADA_LOGGING 35 | #ifndef ada_log 36 | #define ada_log(...) \ 37 | do { \ 38 | ada::log(__VA_ARGS__); \ 39 | } while (0) 40 | #endif // ada_log 41 | #else 42 | #define ada_log(...) 43 | #endif // ADA_LOGGING 44 | 45 | #endif // ADA_LOG_H 46 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu-release.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 22.04 (Release build) 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | ubuntu-release-build: 25 | runs-on: ubuntu-22.04 26 | strategy: 27 | matrix: 28 | cxx: [g++-12, clang++-14] 29 | steps: 30 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 31 | - name: Setup Ninja 32 | run: sudo apt-get install ninja-build 33 | - name: Prepare 34 | run: cmake -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -G Ninja -B build 35 | env: 36 | CXX: ${{matrix.cxx}} 37 | - name: Build 38 | run: cmake --build build -j=4 39 | - name: Test 40 | run: ctest --output-on-failure --test-dir build 41 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "ada-url" 3 | requires-python = ">=3.12" 4 | 5 | [tool.ruff] 6 | line-length = 120 7 | target-version = "py312" 8 | 9 | [tool.ruff.format] 10 | quote-style = "single" 11 | indent-style = "space" 12 | docstring-code-format = true 13 | 14 | [tool.ruff.lint] 15 | select = [ 16 | "C90", # McCabe cyclomatic complexity 17 | "E", # pycodestyle 18 | "F", # Pyflakes 19 | "ICN", # flake8-import-conventions 20 | "INT", # flake8-gettext 21 | "PLC", # Pylint conventions 22 | "PLE", # Pylint errors 23 | "PLR09", # Pylint refactoring: max-args, max-branches, max returns, max-statements 24 | "PYI", # flake8-pyi 25 | "RSE", # flake8-raise 26 | "RUF", # Ruff-specific rules 27 | "T10", # flake8-debugger 28 | "TCH", # flake8-type-checking 29 | "TID", # flake8-tidy-imports 30 | "W", # pycodestyle 31 | "YTT", # flake8-2020 32 | "ANN" # flake8-annotations 33 | ] 34 | ignore = [ 35 | "E722", # Do not use bare `except` 36 | "ANN101", # Missing type annotation for self in method 37 | "TID252", # Prefer absolute imports over relative imports from parent modules 38 | ] 39 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu-sanitized.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 22.04 (GCC 12 SANITIZED) 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | ubuntu-build: 25 | runs-on: ubuntu-22.04 26 | strategy: 27 | matrix: 28 | shared: [ON, OFF] 29 | steps: 30 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 31 | - name: Setup Ninja 32 | run: sudo apt-get install ninja-build 33 | - name: Prepare 34 | run: cmake -D ADA_TESTING=ON -DADA_SANITIZE=ON -DADA_DEVELOPMENT_CHECKS=ON -DBUILD_SHARED_LIBS=${{matrix.shared}} -G Ninja -B build 35 | env: 36 | CXX: g++-12 37 | - name: Build 38 | run: cmake --build build -j=4 39 | - name: Test 40 | run: ctest --output-on-failure --test-dir build 41 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu-undef.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 22.04 (GCC 12 SANITIZE UNDEFINED) 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | ubuntu-build: 25 | runs-on: ubuntu-22.04 26 | strategy: 27 | matrix: 28 | shared: [ON, OFF] 29 | steps: 30 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 31 | - name: Setup Ninja 32 | run: sudo apt-get install ninja-build 33 | - name: Prepare 34 | run: cmake -D ADA_TESTING=ON -D ADA_SANITIZE_UNDEFINED=ON -DADA_DEVELOPMENT_CHECKS=ON -DBUILD_SHARED_LIBS=${{matrix.shared}} -G Ninja -B build 35 | env: 36 | CXX: g++-12 37 | - name: Build 38 | run: cmake --build build -j=4 39 | - name: Test 40 | run: ctest --output-on-failure --test-dir build 41 | -------------------------------------------------------------------------------- /.github/workflows/emscripten.yml: -------------------------------------------------------------------------------- 1 | name: emscripten 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | build: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 28 | - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 29 | - uses: mymindstorm/setup-emsdk@6ab9eb1bda2574c4ddb79809fc9247783eaf9021 # v14 30 | - name: Verify 31 | run: emcc -v 32 | - name: Checkout 33 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v3.6.0 34 | - name: Configure 35 | run: emcmake cmake -B buildwasm -D ADA_TESTING=ON -D ADA_TOOLS=OFF 36 | - name: Build 37 | run: cmake --build buildwasm 38 | - name: Test 39 | run: ctest --test-dir buildwasm 40 | -------------------------------------------------------------------------------- /tools/run-clangcldocker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | COMMAND=$* 4 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" 5 | MAINSOURCE=$SCRIPTPATH/.. 6 | ALL_ADA_FILES=$(cd $MAINSOURCE && git ls-tree --full-tree --name-only -r HEAD | grep -e ".*\.\(c\|h\|cc\|cpp\|hh\)\$") 7 | 8 | if clang-format-17 --version 2>/dev/null | grep -qF 'version 17.'; then 9 | cd $MAINSOURCE; clang-format-17 --style=file --verbose -i "$@" $ALL_ADA_FILES 10 | exit 0 11 | elif clang-format --version 2>/dev/null | grep -qF 'version 17.'; then 12 | cd $MAINSOURCE; clang-format --style=file --verbose -i "$@" $ALL_ADA_FILES 13 | exit 0 14 | fi 15 | echo "Trying to use docker" 16 | command -v docker >/dev/null 2>&1 || { echo >&2 "Please install docker. E.g., go to https://www.docker.com/products/docker-desktop Type 'docker' to diagnose the problem."; exit 1; } 17 | docker info >/dev/null 2>&1 || { echo >&2 "Docker server is not running? type 'docker info'."; exit 1; } 18 | 19 | if [ -t 0 ]; then DOCKER_ARGS=-it; fi 20 | docker pull kszonek/clang-format-17 21 | 22 | docker run --rm $DOCKER_ARGS -v "$MAINSOURCE":"$MAINSOURCE":Z -w "$MAINSOURCE" -u "$(id -u $USER):$(id -g $USER)" kszonek/clang-format-17 --style=file --verbose -i "$@" $ALL_ADA_FILES 23 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu_pedantic.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 22.04 (GCC 12) Fails On Compiler Warnings 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | ubuntu-build: 25 | runs-on: ubuntu-22.04 26 | strategy: 27 | matrix: 28 | shared: [ON, OFF] 29 | steps: 30 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 31 | - name: Setup Ninja 32 | run: sudo apt-get install ninja-build 33 | - name: Prepare 34 | run: cmake -D ADA_TESTING=ON -DBUILD_SHARED_LIBS=${{matrix.shared}} -G Ninja -B build 35 | env: 36 | CXX: g++-12 37 | CXXFLAGS: -Werror -Wextra -Wno-unused-parameter -Wimplicit-fallthrough 38 | - name: Build 39 | run: cmake --build build -j=4 40 | - name: Test 41 | run: ctest --output-on-failure --test-dir build 42 | -------------------------------------------------------------------------------- /tools/update-wpt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | TARGET_MODULE=$1 5 | BASE_DIR=$(pwd) 6 | WPT_DIR="$BASE_DIR/tests/wpt" 7 | 8 | if [ "$#" -ne 1 ]; then 9 | echo "Usage: $0 " 10 | exit 1 11 | fi 12 | 13 | WORKSPACE=$(mktemp -d 2> /dev/null || mktemp -d -t 'tmp') 14 | 15 | cleanup () { 16 | EXIT_CODE=$? 17 | [ -d "$WORKSPACE" ] && rm -rf "$WORKSPACE" 18 | exit $EXIT_CODE 19 | } 20 | 21 | trap cleanup INT TERM EXIT 22 | 23 | cd "$WORKSPACE" 24 | git clone \ 25 | --no-checkout \ 26 | --depth=1 \ 27 | --filter=blob:none \ 28 | --sparse \ 29 | https://github.com/web-platform-tests/wpt.git wpt 30 | cd wpt 31 | # Conditionally sparse-checkout based on TARGET_MODULE 32 | if [ "$TARGET_MODULE" = "url" ]; then 33 | git sparse-checkout add "url/resources" 34 | elif [ "$TARGET_MODULE" = "urlpattern" ]; then 35 | git sparse-checkout add "urlpattern/resources" 36 | else 37 | echo "Invalid target module: $TARGET_MODULE. Must be 'url' or 'urlpattern'." 38 | exit 1 39 | fi 40 | 41 | git checkout 42 | 43 | # Copy the appropriate resources based on the target module 44 | if [ "$TARGET_MODULE" = "url" ]; then 45 | cp url/resources/*.json "$WPT_DIR" 46 | elif [ "$TARGET_MODULE" = "urlpattern" ]; then 47 | cp urlpattern/resources/*.json "$WPT_DIR" 48 | fi 49 | -------------------------------------------------------------------------------- /include/ada.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ada.h 3 | * @brief Includes all definitions for Ada. 4 | */ 5 | #ifndef ADA_H 6 | #define ADA_H 7 | 8 | #include "ada/ada_idna.h" 9 | #include "ada/character_sets.h" 10 | #include "ada/character_sets-inl.h" 11 | #include "ada/checkers-inl.h" 12 | #include "ada/common_defs.h" 13 | #include "ada/log.h" 14 | #include "ada/encoding_type.h" 15 | #include "ada/helpers.h" 16 | #include "ada/parser.h" 17 | #include "ada/parser-inl.h" 18 | #include "ada/scheme.h" 19 | #include "ada/scheme-inl.h" 20 | #include "ada/serializers.h" 21 | #include "ada/state.h" 22 | #include "ada/unicode.h" 23 | #include "ada/url_base.h" 24 | #include "ada/url_base-inl.h" 25 | #include "ada/url-inl.h" 26 | #include "ada/url_components.h" 27 | #include "ada/url_components-inl.h" 28 | #include "ada/url_aggregator.h" 29 | #include "ada/url_aggregator-inl.h" 30 | #include "ada/url_search_params.h" 31 | #include "ada/url_search_params-inl.h" 32 | 33 | #include "ada/url_pattern.h" 34 | #include "ada/url_pattern-inl.h" 35 | #include "ada/url_pattern_helpers.h" 36 | #include "ada/url_pattern_helpers-inl.h" 37 | #include "ada/url_pattern_regex.h" 38 | 39 | // Public API 40 | #include "ada/ada_version.h" 41 | #include "ada/implementation.h" 42 | #include "ada/implementation-inl.h" 43 | 44 | #endif // ADA_H 45 | -------------------------------------------------------------------------------- /.github/workflows/lint_and_format_check.yml: -------------------------------------------------------------------------------- 1 | name: Lint and format 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | lint-and-format: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 28 | 29 | - name: Run clang-format 30 | uses: jidicula/clang-format-action@6cd220de46c89139a0365edae93eee8eb30ca8fe # v4.16.0 31 | with: 32 | clang-format-version: '17' 33 | fallback-style: 'Google' 34 | 35 | - uses: chartboost/ruff-action@e18ae971ccee1b2d7bbef113930f00c670b78da4 # v1.0.0 36 | name: Lint with Ruff 37 | with: 38 | version: 0.6.0 39 | 40 | - name: Run clang-tidy 41 | run: > 42 | cmake -B build -DADA_TESTING=ON -DCMAKE_CXX_CLANG_TIDY=clang-tidy-17 && 43 | cmake --build build -j=4 44 | env: 45 | CXX: clang++-17 46 | -------------------------------------------------------------------------------- /.github/workflows/cifuzz.yml: -------------------------------------------------------------------------------- 1 | name: CIFuzz 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | permissions: read-all 13 | 14 | jobs: 15 | Fuzzing: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | sanitizer: [address, undefined, memory] 21 | steps: 22 | - name: Build Fuzzers (${{ matrix.sanitizer }}) 23 | id: build 24 | uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master 25 | with: 26 | oss-fuzz-project-name: 'ada-url' 27 | language: c++ 28 | sanitizer: ${{ matrix.sanitizer }} 29 | - name: Run Fuzzers (${{ matrix.sanitizer }}) 30 | uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master 31 | with: 32 | oss-fuzz-project-name: 'ada-url' 33 | language: c++ 34 | fuzz-seconds: 600 35 | sanitizer: ${{ matrix.sanitizer }} 36 | - name: Upload Crash 37 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 38 | if: steps.build.outcome == 'success' 39 | with: 40 | name: ${{ matrix.sanitizer }}-artifacts 41 | path: ./out/artifacts 42 | -------------------------------------------------------------------------------- /include/ada/serializers.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file serializers.h 3 | * @brief Definitions for the URL serializers. 4 | */ 5 | #ifndef ADA_SERIALIZERS_H 6 | #define ADA_SERIALIZERS_H 7 | 8 | #include "ada/common_defs.h" 9 | 10 | #include 11 | #include 12 | 13 | /** 14 | * @namespace ada::serializers 15 | * @brief Includes the definitions for URL serializers 16 | */ 17 | namespace ada::serializers { 18 | 19 | /** 20 | * Finds and returns the longest sequence of 0 values in a ipv6 input. 21 | */ 22 | void find_longest_sequence_of_ipv6_pieces( 23 | const std::array& address, size_t& compress, 24 | size_t& compress_length) noexcept; 25 | 26 | /** 27 | * Serializes an ipv6 address. 28 | * @details An IPv6 address is a 128-bit unsigned integer that identifies a 29 | * network address. 30 | * @see https://url.spec.whatwg.org/#concept-ipv6-serializer 31 | */ 32 | std::string ipv6(const std::array& address) noexcept; 33 | 34 | /** 35 | * Serializes an ipv4 address. 36 | * @details An IPv4 address is a 32-bit unsigned integer that identifies a 37 | * network address. 38 | * @see https://url.spec.whatwg.org/#concept-ipv4-serializer 39 | */ 40 | std::string ipv4(uint64_t address) noexcept; 41 | 42 | } // namespace ada::serializers 43 | 44 | #endif // ADA_SERIALIZERS_H 45 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu-s390x.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu s390x (GCC 12) 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | build: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 28 | - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1 29 | name: Test 30 | id: runcmd 31 | with: 32 | arch: s390x 33 | distro: ubuntu_latest 34 | githubToken: ${{ github.token }} 35 | install: | 36 | apt-get update -q -y 37 | apt-get install -y cmake make g++-12 gcc-12 git ninja-build 38 | run: | 39 | CC=gcc-12 CXX=g++-12 cmake -D ADA_TESTING=ON -DCMAKE_BUILD_TYPE=Release -G Ninja -B build 40 | rm -r -f dependencies 41 | CC=gcc-12 CXX=g++-12 cmake --build build -j=4 42 | ctest --output-on-failure --test-dir build 43 | -------------------------------------------------------------------------------- /.github/workflows/macos_install.yml: -------------------------------------------------------------------------------- 1 | name: macOS (Installation) 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | macos-build: 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | shared: [OFF] 29 | runs-on: [macos-13, macos-14, macos-15] 30 | runs-on: ${{matrix.runs-on}} 31 | steps: 32 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 33 | - name: Prepare 34 | run: cmake -D ADA_TESTING=ON -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCMAKE_INSTALL_PREFIX:PATH=destination -B build 35 | - name: Build 36 | run: cmake --build build -j=3 37 | - name: Install 38 | run: cmake --install build 39 | - name: Prepare test package 40 | run: cmake -DCMAKE_INSTALL_PREFIX:PATH=../../destination -S tests/installation -B buildbabyada 41 | - name: Build test package 42 | run: cmake --build buildbabyada 43 | - name: Run example 44 | run: ./buildbabyada/main 45 | -------------------------------------------------------------------------------- /.github/workflows/alpine.yml: -------------------------------------------------------------------------------- 1 | name: Alpine Linux 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | ubuntu-build: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 28 | - name: start docker 29 | run: | 30 | docker run -w /src -dit --name alpine -v $PWD:/src alpine:latest 31 | echo 'docker exec alpine "$@";' > ./alpine.sh 32 | chmod +x ./alpine.sh 33 | - name: install packages 34 | run: | 35 | ./alpine.sh apk update 36 | ./alpine.sh apk add build-base cmake g++ linux-headers git bash icu-dev 37 | - name: cmake 38 | run: | 39 | ./alpine.sh cmake -D ADA_TESTING=ON -DADA_BENCHMARKS=ON -B build_for_alpine 40 | - name: build 41 | run: | 42 | ./alpine.sh cmake --build build_for_alpine 43 | - name: test 44 | run: | 45 | ./alpine.sh bash -c "cd build_for_alpine && ctest ." 46 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 22.04 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | ubuntu-build: 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | shared: [ON, OFF] 29 | cxx: [g++-12, clang++-15] 30 | runs-on: [ubuntu-22.04, ubuntu-22.04-arm] 31 | simdutf: [OFF, ON] 32 | runs-on: ${{matrix.runs-on}} 33 | steps: 34 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 35 | - name: Setup Ninja 36 | run: sudo apt-get install ninja-build 37 | - name: Prepare 38 | run: cmake -D ADA_TESTING=ON -D ADA_BENCHMARKS=ON -DBUILD_SHARED_LIBS=${{matrix.shared}} -D ADA_USE_SIMDUTF=${{matrix.simdutf}} -G Ninja -B build 39 | env: 40 | CXX: ${{matrix.cxx}} 41 | - name: Build 42 | run: cmake --build build -j=4 43 | - name: Test 44 | run: ctest --output-on-failure --test-dir build 45 | - name: Run default benchmark 46 | run: cd build && benchmarks/bench 47 | -------------------------------------------------------------------------------- /.github/workflows/visual_studio_clang.yml: -------------------------------------------------------------------------------- 1 | name: VS17-clang-CI 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | ci: 25 | name: windows-vs17 26 | runs-on: windows-2025 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | include: 31 | - {gen: Visual Studio 17 2022, arch: x64, devchecks: ON} 32 | steps: 33 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 34 | - name: Configure 35 | run: | 36 | cmake -D ADA_TESTING=ON -DADA_DEVELOPMENT_CHECKS="${{matrix.devchecks}}" -G "${{matrix.gen}}" -A ${{matrix.arch}} -T ClangCL -B build 37 | - name: Build Debug 38 | run: cmake --build build --config Debug --verbose 39 | - name: Run Debug tests 40 | working-directory: build 41 | run: ctest -C Debug --output-on-failure 42 | - name: Build Release 43 | run: cmake --build build --config Release --verbose 44 | - name: Run Release tests 45 | working-directory: build 46 | run: ctest -C Release --output-on-failure 47 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * 1' 6 | 7 | permissions: 8 | contents: read 9 | security-events: write 10 | pull-requests: read 11 | actions: read 12 | 13 | jobs: 14 | analyze: 15 | name: Analyze 16 | 17 | runs-on: ubuntu-latest 18 | 19 | permissions: 20 | actions: read 21 | contents: read 22 | security-events: write 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | language: [ 'cpp', 'python' ] 28 | 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 32 | 33 | # Initializes the CodeQL tools for scanning. 34 | - name: Initialize CodeQL 35 | uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v2.2.5 36 | with: 37 | languages: ${{ matrix.language }} 38 | 39 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 40 | # If this step fails, then you should remove it and run the build manually (see below) 41 | - name: Autobuild 42 | uses: github/codeql-action/autobuild@fe4161a26a8629af62121b670040955b330f9af2 # v2.2.5 43 | 44 | - name: Perform CodeQL Analysis 45 | uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v2.2.5 46 | with: 47 | category: "/language:${{matrix.language}}" 48 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu_install.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 24.04 (Installation) 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | ubuntu-build: 25 | runs-on: ubuntu-24.04 26 | strategy: 27 | matrix: 28 | shared: [ON, OFF] 29 | cxx: [g++-12, clang++] 30 | steps: 31 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 32 | - name: Setup Ninja 33 | run: sudo apt-get install ninja-build 34 | - name: Prepare 35 | run: cmake -D ADA_TESTING=ON -G Ninja -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCMAKE_INSTALL_PREFIX:PATH=destination -B build 36 | env: 37 | CXX: ${{matrix.cxx}} 38 | - name: Build 39 | run: cmake --build build -j=4 40 | - name: Install 41 | run: cmake --install build 42 | - name: Prepare test package 43 | run: cmake -DCMAKE_INSTALL_PREFIX:PATH=../../destination -S tests/installation -B buildbabyada 44 | - name: Build test package 45 | run: cmake --build buildbabyada 46 | - name: Run example 47 | run: ./buildbabyada/main 48 | -------------------------------------------------------------------------------- /include/ada/unicode-inl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file unicode-inl.h 3 | * @brief Definitions for unicode operations. 4 | */ 5 | #ifndef ADA_UNICODE_INL_H 6 | #define ADA_UNICODE_INL_H 7 | #include "ada/unicode.h" 8 | #include "ada/character_sets.h" 9 | 10 | /** 11 | * Unicode operations. These functions are not part of our public API and may 12 | * change at any time. 13 | * 14 | * private 15 | * @namespace ada::unicode 16 | * @brief Includes the declarations for unicode operations 17 | */ 18 | namespace ada::unicode { 19 | ada_really_inline size_t percent_encode_index(const std::string_view input, 20 | const uint8_t character_set[]) { 21 | const char* data = input.data(); 22 | const size_t size = input.size(); 23 | 24 | // Process 8 bytes at a time using unrolled loop 25 | size_t i = 0; 26 | for (; i + 8 <= size; i += 8) { 27 | unsigned char chunk[8]; 28 | std::memcpy(&chunk, data + i, 29 | 8); // entices compiler to unconditionally process 8 characters 30 | 31 | // Check 8 characters at once 32 | for (size_t j = 0; j < 8; j++) { 33 | if (character_sets::bit_at(character_set, chunk[j])) { 34 | return i + j; 35 | } 36 | } 37 | } 38 | 39 | // Handle remaining bytes 40 | for (; i < size; i++) { 41 | if (character_sets::bit_at(character_set, data[i])) { 42 | return i; 43 | } 44 | } 45 | 46 | return size; 47 | } 48 | } // namespace ada::unicode 49 | 50 | #endif // ADA_UNICODE_INL_H 51 | -------------------------------------------------------------------------------- /.github/workflows/wpt-updater.yml: -------------------------------------------------------------------------------- 1 | name: Update WPT 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | env: 8 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 9 | 10 | concurrency: 11 | group: wpt-updater 12 | cancel-in-progress: true 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | issue: 19 | runs-on: ubuntu-latest 20 | permissions: 21 | contents: write 22 | pull-requests: write 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | module: [url, urlpattern] 27 | steps: 28 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 29 | - name: Fetch tests 30 | run: tools/update-wpt.sh ${{matrix.module}} 31 | - name: Open pull request 32 | uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e #v6.0.5 33 | with: 34 | token: ${{secrets.GH_PAT}} 35 | commit-message: "test: update web platform tests" 36 | branch: automatic-update-wpt-${{matrix.module}} 37 | title: Update web platform tests (${{matrix.module}}) 38 | body: | 39 | This is an automated pull request for updating the WPT. 40 | 41 | - [Web Platform Tests](https://github.com/web-platform-tests/wpt/tree/master/url) 42 | - [Commit History](https://github.com/web-platform-tests/wpt/commits/master/url/resources) 43 | 44 | cc @anonrig @lemire 45 | team-reviewers: core 46 | delete-branch: true 47 | -------------------------------------------------------------------------------- /singleheader/demo.c: -------------------------------------------------------------------------------- 1 | #include "ada_c.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static void ada_print(ada_string string) { 8 | printf("%.*s\n", (int)string.length, string.data); 9 | } 10 | 11 | int main(int c, char* arg[]) { 12 | const char* input = 13 | "https://username:password@www.google.com:8080/" 14 | "pathname?query=true#hash-exists"; 15 | ada_url url = ada_parse(input, strlen(input)); 16 | if (!ada_is_valid(url)) { 17 | puts("failure"); 18 | return EXIT_FAILURE; 19 | } 20 | ada_print(ada_get_href( 21 | url)); // prints 22 | // https://username:password@host:8080/pathname?query=true#hash-exists 23 | ada_print(ada_get_protocol(url)); // prints https: 24 | ada_print(ada_get_username(url)); // prints username 25 | ada_set_href(url, "https://www.yagiz.co", strlen("https://www.yagiz.co")); 26 | if (!ada_is_valid(url)) { 27 | puts("failure"); 28 | return EXIT_FAILURE; 29 | } 30 | ada_set_hash(url, "new-hash", strlen("new-hash")); 31 | ada_set_hostname(url, "new-host", strlen("new-host")); 32 | ada_set_host(url, "changed-host:9090", strlen("changed-host:9090")); 33 | ada_set_pathname(url, "new-pathname", strlen("new-pathname")); 34 | ada_set_search(url, "new-search", strlen("new-search")); 35 | ada_set_protocol(url, "wss", 3); 36 | ada_print(ada_get_href( 37 | url)); // will print 38 | // wss://changed-host:9090/new-pathname?new-search#new-hash 39 | ada_free(url); 40 | return EXIT_SUCCESS; 41 | } 42 | -------------------------------------------------------------------------------- /src/url_components.cpp: -------------------------------------------------------------------------------- 1 | #include "ada/helpers.h" 2 | #include "ada/url_components-inl.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace ada { 8 | 9 | [[nodiscard]] std::string url_components::to_string() const { 10 | std::string answer; 11 | auto back = std::back_insert_iterator(answer); 12 | answer.append("{\n"); 13 | 14 | answer.append("\t\"protocol_end\":\""); 15 | helpers::encode_json(std::to_string(protocol_end), back); 16 | answer.append("\",\n"); 17 | 18 | answer.append("\t\"username_end\":\""); 19 | helpers::encode_json(std::to_string(username_end), back); 20 | answer.append("\",\n"); 21 | 22 | answer.append("\t\"host_start\":\""); 23 | helpers::encode_json(std::to_string(host_start), back); 24 | answer.append("\",\n"); 25 | 26 | answer.append("\t\"host_end\":\""); 27 | helpers::encode_json(std::to_string(host_end), back); 28 | answer.append("\",\n"); 29 | 30 | answer.append("\t\"port\":\""); 31 | helpers::encode_json(std::to_string(port), back); 32 | answer.append("\",\n"); 33 | 34 | answer.append("\t\"pathname_start\":\""); 35 | helpers::encode_json(std::to_string(pathname_start), back); 36 | answer.append("\",\n"); 37 | 38 | answer.append("\t\"search_start\":\""); 39 | helpers::encode_json(std::to_string(search_start), back); 40 | answer.append("\",\n"); 41 | 42 | answer.append("\t\"hash_start\":\""); 43 | helpers::encode_json(std::to_string(hash_start), back); 44 | answer.append("\",\n"); 45 | 46 | answer.append("\n}"); 47 | return answer; 48 | } 49 | 50 | } // namespace ada 51 | -------------------------------------------------------------------------------- /benchmarks/bbc_bench.cpp: -------------------------------------------------------------------------------- 1 | #include "benchmark_header.h" 2 | 3 | /** 4 | * Realistic URL examples collected from the BBC homepage. 5 | */ 6 | std::string url_examples[] = { 7 | "https://static.files.bbci.co.uk/orbit/737a4ee2bed596eb65afc4d2ce9af568/js/" 8 | "polyfills.js", 9 | "https://static.files.bbci.co.uk/orbit/737a4ee2bed596eb65afc4d2ce9af568/" 10 | "css/orbit-v5-ltr.min.css", 11 | "https://static.files.bbci.co.uk/orbit/737a4ee2bed596eb65afc4d2ce9af568/js/" 12 | "require.min.js", 13 | "https://static.files.bbci.co.uk/fonts/reith/2.512/BBCReithSans_W_Rg.woff2", 14 | "https://nav.files.bbci.co.uk/searchbox/c8bfe8595e453f2b9483fda4074e9d15/" 15 | "css/box.css", 16 | "https://static.files.bbci.co.uk/cookies/d3bb303e79f041fec95388e04f84e716/" 17 | "cookie-banner/cookie-library.bundle.js", 18 | "https://static.files.bbci.co.uk/account/id-cta/597/style/id-cta.css", 19 | "https://gn-web-assets.api.bbc.com/wwhp/" 20 | "20220908-1153-091014d07889c842a7bdc06e00fa711c9e04f049/responsive/css/" 21 | "old-ie.min.css", 22 | "https://gn-web-assets.api.bbc.com/wwhp/" 23 | "20220908-1153-091014d07889c842a7bdc06e00fa711c9e04f049/modules/vendor/" 24 | "bower/modernizr/modernizr.js"}; 25 | 26 | void init_data(const char* v = nullptr) {} 27 | 28 | double url_examples_bytes = []() -> double { 29 | size_t bytes{0}; 30 | for (std::string& url_string : url_examples) { 31 | bytes += url_string.size(); 32 | } 33 | return double(bytes); 34 | }(); 35 | 36 | #define BENCHMARK_PREFIX BBC_ 37 | #define BENCHMARK_PREFIX_STR "BBC_" 38 | #include "benchmark_template.cpp" 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-bug-report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug report 2 | description: Create a report to help us improve 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thank you for reporting an issue. 8 | 9 | Please fill in as much of the following form as you're able. 10 | - type: input 11 | attributes: 12 | label: Version 13 | description: Which Ada version are you referring to? 14 | - type: input 15 | attributes: 16 | label: Platform 17 | description: | 18 | UNIX: output of `uname -a` 19 | Windows: output of `"$([Environment]::OSVersion.VersionString) $(('x86', 'x64')[[Environment]::Is64BitOperatingSystem])"` in PowerShell console 20 | - type: textarea 21 | attributes: 22 | label: What steps will reproduce the bug? 23 | description: Enter details about your bug, preferably a simple code snippet that can be run directly without installing third-party dependencies. 24 | - type: textarea 25 | attributes: 26 | label: How often does it reproduce? Is there a required condition? 27 | - type: textarea 28 | attributes: 29 | label: What is the expected behavior? 30 | description: If possible please provide textual output instead of screenshots. 31 | - type: textarea 32 | attributes: 33 | label: What do you see instead? 34 | description: If possible please provide textual output instead of screenshots. 35 | validations: 36 | required: true 37 | - type: textarea 38 | attributes: 39 | label: Additional information 40 | description: Tell us anything else you think we should know. 41 | -------------------------------------------------------------------------------- /tests/wasm/wasm.cpp: -------------------------------------------------------------------------------- 1 | #include "ada.h" 2 | #include 3 | #include 4 | 5 | using namespace emscripten; 6 | 7 | struct parse_result { 8 | std::string result; 9 | std::string href; 10 | uint32_t type; 11 | ada::url_components components; 12 | }; 13 | 14 | parse_result parse(const std::string &input) { 15 | auto out = ada::parse(input); 16 | parse_result result; 17 | if (!out.has_value()) { 18 | result.result = "fail"; 19 | } else { 20 | result.result = "success"; 21 | result.href = std::string(out->get_href()); 22 | result.type = out->type; 23 | result.components = out->get_components(); 24 | } 25 | return result; 26 | } 27 | 28 | EMSCRIPTEN_BINDINGS(url_components) { 29 | class_("Result") 30 | .property("result", &parse_result::result) 31 | .property("href", &parse_result::href) 32 | .property("type", &parse_result::type) 33 | .property("components", &parse_result::components); 34 | class_("URLComponents") 35 | .property("protocol_end", &ada::url_components::protocol_end) 36 | .property("username_end", &ada::url_components::username_end) 37 | .property("host_start", &ada::url_components::host_start) 38 | .property("host_end", &ada::url_components::host_end) 39 | .property("port", &ada::url_components::port) 40 | .property("pathname_start", &ada::url_components::pathname_start) 41 | .property("search_start", &ada::url_components::search_start) 42 | .property("hash_start", &ada::url_components::hash_start); 43 | 44 | function("parse", &parse); 45 | } -------------------------------------------------------------------------------- /.github/workflows/visual_studio.yml: -------------------------------------------------------------------------------- 1 | name: VS17-CI 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | ci: 25 | name: windows-vs17 26 | runs-on: windows-2025 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | include: 31 | - {gen: Visual Studio 17 2022, arch: x64, devchecks: OFF, shared: OFF, config: Release} 32 | - {gen: Visual Studio 17 2022, arch: x64, devchecks: ON, shared: OFF, config: Debug} 33 | - {gen: Visual Studio 17 2022, arch: x64, devchecks: ON, shared: ON, config: Debug} 34 | - {gen: Visual Studio 17 2022, arch: Win32, devchecks: ON, shared: OFF, config: Debug} 35 | - {gen: Visual Studio 17 2022, arch: Win32, devchecks: ON, shared: ON, config: Debug} 36 | steps: 37 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 38 | - name: Configure 39 | run: | 40 | cmake -D ADA_TESTING=ON -DADA_DEVELOPMENT_CHECKS="${{matrix.devchecks}}" -G "${{matrix.gen}}" -A ${{matrix.arch}} -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build 41 | - name: Build 42 | run: cmake --build build --config "${{matrix.config}}" --verbose 43 | - name: Run tests 44 | working-directory: build 45 | run: ctest -C "${{matrix.config}}" --output-on-failure 46 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu-riscv64-rvv.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu RISC-V Vector Extension 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | jobs: 23 | ubuntu-build: 24 | strategy: 25 | fail-fast: false 26 | runs-on: ubuntu-24.04 27 | steps: 28 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 29 | - name: Setup ENV 30 | run: | 31 | sudo apt-get update -y 32 | sudo apt-get install -y cmake curl ninja-build \ 33 | g++-riscv64-linux-gnu \ 34 | gcc-riscv64-linux-gnu \ 35 | qemu-user-static qemu-user 36 | - name: Build 37 | run: | 38 | export QEMU_LD_PREFIX="/usr/riscv64-linux-gnu" 39 | export QEMU_CPU="rv64,vlen=128" 40 | cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains-dev/riscv64-rvv.cmake \ 41 | -DADA_TESTING=ON \ 42 | -DADA_USE_SIMDUTF=ON \ 43 | -DCMAKE_BUILD_TYPE=Release \ 44 | -G Ninja -B build 45 | cmake --build build -j=4 46 | - name: Test 47 | run: | 48 | export QEMU_LD_PREFIX="/usr/riscv64-linux-gnu" 49 | export QEMU_CPU="rv64,v=on,vlen=128" 50 | ctest --output-on-failure --test-dir build 51 | -------------------------------------------------------------------------------- /fuzz/url_search_params.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "ada.cpp" 7 | #include "ada.h" 8 | 9 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 10 | FuzzedDataProvider fdp(data, size); 11 | std::string source = fdp.ConsumeRandomLengthString(256); 12 | std::string base_source = fdp.ConsumeRandomLengthString(256); 13 | 14 | /** 15 | * ada::url_search_params 16 | */ 17 | 18 | auto base_source_view = 19 | std::string_view(base_source.data(), base_source.length()); 20 | auto initialized = ada::url_search_params(base_source_view); 21 | 22 | auto search_params = ada::url_search_params(); 23 | search_params.append(source, base_source); 24 | search_params.set(source, base_source); 25 | search_params.to_string(); 26 | if (!search_params.has(base_source)) { 27 | search_params.append(base_source, source); 28 | } 29 | search_params.remove(source); 30 | search_params.remove(source, base_source); 31 | if (search_params.has(base_source, source)) { 32 | search_params.remove(base_source); 33 | search_params.remove(base_source, source); 34 | } 35 | 36 | auto keys = search_params.get_keys(); 37 | while (keys.has_next()) { 38 | keys.next(); 39 | } 40 | 41 | auto values = search_params.get_values(); 42 | while (values.has_next()) { 43 | values.next(); 44 | } 45 | 46 | auto entries = search_params.get_entries(); 47 | while (entries.has_next()) { 48 | entries.next(); 49 | } 50 | 51 | // This is testing a private method used only for C API. 52 | std::string resetted_value = fdp.ConsumeRandomLengthString(256); 53 | search_params.reset(resetted_value); 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /cmake/toolchains-dev/README.md: -------------------------------------------------------------------------------- 1 | # LoongArch64 2 | You can now build and run the LoongArch64 code as needed: 3 | GCC >= 14.1 4 | Binutils >= 2.41 5 | QEMU >= 9.2 6 | 7 | Note that the compiler may be named `loongarch64-linux-gnu-g++`, without the `unknown` part. 8 | Please adjust to your system. 9 | 10 | ``` 11 | $ sudo curl -L https://github.com/loongson/build-tools/releases/download/2025.06.06/qemu-loongarch64 --output /opt/qemu-loongarch64 12 | $ sudo chmod +x /opt/qemu-loongarch64 13 | $ export PATH=/opt:$PATH 14 | 15 | $ export QEMU_LD_PREFIX="/usr/loongarch64-linux-gnu" # ubuntu 24.04 16 | $ export QEMU_CPU="la464" 17 | $ mkdir build && cd build 18 | $ cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains-dev/loongarch64.cmake -DADA_TESTING=ON ../ 19 | $ make 20 | ``` 21 | 22 | Running tests with qemu 23 | ``` 24 | $ make test 25 | or 26 | $ ctest --output-on-failure --test-dir build 27 | or 28 | $ qemu-loongarch64 build/singleheader/cdemo 29 | ``` 30 | 31 | # RISC-V Vector Extension 32 | 33 | The RISC-V Vector optimizations are supported by GCC-13, CLANG-16 and above. 34 | 35 | Native builds will use the RVV code, if the specified `-march` ISA string or default target ISA supports the V extension. 36 | 37 | For cross compilation, you may need to adjust the cross compiler target prefix in the toolchain file from `riscv64-linux-gnu` to e.g. `riscv64-unknown-linux-gnu` when using https://github.com/riscv-collab/riscv-gnu-toolchain. 38 | 39 | ``` 40 | # For Debian/Ubuntu 41 | $ sudo apt install g++-riscv64-linux-gnu qemu-system-riscv qemu-user 42 | $ mkdir build; cd build 43 | $ export QEMU_LD_PREFIX="/usr/riscv64-linux-gnu" 44 | $ export QEMU_CPU="rv64,v=on" 45 | $ mkdir build && cd build 46 | $ cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchains-dev/riscv64-rvv.cmake -DADA_TESTING=ON .. 47 | $ cmake --build -j $(nproc) 48 | ``` 49 | -------------------------------------------------------------------------------- /fuzz/ada_c.c: -------------------------------------------------------------------------------- 1 | #include "ada_c.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { 9 | /** 10 | * ada_c 11 | */ 12 | ada_url out = ada_parse((char*)data, size); 13 | bool is_valid = ada_is_valid(out); 14 | 15 | if (is_valid) { 16 | ada_set_href(out, (char*)data, size); 17 | ada_set_host(out, (char*)data, size); 18 | ada_set_hostname(out, (char*)data, size); 19 | ada_set_protocol(out, (char*)data, size); 20 | ada_set_username(out, (char*)data, size); 21 | ada_set_password(out, (char*)data, size); 22 | ada_set_port(out, (char*)data, size); 23 | ada_set_pathname(out, (char*)data, size); 24 | ada_set_search(out, (char*)data, size); 25 | ada_set_hash(out, (char*)data, size); 26 | 27 | ada_get_hash(out); 28 | ada_get_host(out); 29 | ada_get_host_type(out); 30 | ada_get_hostname(out); 31 | ada_get_href(out); 32 | ada_owned_string out_get_origin = ada_get_origin(out); 33 | ada_get_pathname(out); 34 | ada_get_username(out); 35 | ada_get_password(out); 36 | ada_get_protocol(out); 37 | ada_get_port(out); 38 | ada_get_search(out); 39 | ada_get_scheme_type(out); 40 | 41 | ada_has_credentials(out); 42 | ada_has_empty_hostname(out); 43 | ada_has_hostname(out); 44 | ada_has_non_empty_username(out); 45 | ada_has_non_empty_password(out); 46 | ada_has_port(out); 47 | ada_has_password(out); 48 | ada_has_hash(out); 49 | ada_has_search(out); 50 | 51 | ada_get_components(out); 52 | 53 | ada_clear_port(out); 54 | ada_clear_hash(out); 55 | ada_clear_search(out); 56 | 57 | ada_free_owned_string(out_get_origin); 58 | } 59 | 60 | bool can_parse_result = ada_can_parse((char*)data, size); 61 | 62 | ada_free(out); 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu-loongarch64.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu LoongArch64 (GCC 14) 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | jobs: 23 | ubuntu-build: 24 | strategy: 25 | fail-fast: false 26 | runs-on: ubuntu-24.04 27 | steps: 28 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 29 | - name: Setup ENV 30 | run: | 31 | sudo apt-get update -y 32 | sudo apt-get install -y cmake curl ninja-build \ 33 | g++-14-loongarch64-linux-gnu \ 34 | gcc-14-loongarch64-linux-gnu-base \ 35 | gcc-14-loongarch64-linux-gnu 36 | sudo curl -L https://github.com/loongson/build-tools/releases/download/2025.06.06/qemu-loongarch64 --output /usr/local/bin/qemu-loongarch64 37 | sudo chmod +x /usr/local/bin/qemu-loongarch64 38 | - name: Build 39 | run: | 40 | export QEMU_LD_PREFIX="/usr/loongarch64-linux-gnu" 41 | export QEMU_CPU="la464" 42 | cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains-dev/loongarch64.cmake \ 43 | -DADA_TESTING=ON \ 44 | -DADA_USE_SIMDUTF=ON \ 45 | -DCMAKE_BUILD_TYPE=Release \ 46 | -G Ninja -B build 47 | cmake --build build -j=4 48 | - name: Test 49 | run: | 50 | export QEMU_LD_PREFIX="/usr/loongarch64-linux-gnu" 51 | export QEMU_CPU="la464" 52 | ctest --output-on-failure --test-dir build 53 | -------------------------------------------------------------------------------- /benchmarks/competitors/servo-url/lib.rs: -------------------------------------------------------------------------------- 1 | use url::Url; 2 | use std::slice; 3 | use libc::{c_char, size_t}; 4 | 5 | extern crate url; 6 | extern crate libc; 7 | 8 | #[unsafe(no_mangle)] 9 | pub extern "C" fn parse_url(raw_input: *const c_char, raw_input_length: size_t) -> *mut Url { 10 | let input = unsafe { std::str::from_utf8_unchecked(slice::from_raw_parts(raw_input as *const u8, raw_input_length)) }; 11 | // This code would assume that the URL is parsed successfully: 12 | // let result = Url::parse(input).unwrap(); 13 | // Box::into_raw(Box::new(result)) 14 | // But we might get an invalid input. So we want to return null in case of 15 | // error. We can do it in such a manner: 16 | match Url::parse(input) { 17 | Ok(result) => Box::into_raw(Box::new(result)), 18 | Err(_) => std::ptr::null_mut(), 19 | } 20 | } 21 | 22 | #[unsafe(no_mangle)] 23 | pub extern "C" fn parse_url_to_href(raw_input: *const c_char, raw_input_length: size_t) -> *const c_char { 24 | let input = unsafe { std::str::from_utf8_unchecked(slice::from_raw_parts(raw_input as *const u8, raw_input_length)) }; 25 | match Url::parse(input) { 26 | Ok(result) => std::ffi::CString::new(result.as_str()).unwrap().into_raw(), 27 | Err(_) => std::ptr::null_mut(), 28 | } 29 | } 30 | 31 | /// # Safety 32 | /// 33 | /// This function is unsafe because it takes ownership of the pointer and drops it. 34 | #[unsafe(no_mangle)] 35 | pub unsafe extern "C" fn free_url(raw: *mut Url) { 36 | if raw.is_null() { 37 | return; 38 | } 39 | 40 | unsafe { drop(Box::from_raw(raw)) } 41 | } 42 | 43 | /// # Safety 44 | /// 45 | /// This function is unsafe because it takes ownership of the pointer and drops it. 46 | #[unsafe(no_mangle)] 47 | pub unsafe extern "C" fn free_string(ptr: *const c_char) { 48 | // Take the ownership back to rust and drop the owner 49 | let _ = unsafe { std::ffi::CString::from_raw(ptr as *mut _) }; 50 | } 51 | -------------------------------------------------------------------------------- /tools/cli/benchmark_adaparse.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Benchmarking piping function" 4 | 5 | # Set the number of trials 6 | num_trials=50 7 | 8 | if [ -f "linux_files.txt" ]; then 9 | echo "linux_files.txt exists." 10 | else 11 | echo "downloading linux_files.txt." 12 | curl https://raw.githubusercontent.com/ada-url/url-various-datasets/main/files/linux_files.txt -O 13 | fi 14 | 15 | if [ -f "wikipedia_100k.txt" ]; then 16 | echo "wikipedia_100k.txt exists." 17 | else 18 | echo "downloading wikipedia_100k.txt." 19 | curl https://raw.githubusercontent.com/ada-url/url-various-datasets/main/wikipedia/wikipedia_100k.txt -O 20 | fi 21 | 22 | if [ -f "top100.txt" ]; then 23 | echo "top100.txt exists." 24 | else 25 | echo "downloading top100.txt." 26 | curl https://raw.githubusercontent.com/ada-url/url-various-datasets/main/top100/top100.txt -O 27 | fi 28 | 29 | # File list to benchmark against 30 | files=("top100.txt" "linux_files.txt" "wikipedia_100k.txt" ) 31 | 32 | # Run the programs for the specified number of trials 33 | for file in "${files[@]}"; do 34 | echo "Benchmarking $file" 35 | 36 | # Variables to store the sum of the Gb/s values for each program 37 | sum_fastpipespeed=0 38 | 39 | for i in $(seq 1 $num_trials); do 40 | result_fastspeed=$(cat $file | ../../build/tools/adaparse --benchmark 2>&1 | tail -1 | grep -oP '\d+(\.\d+)?') 41 | sum_fastpipespeed=$(echo "$sum_fastpipespeed + $result_fastspeed" | bc) 42 | done 43 | 44 | # Compute the averages 45 | avg_fastpipespeed=$(echo "scale=7; $sum_fastpipespeed / $num_trials" | bc) 46 | 47 | # Display the results 48 | echo "------------------------------" 49 | echo "Finished benchmarking $file" 50 | echo "Number of trials: $num_trials" 51 | echo "Average Gb/s for fastpipespeed: $avg_fastpipespeed" 52 | 53 | echo "----------------------------" 54 | 55 | 56 | 57 | echo "" 58 | done 59 | -------------------------------------------------------------------------------- /benchmarks/benchmark_header.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #if ADA_VARIOUS_COMPETITION_ENABLED 9 | #include 10 | #include 11 | #include 12 | #endif 13 | #if ADA_url_whatwg_ENABLED 14 | #include 15 | #endif 16 | 17 | #include "ada.h" 18 | #include "performancecounters/event_counter.h" 19 | event_collector collector; 20 | size_t N = 1000; 21 | 22 | #include 23 | 24 | bool file_exists(const char* filename) { 25 | namespace fs = std::filesystem; 26 | std::filesystem::path f{filename}; 27 | if (std::filesystem::exists(filename)) { 28 | return true; 29 | } else { 30 | return false; 31 | } 32 | } 33 | 34 | std::string read_file(std::string filename) { 35 | constexpr size_t read_size = 4096; 36 | auto stream = std::ifstream(filename.c_str()); 37 | stream.exceptions(std::ios_base::badbit); 38 | std::string out; 39 | std::string buf(read_size, '\0'); 40 | while (stream.read(&buf[0], read_size)) { 41 | out.append(buf, 0, size_t(stream.gcount())); 42 | } 43 | out.append(buf, 0, size_t(stream.gcount())); 44 | return out; 45 | } 46 | 47 | std::vector split_string(const std::string& str) { 48 | std::vector result; 49 | std::stringstream ss{str}; 50 | for (std::string line; std::getline(ss, line, '\n');) { 51 | std::string_view view = line; 52 | // Some parsers like boost/url will refuse to parse a URL with trailing 53 | // whitespace. 54 | while (!view.empty() && std::isspace(view.back())) { 55 | view.remove_suffix(1); 56 | } 57 | while (!view.empty() && std::isspace(view.front())) { 58 | view.remove_prefix(1); 59 | } 60 | if (!view.empty()) { 61 | result.emplace_back(view); 62 | } 63 | } 64 | return result; 65 | } 66 | -------------------------------------------------------------------------------- /include/ada/checkers-inl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file checkers-inl.h 3 | * @brief Definitions for URL specific checkers used within Ada. 4 | */ 5 | #ifndef ADA_CHECKERS_INL_H 6 | #define ADA_CHECKERS_INL_H 7 | 8 | #include 9 | #include 10 | 11 | namespace ada::checkers { 12 | 13 | constexpr bool has_hex_prefix_unsafe(std::string_view input) { 14 | // This is actually efficient code, see has_hex_prefix for the assembly. 15 | constexpr bool is_little_endian = std::endian::native == std::endian::little; 16 | constexpr uint16_t word0x = 0x7830; 17 | uint16_t two_first_bytes = 18 | static_cast(input[0]) | 19 | static_cast((static_cast(input[1]) << 8)); 20 | if constexpr (is_little_endian) { 21 | two_first_bytes |= 0x2000; 22 | } else { 23 | two_first_bytes |= 0x020; 24 | } 25 | return two_first_bytes == word0x; 26 | } 27 | 28 | constexpr bool has_hex_prefix(std::string_view input) { 29 | return input.size() >= 2 && has_hex_prefix_unsafe(input); 30 | } 31 | 32 | constexpr bool is_digit(char x) noexcept { return (x >= '0') & (x <= '9'); } 33 | 34 | constexpr char to_lower(char x) noexcept { return (x | 0x20); } 35 | 36 | constexpr bool is_alpha(char x) noexcept { 37 | return (to_lower(x) >= 'a') && (to_lower(x) <= 'z'); 38 | } 39 | 40 | constexpr bool is_windows_drive_letter(std::string_view input) noexcept { 41 | return input.size() >= 2 && 42 | (is_alpha(input[0]) && ((input[1] == ':') || (input[1] == '|'))) && 43 | ((input.size() == 2) || (input[2] == '/' || input[2] == '\\' || 44 | input[2] == '?' || input[2] == '#')); 45 | } 46 | 47 | constexpr bool is_normalized_windows_drive_letter( 48 | std::string_view input) noexcept { 49 | return input.size() >= 2 && (is_alpha(input[0]) && (input[1] == ':')); 50 | } 51 | 52 | } // namespace ada::checkers 53 | 54 | #endif // ADA_CHECKERS_INL_H 55 | -------------------------------------------------------------------------------- /tools/cli/benchmark_write_to_file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Benchmarking writing to file from pipe." 4 | 5 | # Set the number of trials 6 | num_trials=50 7 | 8 | if [ -f "linux_files.txt" ]; then 9 | echo "linux_files.txt exists." 10 | else 11 | echo "downloading linux_files.txt." 12 | curl https://raw.githubusercontent.com/ada-url/url-various-datasets/main/files/linux_files.txt -O 13 | fi 14 | 15 | if [ -f "wikipedia_100k.txt" ]; then 16 | echo "wikipedia_100k.txt exists." 17 | else 18 | echo "downloading wikipedia_100k.txt." 19 | curl https://raw.githubusercontent.com/ada-url/url-various-datasets/main/wikipedia/wikipedia_100k.txt -O 20 | fi 21 | 22 | if [ -f "top100.txt" ]; then 23 | echo "top100.txt exists." 24 | else 25 | echo "downloading top100.txt." 26 | curl https://raw.githubusercontent.com/ada-url/url-various-datasets/main/top100/top100.txt -O 27 | fi 28 | 29 | # File list to benchmark against 30 | files=("top100.txt" "linux_files.txt" "wikipedia_100k.txt" ) 31 | 32 | # Run the programs for the specified number of trials 33 | for file in "${files[@]}"; do 34 | echo "Benchmarking $file" 35 | 36 | # Variables to store the sum of the Gb/s values for each program 37 | sum_fastpipespeed=0 38 | 39 | for i in $(seq 1 $num_trials); do 40 | result_fastspeed=$(cat $file | ../../build/tools/adaparse --benchmark --output test_write_speeds.txt 2>&1 | tail -1 | grep -oP '\d+(\.\d+)?') 41 | sum_fastpipespeed=$(echo "$sum_fastpipespeed + $result_fastspeed" | bc) 42 | done 43 | 44 | # Compute the averages 45 | avg_fastpipespeed=$(echo "scale=7; $sum_fastpipespeed / $num_trials" | bc) 46 | 47 | # Display the results 48 | echo "------------------------------" 49 | echo "Finished benchmarking $file" 50 | echo "Number of trials: $num_trials" 51 | echo "Average Gb/s for fastpipespeed: $avg_fastpipespeed" 52 | 53 | echo "----------------------------" 54 | 55 | 56 | 57 | echo "" 58 | done 59 | -------------------------------------------------------------------------------- /.github/workflows/release_create.yml: -------------------------------------------------------------------------------- 1 | name: Release Create 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | 7 | env: 8 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 9 | 10 | jobs: 11 | check-release-conditions: 12 | runs-on: ubuntu-latest 13 | if: | 14 | github.event.pull_request.merged == true && 15 | github.event.pull_request.base.ref == 'main' && 16 | startsWith(github.event.pull_request.head.ref, 'release/v') && 17 | startsWith(github.event.pull_request.user.login, 'github-actions') 18 | 19 | steps: 20 | - name: Check release conditions 21 | run: | 22 | echo "All conditions have been met!" 23 | 24 | release-script-test: 25 | needs: check-release-conditions 26 | uses: ./.github/workflows/release-script-tests.yml 27 | 28 | create-release: 29 | permissions: 30 | contents: write 31 | needs: release-script-test 32 | runs-on: ubuntu-latest 33 | if: ${{ needs.release-script-test.result == 'success' }} 34 | 35 | env: 36 | NEXT_RELEASE_TAG: ${{ github.event.pull_request.head.ref }} 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 40 | 41 | - name: Prepare Python 42 | uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 43 | with: 44 | cache: 'pip' # caching pip dependencies 45 | 46 | - name: Install dependencies 47 | run: pip install -r ./tools/release/requirements.txt 48 | 49 | - name: Extract Tag from branch name 50 | run: | 51 | NEXT_RELEASE_TAG=$(echo $NEXT_RELEASE_TAG | sed 's/^release\///') 52 | echo "NEXT_RELEASE_TAG=${NEXT_RELEASE_TAG}" >> $GITHUB_ENV 53 | 54 | - name: Target release Tag 55 | run: echo "New tag $NEXT_RELEASE_TAG" 56 | 57 | - name: Amalgamation 58 | run: ./singleheader/amalgamate.py 59 | 60 | - name: "Create release" 61 | run: ./tools/release/create_release.py 62 | -------------------------------------------------------------------------------- /tools/release/lib/tests/test_update_versions.py: -------------------------------------------------------------------------------- 1 | from .. import versions 2 | import os 3 | 4 | 5 | def test_update_cmakelists_version() -> None: 6 | current_dir = os.path.dirname(os.path.abspath(__file__)) 7 | sample_path = f'{current_dir}/samples/cmakelists.txt' 8 | sample_expected_path = f'{current_dir}/samples/cmakelists_expected.txt' 9 | 10 | versions.update_cmakelists_version('2.0.0', sample_path) 11 | 12 | with open(sample_path, 'r') as cmake: 13 | given = cmake.read() 14 | 15 | with open(sample_expected_path, 'r') as cmake_expected: 16 | expected = cmake_expected.read() 17 | 18 | assert given == expected 19 | versions.update_cmakelists_version('1.0.0', sample_path) # cleanup 20 | 21 | 22 | def test_update_ada_version_h() -> None: 23 | current_dir = os.path.dirname(os.path.abspath(__file__)) 24 | sample_path = f'{current_dir}/samples/ada_version_h.txt' 25 | sample_expected_path = f'{current_dir}/samples/ada_version_h_expected.txt' 26 | 27 | versions.update_ada_version_h('2.0.0', sample_path) 28 | 29 | with open(sample_path, 'r') as ada_version_h: 30 | given = ada_version_h.read() 31 | 32 | with open(sample_expected_path, 'r') as ada_version_h_expected: 33 | expected = ada_version_h_expected.read() 34 | 35 | assert given == expected 36 | versions.update_ada_version_h('1.0.0', sample_path) # cleanup 37 | 38 | 39 | def test_update_doxygen_version() -> None: 40 | current_dir = os.path.dirname(os.path.abspath(__file__)) 41 | sample_path = f'{current_dir}/samples/doxygen.txt' 42 | sample_expected_path = f'{current_dir}/samples/doxygen_expected.txt' 43 | 44 | versions.update_doxygen_version('2.0.0', sample_path) 45 | 46 | with open(sample_path, 'r') as doxygen: 47 | given = doxygen.read() 48 | 49 | with open(sample_expected_path, 'r') as doxygen_expected: 50 | expected = doxygen_expected.read() 51 | 52 | assert given == expected 53 | versions.update_ada_version_h('1.0.0', sample_path) # cleanup 54 | -------------------------------------------------------------------------------- /.github/workflows/release_prepare.yml: -------------------------------------------------------------------------------- 1 | name: Release Prepare 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | type: string 8 | required: true 9 | description: "Tag for the next release. Ex.: v5.0.0" 10 | 11 | env: 12 | NEXT_RELEASE_TAG: ${{ github.event.inputs.tag }} 13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 14 | 15 | jobs: 16 | release-script-test: 17 | uses: ./.github/workflows/release-script-tests.yml 18 | 19 | prepare-release-and-pull-request: 20 | permissions: 21 | contents: write 22 | pull-requests: write 23 | needs: release-script-test 24 | runs-on: ubuntu-22.04-arm 25 | if: ${{ needs.release-script-test.result == 'success' }} 26 | env: 27 | CXX: clang++-14 28 | steps: 29 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 30 | 31 | - name: Prepare Python 32 | uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 33 | with: 34 | cache: 'pip' # caching pip dependencies 35 | 36 | - name: Install dependencies 37 | run: pip install -r ./tools/release/requirements.txt 38 | 39 | - name: Update source code versions 40 | run: ./tools/release/update_versions.py 41 | 42 | - name: Ada Build 43 | run: cmake -B build && cmake --build build 44 | - name: Ada Test 45 | run: ctest --output-on-failure --test-dir build 46 | 47 | - name: Create PR with code updates for new release 48 | uses: peter-evans/create-pull-request@f3a21bf3404eae73a97f65817ab35f351a1a63fe #v5.0.0 49 | with: 50 | commit-message: "chore: release ${{ env.NEXT_RELEASE_TAG }}" 51 | branch: "release/${{ env.NEXT_RELEASE_TAG }}" 52 | title: "chore: release ${{ env.NEXT_RELEASE_TAG }}" 53 | token: ${{ env.GITHUB_TOKEN }} 54 | body: | 55 | This pull PR updates the source code version to ${{ env.NEXT_RELEASE_TAG }} 56 | delete-branch: true 57 | reviewers: "lemire,anonrig" 58 | -------------------------------------------------------------------------------- /fuzz/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $SRC/ada-url 4 | 5 | mkdir build 6 | AMALGAMATE_OUTPUT_PATH=./build/singleheader python3 singleheader/amalgamate.py 7 | 8 | $CXX $CFLAGS $CXXFLAGS \ 9 | -std=c++20 \ 10 | -I build/singleheader \ 11 | -c fuzz/parse.cc -o parse.o 12 | 13 | $CXX $CFLAGS $CXXFLAGS $LIB_FUZZING_ENGINE parse.o \ 14 | -o $OUT/parse 15 | 16 | $CXX $CFLAGS $CXXFLAGS \ 17 | -std=c++20 \ 18 | -I build/singleheader \ 19 | -c fuzz/can_parse.cc -o can_parse.o 20 | 21 | $CXX $CFLAGS $CXXFLAGS $LIB_FUZZING_ENGINE can_parse.o \ 22 | -o $OUT/can_parse 23 | 24 | $CXX $CFLAGS $CXXFLAGS \ 25 | -std=c++20 \ 26 | -I build/singleheader \ 27 | -c fuzz/idna.cc -o idna.o 28 | 29 | $CXX $CFLAGS $CXXFLAGS $LIB_FUZZING_ENGINE idna.o \ 30 | -o $OUT/idna 31 | 32 | $CXX $CFLAGS $CXXFLAGS \ 33 | -std=c++20 \ 34 | -I build/singleheader \ 35 | -c fuzz/url_search_params.cc -o url_search_params.o 36 | 37 | $CXX $CFLAGS $CXXFLAGS $LIB_FUZZING_ENGINE url_search_params.o \ 38 | -o $OUT/url_search_params 39 | 40 | # IMPORTANT 41 | # 42 | # We use std_regex_provider for testing purposes. 43 | # It is not encouraged or recommended to be used within production 44 | # environments due to security problems. 45 | # 46 | # Please do not enable it on production systems! 47 | # 48 | $CXX -DADA_USE_UNSAFE_STD_REGEX_PROVIDER=1 \ 49 | $CFLAGS $CXXFLAGS \ 50 | -std=c++20 \ 51 | -I build/singleheader \ 52 | -c fuzz/url_pattern.cc -o url_pattern.o 53 | 54 | $CXX -DADA_USE_UNSAFE_STD_REGEX_PROVIDER=1 \ 55 | $CFLAGS $CXXFLAGS $LIB_FUZZING_ENGINE \ 56 | url_pattern.o \ 57 | -o $OUT/url_pattern 58 | 59 | $CXX $CFLAGS $CXXFLAGS \ 60 | -std=c++20 \ 61 | -I build/singleheader \ 62 | -c build/singleheader/ada.cpp -o ada.o 63 | 64 | $CC $CFLAGS $CXXFLAGS \ 65 | -I build/singleheader \ 66 | -c fuzz/ada_c.c -o ada_c.o 67 | 68 | $CXX $CFLAGS $CXXFLAGS $LIB_FUZZING_ENGINE ./ada.o ada_c.o \ 69 | -o $OUT/ada_c 70 | 71 | cp $SRC/ada-url/fuzz/*.dict $SRC/ada-url/fuzz/*.options $OUT/ 72 | -------------------------------------------------------------------------------- /include/ada/url_pattern_regex.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file url_search_params.h 3 | * @brief Declaration for the URL Search Params 4 | */ 5 | #ifndef ADA_URL_PATTERN_REGEX_H 6 | #define ADA_URL_PATTERN_REGEX_H 7 | 8 | #include 9 | #include 10 | 11 | #ifdef ADA_USE_UNSAFE_STD_REGEX_PROVIDER 12 | #include 13 | #endif // ADA_USE_UNSAFE_STD_REGEX_PROVIDER 14 | 15 | #if ADA_INCLUDE_URL_PATTERN 16 | namespace ada::url_pattern_regex { 17 | 18 | template 19 | concept regex_concept = requires(T t, std::string_view pattern, 20 | bool ignore_case, std::string_view input) { 21 | // Ensure the class has a type alias 'regex_type' 22 | typename T::regex_type; 23 | 24 | // Function to create a regex instance 25 | { 26 | T::create_instance(pattern, ignore_case) 27 | } -> std::same_as>; 28 | 29 | // Function to perform regex search 30 | { 31 | T::regex_search(input, std::declval()) 32 | } -> std::same_as>>>; 33 | 34 | // Function to match regex pattern 35 | { 36 | T::regex_match(input, std::declval()) 37 | } -> std::same_as; 38 | 39 | // Copy constructor 40 | { T(std::declval()) } -> std::same_as; 41 | 42 | // Move constructor 43 | { T(std::declval()) } -> std::same_as; 44 | }; 45 | 46 | #ifdef ADA_USE_UNSAFE_STD_REGEX_PROVIDER 47 | class std_regex_provider final { 48 | public: 49 | std_regex_provider() = default; 50 | using regex_type = std::regex; 51 | static std::optional create_instance(std::string_view pattern, 52 | bool ignore_case); 53 | static std::optional>> regex_search( 54 | std::string_view input, const regex_type& pattern); 55 | static bool regex_match(std::string_view input, const regex_type& pattern); 56 | }; 57 | #endif // ADA_USE_UNSAFE_STD_REGEX_PROVIDER 58 | 59 | } // namespace ada::url_pattern_regex 60 | #endif // ADA_INCLUDE_URL_PATTERN 61 | #endif // ADA_URL_PATTERN_REGEX_H 62 | -------------------------------------------------------------------------------- /src/url_pattern_regex.cpp: -------------------------------------------------------------------------------- 1 | #if ADA_INCLUDE_URL_PATTERN 2 | 3 | #include "ada/url_pattern_regex.h" 4 | 5 | namespace ada::url_pattern_regex { 6 | 7 | #ifdef ADA_USE_UNSAFE_STD_REGEX_PROVIDER 8 | std::optional std_regex_provider::create_instance( 9 | std::string_view pattern, bool ignore_case) { 10 | // Let flags be an empty string. 11 | // If options's ignore case is true then set flags to "vi". 12 | // Otherwise set flags to "v" 13 | auto flags = ignore_case 14 | ? std::regex::icase | std::regex_constants::ECMAScript 15 | : std::regex_constants::ECMAScript; 16 | try { 17 | return std::regex(pattern.data(), pattern.size(), flags); 18 | } catch (const std::regex_error& e) { 19 | (void)e; 20 | ada_log("std_regex_provider::create_instance failed:", e.what()); 21 | return std::nullopt; 22 | } 23 | } 24 | 25 | std::optional>> 26 | std_regex_provider::regex_search(std::string_view input, 27 | const std::regex& pattern) { 28 | // Use iterator-based regex_search to avoid string allocation 29 | std::match_results match_result; 30 | if (!std::regex_search(input.begin(), input.end(), match_result, pattern, 31 | std::regex_constants::match_any)) { 32 | return std::nullopt; 33 | } 34 | std::vector> matches; 35 | // If input is empty, let's assume the result will be empty as well. 36 | if (input.empty() || match_result.empty()) { 37 | return matches; 38 | } 39 | matches.reserve(match_result.size()); 40 | for (size_t i = 1; i < match_result.size(); ++i) { 41 | if (auto entry = match_result[i]; entry.matched) { 42 | matches.emplace_back(entry.str()); 43 | } 44 | } 45 | return matches; 46 | } 47 | 48 | bool std_regex_provider::regex_match(std::string_view input, 49 | const std::regex& pattern) { 50 | return std::regex_match(input.begin(), input.end(), pattern); 51 | } 52 | 53 | #endif // ADA_USE_UNSAFE_STD_REGEX_PROVIDER 54 | 55 | } // namespace ada::url_pattern_regex 56 | 57 | #endif // ADA_INCLUDE_URL_PATTERN 58 | -------------------------------------------------------------------------------- /tools/release/lib/versions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import fileinput 4 | import re 5 | 6 | 7 | def update_cmakelists_version(new_version: str, file_path: str) -> None: 8 | inside_project = False 9 | with fileinput.FileInput(file_path, inplace=True) as cmakelists: 10 | for line in cmakelists: 11 | if 'set(ADA_LIB_VERSION' in line: 12 | line = re.sub(r'[0-9]+\.[0-9]+\.[0-9]+', new_version, line) 13 | elif 'set(ADA_LIB_SOVERSION' in line: 14 | line = re.sub(r'[0-9]+', new_version.split('.')[0], line) 15 | 16 | elif 'project(' in line: 17 | inside_project = True 18 | elif inside_project: 19 | if 'VERSION' in line: 20 | line = re.sub(r'[0-9]+\.[0-9]+\.[0-9]+', new_version, line) 21 | inside_project = False 22 | print(line, end='') 23 | 24 | 25 | def update_ada_version_h(new_version: str, file_path: str) -> None: 26 | new_version_list = new_version.split('.') 27 | with fileinput.FileInput(file_path, inplace=True) as ada_version_h: 28 | inside_enum = False 29 | for line in ada_version_h: 30 | if '#define ADA_VERSION' in line: 31 | line = f'#define ADA_VERSION "{new_version}"\n' 32 | 33 | elif 'enum {' in line: 34 | inside_enum = True 35 | elif inside_enum: 36 | if line.strip().startswith('ADA_VERSION_MAJOR'): 37 | line = re.sub(r'\d+', new_version_list[0], line) 38 | elif line.strip().startswith('ADA_VERSION_MINOR'): 39 | line = re.sub(r'\d+', new_version_list[1], line) 40 | elif line.strip().startswith('ADA_VERSION_REVISION'): 41 | line = re.sub(r'\d+', new_version_list[2], line) 42 | 43 | print(line, end='') 44 | 45 | 46 | def update_doxygen_version(new_version: str, file_path: str) -> None: 47 | with fileinput.FileInput(file_path, inplace=True) as doxygen: 48 | for line in doxygen: 49 | if line.strip().startswith('PROJECT_NUMBER ='): 50 | line = f'PROJECT_NUMBER = "{new_version}"\n' 51 | 52 | print(line, end='') 53 | -------------------------------------------------------------------------------- /include/ada/scheme.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file scheme.h 3 | * @brief Declarations for the URL scheme. 4 | */ 5 | #ifndef ADA_SCHEME_H 6 | #define ADA_SCHEME_H 7 | 8 | #include "ada/common_defs.h" 9 | 10 | #include 11 | 12 | /** 13 | * @namespace ada::scheme 14 | * @brief Includes the scheme declarations 15 | */ 16 | namespace ada::scheme { 17 | 18 | /** 19 | * Type of the scheme as an enum. 20 | * Using strings to represent a scheme type is not ideal because 21 | * checking for types involves string comparisons. It is faster to use 22 | * a simple integer. 23 | * In C++11, we are allowed to specify the underlying type of the enum. 24 | * We pick an 8-bit integer (which allows up to 256 types). Specifying the 25 | * type of the enum may help integration with other systems if the type 26 | * variable is exposed (since its value will not depend on the compiler). 27 | */ 28 | enum type : uint8_t { 29 | HTTP = 0, 30 | NOT_SPECIAL = 1, 31 | HTTPS = 2, 32 | WS = 3, 33 | FTP = 4, 34 | WSS = 5, 35 | FILE = 6 36 | }; 37 | 38 | /** 39 | * A special scheme is an ASCII string that is listed in the first column of the 40 | * following table. The default port for a special scheme is listed in the 41 | * second column on the same row. The default port for any other ASCII string is 42 | * null. 43 | * 44 | * @see https://url.spec.whatwg.org/#url-miscellaneous 45 | * @param scheme 46 | * @return If scheme is a special scheme 47 | */ 48 | ada_really_inline constexpr bool is_special(std::string_view scheme); 49 | 50 | /** 51 | * A special scheme is an ASCII string that is listed in the first column of the 52 | * following table. The default port for a special scheme is listed in the 53 | * second column on the same row. The default port for any other ASCII string is 54 | * null. 55 | * 56 | * @see https://url.spec.whatwg.org/#url-miscellaneous 57 | * @param scheme 58 | * @return The special port 59 | */ 60 | constexpr uint16_t get_special_port(std::string_view scheme) noexcept; 61 | 62 | /** 63 | * Returns the port number of a special scheme. 64 | * @see https://url.spec.whatwg.org/#special-scheme 65 | */ 66 | constexpr uint16_t get_special_port(ada::scheme::type type) noexcept; 67 | /** 68 | * Returns the scheme of an input, or NOT_SPECIAL if it's not a special scheme 69 | * defined by the spec. 70 | */ 71 | constexpr ada::scheme::type get_scheme_type(std::string_view scheme) noexcept; 72 | 73 | } // namespace ada::scheme 74 | 75 | #endif // ADA_SCHEME_H 76 | -------------------------------------------------------------------------------- /include/ada/url_components-inl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file url_components.h 3 | * @brief Declaration for the URL Components 4 | */ 5 | #ifndef ADA_URL_COMPONENTS_INL_H 6 | #define ADA_URL_COMPONENTS_INL_H 7 | 8 | #include "ada/url_components.h" 9 | 10 | namespace ada { 11 | 12 | [[nodiscard]] constexpr bool url_components::check_offset_consistency() 13 | const noexcept { 14 | /** 15 | * https://user:pass@example.com:1234/foo/bar?baz#quux 16 | * | | | | ^^^^| | | 17 | * | | | | | | | `----- hash_start 18 | * | | | | | | `--------- search_start 19 | * | | | | | `----------------- pathname_start 20 | * | | | | `--------------------- port 21 | * | | | `----------------------- host_end 22 | * | | `---------------------------------- host_start 23 | * | `--------------------------------------- username_end 24 | * `--------------------------------------------- protocol_end 25 | */ 26 | // These conditions can be made more strict. 27 | if (protocol_end == url_components::omitted) { 28 | return false; 29 | } 30 | uint32_t index = protocol_end; 31 | 32 | if (username_end == url_components::omitted) { 33 | return false; 34 | } 35 | if (username_end < index) { 36 | return false; 37 | } 38 | index = username_end; 39 | 40 | if (host_start == url_components::omitted) { 41 | return false; 42 | } 43 | if (host_start < index) { 44 | return false; 45 | } 46 | index = host_start; 47 | 48 | if (port != url_components::omitted) { 49 | if (port > 0xffff) { 50 | return false; 51 | } 52 | uint32_t port_length = helpers::fast_digit_count(port) + 1; 53 | if (index + port_length < index) { 54 | return false; 55 | } 56 | index += port_length; 57 | } 58 | 59 | if (pathname_start == url_components::omitted) { 60 | return false; 61 | } 62 | if (pathname_start < index) { 63 | return false; 64 | } 65 | index = pathname_start; 66 | 67 | if (search_start != url_components::omitted) { 68 | if (search_start < index) { 69 | return false; 70 | } 71 | index = search_start; 72 | } 73 | 74 | if (hash_start != url_components::omitted) { 75 | if (hash_start < index) { 76 | return false; 77 | } 78 | } 79 | 80 | return true; 81 | } 82 | 83 | } // namespace ada 84 | #endif 85 | -------------------------------------------------------------------------------- /include/ada/parser.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file parser.h 3 | * @brief Definitions for the parser. 4 | */ 5 | #ifndef ADA_PARSER_H 6 | #define ADA_PARSER_H 7 | 8 | #include 9 | #include 10 | 11 | #include "ada/expected.h" 12 | 13 | #include "ada/url_pattern_regex.h" 14 | #include "ada/url_pattern_init.h" 15 | 16 | /** 17 | * @private 18 | */ 19 | namespace ada { 20 | struct url_aggregator; 21 | struct url; 22 | #if ADA_INCLUDE_URL_PATTERN 23 | template 24 | class url_pattern; 25 | struct url_pattern_options; 26 | #endif // ADA_INCLUDE_URL_PATTERN 27 | enum class errors : uint8_t; 28 | } // namespace ada 29 | 30 | /** 31 | * @namespace ada::parser 32 | * @brief Includes the definitions for supported parsers 33 | */ 34 | namespace ada::parser { 35 | /** 36 | * Parses a url. The parameter user_input is the input to be parsed: 37 | * it should be a valid UTF-8 string. The parameter base_url is an optional 38 | * parameter that can be used to resolve relative URLs. If the base_url is 39 | * provided, the user_input is resolved against the base_url. 40 | */ 41 | template 42 | result_type parse_url(std::string_view user_input, 43 | const result_type* base_url = nullptr); 44 | 45 | extern template url_aggregator parse_url( 46 | std::string_view user_input, const url_aggregator* base_url); 47 | extern template url parse_url(std::string_view user_input, 48 | const url* base_url); 49 | 50 | template 51 | result_type parse_url_impl(std::string_view user_input, 52 | const result_type* base_url = nullptr); 53 | 54 | extern template url_aggregator parse_url_impl( 55 | std::string_view user_input, const url_aggregator* base_url); 56 | extern template url parse_url_impl(std::string_view user_input, 57 | const url* base_url); 58 | 59 | #if ADA_INCLUDE_URL_PATTERN 60 | template 61 | tl::expected, errors> parse_url_pattern_impl( 62 | std::variant&& input, 63 | const std::string_view* base_url, const url_pattern_options* options); 64 | #endif // ADA_INCLUDE_URL_PATTERN 65 | 66 | } // namespace ada::parser 67 | 68 | #endif // ADA_PARSER_H 69 | -------------------------------------------------------------------------------- /src/serializers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace ada::serializers { 6 | 7 | void find_longest_sequence_of_ipv6_pieces( 8 | const std::array& address, size_t& compress, 9 | size_t& compress_length) noexcept { 10 | for (size_t i = 0; i < 8; i++) { 11 | if (address[i] == 0) { 12 | size_t next = i + 1; 13 | while (next != 8 && address[next] == 0) ++next; 14 | const size_t count = next - i; 15 | if (compress_length < count) { 16 | compress_length = count; 17 | compress = i; 18 | if (next == 8) break; 19 | i = next; 20 | } 21 | } 22 | } 23 | } 24 | 25 | std::string ipv6(const std::array& address) noexcept { 26 | size_t compress_length = 0; // The length of a long sequence of zeros. 27 | size_t compress = 0; // The start of a long sequence of zeros. 28 | find_longest_sequence_of_ipv6_pieces(address, compress, compress_length); 29 | 30 | if (compress_length <= 1) { 31 | // Optimization opportunity: Find a faster way then snprintf for imploding 32 | // and return here. 33 | compress = compress_length = 8; 34 | } 35 | 36 | std::string output(4 * 8 + 7 + 2, '\0'); 37 | size_t piece_index = 0; 38 | char* point = output.data(); 39 | char* point_end = output.data() + output.size(); 40 | *point++ = '['; 41 | while (true) { 42 | if (piece_index == compress) { 43 | *point++ = ':'; 44 | // If we skip a value initially, we need to write '::', otherwise 45 | // a single ':' will do since it follows a previous ':'. 46 | if (piece_index == 0) { 47 | *point++ = ':'; 48 | } 49 | piece_index += compress_length; 50 | if (piece_index == 8) { 51 | break; 52 | } 53 | } 54 | point = std::to_chars(point, point_end, address[piece_index], 16).ptr; 55 | piece_index++; 56 | if (piece_index == 8) { 57 | break; 58 | } 59 | *point++ = ':'; 60 | } 61 | *point++ = ']'; 62 | output.resize(point - output.data()); 63 | return output; 64 | } 65 | 66 | std::string ipv4(const uint64_t address) noexcept { 67 | std::string output(15, '\0'); 68 | char* point = output.data(); 69 | char* point_end = output.data() + output.size(); 70 | point = std::to_chars(point, point_end, uint8_t(address >> 24)).ptr; 71 | for (int i = 2; i >= 0; i--) { 72 | *point++ = '.'; 73 | point = std::to_chars(point, point_end, uint8_t(address >> (i * 8))).ptr; 74 | } 75 | output.resize(point - output.data()); 76 | return output; 77 | } 78 | 79 | } // namespace ada::serializers 80 | -------------------------------------------------------------------------------- /tools/cli/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(adaparse adaparse.cpp line_iterator.h) 2 | target_link_libraries(adaparse PRIVATE ada) 3 | target_include_directories(adaparse PUBLIC "$") 4 | 5 | 6 | if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") 7 | # We are on an Apple platform, so we can use dead_strip and LTO 8 | # to reduce the size of the final binary. 9 | message(STATUS "Apple platform detected, using dead_strip and LTO") 10 | target_link_options(adaparse PRIVATE "-Wl,-dead_strip") 11 | target_compile_options(adaparse PRIVATE "-flto") 12 | target_link_options(adaparse PRIVATE "-flto") 13 | endif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") 14 | 15 | if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) 16 | execute_process(COMMAND ${CMAKE_CXX_COMPILER} -print-libgcc-file-name OUTPUT_VARIABLE ADA_GCC_LIB) 17 | get_filename_component(ADA_GCC_DIR "${ADA_GCC_LIB}" DIRECTORY) 18 | message(STATUS "looking for static C++ library in " ${ADA_GCC_DIR}) 19 | find_library(LIBSTDCPP libstdc++.a PATHS "${ADA_GCC_DIR}") 20 | if(LIBSTDCPP) 21 | # static linkink for speed 22 | message(STATUS "libstdc++.a found") 23 | target_link_options(adaparse PRIVATE "-static-libstdc++") 24 | else() 25 | message(STATUS "libstdc++.a not found") 26 | endif() 27 | # Note that we are not under Apple at this time. 28 | # We can use the -Wl,--gc-sections option to remove unused sections 29 | # from the final binary, which can reduce the size of the binary. 30 | target_link_options(adaparse PRIVATE "-Wl,--gc-sections") 31 | endif() 32 | 33 | if(MSVC AND BUILD_SHARED_LIBS) 34 | # Copy the ada dll into the directory 35 | add_custom_command(TARGET adaparse POST_BUILD # Adds a post-build event 36 | COMMAND ${CMAKE_COMMAND} -E copy_if_different # which executes "cmake -E copy_if_different..." 37 | "$" # <--this is in-file 38 | "$") # <--this is out-file path 39 | endif() 40 | CPMAddPackage("gh:fmtlib/fmt#11.0.2") 41 | CPMAddPackage( 42 | GITHUB_REPOSITORY jarro2783/cxxopts 43 | VERSION 3.2.0 44 | OPTIONS "CXXOPTS_BUILD_EXAMPLES NO" "CXXOPTS_BUILD_TESTS NO" "CXXOPTS_ENABLE_INSTALL YES" 45 | ) 46 | target_link_libraries(adaparse PRIVATE cxxopts::cxxopts fmt::fmt) 47 | 48 | if(MSVC OR MINGW) 49 | target_compile_definitions(adaparse PRIVATE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE) 50 | endif() 51 | 52 | install( 53 | TARGETS 54 | adaparse 55 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 56 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 57 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 58 | ) 59 | -------------------------------------------------------------------------------- /benchmarks/bench.cpp: -------------------------------------------------------------------------------- 1 | #include "benchmark_header.h" 2 | 3 | /** 4 | * Realistic URL examples collected on the actual web. 5 | */ 6 | std::string url_examples_default[] = { 7 | "https://www.google.com/" 8 | "webhp?hl=en&ictx=2&sa=X&ved=0ahUKEwil_" 9 | "oSxzJj8AhVtEFkFHTHnCGQQPQgI", 10 | "https://support.google.com/websearch/" 11 | "?p=ws_results_help&hl=en-CA&fg=1", 12 | "https://en.wikipedia.org/wiki/Dog#Roles_with_humans", 13 | "https://www.tiktok.com/@aguyandagolden/video/7133277734310038830", 14 | "https://business.twitter.com/en/help/troubleshooting/" 15 | "how-twitter-ads-work.html?ref=web-twc-ao-gbl-adsinfo&utm_source=twc&utm_" 16 | "medium=web&utm_campaign=ao&utm_content=adsinfo", 17 | "https://images-na.ssl-images-amazon.com/images/I/" 18 | "41Gc3C8UysL.css?AUIClients/AmazonGatewayAuiAssets", 19 | "https://www.reddit.com/?after=t3_zvz1ze", 20 | "https://www.reddit.com/login/?dest=https%3A%2F%2Fwww.reddit.com%2F", 21 | "postgresql://other:9818274x1!!@localhost:5432/" 22 | "otherdb?connect_timeout=10&application_name=myapp", 23 | "http://192.168.1.1", // ipv4 24 | "http://[2606:4700:4700::1111]", // ipv6 25 | }; 26 | 27 | std::vector url_examples; 28 | 29 | double url_examples_bytes = []() -> double { 30 | size_t bytes{0}; 31 | for (std::string& url_string : url_examples) { 32 | bytes += url_string.size(); 33 | } 34 | return double(bytes); 35 | }(); 36 | 37 | #ifdef ADA_URL_FILE 38 | const char* default_file = ADA_URL_FILE; 39 | #else 40 | const char* default_file = nullptr; 41 | #endif 42 | 43 | size_t init_data(const char* input = default_file) { 44 | // compute the number of bytes. 45 | auto compute = []() -> double { 46 | size_t bytes{0}; 47 | for (std::string& url_string : url_examples) { 48 | bytes += url_string.size(); 49 | } 50 | return double(bytes); 51 | }; 52 | if (input == nullptr) { 53 | for (const std::string& s : url_examples_default) { 54 | url_examples.emplace_back(s); 55 | } 56 | url_examples_bytes = compute(); 57 | return url_examples.size(); 58 | } 59 | 60 | if (!file_exists(input)) { 61 | std::cout << "File not found !" << input << std::endl; 62 | for (const std::string& s : url_examples_default) { 63 | url_examples.emplace_back(s); 64 | } 65 | } else { 66 | std::cout << "Loading " << input << std::endl; 67 | url_examples = split_string(read_file(input)); 68 | } 69 | url_examples_bytes = compute(); 70 | return url_examples.size(); 71 | } 72 | 73 | #ifndef BENCHMARK_PREFIX 74 | #define BENCHMARK_PREFIX Bench_ 75 | #define BENCHMARK_PREFIX_STR "Bench_" 76 | #endif 77 | #include "benchmark_template.cpp" 78 | -------------------------------------------------------------------------------- /include/ada/url_components.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file url_components.h 3 | * @brief Declaration for the URL Components 4 | */ 5 | #ifndef ADA_URL_COMPONENTS_H 6 | #define ADA_URL_COMPONENTS_H 7 | 8 | namespace ada { 9 | 10 | /** 11 | * @brief URL Component representations using offsets. 12 | * 13 | * @details We design the url_components struct so that it is as small 14 | * and simple as possible. This version uses 32 bytes. 15 | * 16 | * This struct is used to extract components from a single 'href'. 17 | */ 18 | struct url_components { 19 | constexpr static uint32_t omitted = uint32_t(-1); 20 | 21 | url_components() = default; 22 | url_components(const url_components &u) = default; 23 | url_components(url_components &&u) noexcept = default; 24 | url_components &operator=(url_components &&u) noexcept = default; 25 | url_components &operator=(const url_components &u) = default; 26 | ~url_components() = default; 27 | 28 | /* 29 | * By using 32-bit integers, we implicitly assume that the URL string 30 | * cannot exceed 4 GB. 31 | * 32 | * https://user:pass@example.com:1234/foo/bar?baz#quux 33 | * | | | | ^^^^| | | 34 | * | | | | | | | `----- hash_start 35 | * | | | | | | `--------- search_start 36 | * | | | | | `----------------- pathname_start 37 | * | | | | `--------------------- port 38 | * | | | `----------------------- host_end 39 | * | | `---------------------------------- host_start 40 | * | `--------------------------------------- username_end 41 | * `--------------------------------------------- protocol_end 42 | */ 43 | uint32_t protocol_end{0}; 44 | /** 45 | * Username end is not `omitted` by default to make username and password 46 | * getters less costly to implement. 47 | */ 48 | uint32_t username_end{0}; 49 | uint32_t host_start{0}; 50 | uint32_t host_end{0}; 51 | uint32_t port{omitted}; 52 | uint32_t pathname_start{0}; 53 | uint32_t search_start{omitted}; 54 | uint32_t hash_start{omitted}; 55 | 56 | /** 57 | * Check the following conditions: 58 | * protocol_end < username_end < ... < hash_start, 59 | * expect when a value is omitted. It also computes 60 | * a lower bound on the possible string length that may match these 61 | * offsets. 62 | * @return true if the offset values are 63 | * consistent with a possible URL string 64 | */ 65 | [[nodiscard]] constexpr bool check_offset_consistency() const noexcept; 66 | 67 | /** 68 | * Converts a url_components to JSON stringified version. 69 | */ 70 | [[nodiscard]] std::string to_string() const; 71 | 72 | }; // struct url_components 73 | } // namespace ada 74 | #endif 75 | -------------------------------------------------------------------------------- /src/implementation.cpp: -------------------------------------------------------------------------------- 1 | #include "ada/implementation-inl.h" 2 | 3 | #include 4 | 5 | #include "ada/common_defs.h" 6 | #include "ada/parser.h" 7 | #include "ada/url.h" 8 | #include "ada/url_aggregator.h" 9 | 10 | namespace ada { 11 | 12 | template 13 | ada_warn_unused tl::expected parse( 14 | std::string_view input, const result_type* base_url) { 15 | result_type u = 16 | ada::parser::parse_url_impl(input, base_url); 17 | if (!u.is_valid) { 18 | return tl::unexpected(errors::type_error); 19 | } 20 | return u; 21 | } 22 | 23 | template ada::result parse(std::string_view input, 24 | const url* base_url = nullptr); 25 | template ada::result parse( 26 | std::string_view input, const url_aggregator* base_url = nullptr); 27 | 28 | std::string href_from_file(std::string_view input) { 29 | // This is going to be much faster than constructing a URL. 30 | std::string tmp_buffer; 31 | std::string_view internal_input; 32 | if (unicode::has_tabs_or_newline(input)) { 33 | tmp_buffer = input; 34 | helpers::remove_ascii_tab_or_newline(tmp_buffer); 35 | internal_input = tmp_buffer; 36 | } else { 37 | internal_input = input; 38 | } 39 | std::string path; 40 | if (internal_input.empty()) { 41 | path = "/"; 42 | } else if ((internal_input[0] == '/') || (internal_input[0] == '\\')) { 43 | helpers::parse_prepared_path(internal_input.substr(1), 44 | ada::scheme::type::FILE, path); 45 | } else { 46 | helpers::parse_prepared_path(internal_input, ada::scheme::type::FILE, path); 47 | } 48 | return "file://" + path; 49 | } 50 | 51 | bool can_parse(std::string_view input, const std::string_view* base_input) { 52 | ada::url_aggregator base_aggregator; 53 | ada::url_aggregator* base_pointer = nullptr; 54 | 55 | if (base_input != nullptr) { 56 | base_aggregator = ada::parser::parse_url_impl( 57 | *base_input, nullptr); 58 | if (!base_aggregator.is_valid) { 59 | return false; 60 | } 61 | base_pointer = &base_aggregator; 62 | } 63 | 64 | ada::url_aggregator result = 65 | ada::parser::parse_url_impl(input, 66 | base_pointer); 67 | return result.is_valid; 68 | } 69 | 70 | ada_warn_unused std::string_view to_string(ada::encoding_type type) { 71 | switch (type) { 72 | case ada::encoding_type::UTF8: 73 | return "UTF-8"; 74 | case ada::encoding_type::UTF_16LE: 75 | return "UTF-16LE"; 76 | case ada::encoding_type::UTF_16BE: 77 | return "UTF-16BE"; 78 | default: 79 | unreachable(); 80 | } 81 | } 82 | 83 | } // namespace ada 84 | -------------------------------------------------------------------------------- /include/ada/state.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file state.h 3 | * @brief Definitions for the states of the URL state machine. 4 | */ 5 | #ifndef ADA_STATE_H 6 | #define ADA_STATE_H 7 | 8 | #include "ada/common_defs.h" 9 | 10 | #include 11 | 12 | namespace ada { 13 | 14 | /** 15 | * @see https://url.spec.whatwg.org/#url-parsing 16 | */ 17 | enum class state { 18 | /** 19 | * @see https://url.spec.whatwg.org/#authority-state 20 | */ 21 | AUTHORITY, 22 | 23 | /** 24 | * @see https://url.spec.whatwg.org/#scheme-start-state 25 | */ 26 | SCHEME_START, 27 | 28 | /** 29 | * @see https://url.spec.whatwg.org/#scheme-state 30 | */ 31 | SCHEME, 32 | 33 | /** 34 | * @see https://url.spec.whatwg.org/#host-state 35 | */ 36 | HOST, 37 | 38 | /** 39 | * @see https://url.spec.whatwg.org/#no-scheme-state 40 | */ 41 | NO_SCHEME, 42 | 43 | /** 44 | * @see https://url.spec.whatwg.org/#fragment-state 45 | */ 46 | FRAGMENT, 47 | 48 | /** 49 | * @see https://url.spec.whatwg.org/#relative-state 50 | */ 51 | RELATIVE_SCHEME, 52 | 53 | /** 54 | * @see https://url.spec.whatwg.org/#relative-slash-state 55 | */ 56 | RELATIVE_SLASH, 57 | 58 | /** 59 | * @see https://url.spec.whatwg.org/#file-state 60 | */ 61 | FILE, 62 | 63 | /** 64 | * @see https://url.spec.whatwg.org/#file-host-state 65 | */ 66 | FILE_HOST, 67 | 68 | /** 69 | * @see https://url.spec.whatwg.org/#file-slash-state 70 | */ 71 | FILE_SLASH, 72 | 73 | /** 74 | * @see https://url.spec.whatwg.org/#path-or-authority-state 75 | */ 76 | PATH_OR_AUTHORITY, 77 | 78 | /** 79 | * @see https://url.spec.whatwg.org/#special-authority-ignore-slashes-state 80 | */ 81 | SPECIAL_AUTHORITY_IGNORE_SLASHES, 82 | 83 | /** 84 | * @see https://url.spec.whatwg.org/#special-authority-slashes-state 85 | */ 86 | SPECIAL_AUTHORITY_SLASHES, 87 | 88 | /** 89 | * @see https://url.spec.whatwg.org/#special-relative-or-authority-state 90 | */ 91 | SPECIAL_RELATIVE_OR_AUTHORITY, 92 | 93 | /** 94 | * @see https://url.spec.whatwg.org/#query-state 95 | */ 96 | QUERY, 97 | 98 | /** 99 | * @see https://url.spec.whatwg.org/#path-state 100 | */ 101 | PATH, 102 | 103 | /** 104 | * @see https://url.spec.whatwg.org/#path-start-state 105 | */ 106 | PATH_START, 107 | 108 | /** 109 | * @see https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state 110 | */ 111 | OPAQUE_PATH, 112 | 113 | /** 114 | * @see https://url.spec.whatwg.org/#port-state 115 | */ 116 | PORT, 117 | }; 118 | 119 | /** 120 | * Stringify a URL state machine state. 121 | */ 122 | ada_warn_unused std::string to_string(ada::state s); 123 | 124 | } // namespace ada 125 | 126 | #endif // ADA_STATE_H 127 | -------------------------------------------------------------------------------- /include/ada/implementation.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file implementation.h 3 | * @brief Definitions for user facing functions for parsing URL and it's 4 | * components. 5 | */ 6 | #ifndef ADA_IMPLEMENTATION_H 7 | #define ADA_IMPLEMENTATION_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "ada/url.h" 14 | #include "ada/common_defs.h" 15 | #include "ada/errors.h" 16 | #include "ada/url_pattern_init.h" 17 | 18 | namespace ada { 19 | 20 | template 21 | using result = tl::expected; 22 | 23 | /** 24 | * The URL parser takes a scalar value string input, with an optional null or 25 | * base URL base (default null). The parser assumes the input is a valid ASCII 26 | * or UTF-8 string. 27 | * 28 | * @param input the string input to analyze (must be valid ASCII or UTF-8) 29 | * @param base_url the optional URL input to use as a base url. 30 | * @return a parsed URL. 31 | */ 32 | template 33 | ada_warn_unused ada::result parse( 34 | std::string_view input, const result_type* base_url = nullptr); 35 | 36 | extern template ada::result parse(std::string_view input, 37 | const url* base_url); 38 | extern template ada::result parse( 39 | std::string_view input, const url_aggregator* base_url); 40 | 41 | /** 42 | * Verifies whether the URL strings can be parsed. The function assumes 43 | * that the inputs are valid ASCII or UTF-8 strings. 44 | * @see https://url.spec.whatwg.org/#dom-url-canparse 45 | * @return If URL can be parsed or not. 46 | */ 47 | bool can_parse(std::string_view input, 48 | const std::string_view* base_input = nullptr); 49 | 50 | #if ADA_INCLUDE_URL_PATTERN 51 | /** 52 | * Implementation of the URL pattern parsing algorithm. 53 | * @see https://urlpattern.spec.whatwg.org 54 | * 55 | * @param input valid UTF-8 string or URLPatternInit struct 56 | * @param base_url an optional valid UTF-8 string 57 | * @param options an optional url_pattern_options struct 58 | * @return url_pattern instance 59 | */ 60 | template 61 | ada_warn_unused tl::expected, errors> 62 | parse_url_pattern(std::variant&& input, 63 | const std::string_view* base_url = nullptr, 64 | const url_pattern_options* options = nullptr); 65 | #endif // ADA_INCLUDE_URL_PATTERN 66 | 67 | /** 68 | * Computes a href string from a file path. The function assumes 69 | * that the input is a valid ASCII or UTF-8 string. 70 | * @return a href string (starts with file:://) 71 | */ 72 | std::string href_from_file(std::string_view path); 73 | } // namespace ada 74 | 75 | #endif // ADA_IMPLEMENTATION_H 76 | -------------------------------------------------------------------------------- /tests/wpt/IdnaTestV2-removed.json: -------------------------------------------------------------------------------- 1 | [ 2 | "This is generated with the help from ../tools/IdnaTestV2-compare.py.", 3 | "These tests are from an older IdnaTestV2 and thus the comment line may no longer be accurate.", 4 | { 5 | "comment": "P1; V6; V3 (ignored)", 6 | "input": "-\udb40\ude56\ua867\uff0e\udb40\ude82\ud8dc\udd83\ud83c\udd09", 7 | "output": null 8 | }, 9 | { 10 | "comment": "P1; V5; V6", 11 | "input": "\ud83c\udd04\uff0e\u1cdc\u2488\u00df", 12 | "output": null 13 | }, 14 | { 15 | "comment": "P1; V5; V6", 16 | "input": "\ud83c\udd04\uff0e\u1cdc\u2488SS", 17 | "output": null 18 | }, 19 | { 20 | "comment": "P1; V5; V6", 21 | "input": "\ud83c\udd04\uff0e\u1cdc\u2488ss", 22 | "output": null 23 | }, 24 | { 25 | "comment": "P1; V5; V6", 26 | "input": "\ud83c\udd04\uff0e\u1cdc\u2488Ss", 27 | "output": null 28 | }, 29 | { 30 | "comment": "C2; P1; V6", 31 | "input": "\u0756\u3002\u3164\u200d\u03c2", 32 | "output": null 33 | }, 34 | { 35 | "comment": "C2; P1; V6", 36 | "input": "\u0756\u3002\u1160\u200d\u03c2", 37 | "output": null 38 | }, 39 | { 40 | "comment": "C2; P1; V6", 41 | "input": "\u0756\u3002\u1160\u200d\u03a3", 42 | "output": null 43 | }, 44 | { 45 | "comment": "C2; P1; V6", 46 | "input": "\u0756\u3002\u1160\u200d\u03c3", 47 | "output": null 48 | }, 49 | { 50 | "comment": "C2; P1; V6", 51 | "input": "\u0756\u3002\u3164\u200d\u03a3", 52 | "output": null 53 | }, 54 | { 55 | "comment": "C2; P1; V6", 56 | "input": "\u0756\u3002\u3164\u200d\u03c3", 57 | "output": null 58 | }, 59 | { 60 | "comment": "P1; V6", 61 | "input": "\ud83c\udd07\u4f10\ufe12.\ud831\ude5a\ua8c4", 62 | "output": null 63 | }, 64 | { 65 | "comment": "P1; V5; V6", 66 | "input": "\ud802\ude3f.\ud83c\udd06\u2014", 67 | "output": null 68 | }, 69 | { 70 | "comment": "C2; P1; V5; V6", 71 | "input": "\u1c32\ud83c\udd08\u2f9b\u05a6\uff0e\u200d\uda7e\udd64\u07fd", 72 | "output": null 73 | }, 74 | { 75 | "comment": "C2; P1; V5; V6", 76 | "input": "\ud83e\udc9f\ud83c\udd08\u200d\ua84e\uff61\u0f84", 77 | "output": null 78 | }, 79 | { 80 | "comment": "P1; V6", 81 | "input": "\udaa5\udeaa\uff61\ud83c\udd02", 82 | "output": null 83 | }, 84 | { 85 | "comment": "C2; P1; V6", 86 | "input": "\u186f\u2689\u59f6\ud83c\udd09\uff0e\u06f7\u200d\ud83c\udfaa\u200d", 87 | "output": null 88 | }, 89 | { 90 | "comment": "C1; P1; V5; V6", 91 | "input": "\ua67d\u200c\ud87e\uddf5\ud83c\udd06\uff61\u200c\ud804\udc42\u1b01", 92 | "output": null 93 | }, 94 | { 95 | "comment": "C1; P1; V5; V6", 96 | "input": "\ua67d\u200c\u9723\ud83c\udd06\uff61\u200c\ud804\udc42\u1b01", 97 | "output": null 98 | }, 99 | { 100 | "comment": "C1; P1; V5; V6; V3 (ignored)", 101 | "input": "-\u1897\u200c\ud83c\udd04.\ud805\udf22", 102 | "output": null 103 | } 104 | ] 105 | -------------------------------------------------------------------------------- /cmake/ada-flags.cmake: -------------------------------------------------------------------------------- 1 | option(ADA_LOGGING "verbose output (useful for debugging)" OFF) 2 | option(ADA_DEVELOPMENT_CHECKS "development checks (useful for debugging)" OFF) 3 | option(ADA_SANITIZE "Sanitize addresses" OFF) 4 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 5 | option(ADA_SANITIZE_BOUNDS_STRICT "Sanitize bounds (strict): only for GCC" OFF) 6 | endif() 7 | option(ADA_SANITIZE_UNDEFINED "Sanitize undefined behaviour" OFF) 8 | if(ADA_SANITIZE) 9 | message(STATUS "Address sanitizer enabled.") 10 | endif() 11 | if(ADA_SANITIZE_WITHOUT_LEAKS) 12 | message(STATUS "Address sanitizer (but not leak) enabled.") 13 | endif() 14 | if(ADA_SANITIZE_UNDEFINED) 15 | message(STATUS "Undefined sanitizer enabled.") 16 | endif() 17 | option(ADA_COVERAGE "Compute coverage" OFF) 18 | option(ADA_TOOLS "Build cli tools (adaparse)" OFF) 19 | option(ADA_BENCHMARKS "Build benchmarks" OFF) 20 | option(ADA_TESTING "Build tests" OFF) 21 | option(ADA_USE_UNSAFE_STD_REGEX_PROVIDER "Enable unsafe regex provider that uses std::regex" OFF) 22 | option(ADA_INCLUDE_URL_PATTERN "Include URL pattern implementation" ON) 23 | 24 | if (ADA_COVERAGE) 25 | message(STATUS "You want to compute coverage. We assume that you have installed gcovr.") 26 | if (NOT CMAKE_BUILD_TYPE) 27 | set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) 28 | endif() 29 | ####################### 30 | # You need to install gcovr. Under macos, you may do so with brew. 31 | # brew install gcovr 32 | # Then build... 33 | # cmake -D ADA_COVERAGE=ON -B buildcoverage 34 | # cmake --build buildcoverage 35 | # cmake --build buildcoverage --target ada_coverage 36 | # 37 | # open buildcoverage/ada_coverage/index.html 38 | ##################### 39 | include(${PROJECT_SOURCE_DIR}/cmake/codecoverage.cmake) 40 | APPEND_COVERAGE_COMPILER_FLAGS() 41 | setup_target_for_coverage_gcovr_html(NAME ada_coverage EXECUTABLE ctest EXCLUDE "${PROJECT_SOURCE_DIR}/dependencies/*" "${PROJECT_SOURCE_DIR}/tools/*" "${PROJECT_SOURCE_DIR}/singleheader/*" ${PROJECT_SOURCE_DIR}/include/ada/common_defs.h) 42 | endif() 43 | 44 | if (NOT CMAKE_BUILD_TYPE) 45 | if(ADA_SANITIZE OR ADA_SANITIZE_WITHOUT_LEAKS OR ADA_SANITIZE_BOUNDS_STRICT OR ADA_SANITIZE_UNDEFINED) 46 | message(STATUS "No build type selected, default to Debug because you have sanitizers.") 47 | set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) 48 | else() 49 | message(STATUS "No build type selected, default to Release") 50 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) 51 | endif() 52 | endif() 53 | 54 | set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake") 55 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 56 | 57 | set(CMAKE_CXX_STANDARD 20) 58 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 59 | set(CMAKE_CXX_EXTENSIONS OFF) 60 | 61 | find_program(CCACHE_FOUND ccache) 62 | if(CCACHE_FOUND) 63 | message(STATUS "Ccache found using it as compiler launcher.") 64 | set(CMAKE_C_COMPILER_LAUNCHER ccache) 65 | set(CMAKE_CXX_COMPILER_LAUNCHER ccache) 66 | endif(CCACHE_FOUND) 67 | -------------------------------------------------------------------------------- /cmake/add-cpp-test.cmake: -------------------------------------------------------------------------------- 1 | # Helper so we don't have to repeat ourselves so much 2 | # Usage: add_cpp_test(testname [COMPILE_ONLY] [SOURCES a.cpp b.cpp ...] [LABELS acceptance per_implementation ...]) 3 | # SOURCES defaults to testname.cpp if not specified. 4 | function(add_cpp_test TEST_NAME) 5 | # Parse arguments 6 | cmake_parse_arguments(PARSE_ARGV 1 ARGS "COMPILE_ONLY;LIBRARY;WILL_FAIL" "" "SOURCES;LABELS;DEPENDENCY_OF") 7 | if (NOT ARGS_SOURCES) 8 | list(APPEND ARGS_SOURCES ${TEST_NAME}.cpp) 9 | endif() 10 | if (ARGS_COMPILE_ONLY) 11 | list(APPEND ${ARGS_LABELS} compile_only) 12 | endif() 13 | if(ADA_SANITIZE) 14 | add_compile_options(-fsanitize=address -fno-omit-frame-pointer -fno-sanitize-recover=all) 15 | add_compile_definitions(ASAN_OPTIONS=detect_leaks=1) 16 | endif() 17 | if(ADA_SANITIZE_WITHOUT_LEAKS) 18 | add_compile_options(-fsanitize=address -fno-omit-frame-pointer -fno-sanitize-recover=all) 19 | endif() 20 | if(ADA_SANITIZE_BOUNDS_STRICT) 21 | add_compile_options(-fsanitize=bounds-strict -fno-sanitize-recover=all) 22 | add_link_options(-fsanitize=bounds-strict) 23 | endif() 24 | if(ADA_SANITIZE_UNDEFINED) 25 | add_compile_options(-fsanitize=undefined -fno-sanitize-recover=all) 26 | add_link_options(-fsanitize=undefined) 27 | endif() 28 | # Add the compile target 29 | if (ARGS_LIBRARY) 30 | add_library(${TEST_NAME} STATIC ${ARGS_SOURCES}) 31 | else(ARGS_LIBRARY) 32 | add_executable(${TEST_NAME} ${ARGS_SOURCES}) 33 | set_source_files_properties(${ARGS_SOURCES} PROPERTIES SKIP_LINTING ON) 34 | endif(ARGS_LIBRARY) 35 | 36 | # Add test 37 | if (ARGS_COMPILE_ONLY OR ARGS_LIBRARY) 38 | add_test( 39 | NAME ${TEST_NAME} 40 | COMMAND ${CMAKE_COMMAND} --build . --target ${TEST_NAME} --config $ 41 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 42 | ) 43 | set_target_properties(${TEST_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) 44 | else() 45 | if (CMAKE_CROSSCOMPILING_EMULATOR) 46 | add_test(${TEST_NAME} ${CMAKE_CROSSCOMPILING_EMULATOR} ${TEST_NAME}) 47 | else() 48 | add_test(${TEST_NAME} ${TEST_NAME}) 49 | endif() 50 | 51 | # Add to