Sun, 23 Sep 2018 12:51:41 +0200
adds minimal deltav implementation
dav/main.c | file | annotate | diff | comparison | revisions | |
dav/main.h | file | annotate | diff | comparison | revisions | |
libidav/Makefile | file | annotate | diff | comparison | revisions | |
libidav/davqlexec.c | file | annotate | diff | comparison | revisions | |
libidav/methods.c | file | annotate | diff | comparison | revisions | |
libidav/methods.h | file | annotate | diff | comparison | revisions | |
libidav/resource.c | file | annotate | diff | comparison | revisions | |
libidav/versioning.c | file | annotate | diff | comparison | revisions | |
libidav/versioning.h | file | annotate | diff | comparison | revisions | |
libidav/webdav.c | file | annotate | diff | comparison | revisions | |
libidav/webdav.h | file | annotate | diff | comparison | revisions |
--- a/dav/main.c Sun Sep 23 08:13:50 2018 +0200 +++ b/dav/main.c Sun Sep 23 12:51:41 2018 +0200 @@ -140,6 +140,16 @@ ret = cmd_unlock(args); } else if(!strcasecmp(cmd, "info")) { ret = cmd_info(args); + } else if(!strcasecmp(cmd, "checkout")) { + ret = cmd_checkout(args); + } else if(!strcasecmp(cmd, "checkin")) { + ret = cmd_checkin(args); + } else if(!strcasecmp(cmd, "uncheckout")) { + ret = cmd_uncheckout(args); + } else if(!strcasecmp(cmd, "versioncontrol")) { + ret = cmd_versioncontrol(args); + } else if(!strcasecmp(cmd, "list-versions") || !strcasecmp(cmd, "lsv")) { + ret = cmd_list_versions(args); } else if(!strcasecmp(cmd, "add-repository") || !strcasecmp(cmd, "add-repo")) { ret = cmd_add_repository(args); @@ -241,8 +251,7 @@ break; } fprintf(stderr, " %s\n", str); - } - + } fprintf(stderr, "\n"); fprintf(stderr, "Options:\n"); fprintf(stderr, @@ -272,6 +281,8 @@ fprintf(stderr, " -i disable cert verification (all commands)\n"); fprintf(stderr, " -v verbose output (all commands)\n"); fprintf(stderr, "\n"); + fprintf(stderr, "Advanced commands:\n"); + fprintf(stderr, " versioncontrol, list-versions, checkout, checkin, uncheckout\n\n"); fprintf(stderr, "Config commands:\n"); fprintf(stderr, " add-repository\n"); fprintf(stderr, " remove-repository\n"); @@ -1962,6 +1973,161 @@ return -1; } +int cmd_checkout(CmdArgs *a) { + if(a->argc < 1) { + fprintf(stderr, "Too few arguments\n"); + fprintf(stderr, "Usage: dav %s\n", "checkout [-pc] <url>"); + return -1; + } + + char *url = a->argv[0]; + char *path = NULL; + Repository *repo = url2repo(url, &path); + DavSession *sn = connect_to_repo(repo, path, a); + + if(set_session_config(sn, a)) { + return -1; + } + set_session_lock(sn, a); + + int ret = 0; + DavResource *res = dav_resource_new(sn, path); + if(dav_checkout(res)) { + print_resource_error(sn, res->path); + ret = 1; + } + + return ret; +} + +int cmd_checkin(CmdArgs *a) { + if(a->argc < 1) { + fprintf(stderr, "Too few arguments\n"); + fprintf(stderr, "Usage: dav %s\n", "checkin [-pc] <url>"); + return -1; + } + + char *url = a->argv[0]; + char *path = NULL; + Repository *repo = url2repo(url, &path); + DavSession *sn = connect_to_repo(repo, path, a); + + if(set_session_config(sn, a)) { + return -1; + } + set_session_lock(sn, a); + + int ret = 0; + DavResource *res = dav_resource_new(sn, path); + if(dav_checkin(res)) { + print_resource_error(sn, res->path); + ret = 1; + } + + return ret; +} + +int cmd_uncheckout(CmdArgs *a) { + if(a->argc < 1) { + fprintf(stderr, "Too few arguments\n"); + fprintf(stderr, "Usage: dav %s\n", "uncheckout [-pc] <url>"); + return -1; + } + + char *url = a->argv[0]; + char *path = NULL; + Repository *repo = url2repo(url, &path); + DavSession *sn = connect_to_repo(repo, path, a); + + if(set_session_config(sn, a)) { + return -1; + } + set_session_lock(sn, a); + + int ret = 0; + DavResource *res = dav_resource_new(sn, path); + if(dav_uncheckout(res)) { + print_resource_error(sn, res->path); + ret = 1; + } + + return ret; +} +int cmd_versioncontrol(CmdArgs *a) { + if(a->argc < 1) { + fprintf(stderr, "Too few arguments\n"); + fprintf(stderr, "Usage: dav %s\n", "versioncontrol [-pc] <url>"); + return -1; + } + + char *url = a->argv[0]; + char *path = NULL; + Repository *repo = url2repo(url, &path); + DavSession *sn = connect_to_repo(repo, path, a); + + if(set_session_config(sn, a)) { + return -1; + } + set_session_lock(sn, a); + + int ret = 0; + DavResource *res = dav_resource_new(sn, path); + if(dav_versioncontrol(res)) { + print_resource_error(sn, res->path); + ret = 1; + } + + return ret; +} + +int cmd_list_versions(CmdArgs *a) { + if(a->argc < 1) { + fprintf(stderr, "Too few arguments\n"); + fprintf(stderr, "Usage: dav %s\n", "list-versions [-pc] <url>"); + return -1; + } + + char *url = a->argv[0]; + char *path = NULL; + Repository *repo = url2repo(url, &path); + DavSession *sn = connect_to_repo(repo, path, a); + + if(set_session_config(sn, a)) { + return -1; + } + + DavResource *res = dav_resource_new(sn, path); + + int ret = 0; + DavResource *list = dav_versiontree(res, NULL); + if(list) { + char* longlist = cmd_getoption(a, "list"); + + DavResource *v = list; + int addnl = 0; + while(v) { + char *vname = dav_get_string_property(v, "D:version-name"); + + if(longlist) { + if(addnl) { + putchar('\n'); + } + printf("name: %s\n", vname); + printf("href: %s\n", v->href); + addnl = 1; + } else { + printf("%s\n", vname); + } + v = v->next; + } + } else if(sn->error != DAV_OK) { + print_resource_error(sn, path); + ret = 1; + } + + return ret; +} + char* stdin2str() { UcxBuffer *buf = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
--- a/dav/main.h Sun Sep 23 08:13:50 2018 +0200 +++ b/dav/main.h Sun Sep 23 12:51:41 2018 +0200 @@ -94,6 +94,12 @@ int cmd_info(CmdArgs *args); +int cmd_checkout(CmdArgs *args); +int cmd_checkin(CmdArgs *args); +int cmd_uncheckout(CmdArgs *args); +int cmd_versioncontrol(CmdArgs *args); +int cmd_list_versions(CmdArgs *args); + char* stdin2str(); char* xml2str(DavXmlNode *node); void printxmldoc(FILE *out, char *root, char *rootns, DavXmlNode *content);
--- a/libidav/Makefile Sun Sep 23 08:13:50 2018 +0200 +++ b/libidav/Makefile Sun Sep 23 12:51:41 2018 +0200 @@ -40,6 +40,7 @@ SRC += davqlexec.c SRC += crypto.c SRC += xml.c +SRC += versioning.c OBJ = $(SRC:%.c=../build/libidav/%$(OBJ_EXT))
--- a/libidav/davqlexec.c Sun Sep 23 08:13:50 2018 +0200 +++ b/libidav/davqlexec.c Sun Sep 23 12:51:41 2018 +0200 @@ -280,7 +280,7 @@ return create_allprop_propfind_request(); } else if(!sstrcmp(field->name, S("-"))) { ucx_map_free(properties); - return create_propfind_request(sn, NULL); + return create_propfind_request(sn, NULL, "propfind", 0); } else { if(fl_add_properties(sn, mp, properties, field->expr)) { // TODO: set error @@ -298,7 +298,7 @@ list = ucx_list_append(list, value); } - UcxBuffer *reqbuf = create_propfind_request(sn, list); + UcxBuffer *reqbuf = create_propfind_request(sn, list, "propfind", 0); ucx_list_free(list); ucx_map_free(properties); return reqbuf;
--- a/libidav/methods.c Sun Sep 23 08:13:50 2018 +0200 +++ b/libidav/methods.c Sun Sep 23 12:51:41 2018 +0200 @@ -129,7 +129,7 @@ return buf; } -UcxBuffer* create_propfind_request(DavSession *sn, UcxList *properties) { +UcxBuffer* create_propfind_request(DavSession *sn, UcxList *properties, char *rootelm, DavBool nocrypt) { UcxBuffer *buf = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND); sstr_t s; @@ -161,7 +161,7 @@ } DavNamespace idav_ns; - if(add_crypto_name && add_crypto_key && DAV_CRYPTO(sn)) { + if(add_crypto_name && add_crypto_key && DAV_CRYPTO(sn) && !nocrypt) { idav_ns.prefix = "idav"; idav_ns.name = DAV_NS; ucx_map_cstr_put(namespaces, "idav", &idav_ns); @@ -171,8 +171,8 @@ 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); + ucx_bprintf(buf, "<D:%s xmlns:D=\"DAV:\"", rootelm); + UcxMapIterator mapi = ucx_map_iterator(namespaces); UcxKey key; DavNamespace *ns; @@ -205,7 +205,7 @@ ucx_buffer_write(s.ptr, 1, s.length, buf); // crypto properties - if(DAV_CRYPTO(sn)) { + if(DAV_CRYPTO(sn) && !nocrypt) { if(add_crypto_name) { ucx_buffer_putc(buf, '<'); ucx_buffer_puts(buf, crypto_ns); @@ -242,8 +242,7 @@ } // end - s = S("</D:prop>\n</D:propfind>\n"); - ucx_buffer_write(s.ptr, 1, s.length, buf); + ucx_bprintf(buf, "</D:prop>\n</D:%s>\n", rootelm); ucx_map_free(namespaces); return buf; @@ -1223,3 +1222,55 @@ return ret; } + +CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken) { + CURL *handle = sn->handle; + curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, method); + curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L); + + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); + + // set lock-token header + sstr_t ltheader; + struct curl_slist *headers = NULL; + if(locktoken) { + ltheader = ucx_sprintf("Lock-Token: <%s>", locktoken); + headers = curl_slist_append(NULL, ltheader.ptr); + } + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); + + CURLcode ret = dav_session_curl_perform(sn, NULL); + if(locktoken) { + curl_slist_free_all(headers); + free(ltheader.ptr); + } + + return ret; +} + + +CURLcode do_report_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response) { + CURL *handle = sn->handle; + curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "REPORT"); + + 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); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: text/xml"); + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); + + request->pos = 0; + response->size = response->pos = 0; + CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL); + + curl_slist_free_all(headers); + + return ret; +}
--- a/libidav/methods.h Sun Sep 23 08:13:50 2018 +0200 +++ b/libidav/methods.h Sun Sep 23 12:51:41 2018 +0200 @@ -80,7 +80,7 @@ size_t length); UcxBuffer* create_allprop_propfind_request(void); -UcxBuffer* create_propfind_request(DavSession *sn, UcxList *properties); +UcxBuffer* create_propfind_request(DavSession *sn, UcxList *properties, char *rootelm, DavBool nocrypt); UcxBuffer* create_basic_propfind_request(void); PropfindParser* create_propfind_parser(UcxBuffer *response, char *url); @@ -118,6 +118,10 @@ CURLcode do_lock_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response, time_t timeout); CURLcode do_unlock_request(DavSession *sn, char *locktoken); +CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken); + +CURLcode do_report_request(DavSession *sn, UcxBuffer *request, UcxBuffer *response); + #ifdef __cplusplus } #endif
--- a/libidav/resource.c Sun Sep 23 08:13:50 2018 +0200 +++ b/libidav/resource.c Sun Sep 23 12:51:41 2018 +0200 @@ -647,7 +647,7 @@ proplist = ucx_list_append_a(mp->allocator, proplist, p); } - UcxBuffer *rqbuf = create_propfind_request(res->session, proplist); + UcxBuffer *rqbuf = create_propfind_request(res->session, proplist, "propfind", 0); int ret = dav_propfind(res->session, res, rqbuf); ucx_buffer_free(rqbuf); ucx_mempool_destroy(mp); @@ -968,7 +968,7 @@ // if the session has encrypted file names, add crypto infos if(!resource_add_crypto_info(sn, res->href, res->name, NULL)) { // do a minimal propfind request - UcxBuffer *rqbuf = create_propfind_request(sn, NULL); + UcxBuffer *rqbuf = create_propfind_request(sn, NULL, "propfind", 0); int ret = dav_propfind(sn, res, rqbuf); ucx_buffer_free(rqbuf); return ret;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libidav/versioning.c Sun Sep 23 12:51:41 2018 +0200 @@ -0,0 +1,173 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 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 "versioning.h" + +#include "methods.h" +#include "utils.h" +#include "session.h" + +static int basic_deltav_op(DavResource *res, char *method) { + DavSession *sn = res->session; + CURL *handle = sn->handle; + util_set_url(sn, dav_resource_get_href(res)); + + DavLock *lock = dav_get_lock(res->session, res->path); + char *locktoken = lock ? lock->token : NULL; + + CURLcode ret = do_simple_request(sn, method, locktoken); + long status = 0; + curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); + if(!(ret == CURLE_OK && (status >= 200 && status < 300))) { + dav_session_set_error(sn, ret, status); + return 1; + } + return 0; +} + +int dav_versioncontrol(DavResource *res) { + return basic_deltav_op(res, "VERSION-CONTROL"); +} + +int dav_checkout(DavResource *res) { + return basic_deltav_op(res, "CHECKOUT"); +} + +int dav_checkin(DavResource *res) { + return basic_deltav_op(res, "CHECKIN"); +} + +int dav_uncheckout(DavResource *res) { + return basic_deltav_op(res, "UNCHECKOUT"); +} + +DavResource* dav_versiontree(DavResource *res, char *properties) { + DavSession *sn = res->session; + util_set_url(sn, dav_resource_get_href(res)); + + UcxList *proplist = NULL; + if(properties) { + proplist = parse_properties_string(sn->context, sstr(properties)); + } + + // check if the list already contains a D:version-name property + int add_vname = 1; + UCX_FOREACH(elm, proplist) { + DavProperty *p = elm->data; + if(!strcmp(p->ns->name, "DAV:") && !strcmp(p->name, "version-name")) { + add_vname = 0; + break; + } + } + if(add_vname) { + // we need at least the D:version-name prop + DavProperty *p = malloc(sizeof(DavProperty)); + p->ns = dav_get_namespace(sn->context, "D"); + p->name = strdup("version-name"); + p->value = NULL; + proplist = ucx_list_prepend(proplist, p); + } + + // create a version-tree request, which is almost the same as propfind + UcxBuffer *rqbuf = create_propfind_request(sn, proplist, "version-tree", 1); + UcxBuffer *rpbuf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND); + + // do the request + CURLcode ret = do_report_request(sn, rqbuf, rpbuf); + long status = 0; + curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status); + int error = 0; + DavResource *versions = NULL; + if(ret == CURLE_OK && status == 207) { + sn->error = DAV_OK; + + // parse multistatus response + PropfindParser *parser = create_propfind_parser(rpbuf, NULL); + if(parser) { + DavResource *list_end = NULL; + + ResponseTag response; + int r; + + // we don't want name decryption for version resources + int snflags = sn->flags; + sn->flags = 0; + while((r = get_propfind_response(parser, &response)) != 0) { + if(r == -1) { + res->session->error = DAV_ERROR; + error = 1; + break; + } + DavResource *v = response2resource(sn, &response, NULL); + // add version to list + if(!versions) { + versions = v; + } else { + list_end->next = v; + } + list_end = v; + + cleanup_response(&response); + } + sn->flags = snflags; + + destroy_propfind_parser(parser); + } else { + sn->error = DAV_ERROR; + error = 1; + } + } else { + dav_session_set_error(sn, ret, status); + error = 1; + } + + // cleanup + while(proplist) { + DavProperty *p = proplist->data; + free(p->name); + free(p); + UcxList *next = proplist->next; + free(proplist); + proplist = next; + } + if(error && versions) { + DavResource *cur = versions; + while(cur) { + DavResource *next = cur->next; + dav_resource_free(cur); + cur = next; + } + versions = NULL; + } + + return versions; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libidav/versioning.h Sun Sep 23 12:51:41 2018 +0200 @@ -0,0 +1,45 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 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. + */ + +#ifndef VERSIONING_H +#define VERSIONING_H + +#include "webdav.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + +#ifdef __cplusplus +} +#endif + +#endif /* VERSIONING_H */ +
--- a/libidav/webdav.c Sun Sep 23 08:13:50 2018 +0200 +++ b/libidav/webdav.c Sun Sep 23 12:51:41 2018 +0200 @@ -271,7 +271,7 @@ if(properties) { proplist = parse_properties_string(sn->context, sstr(properties)); } - UcxBuffer *rqbuf = create_propfind_request(sn, proplist); + UcxBuffer *rqbuf = create_propfind_request(sn, proplist, "propfind", 0); UcxBuffer *rpbuf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND); //fwrite(rqbuf->space, 1, rqbuf->size, stdout);
--- a/libidav/webdav.h Sun Sep 23 08:13:50 2018 +0200 +++ b/libidav/webdav.h Sun Sep 23 12:51:41 2018 +0200 @@ -309,6 +309,15 @@ // private int dav_propfind(DavSession *sn, DavResource *root, UcxBuffer *rqbuf); + +/* --------------------------- DeltaV ---------------------------- */ + +int dav_versioncontrol(DavResource *res); +int dav_checkout(DavResource *res); +int dav_checkin(DavResource *res); +int dav_uncheckout(DavResource *res); +DavResource* dav_versiontree(DavResource *res, char *properties); + /* ------------------------ xml functions ------------------------ */ char* dav_xml_getstring(DavXmlNode *node); DavBool dav_xml_isstring(DavXmlNode *node);