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