#include #include #include #include #include typedef struct { CURL *curl; struct curl_slist *req_headers; /* Body pipe */ int body_read_fd; int body_write_fd; /* Header buffer */ char *headers; size_t headers_len; size_t headers_cap; /* Synchronization */ int headers_ready; pthread_mutex_t mutex; pthread_cond_t cond; /* Results */ long response_code; CURLcode result; char error_buf[CURL_ERROR_SIZE]; /* Request body */ const char *req_body; size_t req_body_len; size_t req_body_offset; /* Thread */ pthread_t thread; } curl_request_t; static size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata) { curl_request_t *req = (curl_request_t *)userdata; size_t total = size * nitems; /* On HTTP/ line, reset buffer (keeps only final response after redirects) */ if (total >= 5 && strncmp(buffer, "HTTP/", 5) == 0) { req->headers_len = 0; } /* Append to header buffer */ while (req->headers_len + total + 1 > req->headers_cap) { req->headers_cap = req->headers_cap ? req->headers_cap * 2 : 4096; req->headers = realloc(req->headers, req->headers_cap); } memcpy(req->headers + req->headers_len, buffer, total); req->headers_len += total; req->headers[req->headers_len] = '\0'; /* Blank line = end of headers for this response */ if (total == 2 && buffer[0] == '\r' && buffer[1] == '\n') { long code = 0; curl_easy_getinfo(req->curl, CURLINFO_RESPONSE_CODE, &code); req->response_code = code; /* Only signal for final response (not 1xx or 3xx intermediates) */ if (code >= 200 && (code < 300 || code >= 400)) { pthread_mutex_lock(&req->mutex); req->headers_ready = 1; pthread_cond_signal(&req->cond); pthread_mutex_unlock(&req->mutex); } } return total; } static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata) { curl_request_t *req = (curl_request_t *)userdata; size_t total = size * nmemb; size_t written = 0; while (written < total) { ssize_t n = write(req->body_write_fd, ptr + written, total - written); if (n <= 0) return 0; /* signal error to curl */ written += n; } return total; } static size_t read_callback(char *buffer, size_t size, size_t nitems, void *userdata) { curl_request_t *req = (curl_request_t *)userdata; size_t max = size * nitems; size_t remaining = req->req_body_len - req->req_body_offset; size_t to_copy = remaining < max ? remaining : max; if (to_copy == 0) return 0; memcpy(buffer, req->req_body + req->req_body_offset, to_copy); req->req_body_offset += to_copy; return to_copy; } static void *curl_thread_func(void *arg) { curl_request_t *req = (curl_request_t *)arg; req->result = curl_easy_perform(req->curl); close(req->body_write_fd); req->body_write_fd = -1; /* If headers never arrived (connection failure), signal anyway */ pthread_mutex_lock(&req->mutex); req->headers_ready = 1; pthread_cond_signal(&req->cond); pthread_mutex_unlock(&req->mutex); return NULL; } curl_request_t *curl_request_start( const char *url, const char *method, const char *headers_str, /* "Header: value\r\nHeader2: value2\r\n" */ const char *body, size_t body_len, int follow_redirects, long max_redirects, const char *useragent, const char *proxy, const char *userpwd) { int pipefd[2]; curl_request_t *req; if (pipe(pipefd) != 0) return NULL; req = calloc(1, sizeof(curl_request_t)); if (!req) { close(pipefd[0]); close(pipefd[1]); return NULL; } req->body_read_fd = pipefd[0]; req->body_write_fd = pipefd[1]; req->headers_ready = 0; pthread_mutex_init(&req->mutex, NULL); pthread_cond_init(&req->cond, NULL); req->curl = curl_easy_init(); if (!req->curl) { close(pipefd[0]); close(pipefd[1]); free(req); return NULL; } curl_easy_setopt(req->curl, CURLOPT_URL, url); curl_easy_setopt(req->curl, CURLOPT_CUSTOMREQUEST, method); curl_easy_setopt(req->curl, CURLOPT_ERRORBUFFER, req->error_buf); curl_easy_setopt(req->curl, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(req->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); /* Headers */ if (headers_str && headers_str[0]) { const char *p = headers_str; while (*p) { const char *end = strstr(p, "\r\n"); if (!end) end = p + strlen(p); if (end > p) { char *line = strndup(p, end - p); req->req_headers = curl_slist_append(req->req_headers, line); free(line); } if (*end == '\r') p = end + 2; else break; } curl_easy_setopt(req->curl, CURLOPT_HTTPHEADER, req->req_headers); } /* Request body */ if (body && body_len > 0) { req->req_body = body; req->req_body_len = body_len; req->req_body_offset = 0; curl_easy_setopt(req->curl, CURLOPT_POST, 1L); curl_easy_setopt(req->curl, CURLOPT_READFUNCTION, read_callback); curl_easy_setopt(req->curl, CURLOPT_READDATA, req); curl_easy_setopt(req->curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)body_len); } /* Response callbacks */ curl_easy_setopt(req->curl, CURLOPT_HEADERFUNCTION, header_callback); curl_easy_setopt(req->curl, CURLOPT_HEADERDATA, req); curl_easy_setopt(req->curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(req->curl, CURLOPT_WRITEDATA, req); /* Redirects */ if (follow_redirects) { curl_easy_setopt(req->curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(req->curl, CURLOPT_MAXREDIRS, max_redirects); } /* User agent */ if (useragent && useragent[0]) { curl_easy_setopt(req->curl, CURLOPT_USERAGENT, useragent); } /* Proxy */ if (proxy && proxy[0]) { curl_easy_setopt(req->curl, CURLOPT_PROXY, proxy); } /* Auth */ if (userpwd && userpwd[0]) { curl_easy_setopt(req->curl, CURLOPT_USERPWD, userpwd); } /* Start thread */ if (pthread_create(&req->thread, NULL, curl_thread_func, req) != 0) { curl_easy_cleanup(req->curl); if (req->req_headers) curl_slist_free_all(req->req_headers); close(pipefd[0]); close(pipefd[1]); free(req); return NULL; } return req; } long curl_request_wait_headers(curl_request_t *req) { pthread_mutex_lock(&req->mutex); while (!req->headers_ready) { pthread_cond_wait(&req->cond, &req->mutex); } pthread_mutex_unlock(&req->mutex); return req->response_code; } const char *curl_request_get_headers(curl_request_t *req) { return req->headers ? req->headers : ""; } int curl_request_get_body_fd(curl_request_t *req) { return req->body_read_fd; } int curl_request_finish(curl_request_t *req) { int result; pthread_join(req->thread, NULL); result = (int)req->result; /* Cleanup */ close(req->body_read_fd); curl_easy_cleanup(req->curl); if (req->req_headers) curl_slist_free_all(req->req_headers); if (req->headers) free(req->headers); pthread_mutex_destroy(&req->mutex); pthread_cond_destroy(&req->cond); free(req); return result; } const char *curl_request_get_error(curl_request_t *req) { return req->error_buf; }