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