Sun, 05 Jul 2020 11:55:54 +0200
merges feature/dav-edit
docs/html/edit.html | file | annotate | diff | comparison | revisions |
--- a/dav/main.c Sun Dec 15 18:43:01 2019 +0100 +++ b/dav/main.c Sun Jul 05 11:55:54 2020 +0200 @@ -35,6 +35,9 @@ #include <unistd.h> #include <time.h> #include <sys/types.h> +#ifndef _WIN32 +#include <sys/wait.h> +#endif #include <ucx/string.h> #include <ucx/utils.h> #include <dirent.h> @@ -158,6 +161,8 @@ } else if(!strcasecmp(cmd, "cat")) { ucx_map_cstr_put(args->options, "output", "-"); ret = cmd_get(args, FALSE); + } else if(!strcasecmp(cmd, "edit")) { + ret = cmd_edit(args); } else if(!strcasecmp(cmd, "put")) { ret = cmd_put(args, FALSE); } else if( @@ -247,6 +252,7 @@ "list [-altdepcR] [-u <date>] <url>", "get [-pcRK] [-o <file>] [-u <date>] [-V <version>] <url>", "put [-pcR] [-k <key>] [-L <lock>] <url> <file...>", + "edit [-pc] [-k <key>] [-V <version>] [-L <lock>] <url>", "mkdir [-pc] [-k <key>] [-L <lock>] <url> [file...]", "remove [-pc] [-L <lock>] <url> [file...]", "copy [-pcO] [-L <lock>] <url> <url>", @@ -1115,6 +1121,252 @@ return ret; } +static int file_seek(FILE *f, curl_off_t offset, int origin) { + int ret = fseek(f, offset, origin); + return ret == 0 ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK; +} + +static int check_encryption_key(CmdArgs *a, DavSession *sn) { + // override the session key if the -k option is specified + char *keyname = cmd_getoption(a, "key"); + if(keyname) { + DavKey *key = dav_context_get_key(ctx, keyname); + if(key) { + sn->key = key; + } else { + fprintf(stderr, "Key %s not found!\nAbort.\n", keyname); + return 1; + } + + /* + * If a key is explicitly specified, we can safely assume that the user + * wants to encrypt. For security reasons we report an error, if no + * encryption is enabled. + */ + if(!DAV_IS_ENCRYPTED(sn)) { + fprintf(stderr, "A key has been explicitly specified, but no " + "encryption is requested.\n" + "You have the following options:\n" + " - pass '-c' as command line argument to request encryption\n" + " - activate encryption in the config.xml\n" + " - don't use '-k <key>' " + "(warning: encryption will NOT happen)\n"); + return 1; + } + } + + // if encryption is requested, but we still don't know the key, report error + if(DAV_IS_ENCRYPTED(sn) && !(sn->key)) { + fprintf(stderr, "Encryption has been requested, " + "but no default key is configured.\n" + "You may specify a custom key with the '-k' option.\n"); + return 1; + } + + return 0; +} + +int cmd_edit(CmdArgs *a) { +#ifdef _WIN32 + fprintf(stderr, "This feature is not supported on your platform.\n"); + return -1; +#else + if(a->argc != 1) { + fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few":"many"); + fprintf(stderr, "Usage: dav %s\n", find_usage_str("edit")); + 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); + + if(check_encryption_key(a, sn)) { + return -1; + } + + char *version = cmd_getoption(a, "version"); + DavResource *res; + + res = dav_resource_new(sn, path); + int fresh_resource = !dav_exists(res); + if(fresh_resource) { + // create the resource to check if the resource can be created + // we don't want the user to invest time in editing and fail afterwards + if(dav_create(res)) { + fprintf(stderr, "Resource does not exist and cannot be created.\n"); + return -1; + } + } else { + if(!res) { + print_resource_error(sn, path); + return -1; + } + if(res->iscollection) { + fprintf(stderr, "Resource %s is a collection " + "and cannot be opened in an editor.\n", res->path); + return -1; + } + + if(version) { + DavResource *vres = find_version(res, version); + if(!vres) { + fprintf(stderr, "Cannot find version '%s' for resource.\n", version); + return -1; + } + dav_resource_free_all(res); + res = vres; + } + } + + // get temp dir + char* envtmp = getenv("TMPDIR"); + char* outfile; + if(envtmp) { + size_t len = strlen(envtmp); + outfile = malloc(len+24); + memcpy(outfile, envtmp, len+1); + if(outfile[len-1] != '/') { + outfile[len] = '/'; + outfile[len+1] = 0; + } + } else { + outfile = malloc(24); + strncpy(outfile, "/tmp/", 24); + } + + // check temp dir and fall back to $HOME + if(access(outfile, W_OK)) { + char* home = getenv("HOME"); + if(home) { + size_t len = strlen(home); + outfile = malloc(len+24); + memcpy(outfile, home, len+1); + if(outfile[len-1] != '/') { + outfile[len] = '/'; + outfile[len+1] = 0; + } + } else { + // fallback did not work, report last error from access() + perror("Cannot write to temporary location"); + free(outfile); + return -1; + } + } + + // create temp file and open it + outfile = strncat(outfile, ".dav-edit-XXXXXX", 23); + int tmp_fd = mkstemp(outfile); + if(tmp_fd < 0) { + perror("Cannot open temporary file"); + return -1; + } + + // get resource + if(!fresh_resource) { + FILE* tmp_stream = sys_fopen(outfile, "wb"); + if(!tmp_stream) { + perror("Cannot open temporary file"); + free(outfile); + close(tmp_fd); + return -1; + } + if(dav_get_content(res, tmp_stream, (dav_write_func)fwrite)) { + print_resource_error(sn, path); + free(outfile); + close(tmp_fd); + sys_unlink(outfile); + return -1; + } + fclose(tmp_stream); + } + + // remember time s.t. we can later check if the file has changed + SYS_STAT tmp_stat; + if(sys_stat(outfile, &tmp_stat)) { + perror("Cannot stat temporary file"); + free(outfile); + close(tmp_fd); + sys_unlink(outfile); + return -1; + } + time_t dl_mtime = tmp_stat.st_mtime; + + // open in editor + char* default_editor = "vi"; + char* editor = getenv("EDITOR"); + if(!editor) editor = default_editor; + char* viargs[3] = {editor, outfile, NULL}; + + int ret = 0; + pid_t pid = fork(); + if(pid < 0) { + perror("Cannot create process for editor"); + ret = -1; + } else if(pid == 0) { + if(execvp(viargs[0], viargs)) { + perror("Opening the editor failed"); + return -1; + } + } else { + int status = -1; + ret = waitpid(pid, &status, 0); + if(ret < 0) { + perror("Error waiting for editor"); + } else if(WEXITSTATUS(status)) { + fprintf(stderr, + "Editor closed abnormally - file will not be uploaded.\n"); + ret = -1; + } else { + // check if the file has changed + if (sys_stat(outfile, &tmp_stat)) { + // very rare case when someone concurrently changes permissions + perror("Cannot stat temporary file"); + ret = -1; + } else if (dl_mtime < tmp_stat.st_mtime) { + // upload changed file + FILE* tmp_stream = sys_fopen(outfile, "rb"); + if(!tmp_stream) { + perror("Cannot open temporary file"); + ret = -1; + } else { + dav_set_content(res, tmp_stream, + (dav_read_func)fread, + (dav_seek_func)file_seek); + dav_set_content_length(res, tmp_stat.st_size); + ret = dav_store(res); + fclose(tmp_stream); + if(ret) { + print_resource_error(sn, path); + } + } + } else { + printf("No changes by user - file will not be uploaded.\n"); + ret = 0; + } + } + } + + close(tmp_fd); + if(ret) { + // if someone went wrong, we are most likely unable to unlink anyway + fprintf(stderr, "File location: %s\n", outfile); + } else { + sys_unlink(outfile); + } + free(outfile); + free(path); + + return ret; +#endif +} + int get_resource(Repository *repo, GetResource *getres, CmdArgs *a, void *unused) { DavResource *res = getres->res; char *out = getres->path; @@ -1199,46 +1451,6 @@ return tar_end_file(tar); } -static int check_encryption_key(CmdArgs *a, DavSession *sn) { - // override the session key if the -k option is specified - char *keyname = cmd_getoption(a, "key"); - if(keyname) { - DavKey *key = dav_context_get_key(ctx, keyname); - if(key) { - sn->key = key; - } else { - fprintf(stderr, "Key %s not found!\nAbort.\n", keyname); - return 1; - } - - /* - * If a key is explicitly specified, we can safely assume that the user - * wants to encrypt. For security reasons we report an error, if no - * encryption is enabled. - */ - if(!DAV_IS_ENCRYPTED(sn)) { - fprintf(stderr, "A key has been explicitly specified, but no " - "encryption is requested.\n" - "You have the following options:\n" - " - pass '-c' as command line argument to request encryption\n" - " - activate encryption in the config.xml\n" - " - don't use '-k <key>' " - "(warning: encryption will NOT happen)\n"); - return 1; - } - } - - // if encryption is requested, but we still don't know the key, report error - if(DAV_IS_ENCRYPTED(sn) && !(sn->key)) { - fprintf(stderr, "Encryption has been requested, " - "but no default key is configured.\n" - "You may specify a custom key with the '-k' option.\n"); - return 1; - } - - return 0; -} - int cmd_put(CmdArgs *a, DavBool import) { if(a->argc < 2) { fprintf(stderr, "Too few arguments\n"); @@ -1276,7 +1488,7 @@ if(check_encryption_key(a, sn)) { // TODO: free return -1; - } + } DavBool printfile = FALSE; DavBool ignoredirerr = FALSE; @@ -1503,11 +1715,6 @@ return ret; } -static int file_seek(FILE *f, curl_off_t offset, int origin) { - int ret = fseek(f, offset, origin); - return ret == 0 ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK; -} - int put_file( Repository *repo, CmdArgs *a,
--- a/dav/main.h Sun Dec 15 18:43:01 2019 +0100 +++ b/dav/main.h Sun Jul 05 11:55:54 2020 +0200 @@ -96,6 +96,8 @@ FILE *in, off_t len); +int cmd_edit(CmdArgs *args); + int cmd_remove(CmdArgs *args); int cmd_mkdir(CmdArgs *args);
--- a/dav/optparser.c Sun Dec 15 18:43:01 2019 +0100 +++ b/dav/optparser.c Sun Jul 05 11:55:54 2020 +0200 @@ -53,16 +53,27 @@ char *option = NULL; char optchar = 0; + int optterminated = 0; for(int i=0;i<argc;i++) { char *arg = argv[i]; size_t len = strlen(arg); - if(len > 1 && arg[0] == '-') { + if(len == 2 && arg[0] == '-' && arg[1] == '-') { + optterminated = 1; + } else if(!optterminated && len > 1 && arg[0] == '-') { + // argument is in next arg but starts with a dash + // we assume this is not intended and consider this an error + if(option) { + fprintf(stderr, + "Missing argument for option -%c\n\n", optchar); + cmd_args_free(a); + return NULL; + } for(int c=1;c<len;c++) { - if (option) { - fprintf(stderr, - "Missing argument for option -%c\n\n", optchar); - cmd_args_free(a); - return NULL; + // argument is in the same arg + if(option) { + ucx_map_cstr_put(a->options, option, &arg[c]); + option = NULL; + break; } switch(arg[c]) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/html/edit.html Sun Jul 05 11:55:54 2020 +0200 @@ -0,0 +1,105 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta http-equiv="Content-Style-Type" content="text/css" /> + <meta name="generator" content="pandoc" /> + <title>dav edit</title> + <style type="text/css">code{white-space: pre;}</style> + <link rel="stylesheet" href="davdoc.css" type="text/css" /> +</head> +<body> +<div class="header"> + <a href="./index.html"><span>DavUtils documentation</span></a> +</div> +<div class="sidebar"> + <div class="nav"> + <h3>dav</h3> + <ul> + <li><a href="getting-started.html">Getting started</a></li> + <li><a href="commands.html">Commands</a></li> + <ul> + <li><a href="list.html">list</a></li> + <li><a href="get.html">get</a></li> + <li><a href="put.html">put</a></li> + <li><a href="edit.html">edit</a></li> + <li><a href="mkdir.html">mkdir</a></li> + <li><a href="remove.html">remove</a></li> + <li><a href="copy.html">copy</a></li> + <li><a href="move.html">move</a></li> + <li><a href="rename.html">rename</a></li> + <li><a href="export.html">export</a></li> + <li><a href="import.html">import</a></li> + <li><a href="get-property.html">get-property</a></li> + <li><a href="set-property.html">set-property</a></li> + <li><a href="remove-property.html">remove-property</a></li> + <li><a href="lock.html">lock</a></li> + <li><a href="unlock.html">unlock</a></li> + <li><a href="info.html">info</a></li> + <li><a href="date.html">date</a></li> + <li><a href="versioncontrol.html">versioncontrol</a></li> + <li><a href="list-versions.html">list-versions</a></li> + <li><a href="checkout.html">checkout</a></li> + <li><a href="checkin.html">checkin</a></li> + <li><a href="uncheckout.html">uncheckout</a></li> + <li><a href="add-repository.html">add-repository</a></li> + <li><a href="remove-repository.html">remove-repository</a></li> + <li><a href="list-repositories.html">list-repositories</a></li> + <li><a href="repository-url.html">repository-url</a></li> + <li><a href="add-user.html">add-user</a></li> + <li><a href="remove-user.html">remove-user</a></li> + <li><a href="edit-user.html">edit-user</a></li> + <li><a href="list-users.html">list-users</a></li> + <li><a href="check-config.html">check-config</a></li> + </ul> + <li><a href="configuration.html">Configuration</a></li> + <li><a href="encryption.html">Encryption</a></li> + </ul> + </div> + <div class="nav"> + <h3>dav-sync</h3> + <ul> + <li><a href="introduction.html">Introduction</a></li> + <li><a href="sync-commands.html">Commands</a></li> + <ul> + <li><a href="pull.html">pull</a></li> + <li><a href="push.html">push</a></li> + <li><a href="archive.html">archive</a></li> + <li><a href="restore.html">restore</a></li> + <li><a href="list-conflicts.html">list-conflicts</a></li> + <li><a href="resolve-conflicts.html">resolve-conflicts</a></li> + <li><a href="delete-conflicts.html">delete-conflicts</a></li> + <li><a href="trash-info.html">trash-info</a></li> + <li><a href="empty-trash.html">empty-trash</a></li> + <li><a href="list-versions.html">list-versions</a></li> + <li><a href="add-tag.html">add-tag</a></li> + <li><a href="remove-tag.html">remove-tag</a></li> + <li><a href="set-tags.html">set-tags</a></li> + <li><a href="list-tags.html">list-tags</a></li> + <li><a href="add-directory.html">add-directory</a></li> + <li><a href="list-directories.html">list-directories</a></li> + <li><a href="sync-check-config.html">check-config</a></li> + <li><a href="check-repositories.html">check-repositories</a></li> + </ul> + <li><a href="sync-configuration.html">Configuration</a></li> + </ul> + </div> +</div> + +<!-- begin content --> +<div class="content"> +<div id="header"> +<h1 class="title">dav edit</h1> +</div> +<p><strong><code>dav edit [-pc] [-k <key>] [-V <version>] <url></code></strong></p> +<p>Downloads a resources and opens an editor. If there is no resource at the specified location, it is attempted to create a fresh resource before opening the editor.</p> +<p>The default editor <code>vi</code> can be changed with the <code>EDITOR</code> environment variable.</p> +<p>This command transparently handles decryption and encryption. If you download and open a plain text file and request content encryption (either by config or by command line option) the file will be re-uploaded with content encryption. However, a present file will keep its (plain text) file name in any case.</p> +<p><strong><code>-p</code></strong> disable file name encryption and decryption</p> +<p><strong><code>-c</code></strong> enable file name and content encryption</p> +<p><strong><code>-k <key></code></strong> use the specified key for encryption. The key must be configured in the config.xml file</p> +<p><strong><code>-V <version></code></strong> downloads a specific version of the resource. Available versions can be listed with the <em>list-versions</em> command</p> +</div> +<!-- end content --> +</body> +</html>
--- a/docs/src/Makefile Sun Dec 15 18:43:01 2019 +0100 +++ b/docs/src/Makefile Sun Jul 05 11:55:54 2020 +0200 @@ -36,6 +36,7 @@ SRC += list.md SRC += get.md SRC += put.md +SRC += edit.md SRC += mkdir.md SRC += remove.md SRC += copy.md
--- a/docs/src/dav.1.md Sun Dec 15 18:43:01 2019 +0100 +++ b/docs/src/dav.1.md Sun Jul 05 11:55:54 2020 +0200 @@ -25,6 +25,12 @@ put [**-pcR**] [**-k** *key*] [**-L** *lock*] *url* *file* : Uploads a resource to *url*. +edit [**-pc**] [**-k** *key*] [**-V** *version*] [**-L** *lock*] *url* +: Downloads or creates a resource and opens the editor. + + The editor can be specified via the EDITOR environment variable. + Default editor is vi. + mkdir [**-pc**] [**-k** *key*] [**-L** *lock*] *url* : Creates a new collection at *url*.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/src/edit.md Sun Jul 05 11:55:54 2020 +0200 @@ -0,0 +1,31 @@ +--- +title: 'dav edit' +--- + +**`dav edit [-pc] [-k <key>] [-V <version>] [-L <lock>] <url>`** + +Downloads a resources and opens an editor. If there is no resource at the +specified location, it is attempted to create a fresh resource. +The resource is downloaded to a temporary file and re-uploaded only if you save +any changes to that file. + +The default editor `vi` can be changed with the `EDITOR` environment variable. + +This command transparently handles decryption and encryption. If you download +and open a plain text file and request content encryption (either by config or +by command line option) the file will be re-uploaded with content encryption. +However, a present file will keep its (plain text) file name in any case. + +**`-p`** disable file name encryption and decryption + +**`-c`** enable file name and content encryption + +**`-k <key>`** use the specified key for encryption. The key must be configured in + the config.xml file + +**`-V <version>`** downloads a specific version of the resource. Available versions can be listed with the *list-versions* command + +**`-L <lock>`** use a lock token. See [dav lock][1] + +[1]: ./lock.html +
--- a/docs/src/header.html Sun Dec 15 18:43:01 2019 +0100 +++ b/docs/src/header.html Sun Jul 05 11:55:54 2020 +0200 @@ -11,6 +11,7 @@ <li><a href="list.html">list</a></li> <li><a href="get.html">get</a></li> <li><a href="put.html">put</a></li> + <li><a href="edit.html">edit</a></li> <li><a href="mkdir.html">mkdir</a></li> <li><a href="remove.html">remove</a></li> <li><a href="copy.html">copy</a></li>
--- a/docs/src/index.html Sun Dec 15 18:43:01 2019 +0100 +++ b/docs/src/index.html Sun Jul 05 11:55:54 2020 +0200 @@ -22,6 +22,7 @@ <li><a href="list.html">list</a></li> <li><a href="get.html">get</a></li> <li><a href="put.html">put</a></li> + <li><a href="edit.html">edit</a></li> <li><a href="mkdir.html">mkdir</a></li> <li><a href="remove.html">remove</a></li> <li><a href="copy.html">copy</a></li>
--- a/libidav/resource.c Sun Dec 15 18:43:01 2019 +0100 +++ b/libidav/resource.c Sun Jul 05 11:55:54 2020 +0200 @@ -1267,21 +1267,11 @@ } int dav_exists(DavResource *res) { - // TODO: reimplement with PROPFIND - - DavSession *sn = res->session; - CURL *handle = sn->handle; - util_set_url(sn, dav_resource_get_href(res)); - - CURLcode ret = do_head_request(sn); - long status = 0; - curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); - if(ret == CURLE_OK && (status >= 200 && status < 300)) { + if(!dav_load_prop(res, NULL, 0)) { res->exists = 1; return 1; } else { - dav_session_set_error(sn, ret, status); - if(status == 404) { + if(res->session->error == DAV_NOT_FOUND) { res->exists = 0; } return 0;