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