shlwapi: Fix the PathCreateFromUrlW() implementation.

Francois Gouget fgouget at codeweavers.com
Tue Sep 25 21:22:00 CDT 2012


---
 dlls/shlwapi/path.c       |  139 ++++++++++++++++++++++++++++++++++-----------
 dlls/shlwapi/tests/path.c |   66 ++++++++++-----------
 2 files changed, 140 insertions(+), 65 deletions(-)

diff --git a/dlls/shlwapi/path.c b/dlls/shlwapi/path.c
index de89196..dc7bc4b 100644
--- a/dlls/shlwapi/path.c
+++ b/dlls/shlwapi/path.c
@@ -3286,62 +3286,137 @@ HRESULT WINAPI PathCreateFromUrlW(LPCWSTR pszUrl, LPWSTR pszPath,
                                   LPDWORD pcchPath, DWORD dwReserved)
 {
     static const WCHAR file_colon[] = { 'f','i','l','e',':',0 };
-    HRESULT hr;
-    DWORD nslashes = 0;
-    WCHAR *ptr;
+    static const WCHAR localhost[] = { 'l','o','c','a','l','h','o','s','t',0 };
+    DWORD nslashes, unescape, len;
+    const WCHAR *src;
+    WCHAR *tpath, *dst;
+    HRESULT ret;
 
     TRACE("(%s,%p,%p,0x%08x)\n", debugstr_w(pszUrl), pszPath, pcchPath, dwReserved);
 
     if (!pszUrl || !pszPath || !pcchPath || !*pcchPath)
         return E_INVALIDARG;
 
-
-    if (strncmpW(pszUrl, file_colon, 5))
+    if (CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, pszUrl, 5,
+                       file_colon, 5) != CSTR_EQUAL)
         return E_INVALIDARG;
     pszUrl += 5;
+    ret = S_OK;
 
