implement links on Windows (shelllink)

Fri, 02 Aug 2019 22:04:00 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 02 Aug 2019 22:04:00 +0200
changeset 611
a7c48e0dca88
parent 610
aa3baf1dd81b
child 612
66dc8b992d8d

implement links on Windows (shelllink)

dav/db.c file | annotate | diff | comparison | revisions
dav/db.h file | annotate | diff | comparison | revisions
dav/main.c file | annotate | diff | comparison | revisions
dav/main.h file | annotate | diff | comparison | revisions
dav/sync.c file | annotate | diff | comparison | revisions
dav/sync.h file | annotate | diff | comparison | revisions
dav/system.c file | annotate | diff | comparison | revisions
dav/system.h file | annotate | diff | comparison | revisions
libidav/utils.c file | annotate | diff | comparison | revisions
libidav/utils.h file | annotate | diff | comparison | revisions
mingw.mk file | annotate | diff | comparison | revisions
--- a/dav/db.c	Fri Aug 02 21:40:05 2019 +0200
+++ b/dav/db.c	Fri Aug 02 22:04:00 2019 +0200
@@ -215,6 +215,8 @@
                 field = 11;
             } else if(xstreq(name, "link")) {
                 field = 12;
+            } else if(xstreq(name, "localpath")) {
+                field = 13;
             } else if(xstreq(name, "skipped")) {
                 res->skipped = TRUE;
             } else if(xstreq(name, "tags-updated")) {
@@ -314,6 +316,9 @@
                     res->link_target = strdup((char*)value);
                     break;
                 }
+                case 13: {
+                    res->local_path = strdup((char*)value);
+                }
             }
         } else if(XML_READER_TYPE_END_ELEMENT) {
             if(xstreq(name, "resource")) {
@@ -533,6 +538,19 @@
             }
         }
         
+        if(res->local_path) {
+            r = xmlTextWriterWriteElement(
+                    writer,
+                    BAD_CAST "localpath",
+                    BAD_CAST res->local_path);
+            if(r < 0) {
+                fprintf(stderr, "Cannot write localpath: %s\n", res->local_path);
+                xmlFreeTextWriter(writer);
+                return -1;
+            }
+        }
+        
+        
         if(res->xattr_hash) {
             r = xmlTextWriterWriteElement(
                     writer,
--- a/dav/db.h	Fri Aug 02 21:40:05 2019 +0200
+++ b/dav/db.h	Fri Aug 02 22:04:00 2019 +0200
@@ -68,6 +68,8 @@
     char *xattr_hash;
     char *remote_tags_hash;
     
+    char *local_path;
+    
     char *link_target;
     
     FilePart *parts;
--- a/dav/main.c	Fri Aug 02 21:40:05 2019 +0200
+++ b/dav/main.c	Fri Aug 02 22:04:00 2019 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2018 Olaf Wintermann. All rights reserved.
+ * Copyright 2019 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:
@@ -89,6 +89,7 @@
         return -1;
     }
     
+    sys_init();
     xmlGenericErrorFunc fnc = xmlerrorfnc;
     initGenericErrorDefaultFunc(&fnc);
     ctx = dav_context_new();
@@ -211,6 +212,7 @@
     free_config();
     xmlCleanupParser();
     curl_global_cleanup();
+    sys_uninit();
     
     return ret;
 }
--- a/dav/main.h	Fri Aug 02 21:40:05 2019 +0200
+++ b/dav/main.h	Fri Aug 02 22:04:00 2019 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 2018 Olaf Wintermann. All rights reserved.
+ * Copyright 2019 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:
--- a/dav/sync.c	Fri Aug 02 21:40:05 2019 +0200
+++ b/dav/sync.c	Fri Aug 02 22:04:00 2019 +0200
@@ -136,6 +136,7 @@
     
     xmlGenericErrorFunc fnc = xmlerrorfnc;
     initGenericErrorDefaultFunc(&fnc);
+    sys_init();
     ctx = dav_context_new();
     int cfgret = load_config(ctx) || load_sync_config();
     
@@ -212,6 +213,8 @@
     curl_global_cleanup();
     xmlCleanupParser();
     
+    sys_uninit();
+    
     return ret;
 }
 
