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