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