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