| /*************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at https://curl.se/docs/copyright.html. |
| * |
| * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
| * copies of the Software, and permit persons to whom the Software is |
| * furnished to do so, under the terms of the COPYING file. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| * SPDX-License-Identifier: curl |
| * |
| ***************************************************************************/ |
| /* <DESC> |
| * Save failed transfer verbose log to disk |
| * </DESC> |
| */ |
| /* |
| * |
| * This example demonstrates per-transfer verbose logging to memory. |
| * The transfer's log is written to disk only if the transfer fails. |
| * |
| */ |
| |
| #ifndef UNDER_CE |
| #include <errno.h> |
| #endif |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <limits.h> |
| #include <string.h> |
| #include <curl/curl.h> |
| |
| #ifdef _WIN32 |
| #ifndef _CRT_SECURE_NO_WARNINGS |
| #define _CRT_SECURE_NO_WARNINGS |
| #endif |
| #include <windows.h> |
| #define strcasecmp _stricmp |
| #define strncasecmp _strnicmp |
| #define unlink _unlink |
| #else |
| #include <strings.h> |
| #include <unistd.h> |
| #endif |
| |
| struct mem { |
| /* 'buf' points to memory contents that is always zero terminated so that it |
| can be treated like a string if appropriate. 'recent' points to the most |
| recent data written to 'buf'. */ |
| char *buf, *recent; |
| /* 'len' and 'allocsize' are the length and allocated size of 'buf' */ |
| size_t len, allocsize; |
| }; |
| |
| struct transfer { |
| const char *url, *bodyfile, *logfile; |
| struct mem log; |
| FILE *bodyfp; |
| CURL *curl; |
| }; |
| |
| static void mem_reset(struct mem *mem) |
| { |
| free(mem->buf); |
| mem->buf = NULL; |
| mem->recent = NULL; |
| mem->len = 0; |
| mem->allocsize = 0; |
| } |
| |
| /* expand free buffer space to needed size. return -1 or 'needed'. */ |
| static int mem_need(struct mem *mem, size_t needed) |
| { |
| char *newbuf; |
| size_t newsize; |
| |
| if(needed > (unsigned)INT_MAX) |
| return -1; |
| |
| if(needed <= (mem->allocsize - mem->len)) |
| return (int)needed; |
| |
| /* min 4k makes reallocations much less frequent when lengths are small */ |
| newsize = needed < 4096 ? 4096 : needed; |
| |
| newsize += mem->len; |
| |
| if(newsize < mem->len || newsize > (unsigned)INT_MAX) |
| return -1; |
| |
| newbuf = realloc(mem->buf, newsize); |
| |
| if(!newbuf) |
| return -1; |
| |
| if(mem->recent && mem->buf != newbuf) |
| mem->recent = newbuf + (mem->recent - mem->buf); |
| |
| mem->buf = newbuf; |
| mem->allocsize = newsize; |
| |
| return (int)needed; |
| } |
| |
| static int mem_addn(struct mem *mem, const char *buf, size_t len) |
| { |
| if(len + 1 < len || mem_need(mem, len + 1) < 0) |
| return -1; |
| mem->recent = mem->buf + mem->len; |
| memcpy(mem->recent, buf, len); |
| mem->len += len; |
| mem->buf[mem->len] = '\0'; |
| return (int)len; |
| } |
| |
| static int mem_add(struct mem *mem, const char *str) |
| { |
| return mem_addn(mem, str, strlen(str)); |
| } |
| |
| #if defined(__GNUC__) || defined(__clang__) |
| __attribute__((format(printf, 2, 3))) |
| #endif |
| static int mem_addf(struct mem *mem, const char *format, ...) |
| { |
| int i, x; |
| va_list va; |
| |
| /* we need about 100 chars or less to write 95% of lines */ |
| x = 128; |
| |
| /* first try: there's probably enough memory to write everything. |
| second try: there's definitely enough memory to write everything. */ |
| for(i = 0; i < 2; ++i) { |
| if(x < 0 || mem_need(mem, (size_t)x + 1) < 0) |
| break; |
| |
| va_start(va, format); |
| x = vsnprintf(mem->buf + mem->len, mem->allocsize - mem->len, format, va); |
| va_end(va); |
| |
| if(x >= 0 && (size_t)x < (mem->allocsize - mem->len)) { |
| mem->recent = mem->buf + mem->len; |
| mem->len += (size_t)x; |
| return x; |
| } |
| |
| #if defined(_WIN32) && !defined(UNDER_CE) |
| /* Not all versions of Windows CRT vsnprintf are compliant with C99. Some |
| return -1 if buffer too small. Try _vscprintf to get the needed size. */ |
| if(!i && x < 0) { |
| va_start(va, format); |
| x = _vscprintf(format, va); |
| va_end(va); |
| } |
| #endif |
| } |
| |
| if(mem->buf) |
| mem->buf[mem->len] = '\0'; |
| return -1; |
| } |
| |
| static int mydebug(CURL *curl, curl_infotype type, |
| char *data, size_t size, void *userdata) |
| { |
| struct transfer *t = (struct transfer *)userdata; |
| static const char s_infotype[CURLINFO_END][3] = { |
| "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; |
| |
| (void)curl; |
| |
| switch(type) { |
| case CURLINFO_TEXT: |
| case CURLINFO_HEADER_OUT: |
| case CURLINFO_HEADER_IN: |
| /* mem_addn is faster than passing large data as %s to mem_addf */ |
| mem_addn(&t->log, s_infotype[type], 2); |
| mem_addn(&t->log, data, size); |
| if(!size || data[size - 1] != '\n') |
| mem_add(&t->log, "\n"); |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) |
| { |
| struct transfer *t = (struct transfer *)userdata; |
| |
| return fwrite(ptr, size, nmemb, t->bodyfp); |
| } |
| |
| int main(void) |
| { |
| CURLcode res; |
| unsigned i; |
| int total_failed = 0; |
| char errbuf[CURL_ERROR_SIZE] = { 0, }; |
| struct transfer transfer[2]; |
| |
| memset(transfer, 0, sizeof(transfer)); |
| |
| transfer[0].url = "https://httpbin.org/get"; |
| transfer[0].bodyfile = "200.txt"; |
| transfer[0].logfile = "200_transfer_log.txt"; |
| |
| transfer[1].url = "https://httpbin.org/status/400"; |
| transfer[1].bodyfile = "400.txt"; |
| transfer[1].logfile = "400_transfer_log.txt"; |
| |
| res = curl_global_init(CURL_GLOBAL_ALL); |
| if(res) { |
| fprintf(stderr, "curl_global_init failed\n"); |
| return (int)res; |
| } |
| |
| /* You could enable global tracing for extra verbosity when verbosity is |
| enabled for a transfer. */ |
| #if 0 |
| curl_global_trace("all"); |
| #endif |
| |
| for(i = 0; i < sizeof(transfer)/sizeof(transfer[0]); ++i) { |
| int failed = 0; |
| struct transfer *t = &transfer[i]; |
| |
| t->curl = curl_easy_init(); |
| |
| if(!t->curl) { |
| fprintf(stderr, "curl_easy_init failed\n"); |
| curl_global_cleanup(); |
| return 1; |
| } |
| |
| curl_easy_setopt(t->curl, CURLOPT_URL, t->url); |
| |
| /* Enable following redirects */ |
| curl_easy_setopt(t->curl, CURLOPT_FOLLOWLOCATION, 1L); |
| |
| /* Enable verbose logging to memory */ |
| curl_easy_setopt(t->curl, CURLOPT_VERBOSE, 1L); |
| curl_easy_setopt(t->curl, CURLOPT_DEBUGFUNCTION, mydebug); |
| curl_easy_setopt(t->curl, CURLOPT_DEBUGDATA, t); |
| |
| /* Enable writing the body to a file */ |
| curl_easy_setopt(t->curl, CURLOPT_WRITEFUNCTION, write_cb); |
| curl_easy_setopt(t->curl, CURLOPT_WRITEDATA, t); |
| |
| /* Enable immediate error on HTTP status codes >= 400 in most cases, |
| instead of downloading the body to a file */ |
| curl_easy_setopt(t->curl, CURLOPT_FAILONERROR, 1L); |
| |
| /* Enable detailed error messages */ |
| curl_easy_setopt(t->curl, CURLOPT_ERRORBUFFER, errbuf); |
| |
| mem_addf(&t->log, "Downloading %s to file %s\n", t->url, t->bodyfile); |
| printf("%s", t->log.recent); |
| |
| /* Create the body file */ |
| t->bodyfp = fopen(t->bodyfile, "wb"); |
| |
| if(t->bodyfp) { |
| /* Perform the transfer */ |
| CURLcode result = curl_easy_perform(t->curl); |
| |
| /* Save the body file */ |
| fclose(t->bodyfp); |
| t->bodyfp = NULL; |
| |
| if(result == CURLE_OK) { |
| /* You could retrieve more information about the transfer here via |
| curl_easy_getinfo and mark the transfer as failed if needed. */ |
| mem_addf(&t->log, "Transfer successful.\n"); |
| fprintf(stderr, "%s", t->log.recent); |
| failed = 0; |
| } |
| else { |
| mem_addf(&t->log, "Transfer failed: (%d) %s\n", result, |
| (errbuf[0] ? errbuf : curl_easy_strerror(result))); |
| fprintf(stderr, "%s", t->log.recent); |
| failed = 1; |
| } |
| } |
| else { |
| #ifndef UNDER_CE |
| mem_addf(&t->log, "Failed to create body output file %s: %s\n", |
| t->bodyfile, strerror(errno)); |
| fprintf(stderr, "%s", t->log.recent); |
| #endif |
| failed = 1; |
| } |
| |
| if(failed) { |
| FILE *fp = fopen(t->logfile, "wb"); |
| |
| if(fp && t->log.len == fwrite(t->log.buf, 1, t->log.len, fp)) |
| fprintf(stderr, "Transfer log written to %s\n", t->logfile); |
| #ifndef UNDER_CE |
| else { |
| fprintf(stderr, "Failed to write transfer log to %s: %s\n", |
| t->logfile, strerror(errno)); |
| } |
| #endif |
| |
| if(fp) |
| fclose(fp); |
| |
| /* Depending on how the transfer failed a body file may or may not have |
| been written, and you may or may not want it. */ |
| unlink(t->bodyfile); |
| |
| ++total_failed; |
| } |
| |
| mem_reset(&t->log); |
| |
| curl_easy_cleanup(t->curl); |
| |
| t->curl = NULL; |
| |
| printf("\n"); |
| } |
| |
| curl_global_cleanup(); |
| |
| return total_failed ? 1 : 0; |
| } |