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