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