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