Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#versioning-strategy
# Dependabot: security updates only (no version-update PRs).
# Grouped security PRs open weekly on Tuesday morning (Europe/Bucharest).
# Auto-merge: .github/workflows/dependabot-auto-merge.yml (on PR + Tuesday cron).
# https://docs.github.com/en/code-security/dependabot/dependabot-security-updates

version: 2
updates:
# Keep the Github Actions dependencies up to date
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
target-branch: "main"
interval: weekly
day: tuesday
time: "06:00"
timezone: Europe/Bucharest
open-pull-requests-limit: 0
labels:
- "dependabot-security"
- "github dependencies"
groups:
github:
applies-to: version-updates
github-actions-security:
applies-to: security-updates
patterns:
- "*"
3 changes: 2 additions & 1 deletion .github/workflows/auto_assign_ci.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Auto Assign
# Audit 2026-10-14: pull_request_target — do not checkout the PR head here; configuration-path only.

on:
pull_request_target:
Expand All @@ -16,6 +17,6 @@ jobs:
name: Auto assign (me only)
runs-on: ubuntu-latest
steps:
- uses: kentaro-m/auto-assign-action@v2.0.2
- uses: kentaro-m/auto-assign-action@0a2c53d3721e4c5cfcce6afd4014aecc337979f6 # v2.0.2
with:
configuration-path: ".github/auto_assign.yml"
10 changes: 5 additions & 5 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
Expand All @@ -71,7 +71,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4.37.8
uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
Expand Down Expand Up @@ -100,23 +100,23 @@ jobs:
exit 1

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4.37.8
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
category: "/language:${{matrix.language}}"
output: sarif-results
upload: never

