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