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