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