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