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