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