# HG changeset patch # User Mike Becker # Date 1593942954 -7200 # Node ID d78619cc5a4d871d4f2cf07fc71a0259bc792b38 # Parent 3fc7f813b53d2811241a3e615449dd1414a658f9# Parent c401f4af44b1105287c70f9159927f8fedd2b776 merges feature/dav-edit diff -r 3fc7f813b53d -r d78619cc5a4d dav/main.c --- 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 #include #include +#ifndef _WIN32 +#include +#endif #include #include #include @@ -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 ] ", "get [-pcRK] [-o ] [-u ] [-V ] ", "put [-pcR] [-k ] [-L ] ", + "edit [-pc] [-k ] [-V ] [-L ] ", "mkdir [-pc] [-k ] [-L ] [file...]", "remove [-pc] [-L ] [file...]", "copy [-pcO] [-L ] ", @@ -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 ' " + "(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 ' " - "(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, diff -r 3fc7f813b53d -r d78619cc5a4d dav/main.h --- 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); diff -r 3fc7f813b53d -r d78619cc5a4d dav/optparser.c --- 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 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;coptions, option, &arg[c]); + option = NULL; + break; } switch(arg[c]) { diff -r 3fc7f813b53d -r d78619cc5a4d docs/html/edit.html --- /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 @@ + + + + + + + dav edit + + + + + + + + +
+ +

dav edit [-pc] [-k <key>] [-V <version>] <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 before opening the editor.

+

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

+
+ + + diff -r 3fc7f813b53d -r d78619cc5a4d docs/src/Makefile --- 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 diff -r 3fc7f813b53d -r d78619cc5a4d docs/src/dav.1.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*. diff -r 3fc7f813b53d -r d78619cc5a4d docs/src/edit.md --- /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 ] [-V ] [-L ] `** + +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 `** use the specified key for encryption. The key must be configured in + the config.xml file + +**`-V `** downloads a specific version of the resource. Available versions can be listed with the *list-versions* command + +**`-L `** use a lock token. See [dav lock][1] + +[1]: ./lock.html + diff -r 3fc7f813b53d -r d78619cc5a4d docs/src/header.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 @@
  • list
  • get
  • put
  • +
  • edit
  • mkdir
  • remove
  • copy
  • diff -r 3fc7f813b53d -r d78619cc5a4d docs/src/index.html --- 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 @@
  • list
  • get
  • put
  • +
  • edit
  • mkdir
  • remove
  • copy
  • diff -r 3fc7f813b53d -r d78619cc5a4d libidav/resource.c --- 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;