// Build: c++ -std=c++17 -Wall -Wextra convert.cpp -o convert $(pkg-config --cflags --libs libcurl) #include #include #include #include #include #include #include #include static void check(CURLcode code) { if (code != CURLE_OK) throw std::runtime_error(curl_easy_strerror(code)); } struct CurlRuntime { CurlRuntime() { check(curl_global_init(CURL_GLOBAL_DEFAULT)); } ~CurlRuntime() { curl_global_cleanup(); } CurlRuntime(const CurlRuntime&) = delete; CurlRuntime& operator=(const CurlRuntime&) = delete; }; static size_t receive_bytes(char *data, size_t size, size_t count, void *user) noexcept { if (size && count > std::numeric_limits::max() / size) return 0; const size_t length = size * count; try { static_cast(user)->append(data, length); return length; } catch (...) { // An exception must not cross the libcurl callback boundary. return 0; } } static void add_text(curl_mime *form, const char *name, const char *value) { auto *part = curl_mime_addpart(form); if (!part) throw std::bad_alloc(); check(curl_mime_name(part, name)); check(curl_mime_data(part, value, CURL_ZERO_TERMINATED)); } int main(int argc, char **argv) { try { const std::string 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 || (mode != "url" && mode != "file" && mode != "text")) throw std::runtime_error("Usage: convert [url|file|text INPUT [OUTPUT.pdf]]"); const char *username = std::getenv("API_USERNAME"); const char *api_key = std::getenv("API_KEY"); if (!username || !*username || !api_key || !*api_key) throw std::runtime_error("Set API_USERNAME and API_KEY; both may be demo."); CurlRuntime runtime; std::unique_ptr curl( curl_easy_init(), curl_easy_cleanup); if (!curl) throw std::bad_alloc(); std::unique_ptr form( curl_mime_init(curl.get()), curl_mime_free); if (!form) throw std::bad_alloc(); std::string response; check(curl_easy_setopt(curl.get(), CURLOPT_URL, "https://api.pdfcrowd.com/convert/24.04/")); check(curl_easy_setopt(curl.get(), CURLOPT_USERNAME, username)); check(curl_easy_setopt(curl.get(), CURLOPT_PASSWORD, api_key)); check(curl_easy_setopt(curl.get(), CURLOPT_HTTPAUTH, CURLAUTH_BASIC)); check(curl_easy_setopt(curl.get(), CURLOPT_CONNECTTIMEOUT, 30L)); check(curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, 120L)); check(curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, receive_bytes)); check(curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response)); add_text(form.get(), "input_format", "html"); add_text(form.get(), "output_format", "pdf"); add_text(form.get(), "content_viewport_width", "balanced"); add_text(form.get(), "page_size", "A4"); if (mode == "file") { auto *part = curl_mime_addpart(form.get()); if (!part) throw std::bad_alloc(); check(curl_mime_name(part, "file")); check(curl_mime_filedata(part, input)); } else { add_text(form.get(), mode.c_str(), input); } check(curl_easy_setopt(curl.get(), CURLOPT_MIMEPOST, form.get())); check(curl_easy_perform(curl.get())); long status = 0; check(curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &status)); if (status != 200) { std::cerr << "PDFCrowd returned HTTP " << status << ":\n" << response << '\n'; return EXIT_FAILURE; } std::ofstream file(output, std::ios::binary); file.exceptions(std::ios::failbit | std::ios::badbit); file.write(response.data(), static_cast(response.size())); file.close(); std::cout << "Saved " << output << '\n'; return EXIT_SUCCESS; } catch (const std::exception& error) { std::cerr << "Conversion failed: " << error.what() << '\n'; return EXIT_FAILURE; } }