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