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