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