@@ -716,13 +719,16 @@
     // the first thing we need are all directories to put the files in
     UCX_FOREACH(elm, res_mkdir) {
         DavResource *res = elm->data;
+        char *res_path = resource_local_path(res);
         char *local_path = create_local_path(dir, res->path);
+        free(res_path);
         if(sys_mkdir(local_path) && errno != EEXIST) {
             fprintf(stderr,
                     "Cannot create directory %s: %s",
                     local_path, strerror(errno));
         }
         free(local_path);
+        // TODO: create localres for dir
     }
     
     // we need a map for all conflicts for fast lookups
@@ -757,7 +763,7 @@
                 continue;
             }
             
-            char *local_path = util_concat_path(dir->path, local->path);
+            char *local_path = util_concat_path(dir->path, local_resource_path(local));
             int staterr = sys_stat(local_path, &s);
             free(local_path);
             if(staterr) {
@@ -840,7 +846,9 @@
         LocalResource *local = ucx_map_cstr_get(db->resources, res->path);
         if(local) {
             printf("update: %s\n", res->path);
+            char *res_path = resource_local_path(res);
             char *local_path = create_local_path(dir, res->path);
+            free(res_path);
             if(sync_store_metadata(dir, local_path, local, res)) {
                 fprintf(stderr, "Metadata update failed: %s\n", res->path);
                 sync_error++;
@@ -1223,7 +1231,7 @@
     renamefunc fn = copy ? copy_file : sys_rename;
     
     char *new_path = util_concat_path(dir->path, res->path);
-    char *old_path = util_concat_path(dir->path, content->path);
+    char *old_path = util_concat_path(dir->path, local_resource_path(content));
     
     printf("%s: %s -> %s\n", copy?"copy":"move", content->path, res->path);
     if(fn(old_path, new_path)) {
@@ -1287,12 +1295,20 @@
         SyncDatabase *db,
         int *counter)
 { 
-    LocalResource *local = ucx_map_cstr_get(db->resources, path);
-    char *local_path = create_local_path(dir, path);
-    
     char *link = SYNC_SYMLINK(dir) ?
             dav_get_string_property_ns(res, DAV_PROPS_NS, "link") : NULL;
     
+    LocalResource *local = ucx_map_cstr_get(db->resources, path);
+    
+    char *local_path;
+    if(link) {
+        char *res_path = resource_local_path(res);
+        local_path = create_local_path(dir, res_path);
+        free(res_path);
+    } else {
+        local_path = create_local_path(dir, path);
+    }
+    
     char *etag = dav_get_string_property(res, "D:getetag");
     SYS_STAT s;
     memset(&s, 0, sizeof(SYS_STAT));
@@ -1821,7 +1837,7 @@
                 continue;
             }
             
-            char *local_path = util_concat_path(dir->path, local->path);
+            char *local_path = util_concat_path(dir->path, local_resource_path(local));
             char *hash = util_file_hash(local_path);
             local->hash = hash;
             // check if a file with this hash already exists
@@ -2504,29 +2520,38 @@
         res->isdirectory = 1;
     }
     
-    if(S_ISLNK(s.st_mode)) {
-        off_t l_sz = s.st_size + 16;
-        size_t lnksize = l_sz > 256 ? l_sz : 256;
-        char *lnkbuf = malloc(lnksize);
+    if(SYS_ISLINK(file_path, s)) {
+        char *lnkbuf = sys_readlink(file_path, &s);
+#ifdef SYS_LINK_EXT
+        // on Windows, we interpret .lnk files as links
+        // we dont want resource names with this extension
+        // and possibly sync this to other operating systems
+        // therefore we remove the .lnk extension from the file name
+        // change res->path
+        // we only do this, if there isn't any other file with this name
+        sstr_t fpath = sstr(file_path);
+        sstr_t rpath = sstr(path);
+        // remove last 4 chars (.lnk)
+        sstr_t new_file_path = sstrdup(sstrsubsl(fpath, 0 , fpath.length-4));
+        // check if a file with this name exists
+        SYS_STAT nfp_s;
+        if(!sys_stat(new_file_path.ptr, &nfp_s)) {
+            // we can't upload the link without the file extension, because
+            // there is another file with this name
+            free(lnkbuf);
+            lnkbuf = NULL;
+        } else {
+            sstr_t new_path = sstrdup(sstrsubsl(rpath, 0, rpath.length-4));
+            res->local_path = res->path;
+            res->path = new_path.ptr; // remove .lnk  ext from resource path
+        }
+        free(new_file_path.ptr);
+#endif
         
-        ssize_t len = 0;
-        for(int i=0;i<4;i++) {
-            // we try to read the link at most 4 times
-            // only repeat if the buffer is too small
-            len = sys_readlink(file_path, lnkbuf, lnksize);
-            if(len != lnksize) {
-                break;
-            }
-            lnksize *= 2;
-            lnkbuf = realloc(lnkbuf, lnksize);
-        }
-        
-        if(len > 0) {
-            // readlink successful
-            lnkbuf[len] = 0;
-            
+        if(lnkbuf) {
+            // readlink successful           
             char *normalized = NULL;
-            if(lnkbuf[0] != '/') {
+            if(!util_path_isabsolut(lnkbuf)) {
                 char *link_parent = util_parent_path(res->path);
                 char *abs_link_parent = util_concat_path(dir->path, link_parent);
                 char *link = util_concat_path(abs_link_parent, lnkbuf);
@@ -2538,15 +2563,16 @@
                 normalized = util_path_normalize(lnkbuf);
             }
             
-            if(util_path_isrelated(dir->path, normalized)) {
+            char *dirpath = util_path_normalize(dir->path);
+            if(util_path_isrelated(dirpath, normalized)) {
                 // the link points to a file inside the syncdir
                 char *rel = util_create_relative_path(normalized, file_path);
                 res->link_target = rel;
             }
+            free(dirpath);
             free(normalized);
+            free(lnkbuf);
         }
-        
-        free(lnkbuf);
     }
     
     free(file_path);
@@ -2554,6 +2580,10 @@
     return res;
 }
 
+char* local_resource_path(LocalResource *res) {
+    return res->local_path ? res->local_path : res->path;
+}
+
 int local_resource_is_changed(
         SyncDirectory *dir,
         SyncDatabase *db,
@@ -2712,6 +2742,20 @@
     return ret;
 }
 
+char* resource_local_path(DavResource *res) {
+#ifdef SYS_LINK_EXT
+    // on Windows, add .lnk extension to links
+    if(dav_get_property_ns(res, DAV_PROPS_NS, "link")) {
+        return ucx_sprintf("%s%s", res->path, SYS_LINK_EXT).ptr;
+    } else {
+        // not a link
+        return strdup(res->path);
+    }
+#else
+    return strdup(res->path);
+#endif
+}
+
 size_t resource_get_blocksize(SyncDirectory *dir, LocalResource *local, DavResource *res, off_t filesize) {
     size_t local_blocksize = 0;
     if(local->blocksize < 0) {
@@ -3066,7 +3110,7 @@
     UcxBuffer *buf = NULL;
     if(dir->tagconfig->store == TAG_STORE_XATTR) {
         ssize_t tag_length = 0;
-        char *local_path = create_local_path(dir, res->path);
+        char *local_path = create_local_path(dir, local_resource_path(res));
         char* tag_data = xattr_get(
                 local_path,
                 dir->tagconfig->xattr_name,
@@ -3560,7 +3604,7 @@
         LocalResource *local,
         int *counter)
 {
-    char *local_path = create_local_path(dir, res->path);
+    char *local_path = create_local_path(dir, local_resource_path(local));
     
     SYS_STAT s;
     if(sys_stat(local_path, &s)) {
--- a/dav/sync.h	Fri Aug 02 21:40:05 2019 +0200
+++ b/dav/sync.h	Fri Aug 02 22:04:00 2019 +0200
@@ -132,6 +132,7 @@
 UcxList* local_scan(SyncDirectory *dir, SyncDatabase *db);
 UcxList* read_changes(SyncDirectory *dir, SyncDatabase *db);
 LocalResource* local_resource_new(SyncDirectory *dir, SyncDatabase *db, char *path);
+char* local_resource_path(LocalResource *res);
 int local_resource_is_changed(
         SyncDirectory *dir,
         SyncDatabase *db,
@@ -145,6 +146,7 @@
         SyncDatabase *db,
         DavResource *remote,
         LocalResource *res);
+char* resource_local_path(DavResource *res);
 
 size_t resource_get_blocksize(SyncDirectory *dir, LocalResource *local, DavResource *res, off_t filesize);
 
--- a/dav/system.c	Fri Aug 02 21:40:05 2019 +0200
+++ b/dav/system.c	Fri Aug 02 22:04:00 2019 +0200
@@ -26,6 +26,8 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include <libidav/utils.h>
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -34,6 +36,8 @@
 #include <sys/types.h>
 #include <errno.h>
 
+#include <ucx/string.h>
+
 #ifndef _WIN32
 #include <unistd.h>
 #endif
@@ -48,6 +52,13 @@
 #ifndef _WIN32
 /* ---------- POSIX implementation ---------- */
 
+void sys_init(void) {
+    
+}
+void sys_uninit(void) {
+    
+}
+
 SYS_DIR sys_opendir(const char *path) {
     DIR *dir = opendir(path);
     if(!dir) {
@@ -96,6 +107,13 @@
     return lstat(path, s);
 }
 
+int sys_islink(const char *path) {
+    struct stat s;
+    if(!lstat(path, &s)) {
+        return S_ISLNK(s.st_mode);
+    }
+}   return 0;
+
 int sys_rename(const char *oldpath, const char *newpath) {
     return rename(oldpath, newpath);
 }
@@ -108,8 +126,31 @@
     return mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
 }
 
-ssize_t sys_readlink(const char *path, char *buffer, size_t size) {
-    return readlink(path, buffer, size);
+char* sys_readlink(const char *path, SYS_STAT *s) {
+    char *ret = NULL;
+    
+    off_t l_sz = s->st_size + 16;
+    size_t lnksize = l_sz > 256 ? l_sz : 256;
+    char *lnkbuf = malloc(lnksize);
+    
+    ssize_t len = 0;
+    for(int i=0;i<4;i++) {
+        // we try to read the link at most 4 times
+        // only repeat if the buffer is too small
+        len = readlink(path, lnkbuf, lnksize);
+        if(len < lnksize) {
+            ret = lnkbuf; // success
+            lnkbuf[len] = 0; // terminate buffer
+            break;
+        }
+        lnksize *= 2; // retry with bigger buffer
+        lnkbuf = realloc(lnkbuf, lnksize);
+    }
+    
+    if(!ret) {
+        free(lnkbuf);
+    }
+    return ret;
 }
 
 int sys_symlink(const char *target, const char *linkpath) {
@@ -126,6 +167,26 @@
 #else
 /* ---------- Windows implementation ---------- */
 
+#include <windows.h>
+#include <winnls.h>
+#include <shobjidl.h>
+#include <objbase.h>
+#include <objidl.h>
+
+#include <direct.h>
+#include <wchar.h>
+
+void sys_init(void) {
+    HRESULT res =  CoInitialize(NULL);
+    if(res != S_OK) {
+        fprintf(stderr, "Error: CoInitialize failed\n");
+    }
+}
+
+void sys_uninit(void) {
+    CoUninitialize();
+}
+
 static wchar_t* path2winpath(const char *path, int dir, int *newlen) {
     size_t len = strlen(path);
     size_t lenadd = dir ? 2 : 0;
@@ -158,7 +219,25 @@
     wpath[wlen] = 0;
     
     return wpath;
-} 
+}
+
+static char* winpath2multibyte(const wchar_t *wpath, size_t wlen) {
+    size_t maxlen = wlen * 4;
+    char *ret = malloc(maxlen + 1);
+    int ret_len = WideCharToMultiByte(
+        CP_UTF8,
+        0,
+        wpath,
+        wlen,
+        ret,
+        maxlen,
+        NULL,
+        NULL);
+    ret[ret_len] = 0;
+    return ret;
+}
+
+
 
 SYS_DIR sys_opendir(const char *path) {
     struct WinDir *dir = malloc(sizeof(struct WinDir));
@@ -247,7 +326,43 @@
 }
 
 int sys_lstat(const char *path, SYS_STAT *s) {
-	return sys_stat(path, s); // unsupported on windows
+    return sys_stat(path, s); // unsupported on windows
+}
+
+int sys_islink(const char *path) {
+    // don't use symlinks on windows, because it is not really useful
+    // however, we interpret .lnk files as symlinks
+    int ret = 0;
+    
+    scstr_t path_s = scstr(path);
+    if(scstrsuffix(path_s, SC(".lnk"))) {
+        // looks like a .lnk file
+        // check content
+        IShellLink *sl;
+        HRESULT hres;
+        hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID*)&sl);
+        if(SUCCEEDED(hres)) { 
+            IPersistFile *file;
+            hres = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (void**)&file);
+            if(!SUCCEEDED(hres)) {
+                sl->lpVtbl->Release(sl);
+                return ret;
+            }
+            
+            int newlen = 0;
+            wchar_t *wpath = path2winpath(path, 0, &newlen);
+            
+            hres = file->lpVtbl->Load(file, wpath, STGM_READ);
+            if(SUCCEEDED(hres)) {
+                ret = 1;
+                file->lpVtbl->Release(file);
+            }
+            free(wpath);
+            
+            sl->lpVtbl->Release(sl);
+        }
+    }
+    return ret;
 }
 
 int sys_rename(const char *oldpath, const char *newpath) {
@@ -292,16 +407,103 @@
     return ret;
 }
 
-ssize_t sys_readlink(const char *path, char *buffer, size_t size) {
-    // TODO
-    fprintf(stderr, "sys_readlink: implement me\n");
-    return 1;
+char* sys_readlink(const char *path, SYS_STAT *s) {
+    char *ret_link = NULL;
+    
+    // create COM object for using the ShellLink interface
+    IShellLinkW *sl;
+    HRESULT hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID*)&sl);
+    if(!SUCCEEDED(hres)) {
+        return NULL;
+    }
+    
+    IPersistFile *file;
+    hres = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (void**)&file);
+    if(!SUCCEEDED(hres)) {
+        sl->lpVtbl->Release(sl);
+        return NULL;
+    }
+    
+    // load .lnk file
+    int newlen = 0;
+    wchar_t *wpath = path2winpath(path, 0, &newlen);
+    hres = file->lpVtbl->Load(file, wpath, STGM_READ);
+    if(SUCCEEDED(hres)) {
+        WCHAR link_path[MAX_PATH];
+        memset(link_path, 0, MAX_PATH);
+        
+        hres = sl->lpVtbl->Resolve(sl, 0, SLR_NO_UI);
+        if(SUCCEEDED(hres)) {
+            hres = sl->lpVtbl->GetPath(sl, link_path, MAX_PATH, NULL, SLGP_SHORTPATH);
+            if(SUCCEEDED(hres)) {
+                ret_link = winpath2multibyte(link_path, wcslen(link_path));
+            }
+        }
+    }
+    // cleanup
+    free(wpath);
+    file->lpVtbl->Release(file);
+    sl->lpVtbl->Release(sl);
+    
+    return ret_link;
 }
 
 int sys_symlink(const char *target, const char *linkpath) {
-    // TODO
-    fprintf(stderr, "sys_symlink: implement me\n");
-    return 1;
+    // convert relative target to absolut path
+    char *link_parent = util_parent_path(linkpath);
+    char *target_unnormalized = util_concat_path(link_parent, target);
+    char *target_normalized = util_path_normalize(target_unnormalized);
+    
+    free(link_parent);
+    free(target_unnormalized);
+    
+    // convert to wchar_t*
+    int wtargetlen = 0;
+    wchar_t *wtarget = path2winpath(target_normalized, FALSE, &wtargetlen);
+    free(target_normalized);
+    if(!wtarget) {
+        return 1;
+    }
+    
+    int wlinkpathlen = 0;
+    wchar_t *wlinkpath = path2winpath(linkpath, FALSE, &wlinkpathlen);
+    if(!wlinkpath) {
+        free(wtarget);
+        return 1;
+    }
+    
+    int ret = 1;
+    
+    // create COM object for using the ShellLink interface
+    IShellLinkW *sl;
+    HRESULT hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID*)&sl);
+    if(SUCCEEDED(hres)) {    
+        IPersistFile *file;
+        hres = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (void**)&file);
+        if(SUCCEEDED(hres)) {
+            // try to load the shortcut
+            file->lpVtbl->Load(file, wlinkpath, STGM_READ); // ignore error
+            
+            // set path
+            hres = sl->lpVtbl->SetPath(sl, wtarget);
+            if(SUCCEEDED(hres)) {
+                hres = file->lpVtbl->Save(file, wlinkpath, TRUE);
+                if(SUCCEEDED(hres)) {
+                    // successfully created/modified shortcut
+                    ret = 0; // ok
+                }
+            }
+            
+            file->lpVtbl->Release(file);
+        }
+        
+        sl->lpVtbl->Release(sl);
+    }
+
+    free(wtarget);
+    free(wlinkpath);
+    
+    return ret;
 }
 
 #endif
--- a/dav/system.h	Fri Aug 02 21:40:05 2019 +0200
+++ b/dav/system.h	Fri Aug 02 22:04:00 2019 +0200
@@ -57,11 +57,12 @@
 #define SYS_DIR struct WinDir*
 #define SYS_STAT struct __stat64
 
-#define S_ISLNK(s) 0
-
 typedef int uid_t;
 typedef int gid_t;
 
+#define SYS_ISLINK(path, mode) sys_islink(path)
+#define SYS_LINK_EXT ".lnk"
+
 #else
 
 typedef struct SysDir {
@@ -71,10 +72,16 @@
 
 #define SYS_DIR SysDir*
 #define SYS_STAT struct stat
+
+#define SYS_ISLINK(p, s) S_ISLNK(s.st_mode)
+
 #endif
 
 typedef int(*stat_func)(const char*, SYS_STAT *);
 
+void sys_init(void);
+void sys_uninit(void);
+
 void sys_freedirent(SysDirEnt *ent);
 SYS_DIR sys_opendir(const char *path);
 SysDirEnt* sys_readdir(SYS_DIR dir);
@@ -85,11 +92,13 @@
 int sys_stat(const char *path, SYS_STAT *s);
 int sys_lstat(const char *path, SYS_STAT *s);
 
+int sys_islink(const char *path);
+
 int sys_rename(const char *oldpath, const char *newpath);
 int sys_unlink(const char *path);
 int sys_mkdir(const char *path);
 
-ssize_t sys_readlink(const char *path, char *buffer, size_t size);
+char* sys_readlink(const char *path, SYS_STAT *s);
 
 int sys_symlink(const char *target, const char *linkpath);
 
--- a/libidav/utils.c	Fri Aug 02 21:40:05 2019 +0200
+++ b/libidav/utils.c	Fri Aug 02 22:04:00 2019 +0200
@@ -41,9 +41,13 @@
 #ifdef _WIN32
 #include <conio.h>
 #define getpasswordchar() getch()
+#define IS_PATH_SEPARATOR(c) (c == '/' || c == '\\')
+#define PATH_SEPARATOR '\\'
 #else
 #include <termios.h>
 #define getpasswordchar() getchar()
+#define IS_PATH_SEPARATOR(c) (c == '/')
+#define PATH_SEPARATOR '/'
 #endif
 
 #include "webdav.h"
@@ -377,10 +381,10 @@
     scstr_t p1 = scstr(path1);
     scstr_t p2 = scstr(path2);
     
-    if(p1.ptr[p1.length-1] == '/') {
+    if(IS_PATH_SEPARATOR(p1.ptr[p1.length-1])) {
         p1.length--;
     }
-    if(p2.ptr[p2.length-1] == '/') {
+    if(IS_PATH_SEPARATOR(p2.ptr[p2.length-1])) {
         p2.length--;
     }
     
@@ -393,7 +397,7 @@
     }
     
     if(sstrprefix(p2, p1)) {
-        if(p2.ptr[p1.length] == '/') {
+        if(IS_PATH_SEPARATOR(p2.ptr[p1.length])) {
             return 1;
         }
     }
@@ -401,6 +405,29 @@
     return 0;
 }
 
+#ifdef _WIN32
+int util_path_isabsolut(const char *path) {
+    if(strlen(path) < 3) {
+        return 0;
+    }
+    
+    // check if first char is A-Z or a-z
+    char c = path[0];
+    if(!((c >= 65 && c <= 90) || (c >= 97 && c <= 122))) {
+        return 0;
+    }
+    
+    if(path[1] == ':' && path[2] == '\\') {
+        return 1;
+    }
+    return 0;
+}
+#else
+int util_path_isabsolut(const char *path) {
+    return path[0] == '/';
+}
+#endif
+
 char* util_path_normalize(const char *path) {
     size_t len = strlen(path);
     UcxBuffer *buf = ucx_buffer_new(NULL, len+1, UCX_BUFFER_AUTOEXTEND);
@@ -413,10 +440,10 @@
     int seg_start = 0;
     for(int i=0;i<=len;i++) {
         char c = path[i];
-        if(c == '/' || c == '\0') {
+        if(IS_PATH_SEPARATOR(c) || c == '\0') {
             const char *seg_ptr = path+seg_start;
             int seg_len = i - seg_start;
-            if(seg_ptr[0] == '/') {
+            if(IS_PATH_SEPARATOR(seg_ptr[0])) {
                 seg_ptr++;
                 seg_len--;
             }
@@ -426,11 +453,11 @@
                 if(!sstrcmp(seg, SC(".."))) {
                     for(int j=buf->pos;j>=0;j--) {
                         char t = buf->space[j];
-                        if(t == '/' || j == 0) {
+                        if(IS_PATH_SEPARATOR(t) || j == 0) {
                             buf->pos = j;
                             buf->size = j;
                             buf->space[j] = 0;
-                            add_separator = t == '/' ? 1 : 0;
+                            add_separator = IS_PATH_SEPARATOR(t) ? 1 : 0;
                             break;
                         }
                     }
@@ -438,7 +465,7 @@
                     // ignore
                 } else {
                     if(add_separator) {
-                        ucx_buffer_putc(buf, '/');
+                        ucx_buffer_putc(buf, PATH_SEPARATOR);
                     }
                     ucx_buffer_write(seg_ptr, 1, seg_len, buf);
                     add_separator = 1;
@@ -458,19 +485,19 @@
     return space;
 }
 
-char* util_create_relative_path(const char *abspath, const char *base) {
+static char* create_relative_path(const char *abspath, const char *base) {
     size_t path_len = strlen(abspath);
     size_t base_len = strlen(base);
     
-    if(abspath[path_len-1] == '/') {
+    if(IS_PATH_SEPARATOR(abspath[path_len-1])) {
         path_len--;
     }
-    if(base[base_len-1] == '/') {
+    if(IS_PATH_SEPARATOR(base[base_len-1])) {
         base_len--;
     }
     // get base parent
     for(int i=base_len-1;i>=0;i--) {
-        if(base[i] == '/') {
+        if(IS_PATH_SEPARATOR(base[i])) {
             base_len = i+1;
             break;
         }
@@ -486,7 +513,7 @@
         char c = abspath[i];
         if(c != base[i]) {
             break;
-        } else if(c == '/') {
+        } else if(IS_PATH_SEPARATOR(c)) {
             last_dir = i;
         }
     }
@@ -497,7 +524,7 @@
         // base is deeper than the link root, we have to go backwards
         int dircount = 0;
         for(int i=last_dir+1;i<base_len;i++) {
-            if(base[i] == '/') {
+            if(IS_PATH_SEPARATOR(base[i])) {
                 dircount++;
             }
         }
@@ -520,6 +547,35 @@
     return ret;
 }
 
+#ifdef _WIN32
+char* util_create_relative_path(const char *abspath, const char *base) {
+    char *abspath_converted = strdup(abspath);
+    char *base_converted = strdup(base);
+    size_t abs_len = strlen(abspath_converted);
+    size_t base_len = strlen(base_converted);
+    
+    for(int i=0;i<abs_len;i++) {
+        if(abspath_converted[i] == '\\') {
+            abspath_converted[i] = '/';
+        }
+    }
+    for(int i=0;i<base_len;i++) {
+        if(base_converted[i] == '\\') {
+            base_converted[i] = '/';
+        }
+    }
+    
+    char *ret = create_relative_path(abspath_converted, base_converted);
+    free(abspath_converted);
+    free(base_converted);
+    return ret;
+}
+#else
+char* util_create_relative_path(const char *abspath, const char *base) {
+    return create_relative_path(abspath, base);
+}
+#endif
+
 
 void util_capture_header(CURL *handle, UcxMap* map) {
     if(map) {
--- a/libidav/utils.h	Fri Aug 02 21:40:05 2019 +0200
+++ b/libidav/utils.h	Fri Aug 02 22:04:00 2019 +0200
@@ -77,6 +77,8 @@
  */
 int util_path_isrelated(const char *path1, const char *path2);
 
+int util_path_isabsolut(const char *path);
+
 char* util_path_normalize(const char *path);
 char* util_create_relative_path(const char *abspath, const char *base);
 
--- a/mingw.mk	Fri Aug 02 21:40:05 2019 +0200
+++ b/mingw.mk	Fri Aug 02 22:04:00 2019 +0200
@@ -43,5 +43,5 @@
 APP_EXT = .exe
 
 DAV_CFLAGS = `xml2-config --cflags` -I/mingw/include
-DAV_LDFLAGS = `xml2-config --libs` -L/mingw/lib -lcurl -lssl -lcrypto -lws2_32 -lgdi32 -lgnurx
+DAV_LDFLAGS = `xml2-config --libs` -L/mingw/lib -lcurl -lssl -lcrypto -lws2_32 -lgdi32  -luuid -lole32 -loleaut32 -lgnurx
 

mercurial