/* Build: cc -std=c11 -Wall -Wextra convert.c -o convert $(pkg-config --cflags --libs libcurl) */ #include #include #include #include #include struct Buffer { unsigned char *data; size_t length; }; static size_t receive_bytes(char *data, size_t size, size_t count, void *user) { struct Buffer *buffer = user; if (size && count > SIZE_MAX / size) return 0; size_t length = size * count; if (length > SIZE_MAX - buffer->length) return 0; if (!length) return 0; unsigned char *next = realloc(buffer->data, buffer->length + length); if (!next) return 0; memcpy(next + buffer->length, data, length); buffer->data = next; buffer->length += length; return length; } static CURLcode add_text(curl_mime *form, const char *name, const char *value) { curl_mimepart *part = curl_mime_addpart(form); if (!part) return CURLE_OUT_OF_MEMORY; CURLcode result = curl_mime_name(part, name); return result == CURLE_OK ? curl_mime_data(part, value, CURL_ZERO_TERMINATED) : result; } #define CHECK_CURL(call) do { \ CURLcode code = (call); \ if (code != CURLE_OK) { \ fprintf(stderr, "Request failed: %s\n", curl_easy_strerror(code)); \ goto cleanup; \ } \ } while (0) int main(int argc, char **argv) { const char *mode = argc > 1 ? argv[1] : "url"; const char *input = argc > 2 ? argv[2] : "https://example.com/"; const char *output = argc > 3 ? argv[3] : "output.pdf"; if (argc > 4 || (argc == 2) || (strcmp(mode, "url") && strcmp(mode, "file") && strcmp(mode, "text"))) { fprintf(stderr, "Usage: %s [url|file|text INPUT [OUTPUT.pdf]]\n", argv[0]); return EXIT_FAILURE; } const char *username = getenv("API_USERNAME"); const char *api_key = getenv("API_KEY"); if (!username || !*username || !api_key || !*api_key) { fputs("Set API_USERNAME and API_KEY. Both may be demo for testing.\n", stderr); return EXIT_FAILURE; } CURLcode initialized = curl_global_init(CURL_GLOBAL_DEFAULT); if (initialized != CURLE_OK) { fprintf(stderr, "libcurl initialization failed: %s\n", curl_easy_strerror(initialized)); return EXIT_FAILURE; } int result = EXIT_FAILURE; CURL *curl = curl_easy_init(); curl_mime *form = NULL; struct Buffer response = {NULL, 0}; long status = 0; if (!curl || !(form = curl_mime_init(curl))) { fputs("Cannot allocate the request.\n", stderr); goto cleanup; } CHECK_CURL(curl_easy_setopt(curl, CURLOPT_URL, "https://api.pdfcrowd.com/convert/24.04/")); CHECK_CURL(curl_easy_setopt(curl, CURLOPT_USERNAME, username)); CHECK_CURL(curl_easy_setopt(curl, CURLOPT_PASSWORD, api_key)); CHECK_CURL(curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC)); CHECK_CURL(curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L)); CHECK_CURL(curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L)); CHECK_CURL(curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receive_bytes)); CHECK_CURL(curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response)); CHECK_CURL(add_text(form, "input_format", "html")); CHECK_CURL(add_text(form, "output_format", "pdf")); CHECK_CURL(add_text(form, "content_viewport_width", "balanced")); CHECK_CURL(add_text(form, "page_size", "A4")); if (!strcmp(mode, "file")) { curl_mimepart *part = curl_mime_addpart(form); if (!part) { fputs("Cannot allocate the file upload.\n", stderr); goto cleanup; } CHECK_CURL(curl_mime_name(part, "file")); CHECK_CURL(curl_mime_filedata(part, input)); } else { CHECK_CURL(add_text(form, mode, input)); } CHECK_CURL(curl_easy_setopt(curl, CURLOPT_MIMEPOST, form)); CHECK_CURL(curl_easy_perform(curl)); CHECK_CURL(curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status)); if (status != 200) { fprintf(stderr, "PDFCrowd returned HTTP %ld:\n", status); if (response.length) fwrite(response.data, 1, response.length, stderr); fputc('\n', stderr); goto cleanup; } FILE *file = fopen(output, "wb"); if (!file) { perror("Cannot open the output file"); goto cleanup; } size_t written = fwrite(response.data, 1, response.length, file); int closed = fclose(file); if (written != response.length || closed != 0) { fputs("Could not write the complete PDF.\n", stderr); goto cleanup; } printf("Saved %s\n", output); result = EXIT_SUCCESS; cleanup: if (curl) curl_easy_cleanup(curl); curl_mime_free(form); free(response.data); curl_global_cleanup(); return result; }