]> git.proxmox.com Git - libgit2.git/blobdiff - src/win32/path_w32.c
New upstream version 1.0.0+dfsg.1
[libgit2.git] / src / win32 / path_w32.c
index 118e8bcc568b529759eba8e88e138871bb7d4ed9..18b43e728c986ed9235310767e9c89660217f9e3 100644 (file)
@@ -5,9 +5,9 @@
  * a Linking Exception. For full terms see the included COPYING file.
  */
 
-#include "common.h"
-#include "path.h"
 #include "path_w32.h"
+
+#include "path.h"
 #include "utf-conv.h"
 #include "posix.h"
 #include "reparse.h"
 
 #define PATH__ABSOLUTE_LEN     3
 
-#define path__is_dirsep(p) ((p) == '/' || (p) == '\\')
-
-#define path__is_absolute(p) \
-       (git__isalpha((p)[0]) && (p)[1] == ':' && ((p)[2] == '\\' || (p)[2] == '/'))
-
 #define path__is_nt_namespace(p) \
        (((p)[0] == '\\' && (p)[1] == '\\' && (p)[2] == '?' && (p)[3] == '\\') || \
         ((p)[0] == '/' && (p)[1] == '/' && (p)[2] == '?' && (p)[3] == '/'))
@@ -30,6 +25,9 @@
 #define path__is_unc(p) \
        (((p)[0] == '\\' && (p)[1] == '\\') || ((p)[0] == '/' && (p)[1] == '/'))
 
+#define path__startswith_slash(p) \
+       ((p)[0] == '\\' || (p)[0] == '/')
+
 GIT_INLINE(int) path__cwd(wchar_t *path, int size)
 {
        int len;
@@ -59,7 +57,7 @@ static wchar_t *path__skip_server(wchar_t *path)
        wchar_t *c;
 
        for (c = path; *c; c++) {
-               if (path__is_dirsep(*c))
+               if (git_path_is_dirsep(*c))
                        return c + 1;
        }
 
@@ -73,9 +71,9 @@ static wchar_t *path__skip_prefix(wchar_t *path)
 
                if (wcsncmp(path, L"UNC\\", 4) == 0)
                        path = path__skip_server(path + 4);
-               else if (path__is_absolute(path))
+               else if (git_path_is_absolute(path))
                        path += PATH__ABSOLUTE_LEN;
-       } else if (path__is_absolute(path)) {
+       } else if (git_path_is_absolute(path)) {
                path += PATH__ABSOLUTE_LEN;
        } else if (path__is_unc(path)) {
                path = path__skip_server(path + 2);
@@ -145,14 +143,24 @@ int git_win32_path_canonicalize(git_win32_path path)
 
        *to = L'\0';
 
-       return (to - path);
+       if ((to - path) > INT_MAX) {
+               SetLastError(ERROR_FILENAME_EXCED_RANGE);
+               return -1;
+       }
+
+       return (int)(to - path);
 }
 
 int git_win32_path__cwd(wchar_t *out, size_t len)
 {
        int cwd_len;
 
-       if ((cwd_len = path__cwd(out, len)) < 0)
+       if (len > INT_MAX) {
+               errno = ENAMETOOLONG;
+               return -1;
+       }
+
+       if ((cwd_len = path__cwd(out, (int)len)) < 0)
                return -1;
 
        /* UNC paths */
@@ -196,15 +204,15 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src)
        dest += PATH__NT_NAMESPACE_LEN;
 
        /* See if this is an absolute path (beginning with a drive letter) */
-       if (path__is_absolute(src)) {
+       if (git_path_is_absolute(src)) {
                if (git__utf8_to_16(dest, MAX_PATH, src) < 0)
-                       return -1;
+                       goto on_error;
        }
        /* File-prefixed NT-style paths beginning with \\?\ */
        else if (path__is_nt_namespace(src)) {
                /* Skip the NT prefix, the destination already contains it */
                if (git__utf8_to_16(dest, MAX_PATH, src + PATH__NT_NAMESPACE_LEN) < 0)
-                       return -1;
+                       goto on_error;
        }
        /* UNC paths */
        else if (path__is_unc(src)) {
@@ -213,36 +221,67 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src)
 
                /* Skip the leading "\\" */
                if (git__utf8_to_16(dest, MAX_PATH - 2, src + 2) < 0)
-                       return -1;
+                       goto on_error;
        }
        /* Absolute paths omitting the drive letter */
-       else if (src[0] == '\\' || src[0] == '/') {
+       else if (path__startswith_slash(src)) {
                if (path__cwd(dest, MAX_PATH) < 0)
-                       return -1;
+                       goto on_error;
 
-               if (!path__is_absolute(dest)) {
+               if (!git_path_is_absolute(dest)) {
                        errno = ENOENT;
-                       return -1;
+                       goto on_error;
                }
 
-               /* Skip the drive letter specification ("C:") */        
+               /* Skip the drive letter specification ("C:") */
                if (git__utf8_to_16(dest + 2, MAX_PATH - 2, src) < 0)
-                       return -1;
+                       goto on_error;
        }
        /* Relative paths */
        else {
                int cwd_len;
 
                if ((cwd_len = git_win32_path__cwd(dest, MAX_PATH)) < 0)
-                       return -1;
+                       goto on_error;
 
                dest[cwd_len++] = L'\\';
 
                if (git__utf8_to_16(dest + cwd_len, MAX_PATH - cwd_len, src) < 0)
-                       return -1;
+                       goto on_error;
        }
 
        return git_win32_path_canonicalize(out);
+
+on_error:
+       /* set windows error code so we can use its error message */
+       if (errno == ENAMETOOLONG)
+               SetLastError(ERROR_FILENAME_EXCED_RANGE);
+
+       return -1;
+}
+
+int git_win32_path_relative_from_utf8(git_win32_path out, const char *src)
+{
+       wchar_t *dest = out, *p;
+       int len;
+
+       /* Handle absolute paths */
+       if (git_path_is_absolute(src) ||
+           path__is_nt_namespace(src) ||
+           path__is_unc(src) ||
+           path__startswith_slash(src)) {
+               return git_win32_path_from_utf8(out, src);
+       }
+
+       if ((len = git__utf8_to_16(dest, MAX_PATH, src)) < 0)
+               return -1;
+
+       for (p = dest; p < (dest + len); p++) {
+               if (*p == L'/')
+                       *p = L'\\';
+       }
+
+       return len;
 }
 
 int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src)
