Added LGPL standard comment, and copyright notices where necessary.
[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_SPACE_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 the 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, ther returned string 
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 NOERROR;
1176 }
1177
1178 /*************************************************************************
1179  *      UrlApplySchemeA [SHLWAPI.@]
1180  */
1181 HRESULT WINAPI UrlApplySchemeA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1182 {
1183     LPWSTR in, out;
1184     DWORD ret, len, len2;
1185
1186     TRACE("(in %s, out size %ld, flags %08lx) using W version\n",
1187           debugstr_a(pszIn), *pcchOut, dwFlags);
1188
1189     in = (LPWSTR) HeapAlloc(GetProcessHeap(), 0, 
1190                               (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
1191     out = in + INTERNET_MAX_URL_LENGTH;
1192
1193     MultiByteToWideChar(0, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
1194     len = INTERNET_MAX_URL_LENGTH;
1195
1196     ret = UrlApplySchemeW(in, out, &len, dwFlags);
1197     if ((ret != S_OK) && (ret != S_FALSE)) {
1198         HeapFree(GetProcessHeap(), 0, in);
1199         return ret;
1200     }
1201
1202     len2 = WideCharToMultiByte(0, 0, out, len+1, 0, 0, 0, 0);
1203     if (len2 > *pcchOut) {
1204         *pcchOut = len2;
1205         HeapFree(GetProcessHeap(), 0, in);
1206         return E_POINTER;
1207     }
1208     WideCharToMultiByte(0, 0, out, len+1, pszOut, *pcchOut, 0, 0);
1209     *pcchOut = len2;
1210     HeapFree(GetProcessHeap(), 0, in);
1211     return ret;
1212 }
1213
1214 HRESULT URL_GuessScheme(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1215 {
1216     HKEY newkey;
1217     BOOL j;
1218     INT index, i;
1219     DWORD value_len, data_len, dwType;
1220     WCHAR reg_path[MAX_PATH];
1221     WCHAR value[MAX_PATH], data[MAX_PATH];
1222     WCHAR Wxx, Wyy;
1223
1224     MultiByteToWideChar(0, 0,
1225               "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes",
1226                         -1, reg_path, MAX_PATH);
1227     RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
1228     index = 0;
1229     while(value_len = data_len = MAX_PATH,
1230           RegEnumValueW(newkey, index, value, &value_len,
1231                         0, &dwType, (LPVOID)data, &data_len) == 0) {
1232         TRACE("guess %d %s is %s\n",
1233               index, debugstr_w(value), debugstr_w(data));
1234
1235         j = FALSE;
1236         for(i=0; i<value_len; i++) {
1237             Wxx = pszIn[i];
1238             Wyy = value[i];
1239             /* remember that TRUE is not-equal */
1240             j = ChrCmpIW(Wxx, Wyy);
1241             if (j) break;
1242         }
1243         if ((i == value_len) && !j) {
1244             if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1245                 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1246                 RegCloseKey(newkey);
1247                 return E_POINTER;
1248             }
1249             strcpyW(pszOut, data);
1250             strcatW(pszOut, pszIn);
1251             *pcchOut = strlenW(pszOut);
1252             TRACE("matched and set to %s\n", debugstr_w(pszOut));
1253             RegCloseKey(newkey);
1254             return S_OK;
1255         }
1256         index++;
1257     }
1258     RegCloseKey(newkey);
1259     return -1;
1260 }
1261
1262 HRESULT URL_ApplyDefault(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1263 {
1264     HKEY newkey;
1265     DWORD data_len, dwType;
1266     WCHAR reg_path[MAX_PATH];
1267     WCHAR value[MAX_PATH], data[MAX_PATH];
1268
1269     /* get and prepend default */
1270     MultiByteToWideChar(0, 0,
1271          "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\DefaultPrefix",
1272                         -1, reg_path, MAX_PATH);
1273     RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
1274     data_len = MAX_PATH;
1275     value[0] = L'@';
1276     value[1] = L'\0';
1277     RegQueryValueExW(newkey, value, 0, &dwType, (LPBYTE)data, &data_len);
1278     RegCloseKey(newkey);
1279     if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1280         *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1281         return E_POINTER;
1282     }
1283     strcpyW(pszOut, data);
1284     strcatW(pszOut, pszIn);
1285     *pcchOut = strlenW(pszOut);
1286     TRACE("used default %s\n", debugstr_w(pszOut));
1287     return S_OK;
1288 }
1289
1290 /*************************************************************************
1291  *      UrlApplySchemeW [SHLWAPI.@]
1292  */
1293 HRESULT WINAPI UrlApplySchemeW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1294 {
1295     UNKNOWN_SHLWAPI_2 in_scheme;
1296     DWORD res1;
1297     HRESULT ret;
1298
1299     TRACE("(in %s, out size %ld, flags %08lx)\n",
1300           debugstr_w(pszIn), *pcchOut, dwFlags);
1301
1302     if (dwFlags & URL_APPLY_GUESSFILE) {
1303         FIXME("(%s %p %p(%ld) 0x%08lx): stub URL_APPLY_GUESSFILE not implemented\n", 
1304               debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwFlags);
1305         strcpyW(pszOut, pszIn);
1306         *pcchOut = strlenW(pszOut);
1307         return S_FALSE;
1308     }
1309
1310     in_scheme.size = 24;
1311     /* See if the base has a scheme */
1312     res1 = SHLWAPI_2(pszIn, &in_scheme);
1313     if (res1) {
1314         /* no scheme in input, need to see if we need to guess */
1315         if (dwFlags & URL_APPLY_GUESSSCHEME) {
1316             if ((ret = URL_GuessScheme(pszIn, pszOut, pcchOut)) != -1) 
1317                 return ret;
1318         }
1319     }
1320     else {
1321         /* we have a scheme, see if valid (known scheme) */
1322         if (in_scheme.fcncde) {
1323             /* have valid scheme, so just copy and exit */
1324             if (strlenW(pszIn) + 1 > *pcchOut) {
1325                 *pcchOut = strlenW(pszIn) + 1;
1326                 return E_POINTER;
1327             }
1328             strcpyW(pszOut, pszIn);
1329             *pcchOut = strlenW(pszOut);
1330             TRACE("valid scheme, returing copy\n");
1331             return S_OK;
1332         }
1333     }
1334
1335     /* If we are here, then either invalid scheme, 
1336      * or no scheme and can't/failed guess.
1337      */
1338     if ( ( ((res1 == 0) && (dwFlags & URL_APPLY_FORCEAPPLY)) ||
1339            ((res1 != 0)) ) &&
1340          (dwFlags & URL_APPLY_DEFAULT)) {
1341         /* find and apply default scheme */
1342         return URL_ApplyDefault(pszIn, pszOut, pcchOut);
1343     }
1344
1345     /* just copy and give proper return code */
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("returing copy, left alone\n");
1353     return S_FALSE;
1354 }
1355
1356 /*************************************************************************
1357  *      UrlIsA          [SHLWAPI.@]
1358  */
1359 BOOL WINAPI UrlIsA(LPCSTR pszUrl, URLIS Urlis)
1360 {
1361     UNKNOWN_SHLWAPI_1 base;
1362     DWORD res1;
1363
1364     switch (Urlis) {
1365
1366     case URLIS_OPAQUE:
1367         base.size = 24;
1368         res1 = SHLWAPI_1(pszUrl, &base);
1369         if (res1) return FALSE;  /* invalid scheme */
1370         if ((*base.ap2 == '/') && (*(base.ap2+1) == '/'))
1371             /* has scheme followed by 2 '/' */
1372             return FALSE;
1373         return TRUE;
1374
1375     case URLIS_URL:
1376     case URLIS_NOHISTORY:
1377     case URLIS_FILEURL:
1378     case URLIS_APPLIABLE:
1379     case URLIS_DIRECTORY:
1380     case URLIS_HASQUERY:
1381     default:
1382         FIXME("(%s %d): stub\n", debugstr_a(pszUrl), Urlis);
1383     }
1384     return FALSE;
1385 }
1386
1387 /*************************************************************************
1388  *      UrlIsW          [SHLWAPI.@]
1389  */
1390 BOOL WINAPI UrlIsW(LPCWSTR pszUrl, URLIS Urlis)
1391 {
1392     UNKNOWN_SHLWAPI_2 base;
1393     DWORD res1;
1394
1395     switch (Urlis) {
1396
1397     case URLIS_OPAQUE:
1398         base.size = 24;
1399         res1 = SHLWAPI_2(pszUrl, &base);
1400         if (res1) return FALSE;  /* invalid scheme */
1401         if ((*base.ap2 == L'/') && (*(base.ap2+1) == L'/'))
1402             /* has scheme followed by 2 '/' */
1403             return FALSE;
1404         return TRUE;
1405
1406     case URLIS_URL:
1407     case URLIS_NOHISTORY:
1408     case URLIS_FILEURL:
1409     case URLIS_APPLIABLE:
1410     case URLIS_DIRECTORY:
1411     case URLIS_HASQUERY:
1412     default:
1413         FIXME("(%s %d): stub\n", debugstr_w(pszUrl), Urlis);
1414     }
1415     return FALSE;
1416 }
1417
1418 /*************************************************************************
1419  *      UrlIsNoHistoryA         [SHLWAPI.@]
1420  */
1421 BOOL WINAPI UrlIsNoHistoryA(LPCSTR pszUrl)
1422 {
1423     return UrlIsA(pszUrl, URLIS_NOHISTORY);
1424 }
1425
1426 /*************************************************************************
1427  *      UrlIsNoHistoryW         [SHLWAPI.@]
1428  */
1429 BOOL WINAPI UrlIsNoHistoryW(LPCWSTR pszUrl)
1430 {
1431     return UrlIsW(pszUrl, URLIS_NOHISTORY);
1432 }
1433
1434 /*************************************************************************
1435  *      UrlIsOpaqueA    [SHLWAPI.@]
1436  */
1437 BOOL WINAPI UrlIsOpaqueA(LPCSTR pszUrl)
1438 {
1439     return UrlIsA(pszUrl, URLIS_OPAQUE);
1440 }
1441
1442 /*************************************************************************
1443  *      UrlIsOpaqueW    [SHLWAPI.@]
1444  */
1445 BOOL WINAPI UrlIsOpaqueW(LPCWSTR pszUrl)
1446 {
1447     return UrlIsW(pszUrl, URLIS_OPAQUE);
1448 }
1449
1450 /*************************************************************************
1451  *  Scans for characters of type "type" and when not matching found,
1452  *  returns pointer to it and length in size.
1453  *
1454  * Characters tested based on RFC 1738
1455  */
1456 LPCWSTR  URL_ScanID(LPCWSTR start, LPDWORD size, WINE_URL_SCAN_TYPE type)
1457 {
1458     static DWORD alwayszero = 0;
1459     BOOL cont = TRUE;
1460
1461     *size = 0;
1462
1463     switch(type){
1464
1465     case SCHEME:
1466         while (cont) {
1467             if ( (islowerW(*start) && isalphaW(*start)) ||
1468                  isdigitW(*start) ||
1469                  (*start == L'+') ||
1470                  (*start == L'-') ||
1471                  (*start == L'.')) {
1472                 start++;
1473                 (*size)++;
1474             }
1475             else
1476                 cont = FALSE;
1477         }
1478         break;
1479
1480     case USERPASS:
1481         while (cont) {
1482             if ( isalphaW(*start) ||
1483                  isdigitW(*start) ||
1484                  /* user/password only characters */
1485                  (*start == L';') ||
1486                  (*start == L'?') ||
1487                  (*start == L'&') ||
1488                  (*start == L'=') ||
1489                  /* *extra* characters */
1490                  (*start == L'!') ||
1491                  (*start == L'*') ||
1492                  (*start == L'\'') ||
1493                  (*start == L'(') ||
1494                  (*start == L')') ||
1495                  (*start == L',') ||
1496                  /* *safe* characters */
1497                  (*start == L'$') ||
1498                  (*start == L'_') ||
1499                  (*start == L'+') ||
1500                  (*start == L'-') ||
1501                  (*start == L'.')) {
1502                 start++;
1503                 (*size)++;
1504             } else if (*start == L'%') {
1505                 if (isxdigitW(*(start+1)) && 
1506                     isxdigitW(*(start+2))) {
1507                     start += 3;
1508                     *size += 3;
1509                 } else
1510                     cont = FALSE;
1511             } else
1512                 cont = FALSE;
1513         }
1514         break;
1515
1516     case PORT:
1517         while (cont) {
1518             if (isdigitW(*start)) {
1519                 start++;
1520                 (*size)++;
1521             }
1522             else
1523                 cont = FALSE;
1524         }
1525         break;
1526
1527     case HOST:
1528         while (cont) {
1529             if (isalnumW(*start) ||
1530                 (*start == L'-') ||
1531                 (*start == L'.') ) {
1532                 start++;
1533                 (*size)++;
1534             }
1535             else
1536                 cont = FALSE;
1537         }
1538         break;
1539     default:
1540         FIXME("unknown type %d\n", type);
1541         return (LPWSTR)&alwayszero;
1542     }
1543     /* TRACE("scanned %ld characters next char %p<%c>\n",
1544      *size, start, *start); */
1545     return start;
1546 }
1547
1548 /*************************************************************************
1549  *  Attempt to parse URL into pieces.
1550  */
1551 LONG URL_ParseUrl(LPCWSTR pszUrl, WINE_PARSE_URL *pl)
1552 {
1553     LPCWSTR work;
1554
1555     memset(pl, 0, sizeof(WINE_PARSE_URL));
1556     pl->pScheme = pszUrl;
1557     work = URL_ScanID(pl->pScheme, &pl->szScheme, SCHEME);
1558     if (!*work || (*work != L':')) goto ERROR;
1559     work++;
1560     if ((*work != L'/') || (*(work+1) != L'/')) goto ERROR;
1561     pl->pUserName = work + 2;
1562     work = URL_ScanID(pl->pUserName, &pl->szUserName, USERPASS);
1563     if (*work == L':' ) {
1564         /* parse password */
1565         work++;
1566         pl->pPassword = work;
1567         work = URL_ScanID(pl->pPassword, &pl->szPassword, USERPASS);
1568         if (*work != L'@') {
1569             /* what we just parsed must be the hostname and port
1570              * so reset pointers and clear then let it parse */
1571             pl->szUserName = pl->szPassword = 0;
1572             work = pl->pUserName - 1;
1573             pl->pUserName = pl->pPassword = 0;
1574         }
1575     } else if (*work == L'@') {
1576         /* no password */
1577         pl->szPassword = 0;
1578         pl->pPassword = 0;
1579     } else if (!*work || (*work == L'/') || (*work == L'.')) {
1580         /* what was parsed was hostname, so reset pointers and let it parse */
1581         pl->szUserName = pl->szPassword = 0;
1582         work = pl->pUserName - 1;
1583         pl->pUserName = pl->pPassword = 0;
1584     } else goto ERROR;
1585
1586     /* now start parsing hostname or hostnumber */
1587     work++;
1588     pl->pHostName = work;
1589     work = URL_ScanID(pl->pHostName, &pl->szHostName, HOST);
1590     if (*work == L':') {
1591         /* parse port */
1592         work++;
1593         pl->pPort = work;
1594         work = URL_ScanID(pl->pPort, &pl->szPort, PORT);
1595     }
1596     if (*work == L'/') {
1597         /* see if query string */
1598         pl->pQuery = strchrW(work, L'?');
1599         if (pl->pQuery) pl->szQuery = strlenW(pl->pQuery);
1600     }
1601     TRACE("parse successful: scheme=%p(%ld), user=%p(%ld), pass=%p(%ld), host=%p(%ld), port=%p(%ld), query=%p(%ld)\n",
1602           pl->pScheme, pl->szScheme,
1603           pl->pUserName, pl->szUserName,
1604           pl->pPassword, pl->szPassword,
1605           pl->pHostName, pl->szHostName,
1606           pl->pPort, pl->szPort,
1607           pl->pQuery, pl->szQuery);
1608     return S_OK;
1609   ERROR:
1610     FIXME("failed to parse %s\n", debugstr_w(pszUrl));
1611     return E_INVALIDARG;
1612 }
1613
1614 /*************************************************************************
1615  *      UrlGetPartA     [SHLWAPI.@]
1616  */
1617 HRESULT WINAPI UrlGetPartA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut, 
1618                            DWORD dwPart, DWORD dwFlags)
1619 {
1620     LPWSTR in, out;
1621     DWORD ret, len, len2;
1622
1623     in = (LPWSTR) HeapAlloc(GetProcessHeap(), 0, 
1624                               (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
1625     out = in + INTERNET_MAX_URL_LENGTH;
1626
1627     MultiByteToWideChar(0, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
1628
1629     len = INTERNET_MAX_URL_LENGTH;
1630     ret = UrlGetPartW(in, out, &len, dwPart, dwFlags);
1631
1632     if (ret != S_OK) {
1633         HeapFree(GetProcessHeap(), 0, in);
1634         return ret;
1635     }
1636
1637     len2 = WideCharToMultiByte(0, 0, out, len, 0, 0, 0, 0);
1638     if (len2 > *pcchOut) {
1639         *pcchOut = len2;
1640         HeapFree(GetProcessHeap(), 0, in);
1641         return E_POINTER;
1642     }
1643     WideCharToMultiByte(0, 0, out, len+1, pszOut, *pcchOut, 0, 0);
1644     *pcchOut = len2;
1645     HeapFree(GetProcessHeap(), 0, in);
1646     return S_OK;
1647 }
1648
1649 /*************************************************************************
1650  *      UrlGetPartW     [SHLWAPI.@]
1651  */
1652 HRESULT WINAPI UrlGetPartW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut, 
1653                            DWORD dwPart, DWORD dwFlags)
1654 {
1655     WINE_PARSE_URL pl;
1656     HRESULT ret;
1657     DWORD size, schsize;
1658     LPCWSTR addr, schaddr;
1659     LPWSTR work;
1660
1661     TRACE("(%s %p %p(%ld) %08lx %08lx)\n",
1662           debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwPart, dwFlags);
1663
1664     ret = URL_ParseUrl(pszIn, &pl);
1665     if (!ret) {
1666         schaddr = pl.pScheme;
1667         schsize = pl.szScheme;
1668
1669         switch (dwPart) {
1670         case URL_PART_SCHEME:
1671             if (!pl.szScheme) return E_INVALIDARG;
1672             addr = pl.pScheme;
1673             size = pl.szScheme;
1674             break;
1675
1676         case URL_PART_HOSTNAME:
1677             if (!pl.szHostName) return E_INVALIDARG;
1678             addr = pl.pHostName;
1679             size = pl.szHostName;
1680             break;
1681
1682         case URL_PART_USERNAME:
1683             if (!pl.szUserName) return E_INVALIDARG;
1684             addr = pl.pUserName;
1685             size = pl.szUserName;
1686             break;
1687
1688         case URL_PART_PASSWORD:
1689             if (!pl.szPassword) return E_INVALIDARG;
1690             addr = pl.pPassword;
1691             size = pl.szPassword;
1692             break;
1693
1694         case URL_PART_PORT:
1695             if (!pl.szPort) return E_INVALIDARG;
1696             addr = pl.pPort;
1697             size = pl.szPort;
1698             break;
1699
1700         case URL_PART_QUERY:
1701             if (!pl.szQuery) return E_INVALIDARG;
1702             addr = pl.pQuery;
1703             size = pl.szQuery;
1704             break;
1705
1706         default:
1707             return E_INVALIDARG;
1708         }
1709
1710         if (dwFlags == URL_PARTFLAG_KEEPSCHEME) {
1711             if (*pcchOut < size + schsize + 2) {
1712                 *pcchOut = size + schsize + 2;
1713                 return E_POINTER;
1714             }
1715             strncpyW(pszOut, schaddr, schsize);
1716             work = pszOut + schsize;
1717             *work = L':';
1718             strncpyW(work+1, addr, size);
1719             *pcchOut = size + schsize + 1;
1720             work += (size + 1);
1721             *work = L'\0';
1722         }
1723         else {
1724             if (*pcchOut < size + 1) {*pcchOut = size+1; return E_POINTER;}
1725             strncpyW(pszOut, addr, size);
1726             *pcchOut = size;
1727             work = pszOut + size;
1728             *work = L'\0';
1729         }
1730         TRACE("len=%ld %s\n", *pcchOut, debugstr_w(pszOut));
1731     }
1732     return ret;
1733 }