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