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