@@ -313,7 +352,7 @@ static bool path_is_volume(wchar_t *target, size_t target_len)
 }
 
 /* On success, returns the length, in characters, of the path stored in dest.
-* On failure, returns a negative value. */
+ * On failure, returns a negative value. */
 int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path)
 {
        BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
@@ -358,16 +397,16 @@ int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path)
 
        if (path_is_volume(target, target_len)) {
                /* This path is a reparse point that represents another volume mounted
-               * at this location, it is not a symbolic link our input was canonical.
-               */
+                * at this location, it is not a symbolic link our input was canonical.
+                */
                errno = EINVAL;
                error = -1;
        } else if (target_len) {
-               /* The path may need to have a prefix removed. */
-               target_len = git_win32__canonicalize_path(target, target_len);
+               /* The path may need to have a namespace prefix removed. */
+               target_len = git_win32_path_remove_namespace(target, target_len);
 
                /* Need one additional character in the target buffer
-               * for the terminating NULL. */
+                * for the terminating NULL. */
                if (GIT_WIN_PATH_UTF16 > target_len) {
                        wcscpy(dest, target);
                        error = (int)target_len;
@@ -378,3 +417,97 @@ on_error:
        CloseHandle(handle);
        return error;
 }
+
+/**
+ * Removes any trailing backslashes from a path, except in the case of a drive
+ * letter path (C:\, D:\, etc.). This function cannot fail.
+ *
+ * @param path The path which should be trimmed.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32_path_trim_end(wchar_t *str, size_t len)
+{
+       while (1) {
+               if (!len || str[len - 1] != L'\\')
+                       break;
+
+               /*
+                * Don't trim backslashes from drive letter paths, which
+                * are 3 characters long and of the form C:\, D:\, etc.
+                */
+               if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':')
+                       break;
+
+               len--;
+       }
+
+       str[len] = L'\0';
+
+       return len;
+}
+
+/**
+ * Removes any of the following namespace prefixes from a path,
+ * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
+ *
+ * @param path The path which should be converted.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32_path_remove_namespace(wchar_t *str, size_t len)
+{
+       static const wchar_t dosdevices_namespace[] = L"\\\?\?\\";
+       static const wchar_t nt_namespace[] = L"\\\\?\\";
+       static const wchar_t unc_namespace_remainder[] = L"UNC\\";
+       static const wchar_t unc_prefix[] = L"\\\\";
+
+       const wchar_t *prefix = NULL, *remainder = NULL;
+       size_t prefix_len = 0, remainder_len = 0;
+
+       /* "\??\" -- DOS Devices prefix */
+       if (len >= CONST_STRLEN(dosdevices_namespace) &&
+               !wcsncmp(str, dosdevices_namespace, CONST_STRLEN(dosdevices_namespace))) {
+               remainder = str + CONST_STRLEN(dosdevices_namespace);
+               remainder_len = len - CONST_STRLEN(dosdevices_namespace);
+       }
+       /* "\\?\" -- NT namespace prefix */
+       else if (len >= CONST_STRLEN(nt_namespace) &&
+               !wcsncmp(str, nt_namespace, CONST_STRLEN(nt_namespace))) {
+               remainder = str + CONST_STRLEN(nt_namespace);
+               remainder_len = len - CONST_STRLEN(nt_namespace);
+       }
+
+       /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */
+       if (remainder_len >= CONST_STRLEN(unc_namespace_remainder) &&
+               !wcsncmp(remainder, unc_namespace_remainder, CONST_STRLEN(unc_namespace_remainder))) {
+
+               /*
+                * The proper Win32 path for a UNC share has "\\" at beginning of it
+                * and looks like "\\server\share\<folderStructure>".  So remove the
+                * UNC namespace and add a prefix of "\\" in its place.
+                */
+               remainder += CONST_STRLEN(unc_namespace_remainder);
+               remainder_len -= CONST_STRLEN(unc_namespace_remainder);
+
+               prefix = unc_prefix;
+               prefix_len = CONST_STRLEN(unc_prefix);
+       }
+
+       if (remainder) {
+               /*
+                * Sanity check that the new string isn't longer than the old one.
+                * (This could only happen due to programmer error introducing a
+                * prefix longer than the namespace it replaces.)
+                */
+               assert(len >= remainder_len + prefix_len);
+
+               if (prefix)
+                       memmove(str, prefix, prefix_len * sizeof(wchar_t));
+
+               memmove(str + prefix_len, remainder, remainder_len * sizeof(wchar_t));
+
+               len = remainder_len + prefix_len;
+               str[len] = L'\0';
+       }
+
+       return git_win32_path_trim_end(str, len);
+}