shlwapi: Handle URL_WININET_COMPATIBILITY flag in UrlCanonicalize.
[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include "config.h"
22 #include "wine/port.h"
23 #include <stdarg.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include "windef.h"
27 #include "winbase.h"
28 #include "winnls.h"
29 #include "winerror.h"
30 #include "wine/unicode.h"
31 #include "wininet.h"
32 #include "winreg.h"
33 #include "winternl.h"
34 #define NO_SHLWAPI_STREAM
35 #include "shlwapi.h"
36 #include "intshcut.h"
37 #include "wine/debug.h"
38
39 HMODULE WINAPI MLLoadLibraryW(LPCWSTR,HMODULE,DWORD);
40 BOOL    WINAPI MLFreeLibrary(HMODULE);
41 HRESULT WINAPI MLBuildResURLW(LPCWSTR,HMODULE,DWORD,LPCWSTR,LPWSTR,DWORD);
42
43 WINE_DEFAULT_DEBUG_CHANNEL(shell);
44
45 /* The following schemes were identified in the native version of
46  * SHLWAPI.DLL version 5.50
47  */
48 static const struct {
49     URL_SCHEME  scheme_number;
50     WCHAR scheme_name[12];
51 } shlwapi_schemes[] = {
52   {URL_SCHEME_FTP,        {'f','t','p',0}},
53   {URL_SCHEME_HTTP,       {'h','t','t','p',0}},
54   {URL_SCHEME_GOPHER,     {'g','o','p','h','e','r',0}},
55   {URL_SCHEME_MAILTO,     {'m','a','i','l','t','o',0}},
56   {URL_SCHEME_NEWS,       {'n','e','w','s',0}},
57   {URL_SCHEME_NNTP,       {'n','n','t','p',0}},
58   {URL_SCHEME_TELNET,     {'t','e','l','n','e','t',0}},
59   {URL_SCHEME_WAIS,       {'w','a','i','s',0}},
60   {URL_SCHEME_FILE,       {'f','i','l','e',0}},
61   {URL_SCHEME_MK,         {'m','k',0}},
62   {URL_SCHEME_HTTPS,      {'h','t','t','p','s',0}},
63   {URL_SCHEME_SHELL,      {'s','h','e','l','l',0}},
64   {URL_SCHEME_SNEWS,      {'s','n','e','w','s',0}},
65   {URL_SCHEME_LOCAL,      {'l','o','c','a','l',0}},
66   {URL_SCHEME_JAVASCRIPT, {'j','a','v','a','s','c','r','i','p','t',0}},
67   {URL_SCHEME_VBSCRIPT,   {'v','b','s','c','r','i','p','t',0}},
68   {URL_SCHEME_ABOUT,      {'a','b','o','u','t',0}},
69   {URL_SCHEME_RES,        {'r','e','s',0}},
70 };
71
72 typedef struct {
73     LPCWSTR pScheme;      /* [out] start of scheme                     */
74     DWORD   szScheme;     /* [out] size of scheme (until colon)        */
75     LPCWSTR pUserName;    /* [out] start of Username                   */
76     DWORD   szUserName;   /* [out] size of Username (until ":" or "@") */
77     LPCWSTR pPassword;    /* [out] start of Password                   */
78     DWORD   szPassword;   /* [out] size of Password (until "@")        */
79     LPCWSTR pHostName;    /* [out] start of Hostname                   */
80     DWORD   szHostName;   /* [out] size of Hostname (until ":" or "/") */
81     LPCWSTR pPort;        /* [out] start of Port                       */
82     DWORD   szPort;       /* [out] size of Port (until "/" or eos)     */
83     LPCWSTR pQuery;       /* [out] start of Query                      */
84     DWORD   szQuery;      /* [out] size of Query (until eos)           */
85 } WINE_PARSE_URL;
86
87 typedef enum {
88     SCHEME,
89     HOST,
90     PORT,
91     USERPASS,
92 } WINE_URL_SCAN_TYPE;
93
94 static const CHAR hexDigits[] = "0123456789ABCDEF";
95
96 static const WCHAR fileW[] = {'f','i','l','e','\0'};
97
98 static const unsigned char HashDataLookup[256] = {
99  0x01, 0x0E, 0x6E, 0x19, 0x61, 0xAE, 0x84, 0x77, 0x8A, 0xAA, 0x7D, 0x76, 0x1B,
100  0xE9, 0x8C, 0x33, 0x57, 0xC5, 0xB1, 0x6B, 0xEA, 0xA9, 0x38, 0x44, 0x1E, 0x07,
101  0xAD, 0x49, 0xBC, 0x28, 0x24, 0x41, 0x31, 0xD5, 0x68, 0xBE, 0x39, 0xD3, 0x94,
102  0xDF, 0x30, 0x73, 0x0F, 0x02, 0x43, 0xBA, 0xD2, 0x1C, 0x0C, 0xB5, 0x67, 0x46,
103  0x16, 0x3A, 0x4B, 0x4E, 0xB7, 0xA7, 0xEE, 0x9D, 0x7C, 0x93, 0xAC, 0x90, 0xB0,
104  0xA1, 0x8D, 0x56, 0x3C, 0x42, 0x80, 0x53, 0x9C, 0xF1, 0x4F, 0x2E, 0xA8, 0xC6,
105  0x29, 0xFE, 0xB2, 0x55, 0xFD, 0xED, 0xFA, 0x9A, 0x85, 0x58, 0x23, 0xCE, 0x5F,
106  0x74, 0xFC, 0xC0, 0x36, 0xDD, 0x66, 0xDA, 0xFF, 0xF0, 0x52, 0x6A, 0x9E, 0xC9,
107  0x3D, 0x03, 0x59, 0x09, 0x2A, 0x9B, 0x9F, 0x5D, 0xA6, 0x50, 0x32, 0x22, 0xAF,
108  0xC3, 0x64, 0x63, 0x1A, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xBD, 0x79, 0x40,
109  0x4D, 0x48, 0xD0, 0xF5, 0x82, 0x7A, 0x8F, 0x37, 0x69, 0x86, 0x1D, 0xA4, 0xB9,
110  0xC2, 0xC1, 0xEF, 0x65, 0xF2, 0x05, 0xAB, 0x7E, 0x0B, 0x4A, 0x3B, 0x89, 0xE4,
111  0x6C, 0xBF, 0xE8, 0x8B, 0x06, 0x18, 0x51, 0x14, 0x7F, 0x11, 0x5B, 0x5C, 0xFB,
112  0x97, 0xE1, 0xCF, 0x15, 0x62, 0x71, 0x70, 0x54, 0xE2, 0x12, 0xD6, 0xC7, 0xBB,
113  0x0D, 0x20, 0x5E, 0xDC, 0xE0, 0xD4, 0xF7, 0xCC, 0xC4, 0x2B, 0xF9, 0xEC, 0x2D,
114  0xF4, 0x6F, 0xB6, 0x99, 0x88, 0x81, 0x5A, 0xD9, 0xCA, 0x13, 0xA5, 0xE7, 0x47,
115  0xE6, 0x8E, 0x60, 0xE3, 0x3E, 0xB3, 0xF6, 0x72, 0xA2, 0x35, 0xA0, 0xD7, 0xCD,
116  0xB4, 0x2F, 0x6D, 0x2C, 0x26, 0x1F, 0x95, 0x87, 0x00, 0xD8, 0x34, 0x3F, 0x17,
117  0x25, 0x45, 0x27, 0x75, 0x92, 0xB8, 0xA3, 0xC8, 0xDE, 0xEB, 0xF8, 0xF3, 0xDB,
118  0x0A, 0x98, 0x83, 0x7B, 0xE5, 0xCB, 0x4C, 0x78, 0xD1 };
119
120 static DWORD get_scheme_code(LPCWSTR scheme, DWORD scheme_len)
121 {
122     unsigned int i;
123
124     for(i=0; i < sizeof(shlwapi_schemes)/sizeof(shlwapi_schemes[0]); i++) {
125         if(scheme_len == strlenW(shlwapi_schemes[i].scheme_name)
126            && !memcmp(scheme, shlwapi_schemes[i].scheme_name, scheme_len*sizeof(WCHAR)))
127             return shlwapi_schemes[i].scheme_number;
128     }
129
130     return URL_SCHEME_UNKNOWN;
131 }
132
133 /*************************************************************************
134  *      @       [SHLWAPI.1]
135  *
136  * Parse a Url into its constituent parts.
137  *
138  * PARAMS
139  *  x [I] Url to parse
140  *  y [O] Undocumented structure holding the parsed information
141  *
142  * RETURNS
143  *  Success: S_OK. y contains the parsed Url details.
144  *  Failure: An HRESULT error code.
145  */
146 HRESULT WINAPI ParseURLA(LPCSTR x, PARSEDURLA *y)
147 {
148     WCHAR scheme[INTERNET_MAX_SCHEME_LENGTH];
149     const char *ptr = x;
150     int len;
151
152     TRACE("%s %p\n", debugstr_a(x), y);
153
154     if(y->cbSize != sizeof(*y))
155         return E_INVALIDARG;
156
157     while(*ptr && (isalnum(*ptr) || *ptr == '-'))
158         ptr++;
159
160     if (*ptr != ':' || ptr <= x+1) {
161         y->pszProtocol = NULL;
162         return URL_E_INVALID_SYNTAX;
163     }
164
165     y->pszProtocol = x;
166     y->cchProtocol = ptr-x;
167     y->pszSuffix = ptr+1;
168     y->cchSuffix = strlen(y->pszSuffix);
169
170     len = MultiByteToWideChar(CP_ACP, 0, x, ptr-x,
171             scheme, sizeof(scheme)/sizeof(WCHAR));
172     y->nScheme = get_scheme_code(scheme, len);
173
174     return S_OK;
175 }
176
177 /*************************************************************************
178  *      @       [SHLWAPI.2]
179  *
180  * Unicode version of ParseURLA.
181  */
182 HRESULT WINAPI ParseURLW(LPCWSTR x, PARSEDURLW *y)
183 {
184     const WCHAR *ptr = x;
185
186     TRACE("%s %p\n", debugstr_w(x), y);
187
188     if(y->cbSize != sizeof(*y))
189         return E_INVALIDARG;
190
191     while(*ptr && (isalnumW(*ptr) || *ptr == '-'))
192         ptr++;
193
194     if (*ptr != ':' || ptr <= x+1) {
195         y->pszProtocol = NULL;
196         return URL_E_INVALID_SYNTAX;
197     }
198
199     y->pszProtocol = x;
200     y->cchProtocol = ptr-x;
201     y->pszSuffix = ptr+1;
202     y->cchSuffix = strlenW(y->pszSuffix);
203     y->nScheme = get_scheme_code(x, ptr-x);
204
205     return S_OK;
206 }
207
208 /*************************************************************************
209  *        UrlCanonicalizeA     [SHLWAPI.@]
210  *
211  * Canonicalize a Url.
212  *
213  * PARAMS
214  *  pszUrl            [I]   Url to cCanonicalize
215  *  pszCanonicalized  [O]   Destination for converted Url.
216  *  pcchCanonicalized [I/O] Length of pszUrl, destination for length of pszCanonicalized
217  *  dwFlags           [I]   Flags controlling the conversion.
218  *
219  * RETURNS
220  *  Success: S_OK. The pszCanonicalized contains the converted Url.
221  *  Failure: E_POINTER, if *pcchCanonicalized is too small.
222  *
223  * MSDN incorrectly describes the flags for this function. They should be:
224  *|    URL_DONT_ESCAPE_EXTRA_INFO    0x02000000
225  *|    URL_ESCAPE_SPACES_ONLY        0x04000000
226  *|    URL_ESCAPE_PERCENT            0x00001000
227  *|    URL_ESCAPE_UNSAFE             0x10000000
228  *|    URL_UNESCAPE                  0x10000000
229  *|    URL_DONT_SIMPLIFY             0x08000000
230  *|    URL_ESCAPE_SEGMENT_ONLY       0x00002000
231  */
232 HRESULT WINAPI UrlCanonicalizeA(LPCSTR pszUrl, LPSTR pszCanonicalized,
233         LPDWORD pcchCanonicalized, DWORD dwFlags)
234 {
235     LPWSTR url, canonical;
236     HRESULT ret;
237     DWORD   len;
238
239     TRACE("(%s, %p, %p, 0x%08x) *pcchCanonicalized: %d\n", debugstr_a(pszUrl), pszCanonicalized,
240         pcchCanonicalized, dwFlags, pcchCanonicalized ? *pcchCanonicalized : -1);
241
242     if(!pszUrl || !pszCanonicalized || !pcchCanonicalized || !*pcchCanonicalized)
243         return E_INVALIDARG;
244
245     len = strlen(pszUrl)+1;
246     url = HeapAlloc(GetProcessHeap(), 0, len*sizeof(WCHAR));
247     canonical = HeapAlloc(GetProcessHeap(), 0, *pcchCanonicalized*sizeof(WCHAR));
248     if(!url || !canonical) {
249         HeapFree(GetProcessHeap(), 0, url);
250         HeapFree(GetProcessHeap(), 0, canonical);
251         return E_OUTOFMEMORY;
252     }
253
254     MultiByteToWideChar(0, 0, pszUrl, -1, url, len);
255
256     ret = UrlCanonicalizeW(url, canonical, pcchCanonicalized, dwFlags);
257     if(ret == S_OK)
258         WideCharToMultiByte(0, 0, canonical, -1, pszCanonicalized,
259                 *pcchCanonicalized+1, 0, 0);
260
261     HeapFree(GetProcessHeap(), 0, canonical);
262     return ret;
263 }
264
265 /*************************************************************************
266  *        UrlCanonicalizeW     [SHLWAPI.@]
267  *
268  * See UrlCanonicalizeA.
269  */
270 HRESULT WINAPI UrlCanonicalizeW(LPCWSTR pszUrl, LPWSTR pszCanonicalized,
271                                 LPDWORD pcchCanonicalized, DWORD dwFlags)
272 {
273     HRESULT hr = S_OK;
274     DWORD EscapeFlags;
275     LPCWSTR wk1, root;
276     LPWSTR lpszUrlCpy, url, wk2, mp, mp2;
277     INT state;
278     DWORD nByteLen, nLen, nWkLen;
279     BOOL is_file_url;
280     WCHAR slash = '\0';
281
282     static const WCHAR wszFile[] = {'f','i','l','e',':'};
283     static const WCHAR wszRes[] = {'r','e','s',':'};
284     static const WCHAR wszHttp[] = {'h','t','t','p',':'};
285     static const WCHAR wszLocalhost[] = {'l','o','c','a','l','h','o','s','t'};
286     static const WCHAR wszFilePrefix[] = {'f','i','l','e',':','/','/','/'};
287
288     TRACE("(%s, %p, %p, 0x%08x) *pcchCanonicalized: %d\n", debugstr_w(pszUrl), pszCanonicalized,
289         pcchCanonicalized, dwFlags, pcchCanonicalized ? *pcchCanonicalized : -1);
290
291     if(!pszUrl || !pszCanonicalized || !pcchCanonicalized || !*pcchCanonicalized)
292         return E_INVALIDARG;
293
294     if(!*pszUrl) {
295         *pszCanonicalized = 0;
296         return S_OK;
297     }
298
299     /* Remove '\t' characters from URL */
300     nByteLen = (strlenW(pszUrl) + 1) * sizeof(WCHAR); /* length in bytes */
301     url = HeapAlloc(GetProcessHeap(), 0, nByteLen);
302     if(!url)
303         return E_OUTOFMEMORY;
304
305     wk1 = pszUrl;
306     wk2 = url;
307     do {
308         while(*wk1 == '\t')
309             wk1++;
310         *wk2++ = *wk1;
311     } while(*wk1++);
312
313     /* Allocate memory for simplified URL (before escaping) */
314     nByteLen = (wk2-url)*sizeof(WCHAR);
315     lpszUrlCpy = HeapAlloc(GetProcessHeap(), 0,
316             nByteLen+sizeof(wszFilePrefix)+sizeof(WCHAR));
317     if(!lpszUrlCpy) {
318         HeapFree(GetProcessHeap(), 0, url);
319         return E_OUTOFMEMORY;
320     }
321
322     is_file_url = !strncmpW(wszFile, url, sizeof(wszFile)/sizeof(WCHAR));
323
324     if ((nByteLen >= sizeof(wszHttp) &&
325          !memcmp(wszHttp, url, sizeof(wszHttp))) || is_file_url)
326         slash = '/';
327
328     if((dwFlags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY)) && is_file_url)
329         slash = '\\';
330
331     if(nByteLen >= sizeof(wszRes) && !memcmp(wszRes, url, sizeof(wszRes))) {
332         dwFlags &= ~URL_FILE_USE_PATHURL;
333         slash = '\0';
334     }
335
336     /*
337      * state =
338      *         0   initial  1,3
339      *         1   have 2[+] alnum  2,3
340      *         2   have scheme (found :)  4,6,3
341      *         3   failed (no location)
342      *         4   have //  5,3
343      *         5   have 1[+] alnum  6,3
344      *         6   have location (found /) save root location
345      */
346
347     wk1 = url;
348     wk2 = lpszUrlCpy;
349     state = 0;
350
351     if(url[1] == ':') { /* Assume path */
352         memcpy(wk2, wszFilePrefix, sizeof(wszFilePrefix));
353         wk2 += sizeof(wszFilePrefix)/sizeof(WCHAR);
354         if (dwFlags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY))
355         {
356             slash = '\\';
357             --wk2;
358         }
359         else
360             dwFlags |= URL_ESCAPE_UNSAFE;
361         state = 5;
362         is_file_url = TRUE;
363     }
364
365     while (*wk1) {
366         switch (state) {
367         case 0:
368             if (!isalnumW(*wk1)) {state = 3; break;}
369             *wk2++ = *wk1++;
370             if (!isalnumW(*wk1)) {state = 3; break;}
371             *wk2++ = *wk1++;
372             state = 1;
373             break;
374         case 1:
375             *wk2++ = *wk1;
376             if (*wk1++ == ':') state = 2;
377             break;
378         case 2:
379             *wk2++ = *wk1++;
380             if (*wk1 != '/') {state = 6; break;}
381             *wk2++ = *wk1++;
382             if((dwFlags & URL_FILE_USE_PATHURL) && nByteLen >= sizeof(wszLocalhost)
383                         && is_file_url
384                         && !memcmp(wszLocalhost, wk1, sizeof(wszLocalhost))){
385                 wk1 += sizeof(wszLocalhost)/sizeof(WCHAR);
386                 while(*wk1 == '\\' && (dwFlags & URL_FILE_USE_PATHURL))
387                     wk1++;
388             }
389
390             if(*wk1 == '/' && (dwFlags & URL_FILE_USE_PATHURL)){
391                 wk1++;
392             }else if(is_file_url){
393                 const WCHAR *body = wk1;
394
395                 while(*body == '/')
396                     ++body;
397
398                 if(isalnumW(*body) && *(body+1) == ':'){
399                     if(!(dwFlags & (URL_WININET_COMPATIBILITY | URL_FILE_USE_PATHURL))){
400                         if(slash)
401                             *wk2++ = slash;
402                         else
403                             *wk2++ = '/';
404                     }
405                 }else{
406                     if(dwFlags & URL_WININET_COMPATIBILITY){
407                         if(*wk1 == '/' && *(wk1+1) != '/'){
408                             *wk2++ = '\\';
409                         }else{
410                             *wk2++ = '\\';
411                             *wk2++ = '\\';
412                         }
413                     }else{
414                         if(*wk1 == '/' && *(wk1+1) != '/'){
415                             if(slash)
416                                 *wk2++ = slash;
417                             else
418                                 *wk2++ = '/';
419                         }
420                     }
421                 }
422                 wk1 = body;
423             }
424             state = 4;
425             break;
426         case 3:
427             nWkLen = strlenW(wk1);
428             memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
429             mp = wk2;
430             wk1 += nWkLen;
431             wk2 += nWkLen;
432
433             if(slash) {
434                 while(mp < wk2) {
435                     if(*mp == '/' || *mp == '\\')
436                         *mp = slash;
437                     mp++;
438                 }
439             }
440             break;
441         case 4:
442             if (!isalnumW(*wk1) && (*wk1 != '-') && (*wk1 != '.') && (*wk1 != ':'))
443                 {state = 3; break;}
444             while(isalnumW(*wk1) || (*wk1 == '-') || (*wk1 == '.') || (*wk1 == ':'))
445                 *wk2++ = *wk1++;
446             state = 5;
447             if (!*wk1) {
448                 if(slash)
449                     *wk2++ = slash;
450                 else
451                     *wk2++ = '/';
452             }
453             break;
454         case 5:
455             if (*wk1 != '/' && *wk1 != '\\') {state = 3; break;}
456             while(*wk1 == '/' || *wk1 == '\\') {
457                 if(slash)
458                     *wk2++ = slash;
459                 else
460                     *wk2++ = *wk1;
461                 wk1++;
462             }
463             state = 6;
464             break;
465         case 6:
466             if(dwFlags & URL_DONT_SIMPLIFY) {
467                 state = 3;
468                 break;
469             }
470  
471             /* Now at root location, cannot back up any more. */
472             /* "root" will point at the '/' */
473
474             root = wk2-1;
475             while (*wk1) {
476                 mp = strchrW(wk1, '/');
477                 mp2 = strchrW(wk1, '\\');
478                 if(mp2 && (!mp || mp2 < mp))
479                     mp = mp2;
480                 if (!mp) {
481                     nWkLen = strlenW(wk1);
482                     memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
483                     wk1 += nWkLen;
484                     wk2 += nWkLen;
485                     continue;
486                 }
487                 nLen = mp - wk1;
488                 if(nLen) {
489                     memcpy(wk2, wk1, nLen * sizeof(WCHAR));
490                     wk2 += nLen;
491                     wk1 += nLen;
492                 }
493                 if(slash)
494                     *wk2++ = slash;
495                 else
496                     *wk2++ = *wk1;
497                 wk1++;
498
499                 while (*wk1 == '.') {
500                     TRACE("found '/.'\n");
501                     if (wk1[1] == '/' || wk1[1] == '\\') {
502                         /* case of /./ -> skip the ./ */
503                         wk1 += 2;
504                     }
505                     else if (wk1[1] == '.' && (wk1[2] == '/'
506                             || wk1[2] == '\\' || wk1[2] == '?'
507                             || wk1[2] == '#' || !wk1[2])) {
508                         /* case /../ -> need to backup wk2 */
509                         TRACE("found '/../'\n");
510                         *(wk2-1) = '\0';  /* set end of string */
511                         mp = strrchrW(root, '/');
512                         mp2 = strrchrW(root, '\\');
513                         if(mp2 && (!mp || mp2 < mp))
514                             mp = mp2;
515                         if (mp && (mp >= root)) {
516                             /* found valid backup point */
517                             wk2 = mp + 1;
518                             if(wk1[2] != '/' && wk1[2] != '\\')
519                                 wk1 += 2;
520                             else
521                                 wk1 += 3;
522                         }
523                         else {
524                             /* did not find point, restore '/' */
525                             *(wk2-1) = slash;
526                             break;
527                         }
528                     }
529                     else
530                         break;
531                 }
532             }
533             *wk2 = '\0';
534             break;
535         default:
536             FIXME("how did we get here - state=%d\n", state);
537             HeapFree(GetProcessHeap(), 0, lpszUrlCpy);
538             HeapFree(GetProcessHeap(), 0, url);
539             return E_INVALIDARG;
540         }
541         *wk2 = '\0';
542         TRACE("Simplified, orig <%s>, simple <%s>\n",
543               debugstr_w(pszUrl), debugstr_w(lpszUrlCpy));
544     }
545     nLen = lstrlenW(lpszUrlCpy);
546     while ((nLen > 0) && ((lpszUrlCpy[nLen-1] <= ' ')))
547         lpszUrlCpy[--nLen]=0;
548
549     if((dwFlags & URL_UNESCAPE) ||
550        ((dwFlags & URL_FILE_USE_PATHURL) && nByteLen >= sizeof(wszFile)
551                 && !memcmp(wszFile, url, sizeof(wszFile))))
552         UrlUnescapeW(lpszUrlCpy, NULL, &nLen, URL_UNESCAPE_INPLACE);
553
554     if((EscapeFlags = dwFlags & (URL_ESCAPE_UNSAFE |
555                                  URL_ESCAPE_SPACES_ONLY |
556                                  URL_ESCAPE_PERCENT |
557                                  URL_DONT_ESCAPE_EXTRA_INFO |
558                                  URL_ESCAPE_SEGMENT_ONLY ))) {
559         EscapeFlags &= ~URL_ESCAPE_UNSAFE;
560         hr = UrlEscapeW(lpszUrlCpy, pszCanonicalized, pcchCanonicalized,
561                         EscapeFlags);
562     } else { /* No escaping needed, just copy the string */
563         nLen = lstrlenW(lpszUrlCpy);
564         if(nLen < *pcchCanonicalized)
565             memcpy(pszCanonicalized, lpszUrlCpy, (nLen + 1)*sizeof(WCHAR));
566         else {
567             hr = E_POINTER;
568             nLen++;
569         }
570         *pcchCanonicalized = nLen;
571     }
572
573     HeapFree(GetProcessHeap(), 0, lpszUrlCpy);
574     HeapFree(GetProcessHeap(), 0, url);
575
576     if (hr == S_OK)
577         TRACE("result %s\n", debugstr_w(pszCanonicalized));
578
579     return hr;
580 }
581
582 /*************************************************************************
583  *        UrlCombineA     [SHLWAPI.@]
584  *
585  * Combine two Urls.
586  *
587  * PARAMS
588  *  pszBase      [I] Base Url
589  *  pszRelative  [I] Url to combine with pszBase
590  *  pszCombined  [O] Destination for combined Url
591  *  pcchCombined [O] Destination for length of pszCombined
592  *  dwFlags      [I] URL_ flags from "shlwapi.h"
593  *
594  * RETURNS
595  *  Success: S_OK. pszCombined contains the combined Url, pcchCombined
596  *           contains its length.
597  *  Failure: An HRESULT error code indicating the error.
598  */
599 HRESULT WINAPI UrlCombineA(LPCSTR pszBase, LPCSTR pszRelative,
600                            LPSTR pszCombined, LPDWORD pcchCombined,
601                            DWORD dwFlags)
602 {
603     LPWSTR base, relative, combined;
604     DWORD ret, len, len2;
605
606     TRACE("(base %s, Relative %s, Combine size %d, flags %08x) using W version\n",
607           debugstr_a(pszBase),debugstr_a(pszRelative),
608           pcchCombined?*pcchCombined:0,dwFlags);
609
610     if(!pszBase || !pszRelative || !pcchCombined)
611         return E_INVALIDARG;
612
613     base = HeapAlloc(GetProcessHeap(), 0,
614                               (3*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
615     relative = base + INTERNET_MAX_URL_LENGTH;
616     combined = relative + INTERNET_MAX_URL_LENGTH;
617
618     MultiByteToWideChar(0, 0, pszBase, -1, base, INTERNET_MAX_URL_LENGTH);
619     MultiByteToWideChar(0, 0, pszRelative, -1, relative, INTERNET_MAX_URL_LENGTH);
620     len = *pcchCombined;
621
622     ret = UrlCombineW(base, relative, pszCombined?combined:NULL, &len, dwFlags);
623     if (ret != S_OK) {
624         *pcchCombined = len;
625         HeapFree(GetProcessHeap(), 0, base);
626         return ret;
627     }
628
629     len2 = WideCharToMultiByte(0, 0, combined, len, 0, 0, 0, 0);
630     if (len2 > *pcchCombined) {
631         *pcchCombined = len2;
632         HeapFree(GetProcessHeap(), 0, base);
633         return E_POINTER;
634     }
635     WideCharToMultiByte(0, 0, combined, len+1, pszCombined, (*pcchCombined)+1,
636                         0, 0);
637     *pcchCombined = len2;
638     HeapFree(GetProcessHeap(), 0, base);
639     return S_OK;
640 }
641
642 /*************************************************************************
643  *        UrlCombineW     [SHLWAPI.@]
644  *
645  * See UrlCombineA.
646  */
647 HRESULT WINAPI UrlCombineW(LPCWSTR pszBase, LPCWSTR pszRelative,
648                            LPWSTR pszCombined, LPDWORD pcchCombined,
649                            DWORD dwFlags)
650 {
651     PARSEDURLW base, relative;
652     DWORD myflags, sizeloc = 0;
653     DWORD len, res1, res2, process_case = 0;
654     LPWSTR work, preliminary, mbase, mrelative;
655     static const WCHAR myfilestr[] = {'f','i','l','e',':','/','/','/','\0'};
656     HRESULT ret;
657
658     TRACE("(base %s, Relative %s, Combine size %d, flags %08x)\n",
659           debugstr_w(pszBase),debugstr_w(pszRelative),
660           pcchCombined?*pcchCombined:0,dwFlags);
661
662     if(!pszBase || !pszRelative || !pcchCombined)
663         return E_INVALIDARG;
664
665     base.cbSize = sizeof(base);
666     relative.cbSize = sizeof(relative);
667
668     /* Get space for duplicates of the input and the output */
669     preliminary = HeapAlloc(GetProcessHeap(), 0, (3*INTERNET_MAX_URL_LENGTH) *
670                             sizeof(WCHAR));
671     mbase = preliminary + INTERNET_MAX_URL_LENGTH;
672     mrelative = mbase + INTERNET_MAX_URL_LENGTH;
673     *preliminary = '\0';
674
675     /* Canonicalize the base input prior to looking for the scheme */
676     myflags = dwFlags & (URL_DONT_SIMPLIFY | URL_UNESCAPE);
677     len = INTERNET_MAX_URL_LENGTH;
678     ret = UrlCanonicalizeW(pszBase, mbase, &len, myflags);
679
680     /* Canonicalize the relative input prior to looking for the scheme */
681     len = INTERNET_MAX_URL_LENGTH;
682     ret = UrlCanonicalizeW(pszRelative, mrelative, &len, myflags);
683
684     /* See if the base has a scheme */
685     res1 = ParseURLW(mbase, &base);
686     if (res1) {
687         /* if pszBase has no scheme, then return pszRelative */
688         TRACE("no scheme detected in Base\n");
689         process_case = 1;
690     }
691     else do {
692         BOOL manual_search = FALSE;
693
694         /* mk is a special case */
695         if(base.nScheme == URL_SCHEME_MK) {
696             static const WCHAR wsz[] = {':',':',0};
697
698             WCHAR *ptr = strstrW(base.pszSuffix, wsz);
699             if(ptr) {
700                 int delta;
701
702                 ptr += 2;
703                 delta = ptr-base.pszSuffix;
704                 base.cchProtocol += delta;
705                 base.pszSuffix += delta;
706                 base.cchSuffix -= delta;
707             }
708         }else {
709             /* get size of location field (if it exists) */
710             work = (LPWSTR)base.pszSuffix;
711             sizeloc = 0;
712             if (*work++ == '/') {
713                 if (*work++ == '/') {
714                     /* At this point have start of location and
715                      * it ends at next '/' or end of string.
716                      */
717                     while(*work && (*work != '/')) work++;
718                     sizeloc = (DWORD)(work - base.pszSuffix);
719                 }
720             }
721         }
722
723         /* If there is a '#' and the characters immediately preceding it are
724          * ".htm[l]", then begin looking for the last leaf starting from
725          * the '#'. Otherwise the '#' is not meaningful and just start
726          * looking from the end. */
727         if ((work = strchrW(base.pszSuffix + sizeloc, '#'))) {
728             const WCHAR htmlW[] = {'.','h','t','m','l',0};
729             const int len_htmlW = 5;
730             const WCHAR htmW[] = {'.','h','t','m',0};
731             const int len_htmW = 4;
732
733             if (work - base.pszSuffix > len_htmW * sizeof(WCHAR)) {
734                 work -= len_htmW;
735                 if (strncmpiW(work, htmW, len_htmW) == 0)
736                     manual_search = TRUE;
737                 work += len_htmW;
738             }
739
740             if (!manual_search &&
741                     work - base.pszSuffix > len_htmlW * sizeof(WCHAR)) {
742                 work -= len_htmlW;
743                 if (strncmpiW(work, htmlW, len_htmlW) == 0)
744                     manual_search = TRUE;
745                 work += len_htmlW;
746             }
747         }
748
749         if (manual_search) {
750             /* search backwards starting from the current position */
751             while (*work != '/' && work > base.pszSuffix + sizeloc)
752                 --work;
753             if (work > base.pszSuffix + sizeloc)
754                 base.cchSuffix = work - base.pszSuffix + 1;
755         }else {
756             /* search backwards starting from the end of the string */
757             work = strrchrW((base.pszSuffix+sizeloc), '/');
758             if (work) {
759                 len = (DWORD)(work - base.pszSuffix + 1);
760                 base.cchSuffix = len;
761             }
762         }
763
764         /*
765          * At this point:
766          *    .pszSuffix   points to location (starting with '//')
767          *    .cchSuffix   length of location (above) and rest less the last
768          *                 leaf (if any)
769          *    sizeloc   length of location (above) up to but not including
770          *              the last '/'
771          */
772
773         res2 = ParseURLW(mrelative, &relative);
774         if (res2) {
775             /* no scheme in pszRelative */
776             TRACE("no scheme detected in Relative\n");
777             relative.pszSuffix = mrelative;  /* case 3,4,5 depends on this */
778             relative.cchSuffix = strlenW(mrelative);
779             if (*pszRelative  == ':') {
780                 /* case that is either left alone or uses pszBase */
781                 if (dwFlags & URL_PLUGGABLE_PROTOCOL) {
782                     process_case = 5;
783                     break;
784                 }
785                 process_case = 1;
786                 break;
787             }
788             if (isalnum(*mrelative) && (*(mrelative + 1) == ':')) {
789                 /* case that becomes "file:///" */
790                 strcpyW(preliminary, myfilestr);
791                 process_case = 1;
792                 break;
793             }
794             if ((*mrelative == '/') && (*(mrelative+1) == '/')) {
795                 /* pszRelative has location and rest */
796                 process_case = 3;
797                 break;
798             }
799             if (*mrelative == '/') {
800                 /* case where pszRelative is root to location */
801                 process_case = 4;
802                 break;
803             }
804             process_case = (*base.pszSuffix == '/' || base.nScheme == URL_SCHEME_MK) ? 5 : 3;
805             break;
806         }
807
808         /* handle cases where pszRelative has scheme */
809         if ((base.cchProtocol == relative.cchProtocol) &&
810             (strncmpW(base.pszProtocol, relative.pszProtocol, base.cchProtocol) == 0)) {
811
812             /* since the schemes are the same */
813             if ((*relative.pszSuffix == '/') && (*(relative.pszSuffix+1) == '/')) {
814                 /* case where pszRelative replaces location and following */
815                 process_case = 3;
816                 break;
817             }
818             if (*relative.pszSuffix == '/') {
819                 /* case where pszRelative is root to location */
820                 process_case = 4;
821                 break;
822             }
823             /* replace either just location if base's location starts with a
824              * slash or otherwise everything */
825             process_case = (*base.pszSuffix == '/') ? 5 : 1;
826             break;
827         }
828         if ((*relative.pszSuffix == '/') && (*(relative.pszSuffix+1) == '/')) {
829             /* case where pszRelative replaces scheme, location,
830              * and following and handles PLUGGABLE
831              */
832             process_case = 2;
833             break;
834         }
835         process_case = 1;
836         break;
837     } while(FALSE); /* a little trick to allow easy exit from nested if's */
838
839     ret = S_OK;
840     switch (process_case) {
841
842     case 1:  /*
843               * Return pszRelative appended to what ever is in pszCombined,
844               * (which may the string "file:///"
845               */
846         strcatW(preliminary, mrelative);
847         break;
848
849     case 2:  /* case where pszRelative replaces scheme, and location */
850         strcpyW(preliminary, mrelative);
851         break;
852
853     case 3:  /*
854               * Return the pszBase scheme with pszRelative. Basically
855               * keeps the scheme and replaces the domain and following.
856               */
857         memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1)*sizeof(WCHAR));
858         work = preliminary + base.cchProtocol + 1;
859         strcpyW(work, relative.pszSuffix);
860         break;
861
862     case 4:  /*
863               * Return the pszBase scheme and location but everything
864               * after the location is pszRelative. (Replace document
865               * from root on.)
866               */
867         memcpy(preliminary, base.pszProtocol, (base.cchProtocol+1+sizeloc)*sizeof(WCHAR));
868         work = preliminary + base.cchProtocol + 1 + sizeloc;
869         if (dwFlags & URL_PLUGGABLE_PROTOCOL)
870             *(work++) = '/';
871         strcpyW(work, relative.pszSuffix);
872         break;
873
874     case 5:  /*
875               * Return the pszBase without its document (if any) and
876               * append pszRelative after its scheme.
877               */
878         memcpy(preliminary, base.pszProtocol,
879                (base.cchProtocol+1+base.cchSuffix)*sizeof(WCHAR));
880         work = preliminary + base.cchProtocol+1+base.cchSuffix - 1;
881         if (*work++ != '/')
882             *(work++) = '/';
883         strcpyW(work, relative.pszSuffix);
884         break;
885
886     default:
887         FIXME("How did we get here????? process_case=%d\n", process_case);
888         ret = E_INVALIDARG;
889     }
890
891     if (ret == S_OK) {
892         /* Reuse mrelative as temp storage as its already allocated and not needed anymore */
893         if(*pcchCombined == 0)
894             *pcchCombined = 1;
895         ret = UrlCanonicalizeW(preliminary, mrelative, pcchCombined, (dwFlags & ~URL_FILE_USE_PATHURL));
896         if(SUCCEEDED(ret) && pszCombined) {
897             lstrcpyW(pszCombined, mrelative);
898         }
899         TRACE("return-%d len=%d, %s\n",
900               process_case, *pcchCombined, debugstr_w(pszCombined));
901     }
902     HeapFree(GetProcessHeap(), 0, preliminary);
903     return ret;
904 }
905
906 /*************************************************************************
907  *      UrlEscapeA      [SHLWAPI.@]
908  */
909
910 HRESULT WINAPI UrlEscapeA(
911         LPCSTR pszUrl,
912         LPSTR pszEscaped,
913         LPDWORD pcchEscaped,
914         DWORD dwFlags)
915 {
916     WCHAR bufW[INTERNET_MAX_URL_LENGTH];
917     WCHAR *escapedW = bufW;
918     UNICODE_STRING urlW;
919     HRESULT ret;
920     DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
921
922     if (!pszEscaped || !pcchEscaped || !*pcchEscaped)
923         return E_INVALIDARG;
924
925     if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl))
926         return E_INVALIDARG;
927     if((ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags)) == E_POINTER) {
928         escapedW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
929         ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags);
930     }
931     if(ret == S_OK) {
932         RtlUnicodeToMultiByteSize(&lenA, escapedW, lenW * sizeof(WCHAR));
933         if(*pcchEscaped > lenA) {
934             RtlUnicodeToMultiByteN(pszEscaped, *pcchEscaped - 1, &lenA, escapedW, lenW * sizeof(WCHAR));
935             pszEscaped[lenA] = 0;
936             *pcchEscaped = lenA;
937         } else {
938             *pcchEscaped = lenA + 1;
939             ret = E_POINTER;
940         }
941     }
942     if(escapedW != bufW) HeapFree(GetProcessHeap(), 0, escapedW);
943     RtlFreeUnicodeString(&urlW);
944     return ret;
945 }
946
947 #define WINE_URL_BASH_AS_SLASH    0x01
948 #define WINE_URL_COLLAPSE_SLASHES 0x02
949 #define WINE_URL_ESCAPE_SLASH     0x04
950 #define WINE_URL_ESCAPE_HASH      0x08
951 #define WINE_URL_ESCAPE_QUESTION  0x10
952 #define WINE_URL_STOP_ON_HASH     0x20
953 #define WINE_URL_STOP_ON_QUESTION 0x40
954
955 static inline BOOL URL_NeedEscapeW(WCHAR ch, DWORD dwFlags, DWORD int_flags)
956 {
957
958     if (isalnumW(ch))
959         return FALSE;
960
961     if(dwFlags & URL_ESCAPE_SPACES_ONLY) {
962         if(ch == ' ')
963             return TRUE;
964         else
965             return FALSE;
966     }
967
968     if ((dwFlags & URL_ESCAPE_PERCENT) && (ch == '%'))
969         return TRUE;
970
971     if (ch <= 31 || ch >= 127)
972         return TRUE;
973
974     else {
975         switch (ch) {
976         case ' ':
977         case '<':
978         case '>':
979         case '\"':
980         case '{':
981         case '}':
982         case '|':
983         case '\\':
984         case '^':
985         case ']':
986         case '[':
987         case '`':
988         case '&':
989             return TRUE;
990
991         case '/':
992             if (int_flags & WINE_URL_ESCAPE_SLASH) return TRUE;
993             return FALSE;
994
995         case '?':
996             if (int_flags & WINE_URL_ESCAPE_QUESTION) return TRUE;
997             return FALSE;
998
999         case '#':
1000             if (int_flags & WINE_URL_ESCAPE_HASH) return TRUE;
1001             return FALSE;
1002
1003         default:
1004             return FALSE;
1005         }
1006     }
1007 }
1008
1009
1010 /*************************************************************************
1011  *      UrlEscapeW      [SHLWAPI.@]
1012  *
1013  * Converts unsafe characters in a Url into escape sequences.
1014  *
1015  * PARAMS
1016  *  pszUrl      [I]   Url to modify
1017  *  pszEscaped  [O]   Destination for modified Url
1018  *  pcchEscaped [I/O] Length of pszUrl, destination for length of pszEscaped
1019  *  dwFlags     [I]   URL_ flags from "shlwapi.h"
1020  *
1021  * RETURNS
1022  *  Success: S_OK. pszEscaped contains the escaped Url, pcchEscaped
1023  *           contains its length.
1024  *  Failure: E_POINTER, if pszEscaped is not large enough. In this case
1025  *           pcchEscaped is set to the required length.
1026  *
1027  * Converts unsafe characters into their escape sequences.
1028  *
1029  * NOTES
1030  * - By default this function stops converting at the first '?' or
1031  *  '#' character.
1032  * - If dwFlags contains URL_ESCAPE_SPACES_ONLY then only spaces are
1033  *   converted, but the conversion continues past a '?' or '#'.
1034  * - Note that this function did not work well (or at all) in shlwapi version 4.
1035  *
1036  * BUGS
1037  *  Only the following flags are implemented:
1038  *|     URL_ESCAPE_SPACES_ONLY
1039  *|     URL_DONT_ESCAPE_EXTRA_INFO
1040  *|     URL_ESCAPE_SEGMENT_ONLY
1041  *|     URL_ESCAPE_PERCENT
1042  */
1043 HRESULT WINAPI UrlEscapeW(
1044         LPCWSTR pszUrl,
1045         LPWSTR pszEscaped,
1046         LPDWORD pcchEscaped,
1047         DWORD dwFlags)
1048 {
1049     LPCWSTR src;
1050     DWORD needed = 0, ret;
1051     BOOL stop_escaping = FALSE;
1052     WCHAR next[5], *dst = pszEscaped;
1053     INT len;
1054     PARSEDURLW parsed_url;
1055     DWORD int_flags;
1056     DWORD slashes = 0;
1057     static const WCHAR localhost[] = {'l','o','c','a','l','h','o','s','t',0};
1058
1059     TRACE("(%p(%s) %p %p 0x%08x)\n", pszUrl, debugstr_w(pszUrl),
1060             pszEscaped, pcchEscaped, dwFlags);
1061
1062     if(!pszUrl || !pcchEscaped)
1063         return E_INVALIDARG;
1064
1065     if(dwFlags & ~(URL_ESCAPE_SPACES_ONLY |
1066                    URL_ESCAPE_SEGMENT_ONLY |
1067                    URL_DONT_ESCAPE_EXTRA_INFO |
1068                    URL_ESCAPE_PERCENT))
1069         FIXME("Unimplemented flags: %08x\n", dwFlags);
1070
1071     if(pszUrl == pszEscaped) {
1072         dst = HeapAlloc(GetProcessHeap(), 0, *pcchEscaped*sizeof(WCHAR));
1073         if(!dst)
1074             return E_OUTOFMEMORY;
1075     }
1076
1077     /* fix up flags */
1078     if (dwFlags & URL_ESCAPE_SPACES_ONLY)
1079         /* if SPACES_ONLY specified, reset the other controls */
1080         dwFlags &= ~(URL_DONT_ESCAPE_EXTRA_INFO |
1081                      URL_ESCAPE_PERCENT |
1082                      URL_ESCAPE_SEGMENT_ONLY);
1083
1084     else
1085         /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
1086         dwFlags |= URL_DONT_ESCAPE_EXTRA_INFO;
1087
1088
1089     int_flags = 0;
1090     if(dwFlags & URL_ESCAPE_SEGMENT_ONLY) {
1091         int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH;
1092     } else {
1093         parsed_url.cbSize = sizeof(parsed_url);
1094         if(ParseURLW(pszUrl, &parsed_url) != S_OK)
1095             parsed_url.nScheme = URL_SCHEME_INVALID;
1096
1097         TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol));
1098
1099         if(dwFlags & URL_DONT_ESCAPE_EXTRA_INFO)
1100             int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION;
1101
1102         switch(parsed_url.nScheme) {
1103         case URL_SCHEME_FILE:
1104             int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH;
1105             int_flags &= ~WINE_URL_STOP_ON_HASH;
1106             break;
1107
1108         case URL_SCHEME_HTTP:
1109         case URL_SCHEME_HTTPS:
1110             int_flags |= WINE_URL_BASH_AS_SLASH;
1111             if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\')
1112                 int_flags |= WINE_URL_ESCAPE_SLASH;
1113             break;
1114
1115         case URL_SCHEME_MAILTO:
1116             int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH;
1117             int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH);
1118             break;
1119
1120         case URL_SCHEME_INVALID:
1121             break;
1122
1123         case URL_SCHEME_FTP:
1124         default:
1125             if(parsed_url.pszSuffix[0] != '/')
1126                 int_flags |= WINE_URL_ESCAPE_SLASH;
1127             break;
1128         }
1129     }
1130
1131     for(src = pszUrl; *src; ) {
1132         WCHAR cur = *src;
1133         len = 0;
1134         
1135         if((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == pszUrl + parsed_url.cchProtocol + 1) {
1136             int localhost_len = sizeof(localhost)/sizeof(WCHAR) - 1;
1137             while(cur == '/' || cur == '\\') {
1138                 slashes++;
1139                 cur = *++src;
1140             }
1141             if(slashes == 2 && !strncmpiW(src, localhost, localhost_len)) { /* file://localhost/ -> file:/// */
1142                 if(*(src + localhost_len) == '/' || *(src + localhost_len) == '\\')
1143                 src += localhost_len + 1;
1144                 slashes = 3;
1145             }
1146
1147             switch(slashes) {
1148             case 1:
1149             case 3:
1150                 next[0] = next[1] = next[2] = '/';
1151                 len = 3;
1152                 break;
1153             case 0:
1154                 len = 0;
1155                 break;
1156             default:
1157                 next[0] = next[1] = '/';
1158                 len = 2;
1159                 break;
1160             }
1161         }
1162         if(len == 0) {
1163
1164             if(cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH))
1165                 stop_escaping = TRUE;
1166
1167             if(cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION))
1168                 stop_escaping = TRUE;
1169
1170             if(cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/';
1171
1172             if(URL_NeedEscapeW(cur, dwFlags, int_flags) && stop_escaping == FALSE) {
1173                 next[0] = '%';
1174                 next[1] = hexDigits[(cur >> 4) & 0xf];
1175                 next[2] = hexDigits[cur & 0xf];
1176                 len = 3;
1177             } else {
1178                 next[0] = cur;
1179                 len = 1;
1180             }
1181             src++;
1182         }
1183
1184         if(needed + len <= *pcchEscaped) {
1185             memcpy(dst, next, len*sizeof(WCHAR));
1186             dst += len;
1187         }
1188         needed += len;
1189     }
1190
1191     if(needed < *pcchEscaped) {
1192         *dst = '\0';
1193         if(pszUrl == pszEscaped)
1194             memcpy(pszEscaped, dst-needed, (needed+1)*sizeof(WCHAR));
1195
1196         ret = S_OK;
1197     } else {
1198         needed++; /* add one for the '\0' */
1199         ret = E_POINTER;
1200     }
1201     *pcchEscaped = needed;
1202
1203     if(pszUrl == pszEscaped)
1204         HeapFree(GetProcessHeap(), 0, dst);
1205     return ret;
1206 }
1207
1208
1209 /*************************************************************************
1210  *      UrlUnescapeA    [SHLWAPI.@]
1211  *
1212  * Converts Url escape sequences back to ordinary characters.
1213  *
1214  * PARAMS
1215  *  pszUrl        [I/O]  Url to convert
1216  *  pszUnescaped  [O]    Destination for converted Url
1217  *  pcchUnescaped [I/O]  Size of output string
1218  *  dwFlags       [I]    URL_ESCAPE_ Flags from "shlwapi.h"
1219  *
1220  * RETURNS
1221  *  Success: S_OK. The converted value is in pszUnescaped, or in pszUrl if
1222  *           dwFlags includes URL_ESCAPE_INPLACE.
1223  *  Failure: E_POINTER if the converted Url is bigger than pcchUnescaped. In
1224  *           this case pcchUnescaped is set to the size required.
1225  * NOTES
1226  *  If dwFlags includes URL_DONT_ESCAPE_EXTRA_INFO, the conversion stops at
1227  *  the first occurrence of either a '?' or '#' character.
1228  */
1229 HRESULT WINAPI UrlUnescapeA(
1230         LPSTR pszUrl,
1231         LPSTR pszUnescaped,
1232         LPDWORD pcchUnescaped,
1233         DWORD dwFlags)
1234 {
1235     char *dst, next;
1236     LPCSTR src;
1237     HRESULT ret;
1238     DWORD needed;
1239     BOOL stop_unescaping = FALSE;
1240
1241     TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_a(pszUrl), pszUnescaped,
1242           pcchUnescaped, dwFlags);
1243
1244     if (!pszUrl) return E_INVALIDARG;
1245
1246     if(dwFlags & URL_UNESCAPE_INPLACE)
1247         dst = pszUrl;
1248     else
1249     {
1250         if (!pszUnescaped || !pcchUnescaped) return E_INVALIDARG;
1251         dst = pszUnescaped;
1252     }
1253
1254     for(src = pszUrl, needed = 0; *src; src++, needed++) {
1255         if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1256            (*src == '#' || *src == '?')) {
1257             stop_unescaping = TRUE;
1258             next = *src;
1259         } else if(*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2))
1260                   && stop_unescaping == FALSE) {
1261             INT ih;
1262             char buf[3];
1263             memcpy(buf, src + 1, 2);
1264             buf[2] = '\0';
1265             ih = strtol(buf, NULL, 16);
1266             next = (CHAR) ih;
1267             src += 2; /* Advance to end of escape */
1268         } else
1269             next = *src;
1270
1271         if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1272             *dst++ = next;
1273     }
1274
1275     if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1276         *dst = '\0';
1277         ret = S_OK;
1278     } else {
1279         needed++; /* add one for the '\0' */
1280         ret = E_POINTER;
1281     }
1282     if(!(dwFlags & URL_UNESCAPE_INPLACE))
1283         *pcchUnescaped = needed;
1284
1285     if (ret == S_OK) {
1286         TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1287               debugstr_a(pszUrl) : debugstr_a(pszUnescaped));
1288     }
1289
1290     return ret;
1291 }
1292
1293 /*************************************************************************
1294  *      UrlUnescapeW    [SHLWAPI.@]
1295  *
1296  * See UrlUnescapeA.
1297  */
1298 HRESULT WINAPI UrlUnescapeW(
1299         LPWSTR pszUrl,
1300         LPWSTR pszUnescaped,
1301         LPDWORD pcchUnescaped,
1302         DWORD dwFlags)
1303 {
1304     WCHAR *dst, next;
1305     LPCWSTR src;
1306     HRESULT ret;
1307     DWORD needed;
1308     BOOL stop_unescaping = FALSE;
1309
1310     TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszUrl), pszUnescaped,
1311           pcchUnescaped, dwFlags);
1312
1313     if(!pszUrl) return E_INVALIDARG;
1314
1315     if(dwFlags & URL_UNESCAPE_INPLACE)
1316         dst = pszUrl;
1317     else
1318     {
1319         if (!pszUnescaped || !pcchUnescaped) return E_INVALIDARG;
1320         dst = pszUnescaped;
1321     }
1322
1323     for(src = pszUrl, needed = 0; *src; src++, needed++) {
1324         if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1325            (*src == '#' || *src == '?')) {
1326             stop_unescaping = TRUE;
1327             next = *src;
1328         } else if(*src == '%' && isxdigitW(*(src + 1)) && isxdigitW(*(src + 2))
1329                   && stop_unescaping == FALSE) {
1330             INT ih;
1331             WCHAR buf[5] = {'0','x',0};
1332             memcpy(buf + 2, src + 1, 2*sizeof(WCHAR));
1333             buf[4] = 0;
1334             StrToIntExW(buf, STIF_SUPPORT_HEX, &ih);
1335             next = (WCHAR) ih;
1336             src += 2; /* Advance to end of escape */
1337         } else
1338             next = *src;
1339
1340         if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1341             *dst++ = next;
1342     }
1343
1344     if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1345         *dst = '\0';
1346         ret = S_OK;
1347     } else {
1348         needed++; /* add one for the '\0' */
1349         ret = E_POINTER;
1350     }
1351     if(!(dwFlags & URL_UNESCAPE_INPLACE))
1352         *pcchUnescaped = needed;
1353
1354     if (ret == S_OK) {
1355         TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1356               debugstr_w(pszUrl) : debugstr_w(pszUnescaped));
1357     }
1358
1359     return ret;
1360 }
1361
1362 /*************************************************************************
1363  *      UrlGetLocationA         [SHLWAPI.@]
1364  *
1365  * Get the location from a Url.
1366  *
1367  * PARAMS
1368  *  pszUrl [I] Url to get the location from
1369  *
1370  * RETURNS
1371  *  A pointer to the start of the location in pszUrl, or NULL if there is
1372  *  no location.
1373  *
1374  * NOTES
1375  *  - MSDN erroneously states that "The location is the segment of the Url
1376  *    starting with a '?' or '#' character". Neither V4 nor V5 of shlwapi.dll
1377  *    stop at '?' and always return a NULL in this case.
1378  *  - MSDN also erroneously states that "If a file URL has a query string,
1379  *    the returned string is the query string". In all tested cases, if the
1380  *    Url starts with "fi" then a NULL is returned. V5 gives the following results:
1381  *|       Result   Url
1382  *|       ------   ---
1383  *|       NULL     file://aa/b/cd#hohoh
1384  *|       #hohoh   http://aa/b/cd#hohoh
1385  *|       NULL     fi://aa/b/cd#hohoh
1386  *|       #hohoh   ff://aa/b/cd#hohoh
1387  */
1388 LPCSTR WINAPI UrlGetLocationA(
1389         LPCSTR pszUrl)
1390 {
1391     PARSEDURLA base;
1392     DWORD res1;
1393
1394     base.cbSize = sizeof(base);
1395     res1 = ParseURLA(pszUrl, &base);
1396     if (res1) return NULL;  /* invalid scheme */
1397
1398     /* if scheme is file: then never return pointer */
1399     if (strncmp(base.pszProtocol, "file", min(4,base.cchProtocol)) == 0) return NULL;
1400
1401     /* Look for '#' and return its addr */
1402     return strchr(base.pszSuffix, '#');
1403 }
1404
1405 /*************************************************************************
1406  *      UrlGetLocationW         [SHLWAPI.@]
1407  *
1408  * See UrlGetLocationA.
1409  */
1410 LPCWSTR WINAPI UrlGetLocationW(
1411         LPCWSTR pszUrl)
1412 {
1413     PARSEDURLW base;
1414     DWORD res1;
1415
1416     base.cbSize = sizeof(base);
1417     res1 = ParseURLW(pszUrl, &base);
1418     if (res1) return NULL;  /* invalid scheme */
1419
1420     /* if scheme is file: then never return pointer */
1421     if (strncmpW(base.pszProtocol, fileW, min(4,base.cchProtocol)) == 0) return NULL;
1422
1423     /* Look for '#' and return its addr */
1424     return strchrW(base.pszSuffix, '#');
1425 }
1426
1427 /*************************************************************************
1428  *      UrlCompareA     [SHLWAPI.@]
1429  *
1430  * Compare two Urls.
1431  *
1432  * PARAMS
1433  *  pszUrl1      [I] First Url to compare
1434  *  pszUrl2      [I] Url to compare to pszUrl1
1435  *  fIgnoreSlash [I] TRUE = compare only up to a final slash
1436  *
1437  * RETURNS
1438  *  less than zero, zero, or greater than zero indicating pszUrl2 is greater
1439  *  than, equal to, or less than pszUrl1 respectively.
1440  */
1441 INT WINAPI UrlCompareA(
1442         LPCSTR pszUrl1,
1443         LPCSTR pszUrl2,
1444         BOOL fIgnoreSlash)
1445 {
1446     INT ret, len, len1, len2;
1447
1448     if (!fIgnoreSlash)
1449         return strcmp(pszUrl1, pszUrl2);
1450     len1 = strlen(pszUrl1);
1451     if (pszUrl1[len1-1] == '/') len1--;
1452     len2 = strlen(pszUrl2);
1453     if (pszUrl2[len2-1] == '/') len2--;
1454     if (len1 == len2)
1455         return strncmp(pszUrl1, pszUrl2, len1);
1456     len = min(len1, len2);
1457     ret = strncmp(pszUrl1, pszUrl2, len);
1458     if (ret) return ret;
1459     if (len1 > len2) return 1;
1460     return -1;
1461 }
1462
1463 /*************************************************************************
1464  *      UrlCompareW     [SHLWAPI.@]
1465  *
1466  * See UrlCompareA.
1467  */
1468 INT WINAPI UrlCompareW(
1469         LPCWSTR pszUrl1,
1470         LPCWSTR pszUrl2,
1471         BOOL fIgnoreSlash)
1472 {
1473     INT ret;
1474     size_t len, len1, len2;
1475
1476     if (!fIgnoreSlash)
1477         return strcmpW(pszUrl1, pszUrl2);
1478     len1 = strlenW(pszUrl1);
1479     if (pszUrl1[len1-1] == '/') len1--;
1480     len2 = strlenW(pszUrl2);
1481     if (pszUrl2[len2-1] == '/') len2--;
1482     if (len1 == len2)
1483         return strncmpW(pszUrl1, pszUrl2, len1);
1484     len = min(len1, len2);
1485     ret = strncmpW(pszUrl1, pszUrl2, len);
1486     if (ret) return ret;
1487     if (len1 > len2) return 1;
1488     return -1;
1489 }
1490
1491 /*************************************************************************
1492  *      HashData        [SHLWAPI.@]
1493  *
1494  * Hash an input block into a variable sized digest.
1495  *
1496  * PARAMS
1497  *  lpSrc    [I] Input block
1498  *  nSrcLen  [I] Length of lpSrc
1499  *  lpDest   [I] Output for hash digest
1500  *  nDestLen [I] Length of lpDest
1501  *
1502  * RETURNS
1503  *  Success: TRUE. lpDest is filled with the computed hash value.
1504  *  Failure: FALSE, if any argument is invalid.
1505  */
1506 HRESULT WINAPI HashData(const unsigned char *lpSrc, DWORD nSrcLen,
1507                      unsigned char *lpDest, DWORD nDestLen)
1508 {
1509   INT srcCount = nSrcLen - 1, destCount = nDestLen - 1;
1510
1511   if (!lpSrc || !lpDest)
1512     return E_INVALIDARG;
1513
1514   while (destCount >= 0)
1515   {
1516     lpDest[destCount] = (destCount & 0xff);
1517     destCount--;
1518   }
1519
1520   while (srcCount >= 0)
1521   {
1522     destCount = nDestLen - 1;
1523     while (destCount >= 0)
1524     {
1525       lpDest[destCount] = HashDataLookup[lpSrc[srcCount] ^ lpDest[destCount]];
1526       destCount--;
1527     }
1528     srcCount--;
1529   }
1530   return S_OK;
1531 }
1532
1533 /*************************************************************************
1534  *      UrlHashA        [SHLWAPI.@]
1535  *
1536  * Produce a Hash from a Url.
1537  *
1538  * PARAMS
1539  *  pszUrl   [I] Url to hash
1540  *  lpDest   [O] Destinationh for hash
1541  *  nDestLen [I] Length of lpDest
1542  * 
1543  * RETURNS
1544  *  Success: S_OK. lpDest is filled with the computed hash value.
1545  *  Failure: E_INVALIDARG, if any argument is invalid.
1546  */
1547 HRESULT WINAPI UrlHashA(LPCSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1548 {
1549   if (IsBadStringPtrA(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1550     return E_INVALIDARG;
1551
1552   HashData((const BYTE*)pszUrl, (int)strlen(pszUrl), lpDest, nDestLen);
1553   return S_OK;
1554 }
1555
1556 /*************************************************************************
1557  * UrlHashW     [SHLWAPI.@]
1558  *
1559  * See UrlHashA.
1560  */
1561 HRESULT WINAPI UrlHashW(LPCWSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1562 {
1563   char szUrl[MAX_PATH];
1564
1565   TRACE("(%s,%p,%d)\n",debugstr_w(pszUrl), lpDest, nDestLen);
1566
1567   if (IsBadStringPtrW(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1568     return E_INVALIDARG;
1569
1570   /* Win32 hashes the data as an ASCII string, presumably so that both A+W
1571    * return the same digests for the same URL.
1572    */
1573   WideCharToMultiByte(0, 0, pszUrl, -1, szUrl, MAX_PATH, 0, 0);
1574   HashData((const BYTE*)szUrl, (int)strlen(szUrl), lpDest, nDestLen);
1575   return S_OK;
1576 }
1577
1578 /*************************************************************************
1579  *      UrlApplySchemeA [SHLWAPI.@]
1580  *
1581  * Apply a scheme to a Url.
1582  *
1583  * PARAMS
1584  *  pszIn   [I]   Url to apply scheme to
1585  *  pszOut  [O]   Destination for modified Url
1586  *  pcchOut [I/O] Length of pszOut/destination for length of pszOut
1587  *  dwFlags [I]   URL_ flags from "shlwapi.h"
1588  *
1589  * RETURNS
1590  *  Success: S_OK: pszOut contains the modified Url, pcchOut contains its length.
1591  *  Failure: An HRESULT error code describing the error.
1592  */
1593 HRESULT WINAPI UrlApplySchemeA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1594 {
1595     LPWSTR in, out;
1596     HRESULT ret;
1597     DWORD len;
1598
1599     TRACE("(%s, %p, %p:out size %d, 0x%08x)\n", debugstr_a(pszIn),
1600             pszOut, pcchOut, pcchOut ? *pcchOut : 0, dwFlags);
1601
1602     if (!pszIn || !pszOut || !pcchOut) return E_INVALIDARG;
1603
1604     in = HeapAlloc(GetProcessHeap(), 0,
1605                   (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
1606     out = in + INTERNET_MAX_URL_LENGTH;
1607
1608     MultiByteToWideChar(CP_ACP, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
1609     len = INTERNET_MAX_URL_LENGTH;
1610
1611     ret = UrlApplySchemeW(in, out, &len, dwFlags);
1612     if (ret != S_OK) {
1613         HeapFree(GetProcessHeap(), 0, in);
1614         return ret;
1615     }
1616
1617     len = WideCharToMultiByte(CP_ACP, 0, out, -1, NULL, 0, NULL, NULL);
1618     if (len > *pcchOut) {
1619         ret = E_POINTER;
1620         goto cleanup;
1621     }
1622
1623     WideCharToMultiByte(CP_ACP, 0, out, -1, pszOut, *pcchOut, NULL, NULL);
1624     len--;
1625
1626 cleanup:
1627     *pcchOut = len;
1628     HeapFree(GetProcessHeap(), 0, in);
1629     return ret;
1630 }
1631
1632 static HRESULT URL_GuessScheme(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1633 {
1634     HKEY newkey;
1635     BOOL j;
1636     INT index;
1637     DWORD value_len, data_len, dwType, i;
1638     WCHAR reg_path[MAX_PATH];
1639     WCHAR value[MAX_PATH], data[MAX_PATH];
1640     WCHAR Wxx, Wyy;
1641
1642     MultiByteToWideChar(0, 0,
1643               "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes",
1644                         -1, reg_path, MAX_PATH);
1645     RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
1646     index = 0;
1647     while(value_len = data_len = MAX_PATH,
1648           RegEnumValueW(newkey, index, value, &value_len,
1649                         0, &dwType, (LPVOID)data, &data_len) == 0) {
1650         TRACE("guess %d %s is %s\n",
1651               index, debugstr_w(value), debugstr_w(data));
1652
1653         j = FALSE;
1654         for(i=0; i<value_len; i++) {
1655             Wxx = pszIn[i];
1656             Wyy = value[i];
1657             /* remember that TRUE is not-equal */
1658             j = ChrCmpIW(Wxx, Wyy);
1659             if (j) break;
1660         }
1661         if ((i == value_len) && !j) {
1662             if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1663                 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1664                 RegCloseKey(newkey);
1665                 return E_POINTER;
1666             }
1667             strcpyW(pszOut, data);
1668             strcatW(pszOut, pszIn);
1669             *pcchOut = strlenW(pszOut);
1670             TRACE("matched and set to %s\n", debugstr_w(pszOut));
1671             RegCloseKey(newkey);
1672             return S_OK;
1673         }
1674         index++;
1675     }
1676     RegCloseKey(newkey);
1677     return E_FAIL;
1678 }
1679
1680 static HRESULT URL_ApplyDefault(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1681 {
1682     HKEY newkey;
1683     DWORD data_len, dwType;
1684     WCHAR data[MAX_PATH];
1685
1686     static const WCHAR prefix_keyW[] =
1687         {'S','o','f','t','w','a','r','e',
1688          '\\','M','i','c','r','o','s','o','f','t',
1689          '\\','W','i','n','d','o','w','s',
1690          '\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n',
1691          '\\','U','R','L',
1692          '\\','D','e','f','a','u','l','t','P','r','e','f','i','x',0};
1693
1694     /* get and prepend default */
1695     RegOpenKeyExW(HKEY_LOCAL_MACHINE, prefix_keyW, 0, 1, &newkey);
1696     data_len = sizeof(data);
1697     RegQueryValueExW(newkey, NULL, 0, &dwType, (LPBYTE)data, &data_len);
1698     RegCloseKey(newkey);
1699     if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1700         *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1701         return E_POINTER;
1702     }
1703     strcpyW(pszOut, data);
1704     strcatW(pszOut, pszIn);
1705     *pcchOut = strlenW(pszOut);
1706     TRACE("used default %s\n", debugstr_w(pszOut));
1707     return S_OK;
1708 }
1709
1710 /*************************************************************************
1711  *      UrlApplySchemeW [SHLWAPI.@]
1712  *
1713  * See UrlApplySchemeA.
1714  */
1715 HRESULT WINAPI UrlApplySchemeW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1716 {
1717     PARSEDURLW in_scheme;
1718     DWORD res1;
1719     HRESULT ret;
1720
1721     TRACE("(%s, %p, %p:out size %d, 0x%08x)\n", debugstr_w(pszIn),
1722             pszOut, pcchOut, pcchOut ? *pcchOut : 0, dwFlags);
1723
1724     if (!pszIn || !pszOut || !pcchOut) return E_INVALIDARG;
1725
1726     if (dwFlags & URL_APPLY_GUESSFILE) {
1727         FIXME("(%s %p %p(%d) 0x%08x): stub URL_APPLY_GUESSFILE not implemented\n",
1728               debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwFlags);
1729         strcpyW(pszOut, pszIn);
1730         *pcchOut = strlenW(pszOut);
1731         return S_FALSE;
1732     }
1733
1734     in_scheme.cbSize = sizeof(in_scheme);
1735     /* See if the base has a scheme */
1736     res1 = ParseURLW(pszIn, &in_scheme);
1737     if (res1) {
1738         /* no scheme in input, need to see if we need to guess */
1739         if (dwFlags & URL_APPLY_GUESSSCHEME) {
1740             if ((ret = URL_GuessScheme(pszIn, pszOut, pcchOut)) != E_FAIL)
1741                 return ret;
1742         }
1743     }
1744     else {
1745         /* we have a scheme, see if valid (known scheme) */
1746         if (in_scheme.nScheme) {
1747             /* have valid scheme, so just copy and exit */
1748             if (strlenW(pszIn) + 1 > *pcchOut) {
1749                 *pcchOut = strlenW(pszIn) + 1;
1750                 return E_POINTER;
1751             }
1752             strcpyW(pszOut, pszIn);
1753             *pcchOut = strlenW(pszOut);
1754             TRACE("valid scheme, returning copy\n");
1755             return S_OK;
1756         }
1757     }
1758
1759     /* If we are here, then either invalid scheme,
1760      * or no scheme and can't/failed guess.
1761      */
1762     if ( ( ((res1 == 0) && (dwFlags & URL_APPLY_FORCEAPPLY)) ||
1763            ((res1 != 0)) ) &&
1764          (dwFlags & URL_APPLY_DEFAULT)) {
1765         /* find and apply default scheme */
1766         return URL_ApplyDefault(pszIn, pszOut, pcchOut);
1767     }
1768
1769     return S_FALSE;
1770 }
1771
1772 /*************************************************************************
1773  *      UrlIsA          [SHLWAPI.@]
1774  *
1775  * Determine if a Url is of a certain class.
1776  *
1777  * PARAMS
1778  *  pszUrl [I] Url to check
1779  *  Urlis  [I] URLIS_ constant from "shlwapi.h"
1780  *
1781  * RETURNS
1782  *  TRUE if pszUrl belongs to the class type in Urlis.
1783  *  FALSE Otherwise.
1784  */
1785 BOOL WINAPI UrlIsA(LPCSTR pszUrl, URLIS Urlis)
1786 {
1787     PARSEDURLA base;
1788     DWORD res1;
1789     LPCSTR last;
1790
1791     TRACE("(%s %d)\n", debugstr_a(pszUrl), Urlis);
1792
1793     if(!pszUrl)
1794         return FALSE;
1795
1796     switch (Urlis) {
1797
1798     case URLIS_OPAQUE:
1799         base.cbSize = sizeof(base);
1800         res1 = ParseURLA(pszUrl, &base);
1801         if (res1) return FALSE;  /* invalid scheme */
1802         switch (base.nScheme)
1803         {
1804         case URL_SCHEME_MAILTO:
1805         case URL_SCHEME_SHELL:
1806         case URL_SCHEME_JAVASCRIPT:
1807         case URL_SCHEME_VBSCRIPT:
1808         case URL_SCHEME_ABOUT:
1809             return TRUE;
1810         }
1811         return FALSE;
1812
1813     case URLIS_FILEURL:
1814         return !StrCmpNA("file:", pszUrl, 5);
1815
1816     case URLIS_DIRECTORY:
1817         last = pszUrl + strlen(pszUrl) - 1;
1818         return (last >= pszUrl && (*last == '/' || *last == '\\' ));
1819
1820     case URLIS_URL:
1821         return PathIsURLA(pszUrl);
1822
1823     case URLIS_NOHISTORY:
1824     case URLIS_APPLIABLE:
1825     case URLIS_HASQUERY:
1826     default:
1827         FIXME("(%s %d): stub\n", debugstr_a(pszUrl), Urlis);
1828     }
1829     return FALSE;
1830 }
1831
1832 /*************************************************************************
1833  *      UrlIsW          [SHLWAPI.@]
1834  *
1835  * See UrlIsA.
1836  */
1837 BOOL WINAPI UrlIsW(LPCWSTR pszUrl, URLIS Urlis)
1838 {
1839     static const WCHAR stemp[] = { 'f','i','l','e',':',0 };
1840     PARSEDURLW base;
1841     DWORD res1;
1842     LPCWSTR last;
1843
1844     TRACE("(%s %d)\n", debugstr_w(pszUrl), Urlis);
1845
1846     if(!pszUrl)
1847         return FALSE;
1848
1849     switch (Urlis) {
1850
1851     case URLIS_OPAQUE:
1852         base.cbSize = sizeof(base);
1853         res1 = ParseURLW(pszUrl, &base);
1854         if (res1) return FALSE;  /* invalid scheme */
1855         switch (base.nScheme)
1856         {
1857         case URL_SCHEME_MAILTO:
1858         case URL_SCHEME_SHELL:
1859         case URL_SCHEME_JAVASCRIPT:
1860         case URL_SCHEME_VBSCRIPT:
1861         case URL_SCHEME_ABOUT:
1862             return TRUE;
1863         }
1864         return FALSE;
1865
1866     case URLIS_FILEURL:
1867         return !strncmpW(stemp, pszUrl, 5);
1868
1869     case URLIS_DIRECTORY:
1870         last = pszUrl + strlenW(pszUrl) - 1;
1871         return (last >= pszUrl && (*last == '/' || *last == '\\'));
1872
1873     case URLIS_URL:
1874         return PathIsURLW(pszUrl);
1875
1876     case URLIS_NOHISTORY:
1877     case URLIS_APPLIABLE:
1878     case URLIS_HASQUERY:
1879     default:
1880         FIXME("(%s %d): stub\n", debugstr_w(pszUrl), Urlis);
1881     }
1882     return FALSE;
1883 }
1884
1885 /*************************************************************************
1886  *      UrlIsNoHistoryA         [SHLWAPI.@]
1887  *
1888  * Determine if a Url should not be stored in the users history list.
1889  *
1890  * PARAMS
1891  *  pszUrl [I] Url to check
1892  *
1893  * RETURNS
1894  *  TRUE, if pszUrl should be excluded from the history list,
1895  *  FALSE otherwise.
1896  */
1897 BOOL WINAPI UrlIsNoHistoryA(LPCSTR pszUrl)
1898 {
1899     return UrlIsA(pszUrl, URLIS_NOHISTORY);
1900 }
1901
1902 /*************************************************************************
1903  *      UrlIsNoHistoryW         [SHLWAPI.@]
1904  *
1905  * See UrlIsNoHistoryA.
1906  */
1907 BOOL WINAPI UrlIsNoHistoryW(LPCWSTR pszUrl)
1908 {
1909     return UrlIsW(pszUrl, URLIS_NOHISTORY);
1910 }
1911
1912 /*************************************************************************
1913  *      UrlIsOpaqueA    [SHLWAPI.@]
1914  *
1915  * Determine if a Url is opaque.
1916  *
1917  * PARAMS
1918  *  pszUrl [I] Url to check
1919  *
1920  * RETURNS
1921  *  TRUE if pszUrl is opaque,
1922  *  FALSE Otherwise.
1923  *
1924  * NOTES
1925  *  An opaque Url is one that does not start with "<protocol>://".
1926  */
1927 BOOL WINAPI UrlIsOpaqueA(LPCSTR pszUrl)
1928 {
1929     return UrlIsA(pszUrl, URLIS_OPAQUE);
1930 }
1931
1932 /*************************************************************************
1933  *      UrlIsOpaqueW    [SHLWAPI.@]
1934  *
1935  * See UrlIsOpaqueA.
1936  */
1937 BOOL WINAPI UrlIsOpaqueW(LPCWSTR pszUrl)
1938 {
1939     return UrlIsW(pszUrl, URLIS_OPAQUE);
1940 }
1941
1942 /*************************************************************************
1943  *  Scans for characters of type "type" and when not matching found,
1944  *  returns pointer to it and length in size.
1945  *
1946  * Characters tested based on RFC 1738
1947  */
1948 static LPCWSTR  URL_ScanID(LPCWSTR start, LPDWORD size, WINE_URL_SCAN_TYPE type)
1949 {
1950     static DWORD alwayszero = 0;
1951     BOOL cont = TRUE;
1952
1953     *size = 0;
1954
1955     switch(type){
1956
1957     case SCHEME:
1958         while (cont) {
1959             if ( (islowerW(*start) && isalphaW(*start)) ||
1960                  isdigitW(*start) ||
1961                  (*start == '+') ||
1962                  (*start == '-') ||
1963                  (*start == '.')) {
1964                 start++;
1965                 (*size)++;
1966             }
1967             else
1968                 cont = FALSE;
1969         }
1970         break;
1971
1972     case USERPASS:
1973         while (cont) {
1974             if ( isalphaW(*start) ||
1975                  isdigitW(*start) ||
1976                  /* user/password only characters */
1977                  (*start == ';') ||
1978                  (*start == '?') ||
1979                  (*start == '&') ||
1980                  (*start == '=') ||
1981                  /* *extra* characters */
1982                  (*start == '!') ||
1983                  (*start == '*') ||
1984                  (*start == '\'') ||
1985                  (*start == '(') ||
1986                  (*start == ')') ||
1987                  (*start == ',') ||
1988                  /* *safe* characters */
1989                  (*start == '$') ||
1990                  (*start == '_') ||
1991                  (*start == '+') ||
1992                  (*start == '-') ||
1993                  (*start == '.') ||
1994                  (*start == ' ')) {
1995                 start++;
1996                 (*size)++;
1997             } else if (*start == '%') {
1998                 if (isxdigitW(*(start+1)) &&
1999                     isxdigitW(*(start+2))) {
2000                     start += 3;
2001                     *size += 3;
2002                 } else
2003                     cont = FALSE;
2004             } else
2005                 cont = FALSE;
2006         }
2007         break;
2008
2009     case PORT:
2010         while (cont) {
2011             if (isdigitW(*start)) {
2012                 start++;
2013                 (*size)++;
2014             }
2015             else
2016                 cont = FALSE;
2017         }
2018         break;
2019
2020     case HOST:
2021         while (cont) {
2022             if (isalnumW(*start) ||
2023                 (*start == '-') ||
2024                 (*start == '.') ||
2025                 (*start == ' ') ) {
2026                 start++;
2027                 (*size)++;
2028             }
2029             else
2030                 cont = FALSE;
2031         }
2032         break;
2033     default:
2034         FIXME("unknown type %d\n", type);
2035         return (LPWSTR)&alwayszero;
2036     }
2037     /* TRACE("scanned %d characters next char %p<%c>\n",
2038      *size, start, *start); */
2039     return start;
2040 }
2041
2042 /*************************************************************************
2043  *  Attempt to parse URL into pieces.
2044  */
2045 static LONG URL_ParseUrl(LPCWSTR pszUrl, WINE_PARSE_URL *pl)
2046 {
2047     LPCWSTR work;
2048
2049     memset(pl, 0, sizeof(WINE_PARSE_URL));
2050     pl->pScheme = pszUrl;
2051     work = URL_ScanID(pl->pScheme, &pl->szScheme, SCHEME);
2052     if (!*work || (*work != ':')) goto ErrorExit;
2053     work++;
2054     if ((*work != '/') || (*(work+1) != '/')) goto SuccessExit;
2055     pl->pUserName = work + 2;
2056     work = URL_ScanID(pl->pUserName, &pl->szUserName, USERPASS);
2057     if (*work == ':' ) {
2058         /* parse password */
2059         work++;
2060         pl->pPassword = work;
2061         work = URL_ScanID(pl->pPassword, &pl->szPassword, USERPASS);
2062         if (*work != '@') {
2063             /* what we just parsed must be the hostname and port
2064              * so reset pointers and clear then let it parse */
2065             pl->szUserName = pl->szPassword = 0;
2066             work = pl->pUserName - 1;
2067             pl->pUserName = pl->pPassword = 0;
2068         }
2069     } else if (*work == '@') {
2070         /* no password */
2071         pl->szPassword = 0;
2072         pl->pPassword = 0;
2073     } else if (!*work || (*work == '/') || (*work == '.')) {
2074         /* what was parsed was hostname, so reset pointers and let it parse */
2075         pl->szUserName = pl->szPassword = 0;
2076         work = pl->pUserName - 1;
2077         pl->pUserName = pl->pPassword = 0;
2078     } else goto ErrorExit;
2079
2080     /* now start parsing hostname or hostnumber */
2081     work++;
2082     pl->pHostName = work;
2083     work = URL_ScanID(pl->pHostName, &pl->szHostName, HOST);
2084     if (*work == ':') {
2085         /* parse port */
2086         work++;
2087         pl->pPort = work;
2088         work = URL_ScanID(pl->pPort, &pl->szPort, PORT);
2089     }
2090     if (*work == '/') {
2091         /* see if query string */
2092         pl->pQuery = strchrW(work, '?');
2093         if (pl->pQuery) pl->szQuery = strlenW(pl->pQuery);
2094     }
2095   SuccessExit:
2096     TRACE("parse successful: scheme=%p(%d), user=%p(%d), pass=%p(%d), host=%p(%d), port=%p(%d), query=%p(%d)\n",
2097           pl->pScheme, pl->szScheme,
2098           pl->pUserName, pl->szUserName,
2099           pl->pPassword, pl->szPassword,
2100           pl->pHostName, pl->szHostName,
2101           pl->pPort, pl->szPort,
2102           pl->pQuery, pl->szQuery);
2103     return S_OK;
2104   ErrorExit:
2105     FIXME("failed to parse %s\n", debugstr_w(pszUrl));
2106     return E_INVALIDARG;
2107 }
2108
2109 /*************************************************************************
2110  *      UrlGetPartA     [SHLWAPI.@]
2111  *
2112  * Retrieve part of a Url.
2113  *
2114  * PARAMS
2115  *  pszIn   [I]   Url to parse
2116  *  pszOut  [O]   Destination for part of pszIn requested
2117  *  pcchOut [I]   Size of pszOut
2118  *          [O]   length of pszOut string EXCLUDING '\0' if S_OK, otherwise
2119  *                needed size of pszOut INCLUDING '\0'.
2120  *  dwPart  [I]   URL_PART_ enum from "shlwapi.h"
2121  *  dwFlags [I]   URL_ flags from "shlwapi.h"
2122  *
2123  * RETURNS
2124  *  Success: S_OK. pszOut contains the part requested, pcchOut contains its length.
2125  *  Failure: An HRESULT error code describing the error.
2126  */
2127 HRESULT WINAPI UrlGetPartA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut,
2128                            DWORD dwPart, DWORD dwFlags)
2129 {
2130     LPWSTR in, out;
2131     DWORD ret, len, len2;
2132
2133     if(!pszIn || !pszOut || !pcchOut || *pcchOut <= 0)
2134         return E_INVALIDARG;
2135
2136     in = HeapAlloc(GetProcessHeap(), 0,
2137                               (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
2138     out = in + INTERNET_MAX_URL_LENGTH;
2139
2140     MultiByteToWideChar(0, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
2141
2142     len = INTERNET_MAX_URL_LENGTH;
2143     ret = UrlGetPartW(in, out, &len, dwPart, dwFlags);
2144
2145     if (FAILED(ret)) {
2146         HeapFree(GetProcessHeap(), 0, in);
2147         return ret;
2148     }
2149
2150     len2 = WideCharToMultiByte(0, 0, out, len, 0, 0, 0, 0);
2151     if (len2 > *pcchOut) {
2152         *pcchOut = len2+1;
2153         HeapFree(GetProcessHeap(), 0, in);
2154         return E_POINTER;
2155     }
2156     len2 = WideCharToMultiByte(0, 0, out, len+1, pszOut, *pcchOut, 0, 0);
2157     *pcchOut = len2-1;
2158     HeapFree(GetProcessHeap(), 0, in);
2159     return ret;
2160 }
2161
2162 /*************************************************************************
2163  *      UrlGetPartW     [SHLWAPI.@]
2164  *
2165  * See UrlGetPartA.
2166  */
2167 HRESULT WINAPI UrlGetPartW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut,
2168                            DWORD dwPart, DWORD dwFlags)
2169 {
2170     WINE_PARSE_URL pl;
2171     HRESULT ret;
2172     DWORD scheme, size, schsize;
2173     LPCWSTR addr, schaddr;
2174
2175     TRACE("(%s %p %p(%d) %08x %08x)\n",
2176           debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwPart, dwFlags);
2177
2178     if(!pszIn || !pszOut || !pcchOut || *pcchOut <= 0)
2179         return E_INVALIDARG;
2180
2181     *pszOut = '\0';
2182
2183     addr = strchrW(pszIn, ':');
2184     if(!addr)
2185         scheme = URL_SCHEME_UNKNOWN;
2186     else
2187         scheme = get_scheme_code(pszIn, addr-pszIn);
2188
2189     ret = URL_ParseUrl(pszIn, &pl);
2190
2191         switch (dwPart) {
2192         case URL_PART_SCHEME:
2193             if (!pl.szScheme || scheme == URL_SCHEME_UNKNOWN) {
2194                 *pcchOut = 0;
2195                 return S_FALSE;
2196             }
2197             addr = pl.pScheme;
2198             size = pl.szScheme;
2199             break;
2200
2201         case URL_PART_HOSTNAME:
2202             switch(scheme) {
2203             case URL_SCHEME_FTP:
2204             case URL_SCHEME_HTTP:
2205             case URL_SCHEME_GOPHER:
2206             case URL_SCHEME_TELNET:
2207             case URL_SCHEME_FILE:
2208             case URL_SCHEME_HTTPS:
2209                 break;
2210             default:
2211                 *pcchOut = 0;
2212                 return E_FAIL;
2213             }
2214
2215             if(scheme==URL_SCHEME_FILE && (!pl.szHostName ||
2216                         (pl.szHostName==1 && *(pl.pHostName+1)==':'))) {
2217                 *pcchOut = 0;
2218                 return S_FALSE;
2219             }
2220
2221             if (!pl.szHostName) {
2222                 *pcchOut = 0;
2223                 return S_FALSE;
2224             }
2225             addr = pl.pHostName;
2226             size = pl.szHostName;
2227             break;
2228
2229         case URL_PART_USERNAME:
2230             if (!pl.szUserName) {
2231                 *pcchOut = 0;
2232                 return S_FALSE;
2233             }
2234             addr = pl.pUserName;
2235             size = pl.szUserName;
2236             break;
2237
2238         case URL_PART_PASSWORD:
2239             if (!pl.szPassword) {
2240                 *pcchOut = 0;
2241                 return S_FALSE;
2242             }
2243             addr = pl.pPassword;
2244             size = pl.szPassword;
2245             break;
2246
2247         case URL_PART_PORT:
2248             if (!pl.szPort) {
2249                 *pcchOut = 0;
2250                 return S_FALSE;
2251             }
2252             addr = pl.pPort;
2253             size = pl.szPort;
2254             break;
2255
2256         case URL_PART_QUERY:
2257             if (!pl.szQuery) {
2258                 *pcchOut = 0;
2259                 return S_FALSE;
2260             }
2261             addr = pl.pQuery;
2262             size = pl.szQuery;
2263             break;
2264
2265         default:
2266             *pcchOut = 0;
2267             return E_INVALIDARG;
2268         }
2269
2270         if (dwFlags == URL_PARTFLAG_KEEPSCHEME) {
2271             if(!pl.pScheme || !pl.szScheme) {
2272                 *pcchOut = 0;
2273                 return E_FAIL;
2274             }
2275             schaddr = pl.pScheme;
2276             schsize = pl.szScheme;
2277             if (*pcchOut < schsize + size + 2) {
2278                 *pcchOut = schsize + size + 2;
2279                 return E_POINTER;
2280             }
2281             memcpy(pszOut, schaddr, schsize*sizeof(WCHAR));
2282             pszOut[schsize] = ':';
2283             memcpy(pszOut+schsize+1, addr, size*sizeof(WCHAR));
2284             pszOut[schsize+1+size] = 0;
2285             *pcchOut = schsize + 1 + size;
2286         }
2287         else {
2288             if (*pcchOut < size + 1) {*pcchOut = size+1; return E_POINTER;}
2289             memcpy(pszOut, addr, size*sizeof(WCHAR));
2290             pszOut[size] = 0;
2291             *pcchOut = size;
2292         }
2293         TRACE("len=%d %s\n", *pcchOut, debugstr_w(pszOut));
2294
2295     return ret;
2296 }
2297
2298 /*************************************************************************
2299  * PathIsURLA   [SHLWAPI.@]
2300  *
2301  * Check if the given path is a Url.
2302  *
2303  * PARAMS
2304  *  lpszPath [I] Path to check.
2305  *
2306  * RETURNS
2307  *  TRUE  if lpszPath is a Url.
2308  *  FALSE if lpszPath is NULL or not a Url.
2309  */
2310 BOOL WINAPI PathIsURLA(LPCSTR lpstrPath)
2311 {
2312     PARSEDURLA base;
2313     HRESULT hres;
2314
2315     TRACE("%s\n", debugstr_a(lpstrPath));
2316
2317     if (!lpstrPath || !*lpstrPath) return FALSE;
2318
2319     /* get protocol        */
2320     base.cbSize = sizeof(base);
2321     hres = ParseURLA(lpstrPath, &base);
2322     return hres == S_OK && (base.nScheme != URL_SCHEME_INVALID);
2323 }
2324
2325 /*************************************************************************
2326  * PathIsURLW   [SHLWAPI.@]
2327  *
2328  * See PathIsURLA.
2329  */
2330 BOOL WINAPI PathIsURLW(LPCWSTR lpstrPath)
2331 {
2332     PARSEDURLW base;
2333     HRESULT hres;
2334
2335     TRACE("%s\n", debugstr_w(lpstrPath));
2336
2337     if (!lpstrPath || !*lpstrPath) return FALSE;
2338
2339     /* get protocol        */
2340     base.cbSize = sizeof(base);
2341     hres = ParseURLW(lpstrPath, &base);
2342     return hres == S_OK && (base.nScheme != URL_SCHEME_INVALID);
2343 }
2344
2345 /*************************************************************************
2346  *      UrlCreateFromPathA      [SHLWAPI.@]
2347  * 
2348  * See UrlCreateFromPathW
2349  */
2350 HRESULT WINAPI UrlCreateFromPathA(LPCSTR pszPath, LPSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2351 {
2352     WCHAR bufW[INTERNET_MAX_URL_LENGTH];
2353     WCHAR *urlW = bufW;
2354     UNICODE_STRING pathW;
2355     HRESULT ret;
2356     DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
2357
2358     if(!RtlCreateUnicodeStringFromAsciiz(&pathW, pszPath))
2359         return E_INVALIDARG;
2360     if((ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved)) == E_POINTER) {
2361         urlW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
2362         ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved);
2363     }
2364     if(ret == S_OK || ret == S_FALSE) {
2365         RtlUnicodeToMultiByteSize(&lenA, urlW, lenW * sizeof(WCHAR));
2366         if(*pcchUrl > lenA) {
2367             RtlUnicodeToMultiByteN(pszUrl, *pcchUrl - 1, &lenA, urlW, lenW * sizeof(WCHAR));
2368             pszUrl[lenA] = 0;
2369             *pcchUrl = lenA;
2370         } else {
2371             *pcchUrl = lenA + 1;
2372             ret = E_POINTER;
2373         }
2374     }
2375     if(urlW != bufW) HeapFree(GetProcessHeap(), 0, urlW);
2376     RtlFreeUnicodeString(&pathW);
2377     return ret;
2378 }
2379
2380 /*************************************************************************
2381  *      UrlCreateFromPathW      [SHLWAPI.@]
2382  *
2383  * Create a Url from a file path.
2384  *
2385  * PARAMS
2386  *  pszPath [I]    Path to convert
2387  *  pszUrl  [O]    Destination for the converted Url
2388  *  pcchUrl [I/O]  Length of pszUrl
2389  *  dwReserved [I] Reserved, must be 0
2390  *
2391  * RETURNS
2392  *  Success: S_OK pszUrl contains the converted path, S_FALSE if the path is already a Url
2393  *  Failure: An HRESULT error code.
2394  */
2395 HRESULT WINAPI UrlCreateFromPathW(LPCWSTR pszPath, LPWSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2396 {
2397     DWORD needed;
2398     HRESULT ret;
2399     WCHAR *pszNewUrl;
2400     WCHAR file_colonW[] = {'f','i','l','e',':',0};
2401     WCHAR three_slashesW[] = {'/','/','/',0};
2402     PARSEDURLW parsed_url;
2403
2404     TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszPath), pszUrl, pcchUrl, dwReserved);
2405
2406     /* Validate arguments */
2407     if (dwReserved != 0)
2408         return E_INVALIDARG;
2409     if (!pszUrl || !pcchUrl)
2410         return E_INVALIDARG;
2411
2412
2413     parsed_url.cbSize = sizeof(parsed_url);
2414     if(ParseURLW(pszPath, &parsed_url) == S_OK) {
2415         if(parsed_url.nScheme != URL_SCHEME_INVALID && parsed_url.cchProtocol > 1) {
2416             needed = strlenW(pszPath);
2417             if (needed >= *pcchUrl) {
2418                 *pcchUrl = needed + 1;
2419                 return E_POINTER;
2420             } else {
2421                 *pcchUrl = needed;
2422                 strcpyW(pszUrl, pszPath);
2423                 return S_FALSE;
2424             }
2425         }
2426     }
2427
2428     pszNewUrl = HeapAlloc(GetProcessHeap(), 0, (strlenW(pszPath) + 9) * sizeof(WCHAR)); /* "file:///" + pszPath_len + 1 */
2429     strcpyW(pszNewUrl, file_colonW);
2430     if(isalphaW(pszPath[0]) && pszPath[1] == ':')
2431         strcatW(pszNewUrl, three_slashesW);
2432     strcatW(pszNewUrl, pszPath);
2433     ret = UrlEscapeW(pszNewUrl, pszUrl, pcchUrl, URL_ESCAPE_PERCENT);
2434
2435     HeapFree(GetProcessHeap(), 0, pszNewUrl);
2436     return ret;
2437 }
2438
2439 /*************************************************************************
2440  *      SHAutoComplete          [SHLWAPI.@]
2441  *
2442  * Enable auto-completion for an edit control.
2443  *
2444  * PARAMS
2445  *  hwndEdit [I] Handle of control to enable auto-completion for
2446  *  dwFlags  [I] SHACF_ flags from "shlwapi.h"
2447  *
2448  * RETURNS
2449  *  Success: S_OK. Auto-completion is enabled for the control.
2450  *  Failure: An HRESULT error code indicating the error.
2451  */
2452 HRESULT WINAPI SHAutoComplete(HWND hwndEdit, DWORD dwFlags)
2453 {
2454   FIXME("stub\n");
2455   return S_FALSE;
2456 }
2457
2458 /*************************************************************************
2459  *  MLBuildResURLA      [SHLWAPI.405]
2460  *
2461  * Create a Url pointing to a resource in a module.
2462  *
2463  * PARAMS
2464  *  lpszLibName [I] Name of the module containing the resource
2465  *  hMod        [I] Callers module handle
2466  *  dwFlags     [I] Undocumented flags for loading the module
2467  *  lpszRes     [I] Resource name
2468  *  lpszDest    [O] Destination for resulting Url
2469  *  dwDestLen   [I] Length of lpszDest
2470  *
2471  * RETURNS
2472  *  Success: S_OK. lpszDest contains the resource Url.
2473  *  Failure: E_INVALIDARG, if any argument is invalid, or
2474  *           E_FAIL if dwDestLen is too small.
2475  */
2476 HRESULT WINAPI MLBuildResURLA(LPCSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2477                               LPCSTR lpszRes, LPSTR lpszDest, DWORD dwDestLen)
2478 {
2479   WCHAR szLibName[MAX_PATH], szRes[MAX_PATH], szDest[MAX_PATH];
2480   HRESULT hRet;
2481
2482   if (lpszLibName)
2483     MultiByteToWideChar(CP_ACP, 0, lpszLibName, -1, szLibName, sizeof(szLibName)/sizeof(WCHAR));
2484
2485   if (lpszRes)
2486     MultiByteToWideChar(CP_ACP, 0, lpszRes, -1, szRes, sizeof(szRes)/sizeof(WCHAR));
2487
2488   if (dwDestLen > sizeof(szLibName)/sizeof(WCHAR))
2489     dwDestLen = sizeof(szLibName)/sizeof(WCHAR);
2490
2491   hRet = MLBuildResURLW(lpszLibName ? szLibName : NULL, hMod, dwFlags,
2492                         lpszRes ? szRes : NULL, lpszDest ? szDest : NULL, dwDestLen);
2493   if (SUCCEEDED(hRet) && lpszDest)
2494     WideCharToMultiByte(CP_ACP, 0, szDest, -1, lpszDest, dwDestLen, 0, 0);
2495
2496   return hRet;
2497 }
2498
2499 /*************************************************************************
2500  *  MLBuildResURLA      [SHLWAPI.406]
2501  *
2502  * See MLBuildResURLA.
2503  */
2504 HRESULT WINAPI MLBuildResURLW(LPCWSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2505                               LPCWSTR lpszRes, LPWSTR lpszDest, DWORD dwDestLen)
2506 {
2507   static const WCHAR szRes[] = { 'r','e','s',':','/','/','\0' };
2508 #define szResLen ((sizeof(szRes) - sizeof(WCHAR))/sizeof(WCHAR))
2509   HRESULT hRet = E_FAIL;
2510
2511   TRACE("(%s,%p,0x%08x,%s,%p,%d)\n", debugstr_w(lpszLibName), hMod, dwFlags,
2512         debugstr_w(lpszRes), lpszDest, dwDestLen);
2513
2514   if (!lpszLibName || !hMod || hMod == INVALID_HANDLE_VALUE || !lpszRes ||
2515       !lpszDest || (dwFlags && dwFlags != 2))
2516     return E_INVALIDARG;
2517
2518   if (dwDestLen >= szResLen + 1)
2519   {
2520     dwDestLen -= (szResLen + 1);
2521     memcpy(lpszDest, szRes, sizeof(szRes));
2522
2523     hMod = MLLoadLibraryW(lpszLibName, hMod, dwFlags);
2524
2525     if (hMod)
2526     {
2527       WCHAR szBuff[MAX_PATH];
2528       DWORD len;
2529
2530       len = GetModuleFileNameW(hMod, szBuff, sizeof(szBuff)/sizeof(WCHAR));
2531       if (len && len < sizeof(szBuff)/sizeof(WCHAR))
2532       {
2533         DWORD dwPathLen = strlenW(szBuff) + 1;
2534
2535         if (dwDestLen >= dwPathLen)
2536         {
2537           DWORD dwResLen;
2538
2539           dwDestLen -= dwPathLen;
2540           memcpy(lpszDest + szResLen, szBuff, dwPathLen * sizeof(WCHAR));
2541
2542           dwResLen = strlenW(lpszRes) + 1;
2543           if (dwDestLen >= dwResLen + 1)
2544           {
2545             lpszDest[szResLen + dwPathLen-1] = '/';
2546             memcpy(lpszDest + szResLen + dwPathLen, lpszRes, dwResLen * sizeof(WCHAR));
2547             hRet = S_OK;
2548           }
2549         }
2550       }
2551       MLFreeLibrary(hMod);
2552     }
2553   }
2554   return hRet;
2555 }
2556
2557 /***********************************************************************
2558  *             UrlFixupW [SHLWAPI.462]
2559  *
2560  * Checks the scheme part of a URL and attempts to correct misspellings.
2561  *
2562  * PARAMS
2563  *  lpszUrl           [I] Pointer to the URL to be corrected
2564  *  lpszTranslatedUrl [O] Pointer to a buffer to store corrected URL
2565  *  dwMaxChars        [I] Maximum size of corrected URL
2566  *
2567  * RETURNS
2568  *  success: S_OK if URL corrected or already correct
2569  *  failure: S_FALSE if unable to correct / COM error code if other error
2570  *
2571  */
2572 HRESULT WINAPI UrlFixupW(LPCWSTR url, LPWSTR translatedUrl, DWORD maxChars)
2573 {
2574     DWORD srcLen;
2575
2576     FIXME("(%s,%p,%d) STUB\n", debugstr_w(url), translatedUrl, maxChars);
2577
2578     if (!url)
2579         return E_FAIL;
2580
2581     srcLen = lstrlenW(url) + 1;
2582
2583     /* For now just copy the URL directly */
2584     lstrcpynW(translatedUrl, url, (maxChars < srcLen) ? maxChars : srcLen);
2585
2586     return S_OK;
2587 }