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