diff --git a/src/provider_common.c b/src/provider_common.c index 6a0cd01..6a773b7 100644 --- a/src/provider_common.c +++ b/src/provider_common.c @@ -226,7 +226,9 @@ provider_count_array_dimensions(const char *p) * * Reads up to `dim` float values from the current position (after '['). * Advances *pos past the parsed values (to ']' or end of parsed data). - * Returns the number of values successfully parsed. + * Returns the number of values successfully parsed, or + * PROVIDER_PARSE_MALFORMED on a numeric literal too long to be one, in which + * case *pos is left on the offending run rather than past it. */ int provider_parse_float_array(const char **pos, float *output, int dim) @@ -250,8 +252,20 @@ provider_parse_float_array(const char **pos, float *output, int dim) while (*p && (isdigit((unsigned char) *p) || *p == '.' || *p == '-' || *p == '+' || *p == 'e' || *p == 'E')) { - if (value_pos < (int) sizeof(value_buf) - 1) - value_buf[value_pos++] = *p; + /* + * Stop rather than truncate. No double needs this many + * characters -- %.17g never exceeds 24 -- so a longer run means a + * malformed response, and keeping only the leading digits would + * feed atof() a number of an entirely different magnitude and + * score on it silently. + */ + if (value_pos >= (int) sizeof(value_buf) - 1) + { + *pos = p; + return PROVIDER_PARSE_MALFORMED; + } + + value_buf[value_pos++] = *p; p++; } value_buf[value_pos] = '\0'; @@ -457,8 +471,11 @@ provider_parse_openai_embedding_response(const char *json_response, int count, if (parsed != *dim) { - *error_msg = psprintf("Dimension mismatch: expected %d, got %d", - *dim, parsed); + *error_msg = (parsed == PROVIDER_PARSE_MALFORMED) + ? pstrdup("Malformed embedding response: numeric literal too " + "long to be a number") + : psprintf("Dimension mismatch: expected %d, got %d", + *dim, parsed); provider_free_embeddings(embeddings, embedding_idx + 1); return NULL; } diff --git a/src/provider_common.h b/src/provider_common.h index 133d003..eb9f61d 100644 --- a/src/provider_common.h +++ b/src/provider_common.h @@ -75,7 +75,14 @@ float **provider_parse_openai_embedding_response(const char *json_response, /* Count dimensions by counting commas in a JSON float array (after '[') */ int provider_count_array_dimensions(const char *p); -/* Parse a JSON float array into pre-allocated output; returns count parsed */ +/* + * Parse a JSON float array into pre-allocated output; returns count parsed, + * or PROVIDER_PARSE_MALFORMED if a numeric literal was too long to be one. + * Callers that only compare against the expected count still reject that, + * since it can never equal a dimension. + */ +#define PROVIDER_PARSE_MALFORMED (-1) + int provider_parse_float_array(const char **pos, float *output, int dim); /* Append extra headers from GUC to a curl header list */ diff --git a/src/provider_gemini.c b/src/provider_gemini.c index 8df08a6..0ff5c00 100644 --- a/src/provider_gemini.c +++ b/src/provider_gemini.c @@ -127,7 +127,7 @@ gemini_generate_batch(const char **texts, int count, int *dim, char **error_msg) char *json_request; char *url; const char *base_url; - char auth_header[512]; + char *auth_header; StringInfoData request_buf; ResponseBuffer response; float **embeddings; @@ -164,8 +164,7 @@ gemini_generate_batch(const char **texts, int count, int *dim, char **error_msg) pgedge_vectorizer_model); /* Build auth header */ - snprintf(auth_header, sizeof(auth_header), - "x-goog-api-key: %s", api_key); + auth_header = psprintf("x-goog-api-key: %s", api_key); /* Perform request */ if (!provider_do_curl_request(url, auth_header, json_request, @@ -173,6 +172,7 @@ gemini_generate_batch(const char **texts, int count, int *dim, char **error_msg) { pfree(json_request); pfree(url); + pfree(auth_header); if (response.data) pfree(response.data); return NULL; @@ -184,6 +184,7 @@ gemini_generate_batch(const char **texts, int count, int *dim, char **error_msg) pfree(json_request); pfree(url); + pfree(auth_header); pfree(response.data); return embeddings; } @@ -236,8 +237,11 @@ parse_gemini_batch_embedding_response(const char *json_response, int count, if (parsed != *dim) { - *error_msg = psprintf("Dimension mismatch: expected %d, got %d", - *dim, parsed); + *error_msg = (parsed == PROVIDER_PARSE_MALFORMED) + ? pstrdup("Malformed embedding response: numeric literal too " + "long to be a number") + : psprintf("Dimension mismatch: expected %d, got %d", + *dim, parsed); provider_free_embeddings(embeddings, embedding_idx + 1); return NULL; } diff --git a/src/provider_ollama.c b/src/provider_ollama.c index 8da9dc8..4dc87d0 100644 --- a/src/provider_ollama.c +++ b/src/provider_ollama.c @@ -205,8 +205,11 @@ parse_ollama_embedding_response(const char *json_response, int *dim, if (parsed != *dim) { - *error_msg = psprintf("Dimension mismatch: expected %d, got %d", - *dim, parsed); + *error_msg = (parsed == PROVIDER_PARSE_MALFORMED) + ? pstrdup("Malformed embedding response: numeric literal too " + "long to be a number") + : psprintf("Dimension mismatch: expected %d, got %d", + *dim, parsed); pfree(embedding); return NULL; } diff --git a/src/provider_openai.c b/src/provider_openai.c index ed2c19e..9626add 100644 --- a/src/provider_openai.c +++ b/src/provider_openai.c @@ -135,8 +135,7 @@ openai_generate_batch(const char **texts, int count, int *dim, char **error_msg) char *json_request; char *url; const char *base_url; - char auth_header[512]; - const char *auth_header_ptr = NULL; + char *auth_header = NULL; ResponseBuffer response; float **embeddings; @@ -160,17 +159,17 @@ openai_generate_batch(const char **texts, int count, int *dim, char **error_msg) /* Build auth header if we have a key */ if (api_key != NULL) { - snprintf(auth_header, sizeof(auth_header), - "Authorization: Bearer %s", api_key); - auth_header_ptr = auth_header; + auth_header = psprintf("Authorization: Bearer %s", api_key); } /* Perform request */ - if (!provider_do_curl_request(url, auth_header_ptr, json_request, + if (!provider_do_curl_request(url, auth_header, json_request, "OpenAI", &response, error_msg)) { pfree(json_request); pfree(url); + if (auth_header) + pfree(auth_header); if (response.data) pfree(response.data); return NULL; @@ -182,6 +181,8 @@ openai_generate_batch(const char **texts, int count, int *dim, char **error_msg) pfree(json_request); pfree(url); + if (auth_header) + pfree(auth_header); pfree(response.data); return embeddings; } diff --git a/src/provider_voyage.c b/src/provider_voyage.c index 2f37a6b..929da93 100644 --- a/src/provider_voyage.c +++ b/src/provider_voyage.c @@ -116,7 +116,7 @@ voyage_generate_batch(const char **texts, int count, int *dim, char **error_msg) char *json_request; char *url; const char *base_url; - char auth_header[512]; + char *auth_header; ResponseBuffer response; float **embeddings; @@ -138,8 +138,7 @@ voyage_generate_batch(const char **texts, int count, int *dim, char **error_msg) url = psprintf("%s/embeddings", base_url); /* Build auth header */ - snprintf(auth_header, sizeof(auth_header), - "Authorization: Bearer %s", api_key); + auth_header = psprintf("Authorization: Bearer %s", api_key); /* Perform request */ if (!provider_do_curl_request(url, auth_header, json_request, @@ -147,6 +146,7 @@ voyage_generate_batch(const char **texts, int count, int *dim, char **error_msg) { pfree(json_request); pfree(url); + pfree(auth_header); if (response.data) pfree(response.data); return NULL; @@ -158,6 +158,7 @@ voyage_generate_batch(const char **texts, int count, int *dim, char **error_msg) pfree(json_request); pfree(url); + pfree(auth_header); pfree(response.data); return embeddings; } diff --git a/src/worker.c b/src/worker.c index a021dc9..29c64cd 100644 --- a/src/worker.c +++ b/src/worker.c @@ -56,6 +56,11 @@ static time_t last_cleanup_time = 0; * a fixed string tells them the item failed, which they can already see from * its status, but not why. A fixed buffer rather than a palloc'd copy, since * the transaction context this is captured in does not survive the abort. + * + * Truncation here is deliberate and harmless: this is diagnostic text, the + * untruncated message has already gone to the server log via + * EmitErrorReport(), and a bounded buffer is what lets the copy outlive the + * abort at all. */ #define FAILED_ITEM_ERROR_LEN 1024