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