shlwapi/tests: Add 'res:' protocol tests for UrlCanonicalize.
[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 little trick to allow easy exit from nested if's */
754
755     ret = S_OK;
756     switch (process_case) {
757
758     case 1:  /*
759               * Return pszRelative appended to what ever is in pszCombined,
760               * (which may the string "file:///"
761               */
762         strcatW(preliminary, mrelative);
763         break;
764
765     case 2:  /*
766               * Same as case 1, but if URL_PLUGGABLE_PROTOCOL was specified
767               * and pszRelative starts with "//", then append a "/"
768               */
769         strcpyW(preliminary, mrelative);
770         if (!(dwFlags & URL_PLUGGABLE_PROTOCOL) &&
771             URL_JustLocation(relative.pszSuffix))
772             strcatW(preliminary, single_slash);
773         break;
774
775     case 3:  /*
776               * Return the pszBase scheme with pszRelative. Basically
777               * keeps the scheme and replaces the domain and following.
778               */
779         memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1)*sizeof(WCHAR));
780         work = preliminary + base.cchProtocol + 1;
781         strcpyW(work, relative.pszSuffix);
782         break;
783
784     case 4:  /*
785               * Return the pszBase scheme and location but everything
786               * after the location is pszRelative. (Replace document
787               * from root on.)
788               */
789         memcpy(preliminary, base.pszProtocol, (base.cchProtocol+1+sizeloc)*sizeof(WCHAR));
790         work = preliminary + base.cchProtocol + 1 + sizeloc;
791         if (dwFlags & URL_PLUGGABLE_PROTOCOL)
792             *(work++) = '/';
793         strcpyW(work, relative.pszSuffix);
794         break;
795
796     case 5:  /*
797               * Return the pszBase without its document (if any) and
798               * append pszRelative after its scheme.
799               */
800         memcpy(preliminary, base.pszProtocol,
801                (base.cchProtocol+1+base.cchSuffix)*sizeof(WCHAR));
802         work = preliminary + base.cchProtocol+1+base.cchSuffix - 1;
803         if (*work++ != '/')
804             *(work++) = '/';
805         strcpyW(work, relative.pszSuffix);
806         break;
807
808     default:
809         FIXME("How did we get here????? process_case=%d\n", process_case);
810         ret = E_INVALIDARG;
811     }
812
813     if (ret == S_OK) {
814         /* Reuse mrelative as temp storage as its already allocated and not needed anymore */
815         ret = UrlCanonicalizeW(preliminary, mrelative, pcchCombined, (dwFlags & ~URL_FILE_USE_PATHURL));
816         if(SUCCEEDED(ret) && pszCombined) {
817             lstrcpyW(pszCombined, mrelative);
818         }
819         TRACE("return-%d len=%d, %s\n",
820               process_case, *pcchCombined, debugstr_w(pszCombined));
821     }
822     HeapFree(GetProcessHeap(), 0, preliminary);
823     return ret;
824 }
825
826 /*************************************************************************
827  *      UrlEscapeA      [SHLWAPI.@]
828  */
829
830 HRESULT WINAPI UrlEscapeA(
831         LPCSTR pszUrl,
832         LPSTR pszEscaped,
833         LPDWORD pcchEscaped,
834         DWORD dwFlags)
835 {
836     WCHAR bufW[INTERNET_MAX_URL_LENGTH];
837     WCHAR *escapedW = bufW;
838     UNICODE_STRING urlW;
839     HRESULT ret;
840     DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
841
842     if (!pszEscaped || !pcchEscaped || !*pcchEscaped)
843         return E_INVALIDARG;
844
845     if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl))
846         return E_INVALIDARG;
847     if((ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags)) == E_POINTER) {
848         escapedW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
849         ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags);
850     }
851     if(ret == S_OK) {
852         RtlUnicodeToMultiByteSize(&lenA, escapedW, lenW * sizeof(WCHAR));
853         if(*pcchEscaped > lenA) {
854             RtlUnicodeToMultiByteN(pszEscaped, *pcchEscaped - 1, &lenA, escapedW, lenW * sizeof(WCHAR));
855             pszEscaped[lenA] = 0;
856             *pcchEscaped = lenA;
857         } else {
858             *pcchEscaped = lenA + 1;
859             ret = E_POINTER;
860         }
861     }
862     if(escapedW != bufW) HeapFree(GetProcessHeap(), 0, escapedW);
863     RtlFreeUnicodeString(&urlW);
864     return ret;
865 }
866
867 #define WINE_URL_BASH_AS_SLASH    0x01
868 #define WINE_URL_COLLAPSE_SLASHES 0x02
869 #define WINE_URL_ESCAPE_SLASH     0x04
870 #define WINE_URL_ESCAPE_HASH      0x08
871 #define WINE_URL_ESCAPE_QUESTION  0x10
872 #define WINE_URL_STOP_ON_HASH     0x20
873 #define WINE_URL_STOP_ON_QUESTION 0x40
874
875 static inline BOOL URL_NeedEscapeW(WCHAR ch, DWORD dwFlags, DWORD int_flags)
876 {
877
878     if (isalnumW(ch))
879         return FALSE;
880
881     if(dwFlags & URL_ESCAPE_SPACES_ONLY) {
882         if(ch == ' ')
883             return TRUE;
884         else
885             return FALSE;
886     }
887
888     if ((dwFlags & URL_ESCAPE_PERCENT) && (ch == '%'))
889         return TRUE;
890
891     if (ch <= 31 || ch >= 127)
892         return TRUE;
893
894     else {
895         switch (ch) {
896         case ' ':
897         case '<':
898         case '>':
899         case '\"':
900         case '{':
901         case '}':
902         case '|':
903         case '\\':
904         case '^':
905         case ']':
906         case '[':
907         case '`':
908         case '&':
909             return TRUE;
910
911         case '/':
912             if (int_flags & WINE_URL_ESCAPE_SLASH) return TRUE;
913             return FALSE;
914
915         case '?':
916             if (int_flags & WINE_URL_ESCAPE_QUESTION) return TRUE;
917             return FALSE;
918
919         case '#':
920             if (int_flags & WINE_URL_ESCAPE_HASH) return TRUE;
921             return FALSE;
922
923         default:
924             return FALSE;
925         }
926     }
927 }
928
929
930 /*************************************************************************
931  *      UrlEscapeW      [SHLWAPI.@]
932  *
933  * Converts unsafe characters in a Url into escape sequences.
934  *
935  * PARAMS
936  *  pszUrl      [I]   Url to modify
937  *  pszEscaped  [O]   Destination for modified Url
938  *  pcchEscaped [I/O] Length of pszUrl, destination for length of pszEscaped
939  *  dwFlags     [I]   URL_ flags from "shlwapi.h"
940  *
941  * RETURNS
942  *  Success: S_OK. pszEscaped contains the escaped Url, pcchEscaped
943  *           contains its length.
944  *  Failure: E_POINTER, if pszEscaped is not large enough. In this case
945  *           pcchEscaped is set to the required length.
946  *
947  * Converts unsafe characters into their escape sequences.
948  *
949  * NOTES
950  * - By default this function stops converting at the first '?' or
951  *  '#' character.
952  * - If dwFlags contains URL_ESCAPE_SPACES_ONLY then only spaces are
953  *   converted, but the conversion continues past a '?' or '#'.
954  * - Note that this function did not work well (or at all) in shlwapi version 4.
955  *
956  * BUGS
957  *  Only the following flags are implemented:
958  *|     URL_ESCAPE_SPACES_ONLY
959  *|     URL_DONT_ESCAPE_EXTRA_INFO
960  *|     URL_ESCAPE_SEGMENT_ONLY
961  *|     URL_ESCAPE_PERCENT
962  */
963 HRESULT WINAPI UrlEscapeW(
964         LPCWSTR pszUrl,
965         LPWSTR pszEscaped,
966         LPDWORD pcchEscaped,
967         DWORD dwFlags)
968 {
969     LPCWSTR src;
970     DWORD needed = 0, ret;
971     BOOL stop_escaping = FALSE;
972     WCHAR next[5], *dst = pszEscaped;
973     INT len;
974     PARSEDURLW parsed_url;
975     DWORD int_flags;
976     DWORD slashes = 0;
977     static const WCHAR localhost[] = {'l','o','c','a','l','h','o','s','t',0};
978
979     TRACE("(%s %p %p 0x%08x)\n", debugstr_w(pszUrl), pszEscaped,
980           pcchEscaped, dwFlags);
981
982     if(!pszUrl || !pcchEscaped)
983         return E_INVALIDARG;
984
985     if(dwFlags & ~(URL_ESCAPE_SPACES_ONLY |
986                    URL_ESCAPE_SEGMENT_ONLY |
987                    URL_DONT_ESCAPE_EXTRA_INFO |
988                    URL_ESCAPE_PERCENT))
989         FIXME("Unimplemented flags: %08x\n", dwFlags);
990
991     /* fix up flags */
992     if (dwFlags & URL_ESCAPE_SPACES_ONLY)
993         /* if SPACES_ONLY specified, reset the other controls */
994         dwFlags &= ~(URL_DONT_ESCAPE_EXTRA_INFO |
995                      URL_ESCAPE_PERCENT |
996                      URL_ESCAPE_SEGMENT_ONLY);
997
998     else
999         /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
1000         dwFlags |= URL_DONT_ESCAPE_EXTRA_INFO;
1001
1002
1003     int_flags = 0;
1004     if(dwFlags & URL_ESCAPE_SEGMENT_ONLY) {
1005         int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH;
1006     } else {
1007         parsed_url.cbSize = sizeof(parsed_url);
1008         if(ParseURLW(pszUrl, &parsed_url) != S_OK)
1009             parsed_url.nScheme = URL_SCHEME_INVALID;
1010
1011         TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol));
1012
1013         if(dwFlags & URL_DONT_ESCAPE_EXTRA_INFO)
1014             int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION;
1015
1016         switch(parsed_url.nScheme) {
1017         case URL_SCHEME_FILE:
1018             int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH;
1019             int_flags &= ~WINE_URL_STOP_ON_HASH;
1020             break;
1021
1022         case URL_SCHEME_HTTP:
1023         case URL_SCHEME_HTTPS:
1024             int_flags |= WINE_URL_BASH_AS_SLASH;
1025             if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\')
1026                 int_flags |= WINE_URL_ESCAPE_SLASH;
1027             break;
1028
1029         case URL_SCHEME_MAILTO:
1030             int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH;
1031             int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH);
1032             break;
1033
1034         case URL_SCHEME_INVALID:
1035             break;
1036
1037         case URL_SCHEME_FTP:
1038         default:
1039             if(parsed_url.pszSuffix[0] != '/')
1040                 int_flags |= WINE_URL_ESCAPE_SLASH;
1041             break;
1042         }
1043     }
1044
1045     for(src = pszUrl; *src; ) {
1046         WCHAR cur = *src;
1047         len = 0;
1048         
1049         if((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == pszUrl + parsed_url.cchProtocol + 1) {
1050             int localhost_len = sizeof(localhost)/sizeof(WCHAR) - 1;
1051             while(cur == '/' || cur == '\\') {
1052                 slashes++;
1053                 cur = *++src;
1054             }
1055             if(slashes == 2 && !strncmpiW(src, localhost, localhost_len)) { /* file://localhost/ -> file:/// */
1056                 if(*(src + localhost_len) == '/' || *(src + localhost_len) == '\\')
1057                 src += localhost_len + 1;
1058                 slashes = 3;
1059             }
1060
1061             switch(slashes) {
1062             case 1:
1063             case 3:
1064                 next[0] = next[1] = next[2] = '/';
1065                 len = 3;
1066                 break;
1067             case 0:
1068                 len = 0;
1069                 break;
1070             default:
1071                 next[0] = next[1] = '/';
1072                 len = 2;
1073                 break;
1074             }
1075         }
1076         if(len == 0) {
1077
1078             if(cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH))
1079                 stop_escaping = TRUE;
1080
1081             if(cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION))
1082                 stop_escaping = TRUE;
1083
1084             if(cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/';
1085
1086             if(URL_NeedEscapeW(cur, dwFlags, int_flags) && stop_escaping == FALSE) {
1087                 next[0] = '%';
1088                 next[1] = hexDigits[(cur >> 4) & 0xf];
1089                 next[2] = hexDigits[cur & 0xf];
1090                 len = 3;
1091             } else {
1092                 next[0] = cur;
1093                 len = 1;
1094             }
1095             src++;
1096         }
1097
1098         if(needed + len <= *pcchEscaped) {
1099             memcpy(dst, next, len*sizeof(WCHAR));
1100             dst += len;
1101         }
1102         needed += len;
1103     }
1104
1105     if(needed < *pcchEscaped) {
1106         *dst = '\0';
1107         ret = S_OK;
1108     } else {
1109         needed++; /* add one for the '\0' */
1110         ret = E_POINTER;
1111     }
1112     *pcchEscaped = needed;
1113     return ret;
1114 }
1115
1116
1117 /*************************************************************************
1118  *      UrlUnescapeA    [SHLWAPI.@]
1119  *
1120  * Converts Url escape sequences back to ordinary characters.
1121  *
1122  * PARAMS
1123  *  pszUrl        [I/O]  Url to convert
1124  *  pszUnescaped  [O]    Destination for converted Url
1125  *  pcchUnescaped [I/O]  Size of output string
1126  *  dwFlags       [I]    URL_ESCAPE_ Flags from "shlwapi.h"
1127  *
1128  * RETURNS
1129  *  Success: S_OK. The converted value is in pszUnescaped, or in pszUrl if
1130  *           dwFlags includes URL_ESCAPE_INPLACE.
1131  *  Failure: E_POINTER if the converted Url is bigger than pcchUnescaped. In
1132  *           this case pcchUnescaped is set to the size required.
1133  * NOTES
1134  *  If dwFlags includes URL_DONT_ESCAPE_EXTRA_INFO, the conversion stops at
1135  *  the first occurrence of either a '?' or '#' character.
1136  */
1137 HRESULT WINAPI UrlUnescapeA(
1138         LPSTR pszUrl,
1139         LPSTR pszUnescaped,
1140         LPDWORD pcchUnescaped,
1141         DWORD dwFlags)
1142 {
1143     char *dst, next;
1144     LPCSTR src;
1145     HRESULT ret;
1146     DWORD needed;
1147     BOOL stop_unescaping = FALSE;
1148
1149     TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_a(pszUrl), pszUnescaped,
1150           pcchUnescaped, dwFlags);
1151
1152     if(!pszUrl || (!pszUnescaped && !(dwFlags & URL_UNESCAPE_INPLACE)) || !pcchUnescaped)
1153         return E_INVALIDARG;
1154
1155     if(dwFlags & URL_UNESCAPE_INPLACE)
1156         dst = pszUrl;
1157     else
1158         dst = pszUnescaped;
1159
1160     for(src = pszUrl, needed = 0; *src; src++, needed++) {
1161         if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1162            (*src == '#' || *src == '?')) {
1163             stop_unescaping = TRUE;
1164             next = *src;
1165         } else if(*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2))
1166                   && stop_unescaping == FALSE) {
1167             INT ih;
1168             char buf[3];
1169             memcpy(buf, src + 1, 2);
1170             buf[2] = '\0';
1171             ih = strtol(buf, NULL, 16);
1172             next = (CHAR) ih;
1173             src += 2; /* Advance to end of escape */
1174         } else
1175             next = *src;
1176
1177         if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1178             *dst++ = next;
1179     }
1180
1181     if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1182         *dst = '\0';
1183         ret = S_OK;
1184     } else {
1185         needed++; /* add one for the '\0' */
1186         ret = E_POINTER;
1187     }
1188     if(!(dwFlags & URL_UNESCAPE_INPLACE))
1189         *pcchUnescaped = needed;
1190
1191     if (ret == S_OK) {
1192         TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1193               debugstr_a(pszUrl) : debugstr_a(pszUnescaped));
1194     }
1195
1196     return ret;
1197 }
1198
1199 /*************************************************************************
1200  *      UrlUnescapeW    [SHLWAPI.@]
1201  *
1202  * See UrlUnescapeA.
1203  */
1204 HRESULT WINAPI UrlUnescapeW(
1205         LPWSTR pszUrl,
1206         LPWSTR pszUnescaped,
1207         LPDWORD pcchUnescaped,
1208         DWORD dwFlags)
1209 {
1210     WCHAR *dst, next;
1211     LPCWSTR src;
1212     HRESULT ret;
1213     DWORD needed;
1214     BOOL stop_unescaping = FALSE;
1215
1216     TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszUrl), pszUnescaped,
1217           pcchUnescaped, dwFlags);
1218
1219     if(!pszUrl || (!pszUnescaped && !(dwFlags & URL_UNESCAPE_INPLACE))|| !pcchUnescaped)
1220         return E_INVALIDARG;
1221
1222     if(dwFlags & URL_UNESCAPE_INPLACE)
1223         dst = pszUrl;
1224     else
1225         dst = pszUnescaped;
1226
1227     for(src = pszUrl, needed = 0; *src; src++, needed++) {
1228         if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1229            (*src == '#' || *src == '?')) {
1230             stop_unescaping = TRUE;
1231             next = *src;
1232         } else if(*src == '%' && isxdigitW(*(src + 1)) && isxdigitW(*(src + 2))
1233                   && stop_unescaping == FALSE) {
1234             INT ih;
1235             WCHAR buf[5] = {'0','x',0};
1236             memcpy(buf + 2, src + 1, 2*sizeof(WCHAR));
1237             buf[4] = 0;
1238             StrToIntExW(buf, STIF_SUPPORT_HEX, &ih);
1239             next = (WCHAR) ih;
1240             src += 2; /* Advance to end of escape */
1241         } else
1242             next = *src;
1243
1244         if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1245             *dst++ = next;
1246     }
1247
1248     if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1249         *dst = '\0';
1250         ret = S_OK;
1251     } else {
1252         needed++; /* add one for the '\0' */
1253         ret = E_POINTER;
1254     }
1255     if(!(dwFlags & URL_UNESCAPE_INPLACE))
1256         *pcchUnescaped = needed;
1257
1258     if (ret == S_OK) {
1259         TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1260               debugstr_w(pszUrl) : debugstr_w(pszUnescaped));
1261     }
1262
1263     return ret;
1264 }
1265
1266 /*************************************************************************
1267  *      UrlGetLocationA         [SHLWAPI.@]
1268  *
1269  * Get the location from a Url.
1270  *
1271  * PARAMS
1272  *  pszUrl [I] Url to get the location from
1273  *
1274  * RETURNS
1275  *  A pointer to the start of the location in pszUrl, or NULL if there is
1276  *  no location.
1277  *
1278  * NOTES
1279  *  - MSDN erroneously states that "The location is the segment of the Url
1280  *    starting with a '?' or '#' character". Neither V4 nor V5 of shlwapi.dll
1281  *    stop at '?' and always return a NULL in this case.
1282  *  - MSDN also erroneously states that "If a file URL has a query string,
1283  *    the returned string is the query string". In all tested cases, if the
1284  *    Url starts with "fi" then a NULL is returned. V5 gives the following results:
1285  *|       Result   Url
1286  *|       ------   ---
1287  *|       NULL     file://aa/b/cd#hohoh
1288  *|       #hohoh   http://aa/b/cd#hohoh
1289  *|       NULL     fi://aa/b/cd#hohoh
1290  *|       #hohoh   ff://aa/b/cd#hohoh
1291  */
1292 LPCSTR WINAPI UrlGetLocationA(
1293         LPCSTR pszUrl)
1294 {
1295     PARSEDURLA base;
1296     DWORD res1;
1297
1298     base.cbSize = sizeof(base);
1299     res1 = ParseURLA(pszUrl, &base);
1300     if (res1) return NULL;  /* invalid scheme */
1301
1302     /* if scheme is file: then never return pointer */
1303     if (strncmp(base.pszProtocol, "file", min(4,base.cchProtocol)) == 0) return NULL;
1304
1305     /* Look for '#' and return its addr */
1306     return strchr(base.pszSuffix, '#');
1307 }
1308
1309 /*************************************************************************
1310  *      UrlGetLocationW         [SHLWAPI.@]
1311  *
1312  * See UrlGetLocationA.
1313  */
1314 LPCWSTR WINAPI UrlGetLocationW(
1315         LPCWSTR pszUrl)
1316 {
1317     PARSEDURLW base;
1318     DWORD res1;
1319
1320     base.cbSize = sizeof(base);
1321     res1 = ParseURLW(pszUrl, &base);
1322     if (res1) return NULL;  /* invalid scheme */
1323
1324     /* if scheme is file: then never return pointer */
1325     if (strncmpW(base.pszProtocol, fileW, min(4,base.cchProtocol)) == 0) return NULL;
1326
1327     /* Look for '#' and return its addr */
1328     return strchrW(base.pszSuffix, '#');
1329 }
1330
1331 /*************************************************************************
1332  *      UrlCompareA     [SHLWAPI.@]
1333  *
1334  * Compare two Urls.
1335  *
1336  * PARAMS
1337  *  pszUrl1      [I] First Url to compare
1338  *  pszUrl2      [I] Url to compare to pszUrl1
1339  *  fIgnoreSlash [I] TRUE = compare only up to a final slash
1340  *
1341  * RETURNS
1342  *  less than zero, zero, or greater than zero indicating pszUrl2 is greater
1343  *  than, equal to, or less than pszUrl1 respectively.
1344  */
1345 INT WINAPI UrlCompareA(
1346         LPCSTR pszUrl1,
1347         LPCSTR pszUrl2,
1348         BOOL fIgnoreSlash)
1349 {
1350     INT ret, len, len1, len2;
1351
1352     if (!fIgnoreSlash)
1353         return strcmp(pszUrl1, pszUrl2);
1354     len1 = strlen(pszUrl1);
1355     if (pszUrl1[len1-1] == '/') len1--;
1356     len2 = strlen(pszUrl2);
1357     if (pszUrl2[len2-1] == '/') len2--;
1358     if (len1 == len2)
1359         return strncmp(pszUrl1, pszUrl2, len1);
1360     len = min(len1, len2);
1361     ret = strncmp(pszUrl1, pszUrl2, len);
1362     if (ret) return ret;
1363     if (len1 > len2) return 1;
1364     return -1;
1365 }
1366
1367 /*************************************************************************
1368  *      UrlCompareW     [SHLWAPI.@]
1369  *
1370  * See UrlCompareA.
1371  */
1372 INT WINAPI UrlCompareW(
1373         LPCWSTR pszUrl1,
1374         LPCWSTR pszUrl2,
1375         BOOL fIgnoreSlash)
1376 {
1377     INT ret;
1378     size_t len, len1, len2;
1379
1380     if (!fIgnoreSlash)
1381         return strcmpW(pszUrl1, pszUrl2);
1382     len1 = strlenW(pszUrl1);
1383     if (pszUrl1[len1-1] == '/') len1--;
1384     len2 = strlenW(pszUrl2);
1385     if (pszUrl2[len2-1] == '/') len2--;
1386     if (len1 == len2)
1387         return strncmpW(pszUrl1, pszUrl2, len1);
1388     len = min(len1, len2);
1389     ret = strncmpW(pszUrl1, pszUrl2, len);
1390     if (ret) return ret;
1391     if (len1 > len2) return 1;
1392     return -1;
1393 }
1394
1395 /*************************************************************************
1396  *      HashData        [SHLWAPI.@]
1397  *
1398  * Hash an input block into a variable sized digest.
1399  *
1400  * PARAMS
1401  *  lpSrc    [I] Input block
1402  *  nSrcLen  [I] Length of lpSrc
1403  *  lpDest   [I] Output for hash digest
1404  *  nDestLen [I] Length of lpDest
1405  *
1406  * RETURNS
1407  *  Success: TRUE. lpDest is filled with the computed hash value.
1408  *  Failure: FALSE, if any argument is invalid.
1409  */
1410 HRESULT WINAPI HashData(const unsigned char *lpSrc, DWORD nSrcLen,
1411                      unsigned char *lpDest, DWORD nDestLen)
1412 {
1413   INT srcCount = nSrcLen - 1, destCount = nDestLen - 1;
1414
1415   if (IsBadReadPtr(lpSrc, nSrcLen) ||
1416       IsBadWritePtr(lpDest, nDestLen))
1417     return E_INVALIDARG;
1418
1419   while (destCount >= 0)
1420   {
1421     lpDest[destCount] = (destCount & 0xff);
1422     destCount--;
1423   }
1424
1425   while (srcCount >= 0)
1426   {
1427     destCount = nDestLen - 1;
1428     while (destCount >= 0)
1429     {
1430       lpDest[destCount] = HashDataLookup[lpSrc[srcCount] ^ lpDest[destCount]];
1431       destCount--;
1432     }
1433     srcCount--;
1434   }
1435   return S_OK;
1436 }
1437
1438 /*************************************************************************
1439  *      UrlHashA        [SHLWAPI.@]
1440  *
1441  * Produce a Hash from a Url.
1442  *
1443  * PARAMS
1444  *  pszUrl   [I] Url to hash
1445  *  lpDest   [O] Destinationh for hash
1446  *  nDestLen [I] Length of lpDest
1447  * 
1448  * RETURNS
1449  *  Success: S_OK. lpDest is filled with the computed hash value.
1450  *  Failure: E_INVALIDARG, if any argument is invalid.
1451  */
1452 HRESULT WINAPI UrlHashA(LPCSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1453 {
1454   if (IsBadStringPtrA(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1455     return E_INVALIDARG;
1456
1457   HashData((const BYTE*)pszUrl, (int)strlen(pszUrl), lpDest, nDestLen);
1458   return S_OK;
1459 }
1460
1461 /*************************************************************************
1462  * UrlHashW     [SHLWAPI.@]
1463  *
1464  * See UrlHashA.
1465  */
1466 HRESULT WINAPI UrlHashW(LPCWSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1467 {
1468   char szUrl[MAX_PATH];
1469
1470   TRACE("(%s,%p,%d)\n",debugstr_w(pszUrl), lpDest, nDestLen);
1471
1472   if (IsBadStringPtrW(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1473     return E_INVALIDARG;
1474
1475   /* Win32 hashes the data as an ASCII string, presumably so that both A+W
1476    * return the same digests for the same URL.
1477    */
1478   WideCharToMultiByte(0, 0, pszUrl, -1, szUrl, MAX_PATH, 0, 0);
1479   HashData((const BYTE*)szUrl, (int)strlen(szUrl), lpDest, nDestLen);
1480   return S_OK;
1481 }
1482
1483 /*************************************************************************
1484  *      UrlApplySchemeA [SHLWAPI.@]
1485  *
1486  * Apply a scheme to a Url.
1487  *
1488  * PARAMS
1489  *  pszIn   [I]   Url to apply scheme to
1490  *  pszOut  [O]   Destination for modified Url
1491  *  pcchOut [I/O] Length of pszOut/destination for length of pszOut
1492  *  dwFlags [I]   URL_ flags from "shlwapi.h"
1493  *
1494  * RETURNS
1495  *  Success: S_OK: pszOut contains the modified Url, pcchOut contains its length.
1496  *  Failure: An HRESULT error code describing the error.
1497  */
1498 HRESULT WINAPI UrlApplySchemeA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1499 {
1500     LPWSTR in, out;
1501     DWORD ret, len, len2;
1502
1503     TRACE("(in %s, out size %d, flags %08x) using W version\n",
1504           debugstr_a(pszIn), *pcchOut, dwFlags);
1505
1506     in = HeapAlloc(GetProcessHeap(), 0,
1507                               (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
1508     out = in + INTERNET_MAX_URL_LENGTH;
1509
1510     MultiByteToWideChar(0, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
1511     len = INTERNET_MAX_URL_LENGTH;
1512
1513     ret = UrlApplySchemeW(in, out, &len, dwFlags);
1514     if ((ret != S_OK) && (ret != S_FALSE)) {
1515         HeapFree(GetProcessHeap(), 0, in);
1516         return ret;
1517     }
1518
1519     len2 = WideCharToMultiByte(0, 0, out, len+1, 0, 0, 0, 0);
1520     if (len2 > *pcchOut) {
1521         *pcchOut = len2;
1522         HeapFree(GetProcessHeap(), 0, in);
1523         return E_POINTER;
1524     }
1525     WideCharToMultiByte(0, 0, out, len+1, pszOut, *pcchOut, 0, 0);
1526     *pcchOut = len2;
1527     HeapFree(GetProcessHeap(), 0, in);
1528     return ret;
1529 }
1530
1531 static HRESULT URL_GuessScheme(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1532 {
1533     HKEY newkey;
1534     BOOL j;
1535     INT index;
1536     DWORD value_len, data_len, dwType, i;
1537     WCHAR reg_path[MAX_PATH];
1538     WCHAR value[MAX_PATH], data[MAX_PATH];
1539     WCHAR Wxx, Wyy;
1540
1541     MultiByteToWideChar(0, 0,
1542               "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes",
1543                         -1, reg_path, MAX_PATH);
1544     RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
1545     index = 0;
1546     while(value_len = data_len = MAX_PATH,
1547           RegEnumValueW(newkey, index, value, &value_len,
1548                         0, &dwType, (LPVOID)data, &data_len) == 0) {
1549         TRACE("guess %d %s is %s\n",
1550               index, debugstr_w(value), debugstr_w(data));
1551
1552         j = FALSE;
1553         for(i=0; i<value_len; i++) {
1554             Wxx = pszIn[i];
1555             Wyy = value[i];
1556             /* remember that TRUE is not-equal */
1557             j = ChrCmpIW(Wxx, Wyy);
1558             if (j) break;
1559         }
1560         if ((i == value_len) && !j) {
1561             if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1562                 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1563                 RegCloseKey(newkey);
1564                 return E_POINTER;
1565             }
1566             strcpyW(pszOut, data);
1567             strcatW(pszOut, pszIn);
1568             *pcchOut = strlenW(pszOut);
1569             TRACE("matched and set to %s\n", debugstr_w(pszOut));
1570             RegCloseKey(newkey);
1571             return S_OK;
1572         }
1573         index++;
1574     }
1575     RegCloseKey(newkey);
1576     return -1;
1577 }
1578
1579 static HRESULT URL_ApplyDefault(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1580 {
1581     HKEY newkey;
1582     DWORD data_len, dwType;
1583     WCHAR value[MAX_PATH], data[MAX_PATH];
1584
1585     static const WCHAR prefix_keyW[] =
1586         {'S','o','f','t','w','a','r','e',
1587          '\\','M','i','c','r','o','s','o','f','t',
1588          '\\','W','i','n','d','o','w','s',
1589          '\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n',
1590          '\\','U','R','L',
1591          '\\','D','e','f','a','u','l','t','P','r','e','f','i','x',0};
1592
1593     /* get and prepend default */
1594     RegOpenKeyExW(HKEY_LOCAL_MACHINE, prefix_keyW, 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     TRACE("%s\n", debugstr_a(lpstrPath));
2159
2160     if (!lpstrPath || !*lpstrPath) return FALSE;
2161
2162     /* get protocol        */
2163     base.cbSize = sizeof(base);
2164     res1 = ParseURLA(lpstrPath, &base);
2165     return (base.nScheme != URL_SCHEME_INVALID);
2166 }
2167
2168 /*************************************************************************
2169  * PathIsURLW   [SHLWAPI.@]
2170  *
2171  * See PathIsURLA.
2172  */
2173 BOOL WINAPI PathIsURLW(LPCWSTR lpstrPath)
2174 {
2175     PARSEDURLW base;
2176     DWORD res1;
2177
2178     TRACE("%s\n", debugstr_w(lpstrPath));
2179
2180     if (!lpstrPath || !*lpstrPath) return FALSE;
2181
2182     /* get protocol        */
2183     base.cbSize = sizeof(base);
2184     res1 = ParseURLW(lpstrPath, &base);
2185     return (base.nScheme != URL_SCHEME_INVALID);
2186 }
2187
2188 /*************************************************************************
2189  *      UrlCreateFromPathA      [SHLWAPI.@]
2190  * 
2191  * See UrlCreateFromPathW
2192  */
2193 HRESULT WINAPI UrlCreateFromPathA(LPCSTR pszPath, LPSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2194 {
2195     WCHAR bufW[INTERNET_MAX_URL_LENGTH];
2196     WCHAR *urlW = bufW;
2197     UNICODE_STRING pathW;
2198     HRESULT ret;
2199     DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
2200
2201     if(!RtlCreateUnicodeStringFromAsciiz(&pathW, pszPath))
2202         return E_INVALIDARG;
2203     if((ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved)) == E_POINTER) {
2204         urlW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
2205         ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved);
2206     }
2207     if(ret == S_OK || ret == S_FALSE) {
2208         RtlUnicodeToMultiByteSize(&lenA, urlW, lenW * sizeof(WCHAR));
2209         if(*pcchUrl > lenA) {
2210             RtlUnicodeToMultiByteN(pszUrl, *pcchUrl - 1, &lenA, urlW, lenW * sizeof(WCHAR));
2211             pszUrl[lenA] = 0;
2212             *pcchUrl = lenA;
2213         } else {
2214             *pcchUrl = lenA + 1;
2215             ret = E_POINTER;
2216         }
2217     }
2218     if(urlW != bufW) HeapFree(GetProcessHeap(), 0, urlW);
2219     RtlFreeUnicodeString(&pathW);
2220     return ret;
2221 }
2222
2223 /*************************************************************************
2224  *      UrlCreateFromPathW      [SHLWAPI.@]
2225  *
2226  * Create a Url from a file path.
2227  *
2228  * PARAMS
2229  *  pszPath [I]    Path to convert
2230  *  pszUrl  [O]    Destination for the converted Url
2231  *  pcchUrl [I/O]  Length of pszUrl
2232  *  dwReserved [I] Reserved, must be 0
2233  *
2234  * RETURNS
2235  *  Success: S_OK pszUrl contains the converted path, S_FALSE if the path is already a Url
2236  *  Failure: An HRESULT error code.
2237  */
2238 HRESULT WINAPI UrlCreateFromPathW(LPCWSTR pszPath, LPWSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2239 {
2240     DWORD needed;
2241     HRESULT ret;
2242     WCHAR *pszNewUrl;
2243     WCHAR file_colonW[] = {'f','i','l','e',':',0};
2244     WCHAR three_slashesW[] = {'/','/','/',0};
2245     PARSEDURLW parsed_url;
2246
2247     TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszPath), pszUrl, pcchUrl, dwReserved);
2248
2249     /* Validate arguments */
2250     if (dwReserved != 0)
2251         return E_INVALIDARG;
2252     if (!pszUrl || !pcchUrl)
2253         return E_INVALIDARG;
2254
2255
2256     parsed_url.cbSize = sizeof(parsed_url);
2257     if(ParseURLW(pszPath, &parsed_url) == S_OK) {
2258         if(parsed_url.nScheme != URL_SCHEME_INVALID && parsed_url.cchProtocol > 1) {
2259             needed = strlenW(pszPath);
2260             if (needed >= *pcchUrl) {
2261                 *pcchUrl = needed + 1;
2262                 return E_POINTER;
2263             } else {
2264                 *pcchUrl = needed;
2265                 strcpyW(pszUrl, pszPath);
2266                 return S_FALSE;
2267             }
2268         }
2269     }
2270
2271     pszNewUrl = HeapAlloc(GetProcessHeap(), 0, (strlenW(pszPath) + 9) * sizeof(WCHAR)); /* "file:///" + pszPath_len + 1 */
2272     strcpyW(pszNewUrl, file_colonW);
2273     if(isalphaW(pszPath[0]) && pszPath[1] == ':')
2274         strcatW(pszNewUrl, three_slashesW);
2275     strcatW(pszNewUrl, pszPath);
2276     ret = UrlEscapeW(pszNewUrl, pszUrl, pcchUrl, URL_ESCAPE_PERCENT);
2277
2278     HeapFree(GetProcessHeap(), 0, pszNewUrl);
2279     return ret;
2280 }
2281
2282 /*************************************************************************
2283  *      SHAutoComplete          [SHLWAPI.@]
2284  *
2285  * Enable auto-completion for an edit control.
2286  *
2287  * PARAMS
2288  *  hwndEdit [I] Handle of control to enable auto-completion for
2289  *  dwFlags  [I] SHACF_ flags from "shlwapi.h"
2290  *
2291  * RETURNS
2292  *  Success: S_OK. Auto-completion is enabled for the control.
2293  *  Failure: An HRESULT error code indicating the error.
2294  */
2295 HRESULT WINAPI SHAutoComplete(HWND hwndEdit, DWORD dwFlags)
2296 {
2297   FIXME("SHAutoComplete stub\n");
2298   return S_FALSE;
2299 }
2300
2301 /*************************************************************************
2302  *  MLBuildResURLA      [SHLWAPI.405]
2303  *
2304  * Create a Url pointing to a resource in a module.
2305  *
2306  * PARAMS
2307  *  lpszLibName [I] Name of the module containing the resource
2308  *  hMod        [I] Callers module handle
2309  *  dwFlags     [I] Undocumented flags for loading the module
2310  *  lpszRes     [I] Resource name
2311  *  lpszDest    [O] Destination for resulting Url
2312  *  dwDestLen   [I] Length of lpszDest
2313  *
2314  * RETURNS
2315  *  Success: S_OK. lpszDest contains the resource Url.
2316  *  Failure: E_INVALIDARG, if any argument is invalid, or
2317  *           E_FAIL if dwDestLen is too small.
2318  */
2319 HRESULT WINAPI MLBuildResURLA(LPCSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2320                               LPCSTR lpszRes, LPSTR lpszDest, DWORD dwDestLen)
2321 {
2322   WCHAR szLibName[MAX_PATH], szRes[MAX_PATH], szDest[MAX_PATH];
2323   HRESULT hRet;
2324
2325   if (lpszLibName)
2326     MultiByteToWideChar(CP_ACP, 0, lpszLibName, -1, szLibName, sizeof(szLibName)/sizeof(WCHAR));
2327
2328   if (lpszRes)
2329     MultiByteToWideChar(CP_ACP, 0, lpszRes, -1, szRes, sizeof(szRes)/sizeof(WCHAR));
2330
2331   if (dwDestLen > sizeof(szLibName)/sizeof(WCHAR))
2332     dwDestLen = sizeof(szLibName)/sizeof(WCHAR);
2333
2334   hRet = MLBuildResURLW(lpszLibName ? szLibName : NULL, hMod, dwFlags,
2335                         lpszRes ? szRes : NULL, lpszDest ? szDest : NULL, dwDestLen);
2336   if (SUCCEEDED(hRet) && lpszDest)
2337     WideCharToMultiByte(CP_ACP, 0, szDest, -1, lpszDest, dwDestLen, 0, 0);
2338
2339   return hRet;
2340 }
2341
2342 /*************************************************************************
2343  *  MLBuildResURLA      [SHLWAPI.406]
2344  *
2345  * See MLBuildResURLA.
2346  */
2347 HRESULT WINAPI MLBuildResURLW(LPCWSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2348                               LPCWSTR lpszRes, LPWSTR lpszDest, DWORD dwDestLen)
2349 {
2350   static const WCHAR szRes[] = { 'r','e','s',':','/','/','\0' };
2351 #define szResLen ((sizeof(szRes) - sizeof(WCHAR))/sizeof(WCHAR))
2352   HRESULT hRet = E_FAIL;
2353
2354   TRACE("(%s,%p,0x%08x,%s,%p,%d)\n", debugstr_w(lpszLibName), hMod, dwFlags,
2355         debugstr_w(lpszRes), lpszDest, dwDestLen);
2356
2357   if (!lpszLibName || !hMod || hMod == INVALID_HANDLE_VALUE || !lpszRes ||
2358       !lpszDest || (dwFlags && dwFlags != 2))
2359     return E_INVALIDARG;
2360
2361   if (dwDestLen >= szResLen + 1)
2362   {
2363     dwDestLen -= (szResLen + 1);
2364     memcpy(lpszDest, szRes, sizeof(szRes));
2365
2366     hMod = MLLoadLibraryW(lpszLibName, hMod, dwFlags);
2367
2368     if (hMod)
2369     {
2370       WCHAR szBuff[MAX_PATH];
2371       DWORD len;
2372
2373       len = GetModuleFileNameW(hMod, szBuff, sizeof(szBuff)/sizeof(WCHAR));
2374       if (len && len < sizeof(szBuff)/sizeof(WCHAR))
2375       {
2376         DWORD dwPathLen = strlenW(szBuff) + 1;
2377
2378         if (dwDestLen >= dwPathLen)
2379         {
2380           DWORD dwResLen;
2381
2382           dwDestLen -= dwPathLen;
2383           memcpy(lpszDest + szResLen, szBuff, dwPathLen * sizeof(WCHAR));
2384
2385           dwResLen = strlenW(lpszRes) + 1;
2386           if (dwDestLen >= dwResLen + 1)
2387           {
2388             lpszDest[szResLen + dwPathLen + dwResLen] = '/';
2389             memcpy(lpszDest + szResLen + dwPathLen, lpszRes, dwResLen * sizeof(WCHAR));
2390             hRet = S_OK;
2391           }
2392         }
2393       }
2394       MLFreeLibrary(hMod);
2395     }
2396   }
2397   return hRet;
2398 }