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