merges feature/dav-edit

Sun, 05 Jul 2020 11:55:54 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 05 Jul 2020 11:55:54 +0200
changeset 722
d78619cc5a4d
parent 708
3fc7f813b53d (current diff)
parent 721
c401f4af44b1 (diff)
child 723
5ca174b3247a

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 &lt;key&gt;] [-V &lt;version&gt;] &lt;url&gt;</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 &lt;key&gt;</code></strong> use the specified key for encryption. The key must be configured in the config.xml file</p>
+<p><strong><code>-V &lt;version&gt;</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;

mercurial