Fix UrlUnescapeW.
[wine] / dlls / shlwapi / url.c
1 /*
2  * Url functions
3  *
4  * Copyright 2000 Huw D M Davies for CodeWeavers.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include "config.h"
22 #include "wine/port.h"
23 #include <stdarg.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include "windef.h"
27 #include "winbase.h"
28 #include "winnls.h"
29 #include "winerror.h"
30 #include "wine/unicode.h"
31 #include "wininet.h"
32 #include "winreg.h"
33 #include "winternl.h"
34 #define NO_SHLWAPI_STREAM
35 #include "shlwapi.h"
36 #include "wine/debug.h"
37
38 HMODULE WINAPI MLLoadLibraryW(LPCWSTR,HMODULE,DWORD);
39 BOOL    WINAPI MLFreeLibrary(HMODULE);
40 HRESULT WINAPI MLBuildResURLW(LPCWSTR,HMODULE,DWORD,LPCWSTR,LPWSTR,DWORD);
41
42 WINE_DEFAULT_DEBUG_CHANNEL(shell);
43
44 /* The following schemes were identified in the native version of
45  * SHLWAPI.DLL version 5.50
46  */
47 typedef struct {
48     URL_SCHEME  scheme_number;
49     LPCSTR scheme_name;
50 } SHL_2_inet_scheme;
51
52 static const SHL_2_inet_scheme shlwapi_schemes[] = {
53   {URL_SCHEME_FTP,        "ftp"},
54   {URL_SCHEME_HTTP,       "http"},
55   {URL_SCHEME_GOPHER,     "gopher"},
56   {URL_SCHEME_MAILTO,     "mailto"},
57   {URL_SCHEME_NEWS,       "news"},
58   {URL_SCHEME_NNTP,       "nntp"},
59   {URL_SCHEME_TELNET,     "telnet"},
60   {URL_SCHEME_WAIS,       "wais"},
61   {URL_SCHEME_FILE,       "file"},
62   {URL_SCHEME_MK,         "mk"},
63   {URL_SCHEME_HTTPS,      "https"},
64   {URL_SCHEME_SHELL,      "shell"},
65   {URL_SCHEME_SNEWS,      "snews"},
66   {URL_SCHEME_LOCAL,      "local"},
67   {URL_SCHEME_JAVASCRIPT, "javascript"},
68   {URL_SCHEME_VBSCRIPT,   "vbscript"},
69   {URL_SCHEME_ABOUT,      "about"},
70   {URL_SCHEME_RES,        "res"},
71   {0, 0}
72 };
73
74 typedef struct {
75     LPCWSTR pScheme;      /* [out] start of scheme                     */
76     DWORD   szScheme;     /* [out] size of scheme (until colon)        */
77     LPCWSTR pUserName;    /* [out] start of Username                   */
78     DWORD   szUserName;   /* [out] size of Username (until ":" or "@") */
79     LPCWSTR pPassword;    /* [out] start of Password                   */
80     DWORD   szPassword;   /* [out] size of Password (until "@")        */
81     LPCWSTR pHostName;    /* [out] start of Hostname                   */
82     DWORD   szHostName;   /* [out] size of Hostname (until ":" or "/") */
83     LPCWSTR pPort;        /* [out] start of Port                       */
84     DWORD   szPort;       /* [out] size of Port (until "/" or eos)     */
85     LPCWSTR pQuery;       /* [out] start of Query                      */
86     DWORD   szQuery;      /* [out] size of Query (until eos)           */
87 } WINE_PARSE_URL;
88
89 typedef enum {
90     SCHEME,
91     HOST,
92     PORT,
93     USERPASS,
94 } WINE_URL_SCAN_TYPE;
95
96 static const CHAR hexDigits[] = "0123456789ABCDEF";
97
98 static const WCHAR fileW[] = {'f','i','l','e','\0'};
99
100 static const unsigned char HashDataLookup[256] = {
101  0x01, 0x0E, 0x6E, 0x19, 0x61, 0xAE, 0x84, 0x77, 0x8A, 0xAA, 0x7D, 0x76, 0x1B,
102  0xE9, 0x8C, 0x33, 0x57, 0xC5, 0xB1, 0x6B, 0xEA, 0xA9, 0x38, 0x44, 0x1E, 0x07,
103  0xAD, 0x49, 0xBC, 0x28, 0x24, 0x41, 0x31, 0xD5, 0x68, 0xBE, 0x39, 0xD3, 0x94,
104  0xDF, 0x30, 0x73, 0x0F, 0x02, 0x43, 0xBA, 0xD2, 0x1C, 0x0C, 0xB5, 0x67, 0x46,
105  0x16, 0x3A, 0x4B, 0x4E, 0xB7, 0xA7, 0xEE, 0x9D, 0x7C, 0x93, 0xAC, 0x90, 0xB0,
106  0xA1, 0x8D, 0x56, 0x3C, 0x42, 0x80, 0x53, 0x9C, 0xF1, 0x4F, 0x2E, 0xA8, 0xC6,
107  0x29, 0xFE, 0xB2, 0x55, 0xFD, 0xED, 0xFA, 0x9A, 0x85, 0x58, 0x23, 0xCE, 0x5F,
108  0x74, 0xFC, 0xC0, 0x36, 0xDD, 0x66, 0xDA, 0xFF, 0xF0, 0x52, 0x6A, 0x9E, 0xC9,
109  0x3D, 0x03, 0x59, 0x09, 0x2A, 0x9B, 0x9F, 0x5D, 0xA6, 0x50, 0x32, 0x22, 0xAF,
110  0xC3, 0x64, 0x63, 0x1A, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xBD, 0x79, 0x40,
111  0x4D, 0x48, 0xD0, 0xF5, 0x82, 0x7A, 0x8F, 0x37, 0x69, 0x86, 0x1D, 0xA4, 0xB9,
112  0xC2, 0xC1, 0xEF, 0x65, 0xF2, 0x05, 0xAB, 0x7E, 0x0B, 0x4A, 0x3B, 0x89, 0xE4,
113  0x6C, 0xBF, 0xE8, 0x8B, 0x06, 0x18, 0x51, 0x14, 0x7F, 0x11, 0x5B, 0x5C, 0xFB,
114  0x97, 0xE1, 0xCF, 0x15, 0x62, 0x71, 0x70, 0x54, 0xE2, 0x12, 0xD6, 0xC7, 0xBB,
115  0x0D, 0x20, 0x5E, 0xDC, 0xE0, 0xD4, 0xF7, 0xCC, 0xC4, 0x2B, 0xF9, 0xEC, 0x2D,
116  0xF4, 0x6F, 0xB6, 0x99, 0x88, 0x81, 0x5A, 0xD9, 0xCA, 0x13, 0xA5, 0xE7, 0x47,
117  0xE6, 0x8E, 0x60, 0xE3, 0x3E, 0xB3, 0xF6, 0x72, 0xA2, 0x35, 0xA0, 0xD7, 0xCD,
118  0xB4, 0x2F, 0x6D, 0x2C, 0x26, 0x1F, 0x95, 0x87, 0x00, 0xD8, 0x34, 0x3F, 0x17,
119  0x25, 0x45, 0x27, 0x75, 0x92, 0xB8, 0xA3, 0xC8, 0xDE, 0xEB, 0xF8, 0xF3, 0xDB,
120  0x0A, 0x98, 0x83, 0x7B, 0xE5, 0xCB, 0x4C, 0x78, 0xD1 };
121
122 static BOOL URL_JustLocation(LPCWSTR str)
123 {
124     while(*str && (*str == L'/')) str++;
125     if (*str) {
126         while (*str && ((*str == L'-') ||
127                         (*str == L'.') ||
128                         isalnumW(*str))) str++;
129         if (*str == L'/') return FALSE;
130     }
131     return TRUE;
132 }
133
134
135 /*************************************************************************
136  *      @       [SHLWAPI.1]
137  *
138  * Parse a Url into its constituent parts.
139  *
140  * PARAMS
141  *  x [I] Url to parse
142  *  y [O] Undocumented structure holding the parsed information
143  *
144  * RETURNS
145  *  Success: S_OK. y contains the parsed Url details.
146  *  Failure: An HRESULT error code.
147  */
148 HRESULT WINAPI ParseURLA(LPCSTR x, PARSEDURLA *y)
149 {
150     DWORD cnt;
151     const SHL_2_inet_scheme *inet_pro;
152
153     y->nScheme = URL_SCHEME_INVALID;
154     if (y->cbSize != sizeof(*y)) return E_INVALIDARG;
155     /* FIXME: leading white space generates error of 0x80041001 which
156      *        is undefined
157      */
158     if (*x <= ' ') return 0x80041001;
159     cnt = 0;
160     y->cchProtocol = 0;
161     y->pszProtocol = x;
162     while (*x) {
163         if (*x == ':') {
164             y->cchProtocol = cnt;
165             cnt = -1;
166             y->pszSuffix = x+1;
167             break;
168         }
169         x++;
170         cnt++;
171     }
172
173     /* check for no scheme in string start */
174     /* (apparently schemes *must* be larger than a single character)  */
175     if ((*x == '\0') || (y->cchProtocol <= 1)) {
176         y->pszProtocol = NULL;
177         return 0x80041001;
178     }
179
180     /* found scheme, set length of remainder */
181     y->cchSuffix = lstrlenA(y->pszSuffix);
182
183     /* see if known scheme and return indicator number */
184     y->nScheme = URL_SCHEME_UNKNOWN;
185     inet_pro = shlwapi_schemes;
186     while (inet_pro->scheme_name) {
187         if (!strncasecmp(inet_pro->scheme_name, y->pszProtocol,
188                     min(y->cchProtocol, lstrlenA(inet_pro->scheme_name)))) {
189             y->nScheme = inet_pro->scheme_number;
190             break;
191         }
192         inet_pro++;
193     }
194     return S_OK;
195 }
196
197 /*************************************************************************
198  *      @       [SHLWAPI.2]
199  *
200  * Unicode version of ParseURLA.
201  */
202 HRESULT WINAPI ParseURLW(LPCWSTR x, PARSEDURLW *y)
203 {
204     DWORD cnt;
205     const SHL_2_inet_scheme *inet_pro;
206     LPSTR cmpstr;
207     INT len;
208
209     y->nScheme = URL_SCHEME_INVALID;
210     if (y->cbSize != sizeof(*y)) return E_INVALIDARG;
211     /* FIXME: leading white space generates error of 0x80041001 which
212      *        is undefined
213      */
214     if (*x <= L' ') return 0x80041001;
215     cnt = 0;
216     y->cchProtocol = 0;
217     y->pszProtocol = x;
218     while (*x) {
219         if (*x == L':') {
220             y->cchProtocol = cnt;
221             cnt = -1;
222             y->pszSuffix = x+1;
223             break;
224         }
225         x++;
226         cnt++;
227     }
228
229     /* check for no scheme in string start */
230     /* (apparently schemes *must* be larger than a single character)  */
231     if ((*x == L'\0') || (y->cchProtocol <= 1)) {
232         y->pszProtocol = NULL;
233         return 0x80041001;
234     }
235
236     /* found scheme, set length of remainder */
237     y->cchSuffix = lstrlenW(y->pszSuffix);
238
239     /* see if known scheme and return indicator number */
240     len = WideCharToMultiByte(0, 0, y->pszProtocol, y->cchProtocol, 0, 0, 0, 0);
241     cmpstr = (LPSTR)HeapAlloc(GetProcessHeap(), 0, len);
242     WideCharToMultiByte(0, 0, y->pszProtocol, y->cchProtocol, cmpstr, len, 0, 0);
243     y->nScheme = URL_SCHEME_UNKNOWN;
244     inet_pro = shlwapi_schemes;
245     while (inet_pro->scheme_name) {
246         if (!strncasecmp(inet_pro->scheme_name, cmpstr,
247                     min(len, lstrlenA(inet_pro->scheme_name)))) {
248             y->nScheme = inet_pro->scheme_number;
249             break;
250         }
251         inet_pro++;
252     }
253     HeapFree(GetProcessHeap(), 0, cmpstr);
254     return S_OK;
255 }
256
257 /*************************************************************************
258  *        UrlCanonicalizeA     [SHLWAPI.@]
259  *
260  * Canonicalize a Url.
261  *
262  * PARAMS
263  *  pszUrl            [I]   Url to cCanonicalize
264  *  pszCanonicalized  [O]   Destination for converted Url.
265  *  pcchCanonicalized [I/O] Length of pszUrl, destination for length of pszCanonicalized
266  *  dwFlags           [I]   Flags controlling the conversion.
267  *
268  * RETURNS
269  *  Success: S_OK. The pszCanonicalized contains the converted Url.
270  *  Failure: E_POINTER, if *pcchCanonicalized is too small.
271  *
272  * MSDN incorrectly describes the flags for this function. They should be:
273  *|    URL_DONT_ESCAPE_EXTRA_INFO    0x02000000
274  *|    URL_ESCAPE_SPACES_ONLY        0x04000000
275  *|    URL_ESCAPE_PERCENT            0x00001000
276  *|    URL_ESCAPE_UNSAFE             0x10000000
277  *|    URL_UNESCAPE                  0x10000000
278  *|    URL_DONT_SIMPLIFY             0x08000000
279  *|    URL_ESCAPE_SEGMENT_ONLY       0x00002000
280  */
281 HRESULT WINAPI UrlCanonicalizeA(LPCSTR pszUrl, LPSTR pszCanonicalized,
282         LPDWORD pcchCanonicalized, DWORD dwFlags)
283 {
284     LPWSTR base, canonical;
285     DWORD ret, len, len2;
286
287     TRACE("(%s %p %p 0x%08lx) using W version\n",
288           debugstr_a(pszUrl), pszCanonicalized,
289           pcchCanonicalized, dwFlags);
290
291     if(!pszUrl || !pszCanonicalized || !pcchCanonicalized)
292         return E_INVALIDARG;
293
294     base = HeapAlloc(GetProcessHeap(), 0,
295                               (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
296     canonical = base + INTERNET_MAX_URL_LENGTH;
297
298     MultiByteToWideChar(0, 0, pszUrl, -1, base, INTERNET_MAX_URL_LENGTH);
299     len = INTERNET_MAX_URL_LENGTH;
300
301     ret = UrlCanonicalizeW(base, canonical, &len, dwFlags);
302     if (ret != S_OK) {
303         HeapFree(GetProcessHeap(), 0, base);
304         return ret;
305     }
306
307     len2 = WideCharToMultiByte(0, 0, canonical, len, 0, 0, 0, 0);
308     if (len2 > *pcchCanonicalized) {
309         *pcchCanonicalized = len;
310         HeapFree(GetProcessHeap(), 0, base);
311         return E_POINTER;
312     }
313     WideCharToMultiByte(0, 0, canonical, len+1, pszCanonicalized,
314                         *pcchCanonicalized, 0, 0);
315     *pcchCanonicalized = len2;
316     HeapFree(GetProcessHeap(), 0, base);
317     return S_OK;
318 }
319
320 /*************************************************************************
321  *        UrlCanonicalizeW     [SHLWAPI.@]
322  *
323  * See UrlCanonicalizeA.
324  */
325 HRESULT WINAPI UrlCanonicalizeW(LPCWSTR pszUrl, LPWSTR pszCanonicalized,
326                                 LPDWORD pcchCanonicalized, DWORD dwFlags)
327 {
328     HRESULT hr = S_OK;
329     DWORD EscapeFlags;
330     LPWSTR lpszUrlCpy, wk1, wk2, mp, root;
331     INT nByteLen, state;
332     DWORD nLen;
333
334     TRACE("(%s %p %p 0x%08lx)\n", debugstr_w(pszUrl), pszCanonicalized,
335           pcchCanonicalized, dwFlags);
336
337     if(!pszUrl || !pszCanonicalized || !pcchCanonicalized)
338         return E_INVALIDARG;
339
340     nByteLen = (lstrlenW(pszUrl) + 1) * sizeof(WCHAR); /* length in bytes */
341     lpszUrlCpy = HeapAlloc(GetProcessHeap(), 0, nByteLen);
342
343     if (dwFlags & URL_DONT_SIMPLIFY)
344         memcpy(lpszUrlCpy, pszUrl, nByteLen);
345     else {
346
347         /*
348          * state =
349          *         0   initial  1,3
350          *         1   have 2[+] alnum  2,3
351          *         2   have scheme (found :)  4,6,3
352          *         3   failed (no location)
353          *         4   have //  5,3
354          *         5   have 1[+] alnum  6,3
355          *         6   have location (found /) save root location
356          */
357
358         wk1 = (LPWSTR)pszUrl;
359         wk2 = lpszUrlCpy;
360         state = 0;
361         while (*wk1) {
362             switch (state) {
363             case 0:
364                 if (!isalnumW(*wk1)) {state = 3; break;}
365                 *wk2++ = *wk1++;
366                 if (!isalnumW(*wk1)) {state = 3; break;}
367                 *wk2++ = *wk1++;
368                 state = 1;
369                 break;
370             case 1:
371                 *wk2++ = *wk1;
372                 if (*wk1++ == L':') state = 2;
373                 break;
374             case 2:
375                 if (*wk1 != L'/') {state = 3; break;}
376                 *wk2++ = *wk1++;
377                 if (*wk1 != L'/') {state = 6; break;}
378                 *wk2++ = *wk1++;
379                 state = 4;
380                 break;
381             case 3:
382                 strcpyW(wk2, wk1);
383                 wk1 += strlenW(wk1);
384                 wk2 += strlenW(wk2);
385                 break;
386             case 4:
387                 if (!isalnumW(*wk1) && (*wk1 != L'-') && (*wk1 != L'.')) {state = 3; break;}
388                 while(isalnumW(*wk1) || (*wk1 == L'-') || (*wk1 == L'.')) *wk2++ = *wk1++;
389                 state = 5;
390                 break;
391             case 5:
392                 if (*wk1 != L'/') {state = 3; break;}
393                 *wk2++ = *wk1++;
394                 state = 6;
395                 break;
396             case 6:
397                 /* Now at root location, cannot back up any more. */
398                 /* "root" will point at the '/' */
399                 root = wk2-1;
400                 while (*wk1) {
401                     TRACE("wk1=%c\n", (CHAR)*wk1);
402                     mp = strchrW(wk1, L'/');
403                     if (!mp) {
404                         strcpyW(wk2, wk1);
405                         wk1 += strlenW(wk1);
406                         wk2 += strlenW(wk2);
407                         continue;
408                     }
409                     nLen = mp - wk1 + 1;
410                     strncpyW(wk2, wk1, nLen);
411                     wk2 += nLen;
412                     wk1 += nLen;
413                     if (*wk1 == L'.') {
414                         TRACE("found '/.'\n");
415                         if (*(wk1+1) == L'/') {
416                             /* case of /./ -> skip the ./ */
417                             wk1 += 2;
418                         }
419                         else if (*(wk1+1) == L'.') {
420                             /* found /..  look for next / */
421                             TRACE("found '/..'\n");
422                             if (*(wk1+2) == L'/' || *(wk1+2) == L'?' || *(wk1+2) == L'#' || *(wk1+2) == 0) {
423                                 /* case /../ -> need to backup wk2 */
424                                 TRACE("found '/../'\n");
425                                 *(wk2-1) = L'\0';  /* set end of string */
426                                 mp = strrchrW(root, L'/');
427                                 if (mp && (mp >= root)) {
428                                     /* found valid backup point */
429                                     wk2 = mp + 1;
430                                     if(*(wk1+2) != L'/')
431                                         wk1 += 2;
432                                     else
433                                         wk1 += 3;
434                                 }
435                                 else {
436                                     /* did not find point, restore '/' */
437                                     *(wk2-1) = L'/';
438                                 }
439                             }
440                         }
441                     }
442                 }
443                 *wk2 = L'\0';
444                 break;
445             default:
446                 FIXME("how did we get here - state=%d\n", state);
447                 return E_INVALIDARG;
448             }
449         }
450         *wk2 = L'\0';
451         TRACE("Simplified, orig <%s>, simple <%s>\n",
452               debugstr_w(pszUrl), debugstr_w(lpszUrlCpy));
453     }
454     nLen = lstrlenW(lpszUrlCpy);
455     while ((nLen > 0) && ((lpszUrlCpy[nLen-1] == '\r')||(lpszUrlCpy[nLen-1] == '\n')))
456         lpszUrlCpy[--nLen]=0;
457
458     if(dwFlags & URL_UNESCAPE)
459         UrlUnescapeW(lpszUrlCpy, NULL, NULL, URL_UNESCAPE_INPLACE);
460
461     if((EscapeFlags = dwFlags & (URL_ESCAPE_UNSAFE |
462                                  URL_ESCAPE_SPACES_ONLY |
463                                  URL_ESCAPE_PERCENT |
464                                  URL_DONT_ESCAPE_EXTRA_INFO |
465                                  URL_ESCAPE_SEGMENT_ONLY ))) {
466         EscapeFlags &= ~URL_ESCAPE_UNSAFE;
467         hr = UrlEscapeW(lpszUrlCpy, pszCanonicalized, pcchCanonicalized,
468                         EscapeFlags);
469     } else { /* No escaping needed, just copy the string */
470         nLen = lstrlenW(lpszUrlCpy);
471         if(nLen < *pcchCanonicalized)
472             memcpy(pszCanonicalized, lpszUrlCpy, (nLen + 1)*sizeof(WCHAR));
473         else {
474             hr = E_POINTER;
475             nLen++;
476         }
477         *pcchCanonicalized = nLen;
478     }
479
480     HeapFree(GetProcessHeap(), 0, lpszUrlCpy);
481
482     if (hr == S_OK)
483         TRACE("result %s\n", debugstr_w(pszCanonicalized));
484
485     return hr;
486 }
487
488 /*************************************************************************
489  *        UrlCombineA     [SHLWAPI.@]
490  *
491  * Combine two Urls.
492  *
493  * PARAMS
494  *  pszBase      [I] Base Url
495  *  pszRelative  [I] Url to combine with pszBase
496  *  pszCombined  [O] Destination for combined Url
497  *  pcchCombined [O] Destination for length of pszCombined
498  *  dwFlags      [I] URL_ flags from "shlwapi.h"
499  *
500  * RETURNS
501  *  Success: S_OK. pszCombined contains the combined Url, pcchCombined
502  *           contains its length.
503  *  Failure: An HRESULT error code indicating the error.
504  */
505 HRESULT WINAPI UrlCombineA(LPCSTR pszBase, LPCSTR pszRelative,
506                            LPSTR pszCombined, LPDWORD pcchCombined,
507                            DWORD dwFlags)
508 {
509     LPWSTR base, relative, combined;
510     DWORD ret, len, len2;
511
512     TRACE("(base %s, Relative %s, Combine size %ld, flags %08lx) using W version\n",
513           debugstr_a(pszBase),debugstr_a(pszRelative),
514           pcchCombined?*pcchCombined:0,dwFlags);
515
516     if(!pszBase || !pszRelative || !pcchCombined)
517         return E_INVALIDARG;
518
519     base = (LPWSTR) HeapAlloc(GetProcessHeap(), 0,
520                               (3*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
521     relative = base + INTERNET_MAX_URL_LENGTH;
522     combined = relative + INTERNET_MAX_URL_LENGTH;
523
524     MultiByteToWideChar(0, 0, pszBase, -1, base, INTERNET_MAX_URL_LENGTH);
525     MultiByteToWideChar(0, 0, pszRelative, -1, relative, INTERNET_MAX_URL_LENGTH);
526     len = *pcchCombined;
527
528     ret = UrlCombineW(base, relative, pszCombined?combined:NULL, &len, dwFlags);
529     if (ret != S_OK) {
530         *pcchCombined = len;
531         HeapFree(GetProcessHeap(), 0, base);
532         return ret;
533     }
534
535     len2 = WideCharToMultiByte(0, 0, combined, len, 0, 0, 0, 0);
536     if (len2 > *pcchCombined) {
537         *pcchCombined = len2;
538         HeapFree(GetProcessHeap(), 0, base);
539         return E_POINTER;
540     }
541     WideCharToMultiByte(0, 0, combined, len+1, pszCombined, (*pcchCombined)+1,
542                         0, 0);
543     *pcchCombined = len2;
544     HeapFree(GetProcessHeap(), 0, base);
545     return S_OK;
546 }
547
548 /*************************************************************************
549  *        UrlCombineW     [SHLWAPI.@]
550  *
551  * See UrlCombineA.
552  */
553 HRESULT WINAPI UrlCombineW(LPCWSTR pszBase, LPCWSTR pszRelative,
554                            LPWSTR pszCombined, LPDWORD pcchCombined,
555                            DWORD dwFlags)
556 {
557     PARSEDURLW base, relative;
558     DWORD myflags, sizeloc = 0;
559     DWORD len, res1, res2, process_case = 0;
560     LPWSTR work, preliminary, mbase, mrelative;
561     static const WCHAR myfilestr[] = {'f','i','l','e',':','/','/','/','\0'};
562     static const WCHAR single_slash[] = {'/','\0'};
563     HRESULT ret;
564
565     TRACE("(base %s, Relative %s, Combine size %ld, flags %08lx)\n",
566           debugstr_w(pszBase),debugstr_w(pszRelative),
567           pcchCombined?*pcchCombined:0,dwFlags);
568
569     if(!pszBase || !pszRelative || !pcchCombined)
570         return E_INVALIDARG;
571
572     base.cbSize = sizeof(base);
573     relative.cbSize = sizeof(relative);
574
575     /* Get space for duplicates of the input and the output */
576     preliminary = HeapAlloc(GetProcessHeap(), 0, (3*INTERNET_MAX_URL_LENGTH) *
577                             sizeof(WCHAR));
578     mbase = preliminary + INTERNET_MAX_URL_LENGTH;
579     mrelative = mbase + INTERNET_MAX_URL_LENGTH;
580     *preliminary = L'\0';
581
582     /* Canonicalize the base input prior to looking for the scheme */
583     myflags = dwFlags & (URL_DONT_SIMPLIFY | URL_UNESCAPE);
584     len = INTERNET_MAX_URL_LENGTH;
585     ret = UrlCanonicalizeW(pszBase, mbase, &len, myflags);
586
587     /* Canonicalize the relative input prior to looking for the scheme */
588     len = INTERNET_MAX_URL_LENGTH;
589     ret = UrlCanonicalizeW(pszRelative, mrelative, &len, myflags);
590
591     /* See if the base has a scheme */
592     res1 = ParseURLW(mbase, &base);
593     if (res1) {
594         /* if pszBase has no scheme, then return pszRelative */
595         TRACE("no scheme detected in Base\n");
596         process_case = 1;
597     }
598     else do {
599
600         /* get size of location field (if it exists) */
601         work = (LPWSTR)base.pszSuffix;
602         sizeloc = 0;
603         if (*work++ == L'/') {
604             if (*work++ == L'/') {
605                 /* At this point have start of location and
606                  * it ends at next '/' or end of string.
607                  */
608                 while(*work && (*work != L'/')) work++;
609                 sizeloc = (DWORD)(work - base.pszSuffix);
610             }
611         }
612
613         /* Change .sizep2 to not have the last leaf in it,
614          * Note: we need to start after the location (if it exists)
615          */
616         work = strrchrW((base.pszSuffix+sizeloc), L'/');
617         if (work) {
618             len = (DWORD)(work - base.pszSuffix + 1);
619             base.cchSuffix = len;
620         }
621         /*
622          * At this point:
623          *    .pszSuffix   points to location (starting with '//')
624          *    .cchSuffix   length of location (above) and rest less the last
625          *                 leaf (if any)
626          *    sizeloc   length of location (above) up to but not including
627          *              the last '/'
628          */
629
630         res2 = ParseURLW(mrelative, &relative);
631         if (res2) {
632             /* no scheme in pszRelative */
633             TRACE("no scheme detected in Relative\n");
634             relative.pszSuffix = mrelative;  /* case 3,4,5 depends on this */
635             relative.cchSuffix = strlenW(mrelative);
636             if (*pszRelative  == L':') {
637                 /* case that is either left alone or uses pszBase */
638                 if (dwFlags & URL_PLUGGABLE_PROTOCOL) {
639                     process_case = 5;
640                     break;
641                 }
642                 process_case = 1;
643                 break;
644             }
645             if (isalnum(*mrelative) && (*(mrelative + 1) == L':')) {
646                 /* case that becomes "file:///" */
647                 strcpyW(preliminary, myfilestr);
648                 process_case = 1;
649                 break;
650             }
651             if ((*mrelative == L'/') && (*(mrelative+1) == L'/')) {
652                 /* pszRelative has location and rest */
653                 process_case = 3;
654                 break;
655             }
656             if (*mrelative == L'/') {
657                 /* case where pszRelative is root to location */
658                 process_case = 4;
659                 break;
660             }
661             process_case = (*base.pszSuffix == L'/') ? 5 : 3;
662             break;
663         }
664
665         /* handle cases where pszRelative has scheme */
666         if ((base.cchProtocol == relative.cchProtocol) &&
667             (strncmpW(base.pszProtocol, relative.pszProtocol, base.cchProtocol) == 0)) {
668
669             /* since the schemes are the same */
670             if ((*relative.pszSuffix == L'/') && (*(relative.pszSuffix+1) == L'/')) {
671                 /* case where pszRelative replaces location and following */
672                 process_case = 3;
673                 break;
674             }
675             if (*relative.pszSuffix == L'/') {
676                 /* case where pszRelative is root to location */
677                 process_case = 4;
678                 break;
679             }
680             /* case where scheme is followed by document path */
681             process_case = 5;
682             break;
683         }
684         if ((*relative.pszSuffix == L'/') && (*(relative.pszSuffix+1) == L'/')) {
685             /* case where pszRelative replaces scheme, location,
686              * and following and handles PLUGGABLE
687              */
688             process_case = 2;
689             break;
690         }
691         process_case = 1;
692         break;
693     } while(FALSE); /* a litte trick to allow easy exit from nested if's */
694
695
696     ret = S_OK;
697     switch (process_case) {
698
699     case 1:  /*
700               * Return pszRelative appended to what ever is in pszCombined,
701               * (which may the string "file:///"
702               */
703         strcatW(preliminary, mrelative);
704         break;
705
706     case 2:  /*
707               * Same as case 1, but if URL_PLUGGABLE_PROTOCOL was specified
708               * and pszRelative starts with "//", then append a "/"
709               */
710         strcpyW(preliminary, mrelative);
711         if (!(dwFlags & URL_PLUGGABLE_PROTOCOL) &&
712             URL_JustLocation(relative.pszSuffix))
713             strcatW(preliminary, single_slash);
714         break;
715
716     case 3:  /*
717               * Return the pszBase scheme with pszRelative. Basically
718               * keeps the scheme and replaces the domain and following.
719               */
720         strncpyW(preliminary, base.pszProtocol, base.cchProtocol + 1);
721         work = preliminary + base.cchProtocol + 1;
722         strcpyW(work, relative.pszSuffix);
723         if (!(dwFlags & URL_PLUGGABLE_PROTOCOL) &&
724             URL_JustLocation(relative.pszSuffix))
725             strcatW(work, single_slash);
726         break;
727
728     case 4:  /*
729               * Return the pszBase scheme and location but everything
730               * after the location is pszRelative. (Replace document
731               * from root on.)
732               */
733         strncpyW(preliminary, base.pszProtocol, base.cchProtocol+1+sizeloc);
734         work = preliminary + base.cchProtocol + 1 + sizeloc;
735         if (dwFlags & URL_PLUGGABLE_PROTOCOL)
736             *(work++) = L'/';
737         strcpyW(work, relative.pszSuffix);
738         break;
739
740     case 5:  /*
741               * Return the pszBase without its document (if any) and
742               * append pszRelative after its scheme.
743               */
744         strncpyW(preliminary, base.pszProtocol, base.cchProtocol+1+base.cchSuffix);
745         work = preliminary + base.cchProtocol+1+base.cchSuffix - 1;
746         if (*work++ != L'/')
747             *(work++) = L'/';
748         strcpyW(work, relative.pszSuffix);
749         break;
750
751     default:
752         FIXME("How did we get here????? process_case=%ld\n", process_case);
753         ret = E_INVALIDARG;
754     }
755
756     if (ret == S_OK) {
757         /* Reuse mrelative as temp storage as its already allocated and not needed anymore */
758         ret = UrlCanonicalizeW(preliminary, mrelative, pcchCombined, dwFlags);
759         if(SUCCEEDED(ret) && pszCombined) {
760             lstrcpyW(pszCombined, mrelative);
761         }
762         TRACE("return-%ld len=%ld, %s\n",
763               process_case, *pcchCombined, debugstr_w(pszCombined));
764     }
765     HeapFree(GetProcessHeap(), 0, preliminary);
766     return ret;
767 }
768
769 /*************************************************************************
770  *      UrlEscapeA      [SHLWAPI.@]
771  */
772
773 HRESULT WINAPI UrlEscapeA(
774         LPCSTR pszUrl,
775         LPSTR pszEscaped,
776         LPDWORD pcchEscaped,
777         DWORD dwFlags)
778 {
779     WCHAR bufW[INTERNET_MAX_URL_LENGTH];
780     WCHAR *escapedW = bufW;
781     UNICODE_STRING urlW;
782     HRESULT ret;
783     DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
784
785     if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl))
786         return E_INVALIDARG;
787     if((ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags)) == E_POINTER) {
788         escapedW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
789         ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags);
790     }
791     if(ret == S_OK) {
792         RtlUnicodeToMultiByteSize(&lenA, escapedW, lenW * sizeof(WCHAR));
793         if(*pcchEscaped > lenA) {
794             RtlUnicodeToMultiByteN(pszEscaped, *pcchEscaped - 1, &lenA, escapedW, lenW * sizeof(WCHAR));
795             pszEscaped[lenA] = 0;
796             *pcchEscaped = lenA;
797         } else {
798             *pcchEscaped = lenA + 1;
799             ret = E_POINTER;
800         }
801     }
802     if(escapedW != bufW) HeapFree(GetProcessHeap(), 0, escapedW);
803     RtlFreeUnicodeString(&urlW);
804     return ret;
805 }
806
807 #define WINE_URL_BASH_AS_SLASH    0x01
808 #define WINE_URL_COLLAPSE_SLASHES 0x02
809 #define WINE_URL_ESCAPE_SLASH     0x04
810 #define WINE_URL_ESCAPE_HASH      0x08
811 #define WINE_URL_ESCAPE_QUESTION  0x10
812 #define WINE_URL_STOP_ON_HASH     0x20
813 #define WINE_URL_STOP_ON_QUESTION 0x40
814
815 static inline BOOL URL_NeedEscapeW(WCHAR ch, DWORD dwFlags, DWORD int_flags)
816 {
817
818     if (isalnumW(ch))
819         return FALSE;
820
821     if(dwFlags & URL_ESCAPE_SPACES_ONLY) {
822         if(ch == ' ')
823             return TRUE;
824         else
825             return FALSE;
826     }
827
828     if ((dwFlags & URL_ESCAPE_PERCENT) && (ch == '%'))
829         return TRUE;
830
831     if (ch <= 31 || ch >= 127)
832         return TRUE;
833
834     else {
835         switch (ch) {
836         case ' ':
837         case '<':
838         case '>':
839         case '\"':
840         case '{':
841         case '}':
842         case '|':
843         case '\\':
844         case '^':
845         case ']':
846         case '[':
847         case '`':
848         case '&':
849             return TRUE;
850
851         case '/':
852             if (int_flags & WINE_URL_ESCAPE_SLASH) return TRUE;
853             return FALSE;
854
855         case '?':
856             if (int_flags & WINE_URL_ESCAPE_QUESTION) return TRUE;
857             return FALSE;
858
859         case '#':
860             if (int_flags & WINE_URL_ESCAPE_HASH) return TRUE;
861             return FALSE;
862
863         default:
864             return FALSE;
865         }
866     }
867 }
868
869
870 /*************************************************************************
871  *      UrlEscapeW      [SHLWAPI.@]
872  *
873  * Converts unsafe characters in a Url into escape sequences.
874  *
875  * PARAMS
876  *  pszUrl      [I]   Url to modify
877  *  pszEscaped  [O]   Destination for modified Url
878  *  pcchEscaped [I/O] Length of pszUrl, destination for length of pszEscaped
879  *  dwFlags     [I]   URL_ flags from "shlwapi.h"
880  *
881  * RETURNS
882  *  Success: S_OK. pszEscaped contains the escaped Url, pcchEscaped
883  *           contains its length.
884  *  Failure: E_POINTER, if pszEscaped is not large enough. In this case
885  *           pcchEscaped is set to the required length.
886  *
887  * Converts unsafe characters into their escape sequences.
888  *
889  * NOTES
890  * - By default this function stops converting at the first '?' or
891  *  '#' character.
892  * - If dwFlags contains URL_ESCAPE_SPACES_ONLY then only spaces are
893  *   converted, but the conversion continues past a '?' or '#'.
894  * - Note that this function did not work well (or at all) in shlwapi version 4.
895  *
896  * BUGS
897  *  Only the following flags are implemented:
898  *|     URL_ESCAPE_SPACES_ONLY
899  *|     URL_DONT_ESCAPE_EXTRA_INFO
900  *|     URL_ESCAPE_SEGMENT_ONLY
901  *|     URL_ESCAPE_PERCENT
902  */
903 HRESULT WINAPI UrlEscapeW(
904         LPCWSTR pszUrl,
905         LPWSTR pszEscaped,
906         LPDWORD pcchEscaped,
907         DWORD dwFlags)
908 {
909     LPCWSTR src;
910     DWORD needed = 0, ret;
911     BOOL stop_escaping = FALSE;
912     WCHAR next[5], *dst = pszEscaped;
913     INT len;
914     PARSEDURLW parsed_url;
915     DWORD int_flags;
916     DWORD slashes = 0;
917     static const WCHAR localhost[] = {'l','o','c','a','l','h','o','s','t',0};
918
919     TRACE("(%s %p %p 0x%08lx)\n", debugstr_w(pszUrl), pszEscaped,
920           pcchEscaped, dwFlags);
921
922     if(!pszUrl || !pszEscaped || !pcchEscaped)
923         return E_INVALIDARG;
924
925     if(dwFlags & ~(URL_ESCAPE_SPACES_ONLY |
926                    URL_ESCAPE_SEGMENT_ONLY |
927                    URL_DONT_ESCAPE_EXTRA_INFO |
928                    URL_ESCAPE_PERCENT))
929         FIXME("Unimplemented flags: %08lx\n", dwFlags);
930
931     /* fix up flags */
932     if (dwFlags & URL_ESCAPE_SPACES_ONLY)
933         /* if SPACES_ONLY specified, reset the other controls */
934         dwFlags &= ~(URL_DONT_ESCAPE_EXTRA_INFO |
935                      URL_ESCAPE_PERCENT |
936                      URL_ESCAPE_SEGMENT_ONLY);
937
938     else
939         /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
940         dwFlags |= URL_DONT_ESCAPE_EXTRA_INFO;
941
942
943     int_flags = 0;
944     if(dwFlags & URL_ESCAPE_SEGMENT_ONLY) {
945         int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH;
946     } else {
947         parsed_url.cbSize = sizeof(parsed_url);
948         if(ParseURLW(pszUrl, &parsed_url) != S_OK)
949             parsed_url.nScheme = URL_SCHEME_INVALID;
950
951         TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol));
952
953         if(dwFlags & URL_DONT_ESCAPE_EXTRA_INFO)
954             int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION;
955
956         switch(parsed_url.nScheme) {
957         case URL_SCHEME_FILE:
958             int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH;
959             int_flags &= ~WINE_URL_STOP_ON_HASH;
960             break;
961
962         case URL_SCHEME_HTTP:
963         case URL_SCHEME_HTTPS:
964             int_flags |= WINE_URL_BASH_AS_SLASH;
965             if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\')
966                 int_flags |= WINE_URL_ESCAPE_SLASH;
967             break;
968
969         case URL_SCHEME_MAILTO:
970             int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH;
971             int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH);
972             break;
973
974         case URL_SCHEME_INVALID:
975             break;
976
977         case URL_SCHEME_FTP:
978         default:
979             if(parsed_url.pszSuffix[0] != '/')
980                 int_flags |= WINE_URL_ESCAPE_SLASH;
981             break;
982         }
983     }
984
985     for(src = pszUrl; *src; ) {
986         WCHAR cur = *src;
987         len = 0;
988         
989         if((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == pszUrl + parsed_url.cchProtocol + 1) {
990             int localhost_len = sizeof(localhost)/sizeof(WCHAR) - 1;
991             while(cur == '/' || cur == '\\') {
992                 slashes++;
993                 cur = *++src;
994             }
995             if(slashes == 2 && !strncmpiW(src, localhost, localhost_len)) { /* file://localhost/ -> file:/// */
996                 if(*(src + localhost_len) == '/' || *(src + localhost_len) == '\\')
997                 src += localhost_len + 1;
998                 slashes = 3;
999             }
1000
1001             switch(slashes) {
1002             case 1:
1003             case 3:
1004                 next[0] = next[1] = next[2] = '/';
1005                 len = 3;
1006                 break;
1007             case 0:
1008                 len = 0;
1009                 break;
1010             default:
1011                 next[0] = next[1] = '/';
1012                 len = 2;
1013                 break;
1014             }
1015         }
1016         if(len == 0) {
1017
1018             if(cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH))
1019                 stop_escaping = TRUE;
1020
1021             if(cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION))
1022                 stop_escaping = TRUE;
1023
1024             if(cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/';
1025
1026             if(URL_NeedEscapeW(cur, dwFlags, int_flags) && stop_escaping == FALSE) {
1027                 next[0] = L'%';
1028                 next[1] = hexDigits[(cur >> 4) & 0xf];
1029                 next[2] = hexDigits[cur & 0xf];
1030                 len = 3;
1031             } else {
1032                 next[0] = cur;
1033                 len = 1;
1034             }
1035             src++;
1036         }
1037
1038         if(needed + len <= *pcchEscaped) {
1039             memcpy(dst, next, len*sizeof(WCHAR));
1040             dst += len;
1041         }
1042         needed += len;
1043     }
1044
1045     if(needed < *pcchEscaped) {
1046         *dst = '\0';
1047         ret = S_OK;
1048     } else {
1049         needed++; /* add one for the '\0' */
1050         ret = E_POINTER;
1051     }
1052     *pcchEscaped = needed;
1053     return ret;
1054 }
1055
1056
1057 /*************************************************************************
1058  *      UrlUnescapeA    [SHLWAPI.@]
1059  *
1060  * Converts Url escape sequences back to ordinary characters.
1061  *
1062  * PARAMS
1063  *  pszUrl        [I/O]  Url to convert
1064  *  pszUnescaped  [O]    Destination for converted Url
1065  *  pcchUnescaped [I/O]  Size of output string
1066  *  dwFlags       [I]    URL_ESCAPE_ Flags from "shlwapi.h"
1067  *
1068  * RETURNS
1069  *  Success: S_OK. The converted value is in pszUnescaped, or in pszUrl if
1070  *           dwFlags includes URL_ESCAPE_INPLACE.
1071  *  Failure: E_POINTER if the converted Url is bigger than pcchUnescaped. In
1072  *           this case pcchUnescaped is set to the size required.
1073  * NOTES
1074  *  If dwFlags includes URL_DONT_ESCAPE_EXTRA_INFO, the conversion stops at
1075  *  the first occurrence of either a '?' or '#' character.
1076  */
1077 HRESULT WINAPI UrlUnescapeA(
1078         LPSTR pszUrl,
1079         LPSTR pszUnescaped,
1080         LPDWORD pcchUnescaped,
1081         DWORD dwFlags)
1082 {
1083     char *dst, next;
1084     LPCSTR src;
1085     HRESULT ret;
1086     DWORD needed;
1087     BOOL stop_unescaping = FALSE;
1088
1089     TRACE("(%s, %p, %p, 0x%08lx)\n", debugstr_a(pszUrl), pszUnescaped,
1090           pcchUnescaped, dwFlags);
1091
1092     if(!pszUrl || !pszUnescaped || !pcchUnescaped)
1093         return E_INVALIDARG;
1094
1095     if(dwFlags & URL_UNESCAPE_INPLACE)
1096         dst = pszUrl;
1097     else
1098         dst = pszUnescaped;
1099
1100     for(src = pszUrl, needed = 0; *src; src++, needed++) {
1101         if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1102            (*src == '#' || *src == '?')) {
1103             stop_unescaping = TRUE;
1104             next = *src;
1105         } else if(*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2))
1106                   && stop_unescaping == FALSE) {
1107             INT ih;
1108             char buf[3];
1109             memcpy(buf, src + 1, 2);
1110             buf[2] = '\0';
1111             ih = strtol(buf, NULL, 16);
1112             next = (CHAR) ih;
1113             src += 2; /* Advance to end of escape */
1114         } else
1115             next = *src;
1116
1117         if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1118             *dst++ = next;
1119     }
1120
1121     if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1122         *dst = '\0';
1123         ret = S_OK;
1124     } else {
1125         needed++; /* add one for the '\0' */
1126         ret = E_POINTER;
1127     }
1128     if(!(dwFlags & URL_UNESCAPE_INPLACE))
1129         *pcchUnescaped = needed;
1130
1131     if (ret == S_OK) {
1132         TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1133               debugstr_a(pszUrl) : debugstr_a(pszUnescaped));
1134     }
1135
1136     return ret;
1137 }
1138
1139 /*************************************************************************
1140  *      UrlUnescapeW    [SHLWAPI.@]
1141  *
1142  * See UrlUnescapeA.
1143  */
1144 HRESULT WINAPI UrlUnescapeW(
1145         LPWSTR pszUrl,
1146         LPWSTR pszUnescaped,
1147         LPDWORD pcchUnescaped,
1148         DWORD dwFlags)
1149 {
1150     WCHAR *dst, next;
1151     LPCWSTR src;
1152     HRESULT ret;
1153     DWORD needed;
1154     BOOL stop_unescaping = FALSE;
1155
1156     TRACE("(%s, %p, %p, 0x%08lx)\n", debugstr_w(pszUrl), pszUnescaped,
1157           pcchUnescaped, dwFlags);
1158
1159     if(!pszUrl || !pszUnescaped || !pcchUnescaped)
1160         return E_INVALIDARG;
1161
1162     if(dwFlags & URL_UNESCAPE_INPLACE)
1163         dst = pszUrl;
1164     else
1165         dst = pszUnescaped;
1166
1167     for(src = pszUrl, needed = 0; *src; src++, needed++) {
1168         if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1169            (*src == L'#' || *src == L'?')) {
1170             stop_unescaping = TRUE;
1171             next = *src;
1172         } else if(*src == L'%' && isxdigitW(*(src + 1)) && isxdigitW(*(src + 2))
1173                   && stop_unescaping == FALSE) {
1174             INT ih;
1175             WCHAR buf[5] = {'0','x',0};
1176             memcpy(buf + 2, src + 1, 2*sizeof(WCHAR));
1177             buf[4] = 0;
1178             StrToIntExW(buf, STIF_SUPPORT_HEX, &ih);
1179             next = (WCHAR) ih;
1180             src += 2; /* Advance to end of escape */
1181         } else
1182             next = *src;
1183
1184         if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1185             *dst++ = next;
1186     }
1187
1188     if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1189         *dst = L'\0';
1190         ret = S_OK;
1191     } else {
1192         needed++; /* add one for the '\0' */
1193         ret = E_POINTER;
1194     }
1195     if(!(dwFlags & URL_UNESCAPE_INPLACE))
1196         *pcchUnescaped = needed;
1197
1198     if (ret == S_OK) {
1199         TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1200               debugstr_w(pszUrl) : debugstr_w(pszUnescaped));
1201     }
1202
1203     return ret;
1204 }
1205
1206 /*************************************************************************
1207  *      UrlGetLocationA         [SHLWAPI.@]
1208  *
1209  * Get the location from a Url.
1210  *
1211  * PARAMS
1212  *  pszUrl [I] Url to get the location from
1213  *
1214  * RETURNS
1215  *  A pointer to the start of the location in pszUrl, or NULL if there is
1216  *  no location.
1217  *
1218  * NOTES
1219  *  - MSDN erroneously states that "The location is the segment of the Url
1220  *    starting with a '?' or '#' character". Neither V4 nor V5 of shlwapi.dll
1221  *    stop at '?' and always return a NULL in this case.
1222  *  - MSDN also erroneously states that "If a file URL has a query string,
1223  *    the returned string is the query string". In all tested cases, if the
1224  *    Url starts with "fi" then a NULL is returned. V5 gives the following results:
1225  *|       Result   Url
1226  *|       ------   ---
1227  *|       NULL     file://aa/b/cd#hohoh
1228  *|       #hohoh   http://aa/b/cd#hohoh
1229  *|       NULL     fi://aa/b/cd#hohoh
1230  *|       #hohoh   ff://aa/b/cd#hohoh
1231  */
1232 LPCSTR WINAPI UrlGetLocationA(
1233         LPCSTR pszUrl)
1234 {
1235     PARSEDURLA base;
1236     DWORD res1;
1237
1238     base.cbSize = sizeof(base);
1239     res1 = ParseURLA(pszUrl, &base);
1240     if (res1) return NULL;  /* invalid scheme */
1241
1242     /* if scheme is file: then never return pointer */
1243     if (strncmp(base.pszProtocol, "file", min(4,base.cchProtocol)) == 0) return NULL;
1244
1245     /* Look for '#' and return its addr */
1246     return strchr(base.pszSuffix, '#');
1247 }
1248
1249 /*************************************************************************
1250  *      UrlGetLocationW         [SHLWAPI.@]
1251  *
1252  * See UrlGetLocationA.
1253  */
1254 LPCWSTR WINAPI UrlGetLocationW(
1255         LPCWSTR pszUrl)
1256 {
1257     PARSEDURLW base;
1258     DWORD res1;
1259
1260     base.cbSize = sizeof(base);
1261     res1 = ParseURLW(pszUrl, &base);
1262     if (res1) return NULL;  /* invalid scheme */
1263
1264     /* if scheme is file: then never return pointer */
1265     if (strncmpW(base.pszProtocol, fileW, min(4,base.cchProtocol)) == 0) return NULL;
1266
1267     /* Look for '#' and return its addr */
1268     return strchrW(base.pszSuffix, L'#');
1269 }
1270
1271 /*************************************************************************
1272  *      UrlCompareA     [SHLWAPI.@]
1273  *
1274  * Compare two Urls.
1275  *
1276  * PARAMS
1277  *  pszUrl1      [I] First Url to compare
1278  *  pszUrl2      [I] Url to compare to pszUrl1
1279  *  fIgnoreSlash [I] TRUE = compare only up to a final slash
1280  *
1281  * RETURNS
1282  *  less than zero, zero, or greater than zero indicating pszUrl2 is greater
1283  *  than, equal to, or less than pszUrl1 respectively.
1284  */
1285 INT WINAPI UrlCompareA(
1286         LPCSTR pszUrl1,
1287         LPCSTR pszUrl2,
1288         BOOL fIgnoreSlash)
1289 {
1290     INT ret, len, len1, len2;
1291
1292     if (!fIgnoreSlash)
1293         return strcmp(pszUrl1, pszUrl2);
1294     len1 = strlen(pszUrl1);
1295     if (pszUrl1[len1-1] == '/') len1--;
1296     len2 = strlen(pszUrl2);
1297     if (pszUrl2[len2-1] == '/') len2--;
1298     if (len1 == len2)
1299         return strncmp(pszUrl1, pszUrl2, len1);
1300     len = min(len1, len2);
1301     ret = strncmp(pszUrl1, pszUrl2, len);
1302     if (ret) return ret;
1303     if (len1 > len2) return 1;
1304     return -1;
1305 }
1306
1307 /*************************************************************************
1308  *      UrlCompareW     [SHLWAPI.@]
1309  *
1310  * See UrlCompareA.
1311  */
1312 INT WINAPI UrlCompareW(
1313         LPCWSTR pszUrl1,
1314         LPCWSTR pszUrl2,
1315         BOOL fIgnoreSlash)
1316 {
1317     INT ret;
1318     size_t len, len1, len2;
1319
1320     if (!fIgnoreSlash)
1321         return strcmpW(pszUrl1, pszUrl2);
1322     len1 = strlenW(pszUrl1);
1323     if (pszUrl1[len1-1] == '/') len1--;
1324     len2 = strlenW(pszUrl2);
1325     if (pszUrl2[len2-1] == '/') len2--;
1326     if (len1 == len2)
1327         return strncmpW(pszUrl1, pszUrl2, len1);
1328     len = min(len1, len2);
1329     ret = strncmpW(pszUrl1, pszUrl2, len);
1330     if (ret) return ret;
1331     if (len1 > len2) return 1;
1332     return -1;
1333 }
1334
1335 /*************************************************************************
1336  *      HashData        [SHLWAPI.@]
1337  *
1338  * Hash an input block into a variable sized digest.
1339  *
1340  * PARAMS
1341  *  lpSrc    [I] Input block
1342  *  nSrcLen  [I] Length of lpSrc
1343  *  lpDest   [I] Output for hash digest
1344  *  nDestLen [I] Length of lpDest
1345  *
1346  * RETURNS
1347  *  Success: TRUE. lpDest is filled with the computed hash value.
1348  *  Failure: FALSE, if any argument is invalid.
1349  */
1350 HRESULT WINAPI HashData(const unsigned char *lpSrc, DWORD nSrcLen,
1351                      unsigned char *lpDest, DWORD nDestLen)
1352 {
1353   INT srcCount = nSrcLen - 1, destCount = nDestLen - 1;
1354
1355   if (IsBadReadPtr(lpSrc, nSrcLen) ||
1356       IsBadWritePtr(lpDest, nDestLen))
1357     return E_INVALIDARG;
1358
1359   while (destCount >= 0)
1360   {
1361     lpDest[destCount] = (destCount & 0xff);
1362     destCount--;
1363   }
1364
1365   while (srcCount >= 0)
1366   {
1367     destCount = nDestLen - 1;
1368     while (destCount >= 0)
1369     {
1370       lpDest[destCount] = HashDataLookup[lpSrc[srcCount] ^ lpDest[destCount]];
1371       destCount--;
1372     }
1373     srcCount--;
1374   }
1375   return S_OK;
1376 }
1377
1378 /*************************************************************************
1379  *      UrlHashA        [SHLWAPI.@]
1380  *
1381  * Produce a Hash from a Url.
1382  *
1383  * PARAMS
1384  *  pszUrl   [I] Url to hash
1385  *  lpDest   [O] Destinationh for hash
1386  *  nDestLen [I] Length of lpDest
1387  * 
1388  * RETURNS
1389  *  Success: S_OK. lpDest is filled with the computed hash value.
1390  *  Failure: E_INVALIDARG, if any argument is invalid.
1391  */
1392 HRESULT WINAPI UrlHashA(LPCSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1393 {
1394   if (IsBadStringPtrA(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1395     return E_INVALIDARG;
1396
1397   HashData((PBYTE)pszUrl, (int)strlen(pszUrl), lpDest, nDestLen);
1398   return S_OK;
1399 }
1400
1401 /*************************************************************************
1402  * UrlHashW     [SHLWAPI.@]
1403  *
1404  * See UrlHashA.
1405  */
1406 HRESULT WINAPI UrlHashW(LPCWSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1407 {
1408   char szUrl[MAX_PATH];
1409
1410   TRACE("(%s,%p,%ld)\n",debugstr_w(pszUrl), lpDest, nDestLen);
1411
1412   if (IsBadStringPtrW(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1413     return E_INVALIDARG;
1414
1415   /* Win32 hashes the data as an ASCII string, presumably so that both A+W
1416    * return the same digests for the same URL.
1417    */
1418   WideCharToMultiByte(0, 0, pszUrl, -1, szUrl, MAX_PATH, 0, 0);
1419   HashData((PBYTE)szUrl, (int)strlen(szUrl), lpDest, nDestLen);
1420   return S_OK;
1421 }
1422
1423 /*************************************************************************
1424  *      UrlApplySchemeA [SHLWAPI.@]
1425  *
1426  * Apply a scheme to a Url.
1427  *
1428  * PARAMS
1429  *  pszIn   [I]   Url to apply scheme to
1430  *  pszOut  [O]   Destination for modified Url
1431  *  pcchOut [I/O] Length of pszOut/destination for length of pszOut
1432  *  dwFlags [I]   URL_ flags from "shlwapi.h"
1433  *
1434  * RETURNS
1435  *  Success: S_OK: pszOut contains the modified Url, pcchOut contains its length.
1436  *  Failure: An HRESULT error code describing the error.
1437  */
1438 HRESULT WINAPI UrlApplySchemeA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1439 {
1440     LPWSTR in, out;
1441     DWORD ret, len, len2;
1442
1443     TRACE("(in %s, out size %ld, flags %08lx) using W version\n",
1444           debugstr_a(pszIn), *pcchOut, dwFlags);
1445
1446     in = HeapAlloc(GetProcessHeap(), 0,
1447                               (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
1448     out = in + INTERNET_MAX_URL_LENGTH;
1449
1450     MultiByteToWideChar(0, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
1451     len = INTERNET_MAX_URL_LENGTH;
1452
1453     ret = UrlApplySchemeW(in, out, &len, dwFlags);
1454     if ((ret != S_OK) && (ret != S_FALSE)) {
1455         HeapFree(GetProcessHeap(), 0, in);
1456         return ret;
1457     }
1458
1459     len2 = WideCharToMultiByte(0, 0, out, len+1, 0, 0, 0, 0);
1460     if (len2 > *pcchOut) {
1461         *pcchOut = len2;
1462         HeapFree(GetProcessHeap(), 0, in);
1463         return E_POINTER;
1464     }
1465     WideCharToMultiByte(0, 0, out, len+1, pszOut, *pcchOut, 0, 0);
1466     *pcchOut = len2;
1467     HeapFree(GetProcessHeap(), 0, in);
1468     return ret;
1469 }
1470
1471 static HRESULT URL_GuessScheme(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1472 {
1473     HKEY newkey;
1474     BOOL j;
1475     INT index;
1476     DWORD value_len, data_len, dwType, i;
1477     WCHAR reg_path[MAX_PATH];
1478     WCHAR value[MAX_PATH], data[MAX_PATH];
1479     WCHAR Wxx, Wyy;
1480
1481     MultiByteToWideChar(0, 0,
1482               "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes",
1483                         -1, reg_path, MAX_PATH);
1484     RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
1485     index = 0;
1486     while(value_len = data_len = MAX_PATH,
1487           RegEnumValueW(newkey, index, value, &value_len,
1488                         0, &dwType, (LPVOID)data, &data_len) == 0) {
1489         TRACE("guess %d %s is %s\n",
1490               index, debugstr_w(value), debugstr_w(data));
1491
1492         j = FALSE;
1493         for(i=0; i<value_len; i++) {
1494             Wxx = pszIn[i];
1495             Wyy = value[i];
1496             /* remember that TRUE is not-equal */
1497             j = ChrCmpIW(Wxx, Wyy);
1498             if (j) break;
1499         }
1500         if ((i == value_len) && !j) {
1501             if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1502                 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1503                 RegCloseKey(newkey);
1504                 return E_POINTER;
1505             }
1506             strcpyW(pszOut, data);
1507             strcatW(pszOut, pszIn);
1508             *pcchOut = strlenW(pszOut);
1509             TRACE("matched and set to %s\n", debugstr_w(pszOut));
1510             RegCloseKey(newkey);
1511             return S_OK;
1512         }
1513         index++;
1514     }
1515     RegCloseKey(newkey);
1516     return -1;
1517 }
1518
1519 static HRESULT URL_ApplyDefault(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1520 {
1521     HKEY newkey;
1522     DWORD data_len, dwType;
1523     WCHAR reg_path[MAX_PATH];
1524     WCHAR value[MAX_PATH], data[MAX_PATH];
1525
1526     /* get and prepend default */
1527     MultiByteToWideChar(0, 0,
1528          "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\DefaultPrefix",
1529                         -1, reg_path, MAX_PATH);
1530     RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
1531     data_len = MAX_PATH;
1532     value[0] = L'@';
1533     value[1] = L'\0';
1534     RegQueryValueExW(newkey, value, 0, &dwType, (LPBYTE)data, &data_len);
1535     RegCloseKey(newkey);
1536     if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1537         *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1538         return E_POINTER;
1539     }
1540     strcpyW(pszOut, data);
1541     strcatW(pszOut, pszIn);
1542     *pcchOut = strlenW(pszOut);
1543     TRACE("used default %s\n", debugstr_w(pszOut));
1544     return S_OK;
1545 }
1546
1547 /*************************************************************************
1548  *      UrlApplySchemeW [SHLWAPI.@]
1549  *
1550  * See UrlApplySchemeA.
1551  */
1552 HRESULT WINAPI UrlApplySchemeW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1553 {
1554     PARSEDURLW in_scheme;
1555     DWORD res1;
1556     HRESULT ret;
1557
1558     TRACE("(in %s, out size %ld, flags %08lx)\n",
1559           debugstr_w(pszIn), *pcchOut, dwFlags);
1560
1561     if (dwFlags & URL_APPLY_GUESSFILE) {
1562         FIXME("(%s %p %p(%ld) 0x%08lx): stub URL_APPLY_GUESSFILE not implemented\n",
1563               debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwFlags);
1564         strcpyW(pszOut, pszIn);
1565         *pcchOut = strlenW(pszOut);
1566         return S_FALSE;
1567     }
1568
1569     in_scheme.cbSize = sizeof(in_scheme);
1570     /* See if the base has a scheme */
1571     res1 = ParseURLW(pszIn, &in_scheme);
1572     if (res1) {
1573         /* no scheme in input, need to see if we need to guess */
1574         if (dwFlags & URL_APPLY_GUESSSCHEME) {
1575             if ((ret = URL_GuessScheme(pszIn, pszOut, pcchOut)) != -1)
1576                 return ret;
1577         }
1578     }
1579     else {
1580         /* we have a scheme, see if valid (known scheme) */
1581         if (in_scheme.nScheme) {
1582             /* have valid scheme, so just copy and exit */
1583             if (strlenW(pszIn) + 1 > *pcchOut) {
1584                 *pcchOut = strlenW(pszIn) + 1;
1585                 return E_POINTER;
1586             }
1587             strcpyW(pszOut, pszIn);
1588             *pcchOut = strlenW(pszOut);
1589             TRACE("valid scheme, returing copy\n");
1590             return S_OK;
1591         }
1592     }
1593
1594     /* If we are here, then either invalid scheme,
1595      * or no scheme and can't/failed guess.
1596      */
1597     if ( ( ((res1 == 0) && (dwFlags & URL_APPLY_FORCEAPPLY)) ||
1598            ((res1 != 0)) ) &&
1599          (dwFlags & URL_APPLY_DEFAULT)) {
1600         /* find and apply default scheme */
1601         return URL_ApplyDefault(pszIn, pszOut, pcchOut);
1602     }
1603
1604     /* just copy and give proper return code */
1605     if (strlenW(pszIn) + 1 > *pcchOut) {
1606         *pcchOut = strlenW(pszIn) + 1;
1607         return E_POINTER;
1608     }
1609     strcpyW(pszOut, pszIn);
1610     *pcchOut = strlenW(pszOut);
1611     TRACE("returning copy, left alone\n");
1612     return S_FALSE;
1613 }
1614
1615 /*************************************************************************
1616  *      UrlIsA          [SHLWAPI.@]
1617  *
1618  * Determine if a Url is of a certain class.
1619  *
1620  * PARAMS
1621  *  pszUrl [I] Url to check
1622  *  Urlis  [I] URLIS_ constant from "shlwapi.h"
1623  *
1624  * RETURNS
1625  *  TRUE if pszUrl belongs to the class type in Urlis.
1626  *  FALSE Otherwise.
1627  */
1628 BOOL WINAPI UrlIsA(LPCSTR pszUrl, URLIS Urlis)
1629 {
1630     PARSEDURLA base;
1631     DWORD res1;
1632     LPCSTR last;
1633
1634     switch (Urlis) {
1635
1636     case URLIS_OPAQUE:
1637         base.cbSize = sizeof(base);
1638         res1 = ParseURLA(pszUrl, &base);
1639         if (res1) return FALSE;  /* invalid scheme */
1640         if ((*base.pszSuffix == '/') && (*(base.pszSuffix+1) == '/'))
1641             /* has scheme followed by 2 '/' */
1642             return FALSE;
1643         return TRUE;
1644
1645     case URLIS_FILEURL:
1646         return !StrCmpNA("file://", pszUrl, 7);
1647
1648     case URLIS_DIRECTORY:
1649         last = pszUrl + strlen(pszUrl) - 1;
1650         return (last >= pszUrl && (*last == '/' || *last == '\\' ));
1651
1652     case URLIS_URL:
1653     case URLIS_NOHISTORY:
1654     case URLIS_APPLIABLE:
1655     case URLIS_HASQUERY:
1656     default:
1657         FIXME("(%s %d): stub\n", debugstr_a(pszUrl), Urlis);
1658     }
1659     return FALSE;
1660 }
1661
1662 /*************************************************************************
1663  *      UrlIsW          [SHLWAPI.@]
1664  *
1665  * See UrlIsA.
1666  */
1667 BOOL WINAPI UrlIsW(LPCWSTR pszUrl, URLIS Urlis)
1668 {
1669     static const WCHAR stemp[] = { 'f','i','l','e',':','/','/',0 };
1670     PARSEDURLW base;
1671     DWORD res1;
1672     LPCWSTR last;
1673
1674     switch (Urlis) {
1675
1676     case URLIS_OPAQUE:
1677         base.cbSize = sizeof(base);
1678         res1 = ParseURLW(pszUrl, &base);
1679         if (res1) return FALSE;  /* invalid scheme */
1680         if ((*base.pszSuffix == '/') && (*(base.pszSuffix+1) == '/'))
1681             /* has scheme followed by 2 '/' */
1682             return FALSE;
1683         return TRUE;
1684
1685     case URLIS_FILEURL:
1686         return !strncmpW(stemp, pszUrl, 7);
1687
1688     case URLIS_DIRECTORY:
1689         last = pszUrl + strlenW(pszUrl) - 1;
1690         return (last >= pszUrl && (*last == '/' || *last == '\\'));
1691
1692     case URLIS_URL:
1693     case URLIS_NOHISTORY:
1694     case URLIS_APPLIABLE:
1695     case URLIS_HASQUERY:
1696     default:
1697         FIXME("(%s %d): stub\n", debugstr_w(pszUrl), Urlis);
1698     }
1699     return FALSE;
1700 }
1701
1702 /*************************************************************************
1703  *      UrlIsNoHistoryA         [SHLWAPI.@]
1704  *
1705  * Determine if a Url should not be stored in the users history list.
1706  *
1707  * PARAMS
1708  *  pszUrl [I] Url to check
1709  *
1710  * RETURNS
1711  *  TRUE, if pszUrl should be excluded from the history list,
1712  *  FALSE otherwise.
1713  */
1714 BOOL WINAPI UrlIsNoHistoryA(LPCSTR pszUrl)
1715 {
1716     return UrlIsA(pszUrl, URLIS_NOHISTORY);
1717 }
1718
1719 /*************************************************************************
1720  *      UrlIsNoHistoryW         [SHLWAPI.@]
1721  *
1722  * See UrlIsNoHistoryA.
1723  */
1724 BOOL WINAPI UrlIsNoHistoryW(LPCWSTR pszUrl)
1725 {
1726     return UrlIsW(pszUrl, URLIS_NOHISTORY);
1727 }
1728
1729 /*************************************************************************
1730  *      UrlIsOpaqueA    [SHLWAPI.@]
1731  *
1732  * Determine if a Url is opaque.
1733  *
1734  * PARAMS
1735  *  pszUrl [I] Url to check
1736  *
1737  * RETURNS
1738  *  TRUE if pszUrl is opaque,
1739  *  FALSE Otherwise.
1740  *
1741  * NOTES
1742  *  An opaque Url is one that does not start with "<protocol>://".
1743  */
1744 BOOL WINAPI UrlIsOpaqueA(LPCSTR pszUrl)
1745 {
1746     return UrlIsA(pszUrl, URLIS_OPAQUE);
1747 }
1748
1749 /*************************************************************************
1750  *      UrlIsOpaqueW    [SHLWAPI.@]
1751  *
1752  * See UrlIsOpaqueA.
1753  */
1754 BOOL WINAPI UrlIsOpaqueW(LPCWSTR pszUrl)
1755 {
1756     return UrlIsW(pszUrl, URLIS_OPAQUE);
1757 }
1758
1759 /*************************************************************************
1760  *  Scans for characters of type "type" and when not matching found,
1761  *  returns pointer to it and length in size.
1762  *
1763  * Characters tested based on RFC 1738
1764  */
1765 static LPCWSTR  URL_ScanID(LPCWSTR start, LPDWORD size, WINE_URL_SCAN_TYPE type)
1766 {
1767     static DWORD alwayszero = 0;
1768     BOOL cont = TRUE;
1769
1770     *size = 0;
1771
1772     switch(type){
1773
1774     case SCHEME:
1775         while (cont) {
1776             if ( (islowerW(*start) && isalphaW(*start)) ||
1777                  isdigitW(*start) ||
1778                  (*start == L'+') ||
1779                  (*start == L'-') ||
1780                  (*start == L'.')) {
1781                 start++;
1782                 (*size)++;
1783             }
1784             else
1785                 cont = FALSE;
1786         }
1787         break;
1788
1789     case USERPASS:
1790         while (cont) {
1791             if ( isalphaW(*start) ||
1792                  isdigitW(*start) ||
1793                  /* user/password only characters */
1794                  (*start == L';') ||
1795                  (*start == L'?') ||
1796                  (*start == L'&') ||
1797                  (*start == L'=') ||
1798                  /* *extra* characters */
1799                  (*start == L'!') ||
1800                  (*start == L'*') ||
1801                  (*start == L'\'') ||
1802                  (*start == L'(') ||
1803                  (*start == L')') ||
1804                  (*start == L',') ||
1805                  /* *safe* characters */
1806                  (*start == L'$') ||
1807                  (*start == L'_') ||
1808                  (*start == L'+') ||
1809                  (*start == L'-') ||
1810                  (*start == L'.')) {
1811                 start++;
1812                 (*size)++;
1813             } else if (*start == L'%') {
1814                 if (isxdigitW(*(start+1)) &&
1815                     isxdigitW(*(start+2))) {
1816                     start += 3;
1817                     *size += 3;
1818                 } else
1819                     cont = FALSE;
1820             } else
1821                 cont = FALSE;
1822         }
1823         break;
1824
1825     case PORT:
1826         while (cont) {
1827             if (isdigitW(*start)) {
1828                 start++;
1829                 (*size)++;
1830             }
1831             else
1832                 cont = FALSE;
1833         }
1834         break;
1835
1836     case HOST:
1837         while (cont) {
1838             if (isalnumW(*start) ||
1839                 (*start == L'-') ||
1840                 (*start == L'.') ) {
1841                 start++;
1842                 (*size)++;
1843             }
1844             else
1845                 cont = FALSE;
1846         }
1847         break;
1848     default:
1849         FIXME("unknown type %d\n", type);
1850         return (LPWSTR)&alwayszero;
1851     }
1852     /* TRACE("scanned %ld characters next char %p<%c>\n",
1853      *size, start, *start); */
1854     return start;
1855 }
1856
1857 /*************************************************************************
1858  *  Attempt to parse URL into pieces.
1859  */
1860 static LONG URL_ParseUrl(LPCWSTR pszUrl, WINE_PARSE_URL *pl)
1861 {
1862     LPCWSTR work;
1863
1864     memset(pl, 0, sizeof(WINE_PARSE_URL));
1865     pl->pScheme = pszUrl;
1866     work = URL_ScanID(pl->pScheme, &pl->szScheme, SCHEME);
1867     if (!*work || (*work != L':')) goto ErrorExit;
1868     work++;
1869     if ((*work != L'/') || (*(work+1) != L'/')) goto ErrorExit;
1870     pl->pUserName = work + 2;
1871     work = URL_ScanID(pl->pUserName, &pl->szUserName, USERPASS);
1872     if (*work == L':' ) {
1873         /* parse password */
1874         work++;
1875         pl->pPassword = work;
1876         work = URL_ScanID(pl->pPassword, &pl->szPassword, USERPASS);
1877         if (*work != L'@') {
1878             /* what we just parsed must be the hostname and port
1879              * so reset pointers and clear then let it parse */
1880             pl->szUserName = pl->szPassword = 0;
1881             work = pl->pUserName - 1;
1882             pl->pUserName = pl->pPassword = 0;
1883         }
1884     } else if (*work == L'@') {
1885         /* no password */
1886         pl->szPassword = 0;
1887         pl->pPassword = 0;
1888     } else if (!*work || (*work == L'/') || (*work == L'.')) {
1889         /* what was parsed was hostname, so reset pointers and let it parse */
1890         pl->szUserName = pl->szPassword = 0;
1891         work = pl->pUserName - 1;
1892         pl->pUserName = pl->pPassword = 0;
1893     } else goto ErrorExit;
1894
1895     /* now start parsing hostname or hostnumber */
1896     work++;
1897     pl->pHostName = work;
1898     work = URL_ScanID(pl->pHostName, &pl->szHostName, HOST);
1899     if (*work == L':') {
1900         /* parse port */
1901         work++;
1902         pl->pPort = work;
1903         work = URL_ScanID(pl->pPort, &pl->szPort, PORT);
1904     }
1905     if (*work == L'/') {
1906         /* see if query string */
1907         pl->pQuery = strchrW(work, L'?');
1908         if (pl->pQuery) pl->szQuery = strlenW(pl->pQuery);
1909     }
1910     TRACE("parse successful: scheme=%p(%ld), user=%p(%ld), pass=%p(%ld), host=%p(%ld), port=%p(%ld), query=%p(%ld)\n",
1911           pl->pScheme, pl->szScheme,
1912           pl->pUserName, pl->szUserName,
1913           pl->pPassword, pl->szPassword,
1914           pl->pHostName, pl->szHostName,
1915           pl->pPort, pl->szPort,
1916           pl->pQuery, pl->szQuery);
1917     return S_OK;
1918   ErrorExit:
1919     FIXME("failed to parse %s\n", debugstr_w(pszUrl));
1920     return E_INVALIDARG;
1921 }
1922
1923 /*************************************************************************
1924  *      UrlGetPartA     [SHLWAPI.@]
1925  *
1926  * Retrieve part of a Url.
1927  *
1928  * PARAMS
1929  *  pszIn   [I]   Url to parse
1930  *  pszOut  [O]   Destination for part of pszIn requested
1931  *  pcchOut [I/O] Length of pszOut/destination for length of pszOut
1932  *  dwPart  [I]   URL_PART_ enum from "shlwapi.h"
1933  *  dwFlags [I]   URL_ flags from "shlwapi.h"
1934  *
1935  * RETURNS
1936  *  Success: S_OK. pszOut contains the part requested, pcchOut contains its length.
1937  *  Failure: An HRESULT error code describing the error.
1938  */
1939 HRESULT WINAPI UrlGetPartA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut,
1940                            DWORD dwPart, DWORD dwFlags)
1941 {
1942     LPWSTR in, out;
1943     DWORD ret, len, len2;
1944
1945     in = HeapAlloc(GetProcessHeap(), 0,
1946                               (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
1947     out = in + INTERNET_MAX_URL_LENGTH;
1948
1949     MultiByteToWideChar(0, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
1950
1951     len = INTERNET_MAX_URL_LENGTH;
1952     ret = UrlGetPartW(in, out, &len, dwPart, dwFlags);
1953
1954     if (ret != S_OK) {
1955         HeapFree(GetProcessHeap(), 0, in);
1956         return ret;
1957     }
1958
1959     len2 = WideCharToMultiByte(0, 0, out, len, 0, 0, 0, 0);
1960     if (len2 > *pcchOut) {
1961         *pcchOut = len2;
1962         HeapFree(GetProcessHeap(), 0, in);
1963         return E_POINTER;
1964     }
1965     WideCharToMultiByte(0, 0, out, len+1, pszOut, *pcchOut, 0, 0);
1966     *pcchOut = len2;
1967     HeapFree(GetProcessHeap(), 0, in);
1968     return S_OK;
1969 }
1970
1971 /*************************************************************************
1972  *      UrlGetPartW     [SHLWAPI.@]
1973  *
1974  * See UrlGetPartA.
1975  */
1976 HRESULT WINAPI UrlGetPartW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut,
1977                            DWORD dwPart, DWORD dwFlags)
1978 {
1979     WINE_PARSE_URL pl;
1980     HRESULT ret;
1981     DWORD size, schsize;
1982     LPCWSTR addr, schaddr;
1983     LPWSTR work;
1984
1985     TRACE("(%s %p %p(%ld) %08lx %08lx)\n",
1986           debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwPart, dwFlags);
1987
1988     ret = URL_ParseUrl(pszIn, &pl);
1989     if (!ret) {
1990         schaddr = pl.pScheme;
1991         schsize = pl.szScheme;
1992
1993         switch (dwPart) {
1994         case URL_PART_SCHEME:
1995             if (!pl.szScheme) return E_INVALIDARG;
1996             addr = pl.pScheme;
1997             size = pl.szScheme;
1998             break;
1999
2000         case URL_PART_HOSTNAME:
2001             if (!pl.szHostName) return E_INVALIDARG;
2002             addr = pl.pHostName;
2003             size = pl.szHostName;
2004             break;
2005
2006         case URL_PART_USERNAME:
2007             if (!pl.szUserName) return E_INVALIDARG;
2008             addr = pl.pUserName;
2009             size = pl.szUserName;
2010             break;
2011
2012         case URL_PART_PASSWORD:
2013             if (!pl.szPassword) return E_INVALIDARG;
2014             addr = pl.pPassword;
2015             size = pl.szPassword;
2016             break;
2017
2018         case URL_PART_PORT:
2019             if (!pl.szPort) return E_INVALIDARG;
2020             addr = pl.pPort;
2021             size = pl.szPort;
2022             break;
2023
2024         case URL_PART_QUERY:
2025             if (!pl.szQuery) return E_INVALIDARG;
2026             addr = pl.pQuery;
2027             size = pl.szQuery;
2028             break;
2029
2030         default:
2031             return E_INVALIDARG;
2032         }
2033
2034         if (dwFlags == URL_PARTFLAG_KEEPSCHEME) {
2035             if (*pcchOut < size + schsize + 2) {
2036                 *pcchOut = size + schsize + 2;
2037                 return E_POINTER;
2038             }
2039             strncpyW(pszOut, schaddr, schsize);
2040             work = pszOut + schsize;
2041             *work = L':';
2042             strncpyW(work+1, addr, size);
2043             *pcchOut = size + schsize + 1;
2044             work += (size + 1);
2045             *work = L'\0';
2046         }
2047         else {
2048             if (*pcchOut < size + 1) {*pcchOut = size+1; return E_POINTER;}
2049             strncpyW(pszOut, addr, size);
2050             *pcchOut = size;
2051             work = pszOut + size;
2052             *work = L'\0';
2053         }
2054         TRACE("len=%ld %s\n", *pcchOut, debugstr_w(pszOut));
2055     }
2056     return ret;
2057 }
2058
2059 /*************************************************************************
2060  * PathIsURLA   [SHLWAPI.@]
2061  *
2062  * Check if the given path is a Url.
2063  *
2064  * PARAMS
2065  *  lpszPath [I] Path to check.
2066  *
2067  * RETURNS
2068  *  TRUE  if lpszPath is a Url.
2069  *  FALSE if lpszPath is NULL or not a Url.
2070  */
2071 BOOL WINAPI PathIsURLA(LPCSTR lpstrPath)
2072 {
2073     PARSEDURLA base;
2074     DWORD res1;
2075
2076     if (!lpstrPath || !*lpstrPath) return FALSE;
2077
2078     /* get protocol        */
2079     base.cbSize = sizeof(base);
2080     res1 = ParseURLA(lpstrPath, &base);
2081     return (base.nScheme != URL_SCHEME_INVALID);
2082 }
2083
2084 /*************************************************************************
2085  * PathIsURLW   [SHLWAPI.@]
2086  *
2087  * See PathIsURLA.
2088  */
2089 BOOL WINAPI PathIsURLW(LPCWSTR lpstrPath)
2090 {
2091     PARSEDURLW base;
2092     DWORD res1;
2093
2094     if (!lpstrPath || !*lpstrPath) return FALSE;
2095
2096     /* get protocol        */
2097     base.cbSize = sizeof(base);
2098     res1 = ParseURLW(lpstrPath, &base);
2099     return (base.nScheme != URL_SCHEME_INVALID);
2100 }
2101
2102 /*************************************************************************
2103  *      UrlCreateFromPathA      [SHLWAPI.@]
2104  * 
2105  * Create a Url from a file path.
2106  *
2107  * PARAMS
2108  *  pszPath [I]    Path to convert
2109  *  pszUrl  [O]    Destination for the converted Url
2110  *  pcchUrl [I/O]  Length of pszUrl
2111  *  dwReserved [I] Reserved, must be 0
2112  *
2113  * RETURNS
2114  *  Success: S_OK. pszUrl contains the converted path.
2115  *  Failure: An HRESULT error code.
2116  */
2117 HRESULT WINAPI UrlCreateFromPathA(LPCSTR pszPath, LPSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2118 {
2119         DWORD nCharBeforeColon = 0;
2120         DWORD nSlashes = 0;
2121         DWORD dwChRequired = 0;
2122         LPSTR pszNewUrl = NULL;
2123         LPCSTR pszConstPointer = NULL;
2124         LPSTR pszPointer = NULL;
2125         DWORD i;
2126         HRESULT ret;
2127
2128         TRACE("(%s, %p, %p, 0x%08lx)\n", debugstr_a(pszPath), pszUrl, pcchUrl, dwReserved);
2129
2130         /* Validate arguments */
2131         if (dwReserved != 0)
2132         {
2133                 FIXME("dwReserved should be 0: 0x%08lx\n", dwReserved);
2134                 return E_INVALIDARG;
2135         }
2136         if (!pszUrl || !pcchUrl || !pszUrl)
2137         {
2138                 ERR("Invalid argument\n");
2139                 return E_INVALIDARG;
2140         }
2141
2142         for (pszConstPointer = pszPath; *pszConstPointer; pszConstPointer++)
2143         {
2144                 if (isalpha(*pszConstPointer) || isdigit(*pszConstPointer) ||
2145                         *pszConstPointer == '.' || *pszConstPointer == '-')
2146                         nCharBeforeColon++;
2147                 else break;
2148         }
2149         if (*pszConstPointer == ':') /* then already in URL format, so copy */
2150         {
2151                 dwChRequired = lstrlenA(pszPath);
2152                 if (dwChRequired > *pcchUrl)
2153                 {
2154                         *pcchUrl = dwChRequired;
2155                         return E_POINTER;
2156                 }
2157                 else
2158                 {
2159                         *pcchUrl = dwChRequired;
2160                         StrCpyA(pszUrl, pszPath);
2161                         return S_FALSE;
2162                 }
2163         }
2164         /* then must need converting to file: format */
2165
2166         /* Strip off leading slashes */
2167         while (*pszPath == '\\' || *pszPath == '/')
2168         {
2169                 pszPath++;
2170                 nSlashes++;
2171         }
2172
2173         dwChRequired = *pcchUrl; /* UrlEscape will fill this in with the correct amount */
2174         TRACE("pszUrl: %s\n", debugstr_a(pszPath));
2175         pszNewUrl = HeapAlloc(GetProcessHeap(), 0, dwChRequired + 1);
2176         ret = UrlEscapeA(pszPath, pszNewUrl, &dwChRequired, URL_ESCAPE_PERCENT);
2177         TRACE("ret: 0x%08lx, pszUrl: %s\n", ret, debugstr_a(pszNewUrl));
2178         TRACE("%ld\n", dwChRequired);
2179         if (ret != E_POINTER && FAILED(ret))
2180                 return ret;
2181         dwChRequired += 5; /* "file:" */
2182         if ((lstrlenA(pszUrl) > 1) && isalpha(pszUrl[0]) && (pszUrl[1] == ':'))
2183         {
2184                 dwChRequired += 3; /* "///" */
2185                 nSlashes = 3;
2186         }
2187         else
2188                 switch (nSlashes)
2189                 {
2190                 case 0: /* no slashes */
2191                         break;
2192                 case 2: /* two slashes */
2193                 case 4:
2194                 case 5:
2195                 case 6:
2196                         dwChRequired += 2;
2197                         nSlashes = 2;
2198                         break;
2199                 default: /* three slashes */
2200                         dwChRequired += 3;
2201                         nSlashes = 3;
2202                 }
2203
2204         if (dwChRequired > *pcchUrl)
2205                 return E_POINTER;
2206         *pcchUrl = dwChRequired; /* Return number of chars required (not including termination) */
2207         StrCpyA(pszUrl, "file:");
2208         pszPointer = pszUrl + lstrlenA(pszUrl);
2209         for (i=0; i < nSlashes; i++)
2210         {
2211                 *pszPointer = '/';
2212                 pszPointer++;
2213         }
2214         StrCpyA(pszPointer, pszNewUrl);
2215         TRACE("<- %s\n", debugstr_a(pszUrl));
2216         return S_OK;
2217 }
2218
2219 /*************************************************************************
2220  *      UrlCreateFromPathW      [SHLWAPI.@]
2221  *
2222  * See UrlCreateFromPathA.
2223  */
2224 HRESULT WINAPI UrlCreateFromPathW(LPCWSTR pszPath, LPWSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2225 {
2226         DWORD nCharBeforeColon = 0;
2227         DWORD nSlashes = 0;
2228         DWORD dwChRequired = 0;
2229         LPWSTR pszNewUrl = NULL;
2230         LPCWSTR pszConstPointer = NULL;
2231         LPWSTR pszPointer = NULL;
2232         DWORD i;
2233         HRESULT ret;
2234
2235         TRACE("(%s, %p, %p, 0x%08lx)\n", debugstr_w(pszPath), pszUrl, pcchUrl, dwReserved);
2236
2237         /* Validate arguments */
2238         if (dwReserved != 0)
2239                 return E_INVALIDARG;
2240         if (!pszUrl || !pcchUrl || !pszUrl)
2241                 return E_INVALIDARG;
2242
2243         for (pszConstPointer = pszPath; *pszConstPointer; pszConstPointer++)
2244         {
2245                 if (isalphaW(*pszConstPointer) || isdigitW(*pszConstPointer) ||
2246                         *pszConstPointer == '.' || *pszConstPointer == '-')
2247                         nCharBeforeColon++;
2248                 else break;
2249         }
2250         if (*pszConstPointer == ':') /* then already in URL format, so copy */
2251         {
2252                 dwChRequired = lstrlenW(pszPath);
2253                 *pcchUrl = dwChRequired;
2254                 if (dwChRequired > *pcchUrl)
2255                         return E_POINTER;
2256                 else
2257                 {
2258                         StrCpyW(pszUrl, pszPath);
2259                         return S_FALSE;
2260                 }
2261         }
2262         /* then must need converting to file: format */
2263
2264         /* Strip off leading slashes */
2265         while (*pszPath == '\\' || *pszPath == '/')
2266         {
2267                 pszPath++;
2268                 nSlashes++;
2269         }
2270
2271         dwChRequired = *pcchUrl; /* UrlEscape will fill this in with the correct amount */
2272         ret = UrlEscapeW(pszPath, pszUrl, &dwChRequired, URL_ESCAPE_PERCENT);
2273         if (ret != E_POINTER && FAILED(ret))
2274                 return ret;
2275         dwChRequired += 5; /* "file:" */
2276         if ((lstrlenW(pszUrl) > 1) && isalphaW(pszUrl[0]) && (pszUrl[1] == ':'))
2277         {
2278                 dwChRequired += 3; /* "///" */
2279                 nSlashes = 3;
2280         }
2281         else
2282                 switch (nSlashes)
2283                 {
2284                 case 0: /* no slashes */
2285                         break;
2286                 case 2: /* two slashes */
2287                 case 4:
2288                 case 5:
2289                 case 6:
2290                         dwChRequired += 2;
2291                         nSlashes = 2;
2292                         break;
2293                 default: /* three slashes */
2294                         dwChRequired += 3;
2295                         nSlashes = 3;
2296                 }
2297
2298         *pcchUrl = dwChRequired; /* Return number of chars required (not including termination) */
2299         if (dwChRequired > *pcchUrl)
2300                 return E_POINTER;
2301         pszNewUrl = HeapAlloc(GetProcessHeap(), 0, (dwChRequired + 1) * sizeof(WCHAR));
2302         StrCpyW(pszNewUrl, fileW);
2303         pszPointer = pszNewUrl + 4;
2304         *pszPointer = ':';
2305         pszPointer++;
2306         for (i=0; i < nSlashes; i++)
2307         {
2308                 *pszPointer = '/';
2309                 pszPointer++;
2310         }
2311         StrCpyW(pszPointer, pszPath);
2312         StrCpyW(pszUrl, pszNewUrl);
2313         return S_OK;
2314 }
2315
2316 /*************************************************************************
2317  *      SHAutoComplete          [SHLWAPI.@]
2318  *
2319  * Enable auto-completion for an edit control.
2320  *
2321  * PARAMS
2322  *  hwndEdit [I] Handle of control to enable auto-completion for
2323  *  dwFlags  [I] SHACF_ flags from "shlwapi.h"
2324  *
2325  * RETURNS
2326  *  Success: S_OK. Auto-completion is enabled for the control.
2327  *  Failure: An HRESULT error code indicating the error.
2328  */
2329 HRESULT WINAPI SHAutoComplete(HWND hwndEdit, DWORD dwFlags)
2330 {
2331   FIXME("SHAutoComplete stub\n");
2332   return S_FALSE;
2333 }
2334
2335 /*************************************************************************
2336  *  MLBuildResURLA      [SHLWAPI.405]
2337  *
2338  * Create a Url pointing to a resource in a module.
2339  *
2340  * PARAMS
2341  *  lpszLibName [I] Name of the module containing the resource
2342  *  hMod        [I] Callers module handle
2343  *  dwFlags     [I] Undocumented flags for loading the module
2344  *  lpszRes     [I] Resource name
2345  *  lpszDest    [O] Destination for resulting Url
2346  *  dwDestLen   [I] Length of lpszDest
2347  *
2348  * RETURNS
2349  *  Success: S_OK. lpszDest constains the resource Url.
2350  *  Failure: E_INVALIDARG, if any argument is invalid, or
2351  *           E_FAIL if dwDestLen is too small.
2352  */
2353 HRESULT WINAPI MLBuildResURLA(LPCSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2354                               LPCSTR lpszRes, LPSTR lpszDest, DWORD dwDestLen)
2355 {
2356   WCHAR szLibName[MAX_PATH], szRes[MAX_PATH], szDest[MAX_PATH];
2357   HRESULT hRet;
2358
2359   if (lpszLibName)
2360     MultiByteToWideChar(CP_ACP, 0, lpszLibName, -1, szLibName, sizeof(szLibName)/sizeof(WCHAR));
2361
2362   if (lpszRes)
2363     MultiByteToWideChar(CP_ACP, 0, lpszRes, -1, szRes, sizeof(szRes)/sizeof(WCHAR));
2364
2365   if (dwDestLen > sizeof(szLibName)/sizeof(WCHAR))
2366     dwDestLen = sizeof(szLibName)/sizeof(WCHAR);
2367
2368   hRet = MLBuildResURLW(lpszLibName ? szLibName : NULL, hMod, dwFlags,
2369                         lpszRes ? szRes : NULL, lpszDest ? szDest : NULL, dwDestLen);
2370   if (SUCCEEDED(hRet) && lpszDest)
2371     WideCharToMultiByte(CP_ACP, 0, szDest, -1, lpszDest, dwDestLen, 0, 0);
2372
2373   return hRet;
2374 }
2375
2376 /*************************************************************************
2377  *  MLBuildResURLA      [SHLWAPI.406]
2378  *
2379  * See MLBuildResURLA.
2380  */
2381 HRESULT WINAPI MLBuildResURLW(LPCWSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2382                               LPCWSTR lpszRes, LPWSTR lpszDest, DWORD dwDestLen)
2383 {
2384   static const WCHAR szRes[] = { 'r','e','s',':','/','/','\0' };
2385 #define szResLen ((sizeof(szRes) - sizeof(WCHAR))/sizeof(WCHAR))
2386   HRESULT hRet = E_FAIL;
2387
2388   TRACE("(%s,%p,0x%08lx,%s,%p,%ld)\n", debugstr_w(lpszLibName), hMod, dwFlags,
2389         debugstr_w(lpszRes), lpszDest, dwDestLen);
2390
2391   if (!lpszLibName || !hMod || hMod == INVALID_HANDLE_VALUE || !lpszRes ||
2392       !lpszDest || (dwFlags && dwFlags != 2))
2393     return E_INVALIDARG;
2394
2395   if (dwDestLen >= szResLen + 1)
2396   {
2397     dwDestLen -= (szResLen + 1);
2398     memcpy(lpszDest, szRes, sizeof(szRes));
2399
2400     hMod = MLLoadLibraryW(lpszLibName, hMod, dwFlags);
2401
2402     if (hMod)
2403     {
2404       WCHAR szBuff[MAX_PATH];
2405       DWORD len;
2406
2407       len = GetModuleFileNameW(hMod, szBuff, sizeof(szBuff)/sizeof(WCHAR));
2408       if (len && len < sizeof(szBuff)/sizeof(WCHAR))
2409       {
2410         DWORD dwPathLen = strlenW(szBuff) + 1;
2411
2412         if (dwDestLen >= dwPathLen)
2413         {
2414           DWORD dwResLen;
2415
2416           dwDestLen -= dwPathLen;
2417           memcpy(lpszDest + szResLen, szBuff, dwPathLen * sizeof(WCHAR));
2418
2419           dwResLen = strlenW(lpszRes) + 1;
2420           if (dwDestLen >= dwResLen + 1)
2421           {
2422             lpszDest[szResLen + dwPathLen + dwResLen] = '/';
2423             memcpy(lpszDest + szResLen + dwPathLen, lpszRes, dwResLen * sizeof(WCHAR));
2424             hRet = S_OK;
2425           }
2426         }
2427       }
2428       MLFreeLibrary(hMod);
2429     }
2430   }
2431   return hRet;
2432 }