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