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