/********************************************************************* * Copyright 2018, UCAR/Unidata * See netcdf/COPYRIGHT file for copying and redistribution conditions. *********************************************************************/ #include "d4includes.h" #include "d4curlfunctions.h" static size_t WriteFileCallback(void*, size_t, size_t, void*); static size_t WriteMemoryCallback(void*, size_t, size_t, void*); static int curlerrtoncerr(CURLcode cstat); struct Fetchdata { FILE* stream; size_t size; }; long NCD4_fetchhttpcode(CURL* curl) { long httpcode = 200; CURLcode cstat = CURLE_OK; /* Extract the http code */ #ifdef HAVE_CURLINFO_RESPONSE_CODE cstat = curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE,&httpcode); #else cstat = curl_easy_getinfo(curl,CURLINFO_HTTP_CONNECTCODE,&httpcode); #endif if(cstat != CURLE_OK) { httpcode = 0; nclog(NCLOGERR, "curl error: %s", curl_easy_strerror(cstat)); } return httpcode; } int NCD4_fetchurl_file(CURL* curl, const char* url, FILE* stream, d4size_t* sizep, long* filetime) { int ret = NC_NOERR; CURLcode cstat = CURLE_OK; struct Fetchdata fetchdata; /* Set the URL */ cstat = curl_easy_setopt(curl, CURLOPT_URL, (void*)url); if (cstat != CURLE_OK) goto fail; /* send all data to this function */ cstat = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteFileCallback); if (cstat != CURLE_OK) goto fail; /* we pass our file to the callback function */ cstat = curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&fetchdata); if(cstat != CURLE_OK) goto fail; /* One last thing; always try to get the last modified time */ cstat = curl_easy_setopt(curl, CURLOPT_FILETIME, (long)1); if (cstat != CURLE_OK) goto fail; fetchdata.stream = stream; fetchdata.size = 0; cstat = curl_easy_perform(curl); if (cstat != CURLE_OK) {ret = NC_EDAPSVC; goto fail;} if (ret == NC_NOERR) { /* return the file size*/ #ifdef D4DEBUG nclog(NCLOGNOTE,"filesize: %lu bytes",fetchdata.size); #endif if (sizep != NULL) *sizep = fetchdata.size; /* Get the last modified time */ if(filetime != NULL) cstat = curl_easy_getinfo(curl,CURLINFO_FILETIME,filetime); if(cstat != CURLE_OK) {ret = NC_ECURL; goto fail;} } return THROW(ret); fail: if(cstat != CURLE_OK) { nclog(NCLOGERR, "curl error: %s", curl_easy_strerror(cstat)); ret = curlerrtoncerr(cstat); } return THROW(ret); } int NCD4_fetchurl(CURL* curl, const char* url, NCbytes* buf, long* filetime, int* httpcodep) { int ret = NC_NOERR; CURLcode cstat = CURLE_OK; size_t len; long httpcode = 0; /* send all data to this function */ cstat = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); if (cstat != CURLE_OK) goto done; /* we pass our file to the callback function */ cstat = curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)buf); if (cstat != CURLE_OK) goto done; /* One last thing; always try to get the last modified time */ cstat = curl_easy_setopt(curl, CURLOPT_FILETIME, (long)1); /* Set the URL */ cstat = curl_easy_setopt(curl, CURLOPT_URL, (void*)""); cstat = curl_easy_setopt(curl, CURLOPT_URL, (void*)url); if (cstat != CURLE_OK) goto done; cstat = curl_easy_perform(curl); if(cstat == CURLE_PARTIAL_FILE) { /* Log it but otherwise ignore */ nclog(NCLOGWARN, "curl error: %s; ignored", curl_easy_strerror(cstat)); cstat = CURLE_OK; } if(cstat != CURLE_OK) goto done; httpcode = NCD4_fetchhttpcode(curl); if(httpcodep) *httpcodep = httpcode; /* Get the last modified time */ if(filetime != NULL) cstat = curl_easy_getinfo(curl,CURLINFO_FILETIME,filetime); if(cstat != CURLE_OK) goto done; /* Null terminate the buffer*/ len = ncbyteslength(buf); ncbytesappend(buf, '\0'); ncbytessetlength(buf, len); /* don't count null in buffer size*/ #ifdef D4DEBUG nclog(NCLOGNOTE,"buffersize: %lu bytes",(d4size_t)ncbyteslength(buf)); #endif done: if(cstat != CURLE_OK) { nclog(NCLOGERR, "curl error: %s", curl_easy_strerror(cstat)); ret = curlerrtoncerr(cstat); } else switch (httpcode) { case 400: ret = NC_EDATADAP; break; case 401: ret = NC_EACCESS; break; case 403: ret = NC_EAUTH; break; case 404: ret = ENOENT; break; case 500: ret = NC_EDAPSVC; break; case 200: break; default: ret = NC_ECURL; break; } return THROW(ret); } static size_t WriteFileCallback(void* ptr, size_t size, size_t nmemb, void* data) { size_t realsize = size * nmemb; size_t count; struct Fetchdata* fetchdata; fetchdata = (struct Fetchdata*) data; if(realsize == 0) nclog(NCLOGWARN,"WriteFileCallback: zero sized chunk"); count = fwrite(ptr, size, nmemb, fetchdata->stream); if (count > 0) { fetchdata->size += (count * size); } else { nclog(NCLOGWARN,"WriteFileCallback: zero sized write"); } #ifdef PROGRESS nclog(NCLOGNOTE,"callback: %lu bytes",(d4size_t)realsize); #endif return count; } static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data) { size_t realsize = size * nmemb; NCbytes* buf = (NCbytes*) data; if(realsize == 0) nclog(NCLOGWARN,"WriteMemoryCallback: zero sized chunk"); /* Optimize for reading potentially large dods datasets */ if(!ncbytesavail(buf,realsize)) { /* double the size of the packet */ ncbytessetalloc(buf,2*ncbytesalloc(buf)); } ncbytesappendn(buf, ptr, realsize); #ifdef PROGRESS nclog(NCLOGNOTE,"callback: %lu bytes",(d4size_t)realsize); #endif return realsize; } int NCD4_curlopen(CURL** curlp) { int ret = NC_NOERR; CURLcode cstat = CURLE_OK; CURL* curl; /* initialize curl*/ curl = curl_easy_init(); if (curl == NULL) ret = NC_ECURL; else { cstat = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); if (cstat != CURLE_OK) ret = NC_ECURL; } if (curlp) *curlp = curl; if(cstat != CURLE_OK) { nclog(NCLOGERR, "curl error: %s", curl_easy_strerror(cstat)); ret = curlerrtoncerr(cstat); } return THROW(ret); } void NCD4_curlclose(CURL* curl) { if (curl != NULL) curl_easy_cleanup(curl); } int NCD4_fetchlastmodified(CURL* curl, char* url, long* filetime) { int ret = NC_NOERR; CURLcode cstat = CURLE_OK; /* Set the URL */ cstat = curl_easy_setopt(curl, CURLOPT_URL, (void*)url); if (cstat != CURLE_OK) goto fail; /* Ask for head */ cstat = curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30); /* 30sec timeout*/ cstat = curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 2); cstat = curl_easy_setopt(curl, CURLOPT_HEADER, 1); cstat = curl_easy_setopt(curl, CURLOPT_NOBODY, 1); cstat = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); cstat = curl_easy_setopt(curl, CURLOPT_FILETIME, (long)1); cstat = curl_easy_perform(curl); if(cstat != CURLE_OK) goto fail; if(filetime != NULL) cstat = curl_easy_getinfo(curl,CURLINFO_FILETIME,filetime); if(cstat != CURLE_OK) goto fail; return THROW(ret); fail: if(cstat != CURLE_OK) { nclog(NCLOGERR, "curl error: %s", curl_easy_strerror(cstat)); ret = curlerrtoncerr(cstat); } return THROW(ret); } int NCD4_ping(const char* url) { int ret = NC_NOERR; CURLcode cstat = CURLE_OK; CURL* curl = NULL; NCbytes* buf = NULL; /* Create a CURL instance */ ret = NCD4_curlopen(&curl); if(ret != NC_NOERR) return THROW(ret); /* Use redirects */ cstat = curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 10L); if (cstat != CURLE_OK) goto done; cstat = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); if (cstat != CURLE_OK) goto done; /* use a very short timeout: 10 seconds */ cstat = curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)5); if (cstat != CURLE_OK) goto done; /* fail on HTTP 400 code errors */ cstat = curl_easy_setopt(curl, CURLOPT_FAILONERROR, (long)1); if (cstat != CURLE_OK) goto done; /* Try to get the file */ buf = ncbytesnew(); ret = NCD4_fetchurl(curl,url,buf,NULL,NULL); if(ret == NC_NOERR) { /* Don't trust curl to return an error when request gets 404 */ long http_code = 0; cstat = curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE, &http_code); if (cstat != CURLE_OK) goto done; if(http_code >= 400) { cstat = CURLE_HTTP_RETURNED_ERROR; goto done; } } else goto done; done: ncbytesfree(buf); NCD4_curlclose(curl); if(cstat != CURLE_OK) { nclog(NCLOGERR, "curl error: %s", curl_easy_strerror(cstat)); ret = curlerrtoncerr(cstat); } return THROW(ret); } static int curlerrtoncerr(CURLcode cstat) { switch (cstat) { case CURLE_OK: return THROW(NC_NOERR); case CURLE_URL_MALFORMAT: return THROW(NC_EURL); case CURLE_COULDNT_RESOLVE_HOST: case CURLE_COULDNT_CONNECT: case CURLE_REMOTE_ACCESS_DENIED: case CURLE_TOO_MANY_REDIRECTS: return THROW(NC_EDAPSVC); case CURLE_OUT_OF_MEMORY: return THROW(NC_ENOMEM); /* Eventually would be nice to convert these */ case CURLE_SSL_CONNECT_ERROR: case CURLE_READ_ERROR: case CURLE_OPERATION_TIMEDOUT: case CURLE_SSL_CERTPROBLEM: case CURLE_FILESIZE_EXCEEDED: case CURLE_SSL_CACERT_BADFILE: default: break; } return THROW(NC_ECURL); }