diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ba0b4b0..b3f5fb4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -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: - "*" diff --git a/.github/workflows/auto_assign_ci.yaml b/.github/workflows/auto_assign_ci.yaml index 0e6c515..59aa47c 100644 --- a/.github/workflows/auto_assign_ci.yaml +++ b/.github/workflows/auto_assign_ci.yaml @@ -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: @@ -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" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e7ce03a..fd7bb76 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -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` @@ -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 }} @@ -100,7 +100,7 @@ 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 @@ -108,7 +108,7 @@ jobs: # 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/** @@ -116,7 +116,7 @@ jobs: 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}}" diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..138a59d --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -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 }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7bfbae2..eb96b67 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,15 +13,28 @@ 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 }}" @@ -29,7 +42,7 @@ jobs: 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 @@ -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 @@ -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 @@ -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 diff --git a/.github/workflows/sdk_ci.yml b/.github/workflows/sdk_ci.yml index 3c2e14f..8395bc8 100644 --- a/.github/workflows/sdk_ci.yml +++ b/.github/workflows/sdk_ci.yml @@ -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: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 50c2b90..3def030 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/Dockerfile b/Dockerfile index 2333f4e..55dd849 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] diff --git a/include/ZeroBounce/utils.h b/include/ZeroBounce/utils.h index 9dae595..e3cf512 100644 --- a/include/ZeroBounce/utils.h +++ b/include/ZeroBounce/utils.h @@ -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 \ No newline at end of file diff --git a/src/ZBDomainSearchResponse.c b/src/ZBDomainSearchResponse.c index 1eb705b..385d5b0 100644 --- a/src/ZBDomainSearchResponse.c +++ b/src/ZBDomainSearchResponse.c @@ -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; } @@ -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( @@ -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); diff --git a/src/ZBErrorResponse.c b/src/ZBErrorResponse.c index ee393a4..f76379d 100644 --- a/src/ZBErrorResponse.c +++ b/src/ZBErrorResponse.c @@ -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)) { @@ -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; } diff --git a/src/ZBFindEmailResponse.c b/src/ZBFindEmailResponse.c index f4519f3..2062096 100644 --- a/src/ZBFindEmailResponse.c +++ b/src/ZBFindEmailResponse.c @@ -63,12 +63,12 @@ char* zb_find_email_response_to_string(ZBFindEmailResponse* response) { ZBFindEmailResponse zb_find_email_response_from_json(const json_object* j) { ZBFindEmailResponse response = zb_find_email_response_init(); - response.email = *(char**)get_json_value(j, json_type_string, "email", &(char*){""}); - response.email_confidence = *(char**)get_json_value(j, json_type_string, "email_confidence", &(char*){""}); - 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.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.email = get_json_string_copy(j, "email", ""); + response.email_confidence = get_json_string_copy(j, "email_confidence", ""); + response.domain = get_json_string_copy(j, "domain", ""); + response.company_name = get_json_string_copy(j, "company_name", ""); + response.did_you_mean = get_json_string_copy(j, "did_you_mean", ""); + response.failure_reason = get_json_string_copy(j, "failure_reason", ""); return response; } diff --git a/src/ZBValidateBatchResponse.c b/src/ZBValidateBatchResponse.c index 939a887..97fa6ea 100644 --- a/src/ZBValidateBatchResponse.c +++ b/src/ZBValidateBatchResponse.c @@ -119,6 +119,8 @@ void validate_response_vector_append(ValidateResponseVector* vector, const ZBVal vector->data[vector->size].domain_age_days = strdup(response.domain_age_days); vector->data[vector->size].free_email = response.free_email; vector->data[vector->size].mx_found = response.mx_found; + vector->data[vector->size].catchall_domain = response.catchall_domain; + vector->data[vector->size].catchall_domain_set = response.catchall_domain_set; vector->data[vector->size].mx_record = strdup(response.mx_record); vector->data[vector->size].smtp_provider = strdup(response.smtp_provider); vector->data[vector->size].first_name = strdup(response.first_name); @@ -280,8 +282,10 @@ ZBValidateBatchResponse zb_validate_batch_response_from_json(const json_object* return r; } - size_t email_batch_size = json_object_array_length(j_email_batch); - size_t errors_size = json_object_array_length(j_errors); + size_t email_batch_size = json_object_is_type(j_email_batch, json_type_array) + ? json_object_array_length(j_email_batch) : 0; + size_t errors_size = json_object_is_type(j_errors, json_type_array) + ? json_object_array_length(j_errors) : 0; for (size_t i = 0; i < email_batch_size; i++) { json_object* j_response = json_object_array_get_idx(j_email_batch, i); diff --git a/src/ZeroBounce.c b/src/ZeroBounce.c index 9fb940e..79c8029 100644 --- a/src/ZeroBounce.c +++ b/src/ZeroBounce.c @@ -40,6 +40,34 @@ static void zero_bounce_record_request_url_for_test(const char* url) { s_last_request_url_for_test[sizeof(s_last_request_url_for_test) - 1] = '\0'; } +static char* zb_url_escape(const char* value) { + char* escaped = curl_easy_escape(NULL, value ? value : "", 0); + char* copy = escaped ? strdup(escaped) : NULL; + curl_free(escaped); + if (!copy) { + fprintf(stderr, "Memory allocation failed.\n"); + exit(EXIT_FAILURE); + } + return copy; +} + +static void zb_append_query_param(StringVector* vector, const char* name, const char* value) { + if (!value || !strlen(value)) { + return; + } + size_t prefix_len = strlen(name) + 3; + char* prefix = (char*)malloc(prefix_len); + if (!prefix) { + fprintf(stderr, "Memory allocation failed.\n"); + exit(EXIT_FAILURE); + } + snprintf(prefix, prefix_len, "&%s=", name); + string_vector_append(vector, prefix); + char* escaped = zb_url_escape(value); + string_vector_append(vector, escaped); + free(escaped); +} + static size_t write_callback(void *data, size_t size, size_t nmemb, void *clientp) { size_t real_size = size * nmemb; memory *mem = (memory*)clientp; @@ -71,8 +99,11 @@ long get_http_code(CURL* curl) { } char* get_content_type_value(CURL* curl) { - struct curl_header *content_type_header; - curl_easy_header(curl, "Content-Type", 0, CURLH_HEADER, -1, &content_type_header); + struct curl_header *content_type_header = NULL; + if (curl_easy_header(curl, "Content-Type", 0, CURLH_HEADER, -1, &content_type_header) != CURLHE_OK || + !content_type_header) { + return NULL; + } return content_type_header->value; } @@ -604,19 +635,25 @@ static void file_status_internal( const char *url_pattern = "%s/filestatus?api_key=%s&file_id=%s"; const char* base_url = scoring ? zb->bulk_api_scoring_base_url : zb->bulk_api_base_url; + char* api_key_esc = zb_url_escape(zb->api_key); + char* file_id_esc = zb_url_escape(file_id); int url_path_len = snprintf( - NULL, 0, url_pattern, base_url, zb->api_key, file_id + NULL, 0, url_pattern, base_url, api_key_esc, file_id_esc ); char *url_path = malloc(url_path_len + 1); if (!url_path) { + free(api_key_esc); + free(file_id_esc); fprintf(stderr, "Memory allocation failed.\n"); exit(EXIT_FAILURE); } snprintf( - url_path, url_path_len + 1, url_pattern, base_url, zb->api_key, file_id + url_path, url_path_len + 1, url_pattern, base_url, api_key_esc, file_id_esc ); + free(api_key_esc); + free(file_id_esc); memory response_data = {0}; long http_code; @@ -805,19 +842,25 @@ static void delete_file_internal( const char *url_pattern = "%s/deletefile?api_key=%s&file_id=%s"; const char* base_url = scoring ? zb->bulk_api_scoring_base_url : zb->bulk_api_base_url; + char* api_key_esc = zb_url_escape(zb->api_key); + char* file_id_esc = zb_url_escape(file_id); int url_path_len = snprintf( - NULL, 0, url_pattern, base_url, zb->api_key, file_id + NULL, 0, url_pattern, base_url, api_key_esc, file_id_esc ); char *url_path = malloc(url_path_len + 1); if (!url_path) { + free(api_key_esc); + free(file_id_esc); fprintf(stderr, "Memory allocation failed.\n"); exit(EXIT_FAILURE); } snprintf( - url_path, url_path_len + 1, url_pattern, base_url, zb->api_key, file_id + url_path, url_path_len + 1, url_pattern, base_url, api_key_esc, file_id_esc ); + free(api_key_esc); + free(file_id_esc); memory response_data = {0}; long http_code; @@ -863,28 +906,14 @@ static void find_email_internal( StringVector string_vector = string_vector_init(); string_vector_append(&string_vector, zb->api_base_url); string_vector_append(&string_vector, strdup("/guessformat?api_key=")); - string_vector_append(&string_vector, zb->api_key); - - if (domain != NULL && strlen(domain)) { - string_vector_append(&string_vector, strdup("&domain=")); - string_vector_append(&string_vector, domain); - } - if (company_name != NULL && strlen(company_name)) { - string_vector_append(&string_vector, strdup("&company_name=")); - string_vector_append(&string_vector, company_name); - } - if (first_name != NULL && strlen(first_name)) { - string_vector_append(&string_vector, strdup("&first_name=")); - string_vector_append(&string_vector, first_name); - } - if (middle_name != NULL && strlen(middle_name)) { - string_vector_append(&string_vector, strdup("&middle_name=")); - string_vector_append(&string_vector, middle_name); - } - if (last_name != NULL && strlen(last_name)) { - string_vector_append(&string_vector, strdup("&last_name=")); - string_vector_append(&string_vector, last_name); - } + char* api_key_esc = zb_url_escape(zb->api_key); + string_vector_append(&string_vector, api_key_esc); + free(api_key_esc); + zb_append_query_param(&string_vector, "domain", domain); + zb_append_query_param(&string_vector, "company_name", company_name); + zb_append_query_param(&string_vector, "first_name", first_name); + zb_append_query_param(&string_vector, "middle_name", middle_name); + zb_append_query_param(&string_vector, "last_name", last_name); char *url_path = concatenate_strings(&string_vector, ""); if (!url_path) { fprintf(stderr, "Memory allocation failed.\n"); @@ -934,16 +963,11 @@ static void search_domain_internal( StringVector string_vector = string_vector_init(); string_vector_append(&string_vector, zb->api_base_url); string_vector_append(&string_vector, strdup("/guessformat?api_key=")); - string_vector_append(&string_vector, zb->api_key); - - if (domain != NULL && strlen(domain)) { - string_vector_append(&string_vector, strdup("&domain=")); - string_vector_append(&string_vector, domain); - } - if (company_name != NULL && strlen(company_name)) { - string_vector_append(&string_vector, strdup("&company_name=")); - string_vector_append(&string_vector, company_name); - } + char* api_key_esc = zb_url_escape(zb->api_key); + string_vector_append(&string_vector, api_key_esc); + free(api_key_esc); + zb_append_query_param(&string_vector, "domain", domain); + zb_append_query_param(&string_vector, "company_name", company_name); char *url_path = concatenate_strings(&string_vector, ""); if (!url_path) { fprintf(stderr, "Memory allocation failed.\n"); @@ -991,19 +1015,22 @@ void get_credits( const char *url_pattern = "%s/getcredits?api_key=%s"; + char* api_key_esc = zb_url_escape(zb->api_key); int url_path_len = snprintf( - NULL, 0, url_pattern, zb->api_base_url, zb->api_key + NULL, 0, url_pattern, zb->api_base_url, api_key_esc ); char *url_path = malloc(url_path_len + 1); if (!url_path) { + free(api_key_esc); fprintf(stderr, "Memory allocation failed.\n"); exit(EXIT_FAILURE); } snprintf( - url_path, url_path_len + 1, url_pattern, zb->api_base_url, zb->api_key + url_path, url_path_len + 1, url_pattern, zb->api_base_url, api_key_esc ); + free(api_key_esc); memory response_data = {0}; long http_code; @@ -1053,21 +1080,28 @@ void get_api_usage( const char *url_pattern = "%s/getapiusage?api_key=%s&start_date=%s&end_date=%s"; + char* api_key_esc = zb_url_escape(zb->api_key); + char* start_esc = zb_url_escape(start_date_str); + char* end_esc = zb_url_escape(end_date_str); int url_path_len = snprintf( - NULL, 0, url_pattern, zb->api_base_url, zb->api_key, - start_date_str, end_date_str + NULL, 0, url_pattern, zb->api_base_url, api_key_esc, start_esc, end_esc ); char *url_path = malloc(url_path_len + 1); if (!url_path) { + free(api_key_esc); + free(start_esc); + free(end_esc); fprintf(stderr, "Memory allocation failed.\n"); exit(EXIT_FAILURE); } snprintf( - url_path, url_path_len + 1, url_pattern, zb->api_base_url, zb->api_key, - start_date_str, end_date_str + url_path, url_path_len + 1, url_pattern, zb->api_base_url, api_key_esc, start_esc, end_esc ); + free(api_key_esc); + free(start_esc); + free(end_esc); memory response_data = {0}; long http_code; @@ -1110,19 +1144,28 @@ void validate_email( const char *url_pattern = "%s/validate?api_key=%s&email=%s&ip_address=%s"; + char* api_key_esc = zb_url_escape(zb->api_key); + char* email_esc = zb_url_escape(email ? email : ""); + char* ip_esc = zb_url_escape(ip_address ? ip_address : ""); int url_path_len = snprintf( - NULL, 0, url_pattern, zb->api_base_url, zb->api_key, email, ip_address + NULL, 0, url_pattern, zb->api_base_url, api_key_esc, email_esc, ip_esc ); char *url_path = malloc(url_path_len + 1); if (!url_path) { + free(api_key_esc); + free(email_esc); + free(ip_esc); fprintf(stderr, "Memory allocation failed.\n"); exit(EXIT_FAILURE); } snprintf( - url_path, url_path_len + 1, url_pattern, zb->api_base_url, zb->api_key, email, ip_address + url_path, url_path_len + 1, url_pattern, zb->api_base_url, api_key_esc, email_esc, ip_esc ); + free(api_key_esc); + free(email_esc); + free(ip_esc); memory response_data = {0}; long http_code; @@ -1387,19 +1430,25 @@ void get_activity_data( const char *url_pattern = "%s/activity?api_key=%s&email=%s"; + char* api_key_esc = zb_url_escape(zb->api_key); + char* email_esc = zb_url_escape(email ? email : ""); int url_path_len = snprintf( - NULL, 0, url_pattern, zb->api_base_url, zb->api_key, email + NULL, 0, url_pattern, zb->api_base_url, api_key_esc, email_esc ); char *url_path = malloc(url_path_len + 1); if (!url_path) { + free(api_key_esc); + free(email_esc); fprintf(stderr, "Memory allocation failed.\n"); exit(EXIT_FAILURE); } snprintf( - url_path, url_path_len + 1, url_pattern, zb->api_base_url, zb->api_key, email + url_path, url_path_len + 1, url_pattern, zb->api_base_url, api_key_esc, email_esc ); + free(api_key_esc); + free(email_esc); memory response_data = {0}; long http_code; @@ -1530,24 +1579,13 @@ void find_email( StringVector string_vector = string_vector_init(); string_vector_append(&string_vector, zb->api_base_url); string_vector_append(&string_vector, strdup("/guessformat?api_key=")); - string_vector_append(&string_vector, zb->api_key); - - if (domain != NULL && strlen(domain)) { - string_vector_append(&string_vector, strdup("&domain=")); - string_vector_append(&string_vector, domain); - } - if (first_name != NULL && strlen(first_name)) { - string_vector_append(&string_vector, strdup("&first_name=")); - string_vector_append(&string_vector, first_name); - } - if (middle_name != NULL && strlen(middle_name)) { - string_vector_append(&string_vector, strdup("&middle_name=")); - string_vector_append(&string_vector, middle_name); - } - if (last_name != NULL && strlen(last_name)) { - string_vector_append(&string_vector, strdup("&last_name=")); - string_vector_append(&string_vector, last_name); - } + char* api_key_esc = zb_url_escape(zb->api_key); + string_vector_append(&string_vector, api_key_esc); + free(api_key_esc); + zb_append_query_param(&string_vector, "domain", domain); + zb_append_query_param(&string_vector, "first_name", first_name); + zb_append_query_param(&string_vector, "middle_name", middle_name); + zb_append_query_param(&string_vector, "last_name", last_name); char *url_path = concatenate_strings(&string_vector, ""); if (!url_path) { fprintf(stderr, "Memory allocation failed.\n"); diff --git a/src/utils.c b/src/utils.c index 9fe34df..4deecb7 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,5 +1,6 @@ #include #include +#include #include #include "ZeroBounce/utils.h" @@ -137,4 +138,16 @@ void* get_json_value(const json_object* obj, json_type type, const char* key, vo } } return default_value; +} + +char* get_json_string_copy(const json_object* obj, const char* key, const char* default_value) { + const char* def = default_value ? default_value : ""; + json_object* value_obj = NULL; + + if (obj && json_object_object_get_ex(obj, key, &value_obj)) { + if (json_object_is_type(value_obj, json_type_string)) { + return strdup(json_object_get_string(value_obj)); + } + } + return strdup(def); } \ No newline at end of file