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