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