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