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