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