# Exclude alerts in fetched dependencies (e.g. libcurl curl_log.c) from code scanning results
- name: Filter dependency alerts from SARIF
uses: advanced-security/filter-sarif@v1
uses: advanced-security/filter-sarif@f3b8118a9349d88f7b1c0c488476411145b6270d # v1.0.1
with:
patterns: |
-**/libcurl-src/**
input: sarif-results/cpp.sarif
output: sarif-results/cpp.sarif

- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v4.37.8
uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
sarif_file: sarif-results
category: "/language:${{matrix.language}}"
50 changes: 50 additions & 0 deletions .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Dependabot security auto-merge

on:
pull_request:
types: [opened, synchronize, reopened]
schedule:
- cron: "0 6 * * 2"
timezone: Europe/Bucharest

permissions:
contents: write
pull-requests: write

jobs:
approve-and-queue-merge:
runs-on: ubuntu-latest
if: >-
github.event_name == 'pull_request'
&& github.event.pull_request.user.login == 'dependabot[bot]'
steps:
- name: Approve Dependabot pull request
run: gh pr review --approve "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Enable auto-merge when checks pass
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

weekly-merge-ready:
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- name: Approve and queue merge for open Dependabot PRs
run: |
set -euo pipefail
mapfile -t urls < <(gh pr list --author app/dependabot --state open --json url --jq '.[].url')
if [ "${#urls[@]}" -eq 0 ]; then
echo "No open Dependabot pull requests."
exit 0
fi
for url in "${urls[@]}"; do
gh pr review --approve "$url" || true
gh pr merge --auto --merge "$url" || true
done
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45 changes: 33 additions & 12 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,36 @@ permissions:

jobs:
validate-tag:
outputs:
tag_sha: ${{ steps.tag_sha.outputs.sha }}
name: Validate tag
runs-on: ubuntu-latest
steps:
- name: Validate tag input
env:
TAG: ${{ github.event.inputs.tag }}
run: |
re='^v?[0-9]+\.[0-9]+\.[0-9]+([.+-][0-9A-Za-z.-]+)?$'
if [[ ! "$TAG" =~ $re ]]; then
echo "Error: the tag input must be a release tag such as v1.2.3."
exit 1
fi

- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.inputs.tag }}
ref: refs/tags/${{ github.event.inputs.tag }}

persist-credentials: false
- name: Verify tag and project version
run: |
TAG="${{ github.event.inputs.tag }}"
if [ -z "$TAG" ]; then
echo "Error: Tag is required."
exit 1
fi
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
if ! git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
echo "Error: Tag '$TAG' does not exist."
exit 1
fi
Expand All @@ -43,7 +56,14 @@ jobs:
echo "Error: CMakeLists.txt version ($PKG_VERSION) does not match tag ($TAG)."
exit 1
fi
echo "Publishing tag: $TAG (ZeroBounce C SDK $PKG_VERSION)"

- name: Record tag commit SHA
id: tag_sha
env:
TAG: ${{ github.event.inputs.tag }}
run: |
SHA=$(git rev-parse "refs/tags/$TAG^{commit}")
echo "sha=$SHA" >> "$GITHUB_OUTPUT"

test:
name: Test
Expand All @@ -52,10 +72,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout tag
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.inputs.tag }}

ref: ${{ needs.validate-tag.outputs.tag_sha }}
persist-credentials: false
- name: Install build dependencies
run: |
sudo apt-get update
Expand All @@ -69,28 +89,29 @@ jobs:

release:
name: Create GitHub Release
needs: [test]
needs: [validate-tag, test]
if: ${{ !cancelled() && !failure() }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.inputs.tag }}
ref: ${{ needs.validate-tag.outputs.tag_sha }}
fetch-depth: 0

persist-credentials: false
- name: Create release if missing
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ github.event.inputs.tag }}"
if gh release view "$TAG" --repo "${{ github.repository }}" >/dev/null 2>&1; then
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "Release $TAG already exists, skipping."
else
gh release create "$TAG" \
--repo "${{ github.repository }}" \
--repo "$GITHUB_REPOSITORY" \
--title "$TAG" \
--generate-notes
fi
4 changes: 3 additions & 1 deletion .github/workflows/sdk_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ jobs:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Install build dependencies
run: |
Expand Down
7 changes: 6 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ FetchContent_Declare(
FetchContent_Declare(
libcurl
GIT_REPOSITORY https://github.com/curl/curl.git
GIT_TAG curl-8_0_1
GIT_TAG curl-8_11_1
)
set(CURL_DISABLE_NTLM ON CACHE BOOL "" FORCE)
set(CURL_DISABLE_TESTS ON CACHE BOOL "" FORCE)
set(CURL_USE_LIBPSL OFF CACHE BOOL "" FORCE)
set(BUILD_TESTING OFF CACHE BOOL "" FORCE)
if(APPLE)
set(CURL_USE_SECTRANSP ON CACHE BOOL "" FORCE)
endif()
# Download and build unity
FetchContent_Declare(
unity
Expand Down
11 changes: 8 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ WORKDIR /app

COPY . .

RUN cmake -S . -B build -G "Unix Makefiles" \
&& cmake --build build
RUN cmake -S . -B build -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release \
&& cmake --build build \
&& ./build/bin/ZeroBounceTests

RUN useradd --uid 65532 --create-home --shell /usr/sbin/nologin zbtest \
&& chown -R zbtest:zbtest /app

USER zbtest

# Run the test executable (built by CMake)
CMD ["build/bin/ZeroBounceTests"]
5 changes: 5 additions & 0 deletions include/ZeroBounce/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,9 @@ char* concatenate_strings(const StringVector* vec, char* separator) ;
*/
void* get_json_value(const json_object* obj, json_type type, const char* key, void* default_value);

/**
* @brief Heap-allocated copy of a JSON string field (or default). Caller must free().
*/
char* get_json_string_copy(const json_object* obj, const char* key, const char* default_value);

