Sat, 04 Apr 2015 20:37:03 +0200
tokenizer now correctly handles quoted tokens
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2015 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "utils.h" #include "methods.h" #include "davql.h" #include "crypto.h" #include "session.h" #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) /* ----------------------------- PROPFIND ----------------------------- */ CURLcode do_propfind_request( CURL *handle, UcxBuffer *request, UcxBuffer *response) { curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PROPFIND"); struct curl_slist *headers = NULL; headers = curl_slist_append(headers, "Content-Type: text/xml"); headers = curl_slist_append(headers, "Depth: 1"); curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); curl_easy_setopt(handle, CURLOPT_READFUNCTION, ucx_buffer_read); curl_easy_setopt(handle, CURLOPT_READDATA, request); curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write); curl_easy_setopt(handle, CURLOPT_WRITEDATA, response); ucx_buffer_seek(request, 0, SEEK_SET); CURLcode ret = curl_easy_perform(handle); curl_slist_free_all(headers); return ret; } UcxBuffer* create_allprop_propfind_request() { UcxBuffer *buf = ucx_buffer_new(NULL, 512, 0); sstr_t s; s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("<D:propfind xmlns:D=\"DAV:\">\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("<D:allprop/></D:propfind>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); return buf; } UcxBuffer* create_propfind_request(DavSession *sn, UcxList *properties) { UcxBuffer *buf = ucx_buffer_new(NULL, 512, 0); sstr_t s; int add_crypto_name = 1; int add_crypto_key = 1; char *crypto_ns = "idav"; UcxMap *namespaces = ucx_map_new(8); UCX_FOREACH(elm, properties) { DavProperty *p = elm->data; if(strcmp(p->ns->name, "DAV:")) { ucx_map_cstr_put(namespaces, p->ns->prefix, p->ns); } // if the properties list contains the idav properties crypto-name // and crypto-key, mark them as existent if(!strcmp(p->ns->name, DAV_NS)) { if(!strcmp(p->name, "crypto-name")) { add_crypto_name = 0; crypto_ns = p->ns->prefix; } else if(!strcmp(p->name, "crypto-key")) { add_crypto_key = 0; crypto_ns = p->ns->prefix; } } } DavNamespace idav_ns; if(add_crypto_name && add_crypto_key && DAV_CRYPTO(sn)) { idav_ns.prefix = "idav"; idav_ns.name = DAV_NS; ucx_map_cstr_put(namespaces, "idav", &idav_ns); } s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); // write root element and namespaces s = S("<D:propfind xmlns:D=\"DAV:\""); ucx_buffer_write(s.ptr, 1, s.length, buf); UcxMapIterator mapi = ucx_map_iterator(namespaces); UcxKey key; DavNamespace *ns; UCX_MAP_FOREACH(key, ns, mapi) { s = S(" xmlns:"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = sstr(ns->prefix); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("=\""); ucx_buffer_write(s.ptr, 1, s.length, buf); s = sstr(ns->name); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("\""); ucx_buffer_write(s.ptr, 1, s.length, buf); } s = S(">\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); // default properties s = S("<D:prop>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("<D:creationdate />\n<D:getlastmodified />\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("<D:getcontentlength />\n<D:getcontenttype />\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("<D:resourcetype />\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); // crypto properties if(DAV_CRYPTO(sn)) { if(add_crypto_name) { ucx_buffer_putc(buf, '<'); ucx_buffer_puts(buf, crypto_ns); s = S(":crypto-name />\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); } if(add_crypto_key) { ucx_buffer_putc(buf, '<'); ucx_buffer_puts(buf, crypto_ns); s = S(":crypto-key />\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); } } // extra properties UCX_FOREACH(elm, properties) { DavProperty *prop = elm->data; s = S("<"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = sstr(prop->ns->prefix); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S(":"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = sstr(prop->name); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S(" />\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); } // end s = S("</D:prop>\n</D:propfind>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); ucx_map_free(namespaces); return buf; } UcxBuffer* create_basic_propfind_request() { UcxBuffer *buf = ucx_buffer_new(NULL, 512, 0); sstr_t s; s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("<D:propfind xmlns:D=\"DAV:\" xmlns:i=\""); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S(DAV_NS); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("\" >\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); // properties s = S("<D:prop>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("<D:resourcetype />\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("<i:crypto-key />\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("<i:crypto-name />\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("</D:prop>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); // end s = S("</D:propfind>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); return buf; } DavResource* parse_propfind_response(DavSession *sn, DavResource *root, UcxBuffer *response, DavQOp *cond, size_t len) { char *url = NULL; curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &url); if(!root) { printf("method.c: TODO: remove\n"); root = dav_resource_new_href(sn, util_url_path(url)); // TODO: remove } //printf("%.*s\n\n", response->size, response->space); xmlDoc *doc = xmlReadMemory(response->space, response->size, url, NULL, 0); if(!doc) { // TODO: free stuff sn->error = DAV_ERROR; return NULL; } xmlNode *xml_root = xmlDocGetRootElement(doc); xmlNode *node = xml_root->children; while(node) { if(node->type == XML_ELEMENT_NODE) { if(xstreq(node->name, "response")) { parse_response_tag(root, node, cond, len); } } node = node->next; } xmlFreeDoc(doc); return root; } int parse_response_tag(DavResource *resource, xmlNode *node, DavQOp *cond, size_t clen) { DavSession *sn = resource->session; //DavResource *res = resource; DavResource *res = NULL; char *href = NULL; UcxList *properties = NULL; // xmlNode list char *crypto_name = NULL; // name set by crypto-name property char *crypto_key = NULL; int iscollection = 0; // TODO: remove node = node->children; while(node) { if(node->type == XML_ELEMENT_NODE) { if(xstreq(node->name, "href")) { xmlNode *href_node = node->children; if(href_node->type != XML_TEXT_NODE) { // error sn->error = DAV_ERROR; return 1; } //char *href = (char*)href_node->content; href = util_url_path((char*)href_node->content); sstr_t href_s = sstr(util_url_decode(resource->session, href)); sstr_t href_r = sstr(util_url_decode(resource->session, resource->href)); if(!sstrcmp(href_s, href_r)) { res = resource; } else if(href_s.length == href_r.length + 1) { if(href_s.ptr[href_s.length-1] == '/') { href_s.length--; if(!sstrcmp(href_s, href_r)) { res = resource; } } } free(href_s.ptr); free(href_r.ptr); } else if(xstreq(node->name, "propstat")) { xmlNode *n = node->children; xmlNode *prop_node = NULL; int ok = 0; // get the status code while(n) { if(n->type == XML_ELEMENT_NODE) { if(xstreq(n->name, "prop")) { prop_node = n; } else if(xstreq(n->name, "status")) { xmlNode *status_node = n->children; if(status_node->type != XML_TEXT_NODE) { sn->error = DAV_ERROR; return 1; } sstr_t status_str = sstr((char*)status_node->content); if(status_str.length < 13) { sn->error = DAV_ERROR; return 1; } status_str = sstrsubsl(status_str, 9, 3); if(!sstrcmp(status_str, S("200"))) { ok = 1; } } } n = n->next; } // if status is ok, get all properties if(ok) { n = prop_node->children; while(n) { if(n->type == XML_ELEMENT_NODE) { properties = ucx_list_append(properties, n); if(xstreq(n->name, "resourcetype")) { xmlNode *rsnode = n->children; if(rsnode && rsnode->type == XML_ELEMENT_NODE) { // TODO: this is a ugly lazy hack //resource_add_property(res, "DAV:", (char*)n->name, "collection"); iscollection = 1; } } else if(xstreq(n->ns->href, DAV_NS)) { if(xstreq(n->name, "crypto-name")) { crypto_name = util_xml_get_text(n); } else if(xstreq(n->name, "crypto-key")) { crypto_key = util_xml_get_text(n); } } } n = n->next; } } } } node = node->next; } if(!res) { // create new resource object char *name = NULL; if(DAV_DECRYPT_NAME(sn) && crypto_name) { if(!crypto_key) { // TODO: error fprintf(stderr, "encrypted resource without key\n"); } name = util_decrypt_str(sn, crypto_name, crypto_key); if(!name) { // TODO: error fprintf(stderr, "decrypted name is null\n"); } } else { sstr_t resname = sstr(util_resource_name(href)); int nlen = 0; char *uname = curl_easy_unescape( sn->handle, resname.ptr, resname.length, &nlen); name = dav_session_strdup(sn, uname); curl_free(uname); } href = dav_session_strdup(sn, href); res = dav_resource_new_full(sn, resource->path, name, href); dav_session_free(sn, name); } res->iscollection = iscollection; // add properties UCX_FOREACH(elm, properties) { xmlNode *prop = elm->data; // TODO: add xml data instead of a string char *text = util_xml_get_text(prop); if(text) { resource_add_property(res, (char*)prop->ns->href, (char*)prop->name, text); } } ucx_list_free(properties); set_davprops(res); if(res != resource) { if(clen > 0) { if(!condition_eval(res, cond, clen)) { // skip resource return 0; } } resource_add_child(resource, res); } return 0; } void set_davprops(DavResource *res) { char *cl = dav_get_property_ns(res, "DAV:", "getcontentlength"); char *ct = dav_get_property_ns(res, "DAV:", "getcontenttype"); char *cd = dav_get_property_ns(res, "DAV:", "creationdate"); char *lm = dav_get_property_ns(res, "DAV:", "getlastmodified"); res->contenttype = ct; if(cl) { char *end = NULL; res->contentlength = strtoull(cl, &end, 0); } res->creationdate = util_parse_creationdate(cd); res->lastmodified = util_parse_lastmodified(lm); } /* ----------------------------- PROPPATCH ----------------------------- */ CURLcode do_proppatch_request( CURL *handle, UcxBuffer *request, UcxBuffer *response) { curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PROPPATCH"); struct curl_slist *headers = NULL; headers = curl_slist_append(headers, "Content-Type: text/xml"); curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(handle, CURLOPT_UPLOAD, 1); curl_easy_setopt(handle, CURLOPT_READFUNCTION, ucx_buffer_read); curl_easy_setopt(handle, CURLOPT_READDATA, request); curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write); curl_easy_setopt(handle, CURLOPT_WRITEDATA, response); ucx_buffer_seek(request, 0, SEEK_SET); CURLcode ret = curl_easy_perform(handle); curl_slist_free_all(headers); return ret; } UcxBuffer* create_proppatch_request(DavResourceData *data) { UcxBuffer *buf = ucx_buffer_new(NULL, 512, 0); sstr_t s; UcxMap *namespaces = ucx_map_new(8); char prefix[8]; int pfxnum = 0; UCX_FOREACH(elm, data->set) { DavProperty *p = elm->data; if(strcmp(p->ns->name, "DAV:")) { snprintf(prefix, 8, "x%d\0", pfxnum++); ucx_map_cstr_put(namespaces, p->ns->name, prefix); } } UCX_FOREACH(elm, data->remove) { DavProperty *p = elm->data; if(strcmp(p->ns->name, "DAV:")) { snprintf(prefix, 8, "x%d\0", pfxnum++); ucx_map_cstr_put(namespaces, p->ns->name, prefix); } } s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); // write root element and namespaces s = S("<D:propertyupdate xmlns:D=\"DAV:\""); ucx_buffer_write(s.ptr, 1, s.length, buf); UcxMapIterator mapi = ucx_map_iterator(namespaces); UcxKey key; char *pfxval; UCX_MAP_FOREACH(key, pfxval, mapi) { s = S(" xmlns:"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = sstr(pfxval); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("=\""); ucx_buffer_write(s.ptr, 1, s.length, buf); s = sstrn(key.data, key.len); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("\""); ucx_buffer_write(s.ptr, 1, s.length, buf); } s = S(">\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); if(data->set) { s = S("<D:set>\n<D:prop>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); UCX_FOREACH(elm, data->set) { DavProperty *property = elm->data; char *prefix = ucx_map_cstr_get(namespaces, property->ns->name); s = S("<"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = sstr(prefix); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S(":"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = sstr(property->name); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S(">"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = sstr(property->value); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("</"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = sstr(prefix); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S(":"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = sstr(property->name); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S(">\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); } s = S("</D:prop>\n</D:set>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); } if(data->remove) { s = S("<D:remove>\n<D:prop>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); UCX_FOREACH(elm, data->remove) { DavProperty *property = elm->data; char *prefix = ucx_map_cstr_get(namespaces, property->ns->name); s = S("<"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = sstr(prefix); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S(":"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = sstr(property->name); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S(" />\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); } s = S("</D:prop>\n</D:remove>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); } s = S("</D:propertyupdate>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); return buf; } UcxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, char *name) { UcxBuffer *buf = ucx_buffer_new(NULL, 512, 0); sstr_t s; s = S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("<D:propertyupdate xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("<D:set>\n<D:prop>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); if(DAV_ENCRYPT_NAME(sn)) { s = S("<idav:crypto-name>"); ucx_buffer_write(s.ptr, 1, s.length, buf); char *crname = aes_encrypt(name, key); ucx_buffer_puts(buf, crname); free(crname); s = S("</idav:crypto-name>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); } s = S("<idav:crypto-key>"); ucx_buffer_write(s.ptr, 1, s.length, buf); ucx_buffer_puts(buf, key->name); s = S("</idav:crypto-key>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); s = S("</D:prop>\n</D:set>\n</D:propertyupdate>\n"); ucx_buffer_write(s.ptr, 1, s.length, buf); return buf; } /* ----------------------------- PUT ----------------------------- */ static size_t dummy_write(void *buf, size_t s, size_t n, void *data) { return s*n; } CURLcode do_put_request(CURL *handle, void *data, dav_read_func read_func, size_t length) { curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL); curl_easy_setopt(handle, CURLOPT_PUT, 1L); curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L); curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0); // clear headers struct curl_slist *headers = NULL; curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); UcxBuffer *buf = NULL; if(!read_func) { buf = ucx_buffer_new(data, length, 0); buf->size = length; data = buf; read_func = (dav_read_func)ucx_buffer_read; curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length); } else if(length == 0) { headers = curl_slist_append(headers, "Transfer-Encoding: chunked"); curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)1); curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); } else { curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length); } curl_easy_setopt(handle, CURLOPT_READFUNCTION, read_func); curl_easy_setopt(handle, CURLOPT_READDATA, data); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write); curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); CURLcode ret = curl_easy_perform(handle); curl_slist_free_all(headers); if(buf) { ucx_buffer_free(buf); } return ret; } CURLcode do_delete_request(CURL *handle, UcxBuffer *response) { struct curl_slist *headers = NULL; curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "DELETE"); curl_easy_setopt(handle, CURLOPT_PUT, 0L); curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L); curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, ucx_buffer_write); curl_easy_setopt(handle, CURLOPT_WRITEDATA, response); CURLcode ret = curl_easy_perform(handle); return ret; } CURLcode do_mkcol_request(CURL *handle) { struct curl_slist *headers = NULL; curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "MKCOL"); curl_easy_setopt(handle, CURLOPT_PUT, 0L); curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L); curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write); curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); CURLcode ret = curl_easy_perform(handle); return ret; } CURLcode do_head_request(CURL *handle) { curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "HEAD"); curl_easy_setopt(handle, CURLOPT_PUT, 0L); curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L); curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(handle, CURLOPT_NOBODY, 1L); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write); curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); CURLcode ret = curl_easy_perform(handle); curl_easy_setopt(handle, CURLOPT_NOBODY, 0L); return ret; }