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