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