#endif
35 changes: 18 additions & 17 deletions src/ZBDomainSearchResponse.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ char* zb_domain_format_to_string(ZBDomainFormat* domain_format) {

ZBDomainFormat zb_domain_format_from_json(const json_object* j) {
ZBDomainFormat domain_format = zb_domain_format_init();
domain_format.format = *(char**)get_json_value(j, json_type_string, "format", &(char*){""});
domain_format.confidence = *(char**)get_json_value(j, json_type_string, "confidence", &(char*){""});
domain_format.format = get_json_string_copy(j, "format", "");
domain_format.confidence = get_json_string_copy(j, "confidence", "");
return domain_format;
}

Expand Down Expand Up @@ -135,19 +135,20 @@ char* zb_domain_format_vector_to_string(ZBDomainFormatVector* vector){
}

ZBDomainFormatVector zb_domain_format_vector_from_json(const json_object* j) {
if (json_object_get_type(j) != json_type_array) {
fprintf(
stderr,
"Invalid json type found for domain format vector.\n"
"Expected 'json_type_array', found '%s'.\n",
json_type_to_name(json_object_get_type(j))
);
exit(EXIT_FAILURE);
ZBDomainFormatVector vector = zb_domain_format_vector_init();
if (!j || json_object_get_type(j) != json_type_array) {
return vector;
}

size_t vector_size = json_object_array_length(j);
ZBDomainFormatVector vector = zb_domain_format_vector_init();
if (vector_size == 0) {
return vector;
}
vector.data = malloc(vector_size * sizeof(ZBDomainFormat));
if (!vector.data) {
fprintf(stderr, "Memory allocation failed.\n");
exit(EXIT_FAILURE);
}
vector.size = vector_size;
for (int index = 0; index < vector_size; index ++) {
vector.data[index] = zb_domain_format_from_json(
Expand Down Expand Up @@ -237,12 +238,12 @@ char* zb_domain_search_response_to_string(ZBDomainSearchResponse* response) {

ZBDomainSearchResponse zb_domain_search_response_from_json(const json_object* j) {
ZBDomainSearchResponse response = zb_domain_search_response_init();
response.domain = *(char**)get_json_value(j, json_type_string, "domain", &(char*){""});
response.company_name = *(char**)get_json_value(j, json_type_string, "company_name", &(char*){""});
response.format = *(char**)get_json_value(j, json_type_string, "format", &(char*){""});
response.confidence = *(char**)get_json_value(j, json_type_string, "confidence", &(char*){""});
response.did_you_mean = *(char**)get_json_value(j, json_type_string, "did_you_mean", &(char*){""});
response.failure_reason = *(char**)get_json_value(j, json_type_string, "failure_reason", &(char*){""});
response.domain = get_json_string_copy(j, "domain", "");
response.company_name = get_json_string_copy(j, "company_name", "");
response.format = get_json_string_copy(j, "format", "");
response.confidence = get_json_string_copy(j, "confidence", "");
response.did_you_mean = get_json_string_copy(j, "did_you_mean", "");
response.failure_reason = get_json_string_copy(j, "failure_reason", "");

json_object * domain_formats_json = json_object_object_get(j, "other_domain_formats");
response.other_domain_formats = zb_domain_format_vector_from_json(domain_formats_json);
Expand Down
6 changes: 4 additions & 2 deletions src/ZBErrorResponse.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ ZBErrorResponse parse_error(const char* error) {

json_object* hash_map = json_tokener_parse(error);

if (hash_map != NULL) {
if (hash_map != NULL && json_object_is_type(hash_map, json_type_object)) {
json_object_object_foreach(hash_map, key, val) {
if (strstr(key, "error") != NULL || strstr(key, "message") != NULL) {
if (json_object_is_type(val, json_type_array)) {
Expand Down Expand Up @@ -82,7 +82,9 @@ ZBErrorResponse parse_error(const char* error) {
}

response.errors = errors;
json_object_put(hash_map);
if (hash_map != NULL) {
json_object_put(hash_map);
}

return response;
}
Expand Down
Loading
Loading