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