-    while(*pszUrl == '/' || *pszUrl == '\\') {
+    src = pszUrl;
+    nslashes = 0;
+    while (*src == '/' || *src == '\\') {
         nslashes++;
-        pszUrl++;
+        src++;
     }
 
-    if(isalphaW(*pszUrl) && (pszUrl[1] == ':' || pszUrl[1] == '|') && (pszUrl[2] == '/' || pszUrl[2] == '\\'))
-        nslashes = 0;
+    /* We need a temporary buffer so we can compute what size to ask for.
+     * We know that the final string won't be longer than the current pszUrl
+     * plus at most two backslashes. All the other transformations make it
+     * shorter.
+     */
+    len = 2 + lstrlenW(pszUrl) + 1;
+    if (*pcchPath < len)
+        tpath = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
+    else
+        tpath = pszPath;
 
-    switch(nslashes) {
-    case 2:
-        pszUrl -= 2;
-        break;
+    len = 0;
+    dst = tpath;
+    unescape = 1;
+    switch (nslashes)
+    {
     case 0:
+        /* 'file:' + escaped DOS path */
         break;
-    default:
-        pszUrl -= 1;
+    case 1:
+        /* 'file:/' + escaped DOS path */
+        /* fall through */
+    case 3:
+        /* 'file:///' (implied localhost) + escaped DOS path */
+        if (!isalphaW(*src) || (src[1] != ':' && src[1] != '|'))
+            src -= 1;
+        break;
+    case 2:
+        if (CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, src, 9,
+                           localhost, 9) == CSTR_EQUAL &&
+            (src[9] == '/' || src[9] == '\\'))
+        {
+            /* 'file://localhost/' + escaped DOS path */
+            src += 10;
+        }
+        else if (isalphaW(*src) && (src[1] == ':' || src[1] == '|'))
+        {
+            /* 'file://' + unescaped DOS path */
+            unescape = 0;
+        }
+        else
+        {
+            /*    'file://hostname:port/path' (where path is escaped)
+             * or 'file:' + escaped UNC path (\\server\share\path)
+             * The second form is clearly specific to Windows and it might
+             * even be doing a network lookup to try to figure it out.
+             */
+            while (*src && *src != '/' && *src != '\\')
+                src++;
+            len = src - pszUrl;
+            StrCpyNW(dst, pszUrl, len + 1);
+            dst += len;
+            if (isalphaW(src[1]) && (src[2] == ':' || src[2] == '|'))
+            {
+                /* 'Forget' to add a trailing '/', just like Windows */
+                src++;
+            }
+        }
         break;
+    case 4:
+        /* 'file://' + unescaped UNC path (\\server\share\path) */
+        unescape = 0;
+        if (isalphaW(*src) && (src[1] == ':' || src[1] == '|'))
+            break;
+        /* fall through */
+    default:
+        /* 'file:/...' + escaped UNC path (\\server\share\path) */
+        src -= 2;
     }
 
-    hr = UrlUnescapeW((LPWSTR)pszUrl, pszPath, pcchPath, 0);
-    if(hr != S_OK) return hr;
-
-    for(ptr = pszPath; *ptr; ptr++)
-        if(*ptr == '/') *ptr = '\\';
+    /* Copy the remainder of the path */
+    len += lstrlenW(src);
+    StrCpyW(dst, src);
 
-    while(*pszPath == '\\')
-        pszPath++;
- 
-    if(isalphaW(*pszPath) && pszPath[1] == '|' && pszPath[2] == '\\') /* c|\ -> c:\ */
-        pszPath[1] = ':';
+     /* First do the Windows-specific path conversions */
+    for (dst = tpath; *dst; dst++)
+        if (*dst == '/') *dst = '\\';
+    if (isalphaW(*tpath) && tpath[1] == '|')
+        tpath[1] = ':'; /* c| -> c: */
 
-    if(nslashes == 2 && (ptr = strchrW(pszPath, '\\'))) { /* \\host\c:\ -> \\hostc:\ */
-        ptr++;
-        if(isalphaW(*ptr) && (ptr[1] == ':' || ptr[1] == '|') && ptr[2] == '\\') {
-            memmove(ptr - 1, ptr, (strlenW(ptr) + 1) * sizeof(WCHAR));
-            (*pcchPath)--;
+    /* And only then unescape the path (i.e. escaped slashes are left as is) */
+    if (unescape)
+    {
+        ret = UrlUnescapeW(tpath, NULL, &len, URL_UNESCAPE_INPLACE);
+        if (ret == S_OK)
+        {
+            /* When working in-place UrlUnescapeW() does not set len */
+            len = lstrlenW(tpath);
         }
     }
 
-    TRACE("Returning %s\n",debugstr_w(pszPath));
+    if (*pcchPath < len + 1)
+    {
+        ret = E_POINTER;
+        *pcchPath = len + 1;
+    }
+    else
+    {
+        *pcchPath = len;
+        if (tpath != pszPath)
+            StrCpyW(pszPath, tpath);
+    }
+    if (tpath != pszPath)
+      HeapFree(GetProcessHeap(), 0, tpath);
 
-    return hr;
+    TRACE("Returning (%u) %s\n", *pcchPath, debugstr_w(pszPath));
+    return ret;
 }
 
 /*************************************************************************
diff --git a/dlls/shlwapi/tests/path.c b/dlls/shlwapi/tests/path.c
index 0dd4e00d..6417988 100644
--- a/dlls/shlwapi/tests/path.c
+++ b/dlls/shlwapi/tests/path.c
@@ -46,9 +46,9 @@ static const struct {
     {"file:c|/foo/bar", "c:\\foo\\bar", S_OK, 0},
     {"file:cx|/foo/bar", "cx|\\foo\\bar", S_OK, 0},
     {"file:c:foo/bar", "c:foo\\bar", S_OK, 0},
-    {"file:c|foo/bar", "c:foo\\bar", S_OK, 0x2},
-    {"file:c:/foo%20ba%2fr", "c:\\foo ba/r", S_OK, 0x2},
-    {"file:foo%20ba%2fr", "foo ba/r", S_OK, 0x2},
+    {"file:c|foo/bar", "c:foo\\bar", S_OK, 0},
+    {"file:c:/foo%20ba%2fr", "c:\\foo ba/r", S_OK, 0},
+    {"file:foo%20ba%2fr", "foo ba/r", S_OK, 0},
     {"file:foo/bar/", "foo\\bar\\", S_OK, 0},
 
     /* 1 leading (back)slash */
@@ -56,10 +56,10 @@ static const struct {
     {"file:\\c:/foo/bar", "c:\\foo\\bar", S_OK, 0},
     {"file:/c|/foo/bar", "c:\\foo\\bar", S_OK, 0},
     {"file:/cx|/foo/bar", "\\cx|\\foo\\bar", S_OK, 0},
-    {"file:/c:foo/bar", "c:foo\\bar", S_OK, 0x2},
-    {"file:/c|foo/bar", "c:foo\\bar", S_OK, 0x2},
-    {"file:/c:/foo%20ba%2fr", "c:\\foo ba/r", S_OK, 0x2},
-    {"file:/foo%20ba%2fr", "\\foo ba/r", S_OK, 0x2},
+    {"file:/c:foo/bar", "c:foo\\bar", S_OK, 0},
+    {"file:/c|foo/bar", "c:foo\\bar", S_OK, 0},
+    {"file:/c:/foo%20ba%2fr", "c:\\foo ba/r", S_OK, 0},
+    {"file:/foo%20ba%2fr", "\\foo ba/r", S_OK, 0},
     {"file:/foo/bar/", "\\foo\\bar\\", S_OK, 0},
 
     /* 2 leading (back)slashes */
@@ -67,52 +67,52 @@ static const struct {
     {"file://c:/d:/foo/bar", "c:\\d:\\foo\\bar", S_OK, 0},
     {"file://c|/d|/foo/bar", "c:\\d|\\foo\\bar", S_OK, 0},
     {"file://cx|/foo/bar", "\\\\cx|\\foo\\bar", S_OK, 0},
-    {"file://c:foo/bar", "c:foo\\bar", S_OK, 0x2},
-    {"file://c|foo/bar", "c:foo\\bar", S_OK, 0x2},
-    {"file://c:/foo%20ba%2fr", "c:\\foo%20ba%2fr", S_OK, 0x2},
+    {"file://c:foo/bar", "c:foo\\bar", S_OK, 0},
+    {"file://c|foo/bar", "c:foo\\bar", S_OK, 0},
+    {"file://c:/foo%20ba%2fr", "c:\\foo%20ba%2fr", S_OK, 0},
     {"file://c%3a/foo/../bar", "\\\\c:\\foo\\..\\bar", S_OK, 0},
-    {"file://c%7c/foo/../bar", "\\\\c|\\foo\\..\\bar", S_OK, 0x2},
-    {"file://foo%20ba%2fr", "\\\\foo ba/r", S_OK, 0x2},
-    {"file://localhost/c:/foo/bar", "c:\\foo\\bar", S_OK, 0x6},
-    {"file://localhost/c:/foo%20ba%5Cr", "c:\\foo ba\\r", S_OK, 0x6},
-    {"file://LocalHost/c:/foo/bar", "c:\\foo\\bar", S_OK, 0x6},
-    {"file:\\\\localhost\\c:\\foo\\bar", "c:\\foo\\bar", S_OK, 0x6},
+    {"file://c%7c/foo/../bar", "\\\\c|\\foo\\..\\bar", S_OK, 0},
+    {"file://foo%20ba%2fr", "\\\\foo ba/r", S_OK, 0},
+    {"file://localhost/c:/foo/bar", "c:\\foo\\bar", S_OK, 0},
+    {"file://localhost/c:/foo%20ba%5Cr", "c:\\foo ba\\r", S_OK, 0},
+    {"file://LocalHost/c:/foo/bar", "c:\\foo\\bar", S_OK, 0},
+    {"file:\\\\localhost\\c:\\foo\\bar", "c:\\foo\\bar", S_OK, 0},
     {"file://incomplete", "\\\\incomplete", S_OK, 0},
 
     /* 3 leading (back)slashes (omitting hostname) */
     {"file:///c:/foo/bar", "c:\\foo\\bar", S_OK, 0},
-    {"File:///c:/foo/bar", "c:\\foo\\bar", S_OK, 0x1},
-    {"file:///c:/foo%20ba%2fr", "c:\\foo ba/r", S_OK, 0x2},
-    {"file:///foo%20ba%2fr", "\\foo ba/r", S_OK, 0x2},
+    {"File:///c:/foo/bar", "c:\\foo\\bar", S_OK, 0},
+    {"file:///c:/foo%20ba%2fr", "c:\\foo ba/r", S_OK, 0},
+    {"file:///foo%20ba%2fr", "\\foo ba/r", S_OK, 0},
     {"file:///foo/bar/", "\\foo\\bar\\", S_OK, 0},
     {"file:///localhost/c:/foo/bar", "\\localhost\\c:\\foo\\bar", S_OK, 0},
 
     /* 4 leading (back)slashes */
     {"file:////c:/foo/bar", "c:\\foo\\bar", S_OK, 0},
-    {"file:////c:/foo%20ba%2fr", "c:\\foo%20ba%2fr", S_OK, 0x2},
-    {"file:////foo%20ba%2fr", "\\\\foo%20ba%2fr", S_OK, 0x2},
+    {"file:////c:/foo%20ba%2fr", "c:\\foo%20ba%2fr", S_OK, 0},
+    {"file:////foo%20ba%2fr", "\\\\foo%20ba%2fr", S_OK, 0},
 
     /* 5 and more leading (back)slashes */
-    {"file://///c:/foo/bar", "\\\\c:\\foo\\bar", S_OK, 0x2},
-    {"file://///c:/foo%20ba%2fr", "\\\\c:\\foo ba/r", S_OK, 0x2},
-    {"file://///foo%20ba%2fr", "\\\\foo ba/r", S_OK, 0x2},
-    {"file://////c:/foo/bar", "\\\\c:\\foo\\bar", S_OK, 0x2},
+    {"file://///c:/foo/bar", "\\\\c:\\foo\\bar", S_OK, 0},
+    {"file://///c:/foo%20ba%2fr", "\\\\c:\\foo ba/r", S_OK, 0},
+    {"file://///foo%20ba%2fr", "\\\\foo ba/r", S_OK, 0},
+    {"file://////c:/foo/bar", "\\\\c:\\foo\\bar", S_OK, 0},
 
     /* Leading (back)slashes cannot be escaped */
-    {"file:%2f%2flocalhost%2fc:/foo/bar", "//localhost/c:\\foo\\bar", S_OK, 0x2},
+    {"file:%2f%2flocalhost%2fc:/foo/bar", "//localhost/c:\\foo\\bar", S_OK, 0},
     {"file:%5C%5Clocalhost%5Cc:/foo/bar", "\\\\localhost\\c:\\foo\\bar", S_OK, 0},
 
     /* Hostname handling */
-    {"file://l%6fcalhost/c:/foo/bar", "\\\\localhostc:\\foo\\bar", S_OK, 0x4},
-    {"file://localhost:80/c:/foo/bar", "\\\\localhost:80c:\\foo\\bar", S_OK, 0x4},
-    {"file://host/c:/foo/bar", "\\\\hostc:\\foo\\bar", S_OK, 0x4},
+    {"file://l%6fcalhost/c:/foo/bar", "\\\\localhostc:\\foo\\bar", S_OK, 0},
+    {"file://localhost:80/c:/foo/bar", "\\\\localhost:80c:\\foo\\bar", S_OK, 0},
+    {"file://host/c:/foo/bar", "\\\\hostc:\\foo\\bar", S_OK, 0},
     {"file://host//c:/foo/bar", "\\\\host\\\\c:\\foo\\bar", S_OK, 0},
     {"file://host/\\c:/foo/bar", "\\\\host\\\\c:\\foo\\bar", S_OK, 0},
-    {"file://host/c:foo/bar", "\\\\hostc:foo\\bar", S_OK, 0x2},
+    {"file://host/c:foo/bar", "\\\\hostc:foo\\bar", S_OK, 0},
     {"file://host/foo/bar", "\\\\host\\foo\\bar", S_OK, 0},
-    {"file:\\\\host\\c:\\foo\\bar", "\\\\hostc:\\foo\\bar", S_OK, 0x4},
+    {"file:\\\\host\\c:\\foo\\bar", "\\\\hostc:\\foo\\bar", S_OK, 0},
     {"file:\\\\host\\ca\\foo\\bar", "\\\\host\\ca\\foo\\bar", S_OK, 0},
-    {"file:\\\\host\\c|\\foo\\bar", "\\\\hostc|\\foo\\bar", S_OK, 0x4},
+    {"file:\\\\host\\c|\\foo\\bar", "\\\\hostc|\\foo\\bar", S_OK, 0},
     {"file:\\%5Chost\\c:\\foo\\bar", "\\\\host\\c:\\foo\\bar", S_OK, 0},
     {"file:\\\\host\\cx:\\foo\\bar", "\\\\host\\cx:\\foo\\bar", S_OK, 0},
     {"file:///host/c:/foo/bar", "\\host\\c:\\foo\\bar", S_OK, 0},
@@ -305,7 +305,7 @@ static void test_PathCreateFromUrl(void)
                        ret_path, TEST_PATHFROMURL[i].path, TEST_PATHFROMURL[i].url);
                     ok(len == lstrlenW(ret_pathW), "ret len %d from url L\"%s\"\n", len, TEST_PATHFROMURL[i].url);
                 } else todo_wine
-                /* Wrong string, don't bother checking the length */
+                    /* Wrong string, don't bother checking the length */
                     ok(!lstrcmpiW(ret_pathW, pathW), "got %s expected %s from url L\"%s\"\n",
                        ret_path, TEST_PATHFROMURL[i].path, TEST_PATHFROMURL[i].url);
             }
-- 
1.7.10.4



More information about the wine-patches mailing list