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