LVS_OWNERDRAWFIXED should only take effect in REPORT mode.
[wine/multimedia.git] / dlls / shlwapi / url.c
blob387ae9da569614ac0e728848c49beed88dea143d
1 /*
2 * Url functions
4 * Copyright 2000 Huw D M Davies for CodeWeavers.
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.
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.
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
21 #include <string.h>
22 #include <stdlib.h>
23 #include "windef.h"
24 #include "winnls.h"
25 #include "winbase.h"
26 #include "winerror.h"
27 #include "wine/unicode.h"
28 #include "wininet.h"
29 #include "winreg.h"
30 #define NO_SHLWAPI_STREAM
31 #include "shlwapi.h"
32 #include "wine/debug.h"
34 WINE_DEFAULT_DEBUG_CHANNEL(shell);
36 /* The following schemes were identified in the native version of
37 * SHLWAPI.DLL version 5.50
39 typedef enum {
40 URL_SCHEME_INVALID = -1,
41 URL_SCHEME_UNKNOWN = 0,
42 URL_SCHEME_FTP,
43 URL_SCHEME_HTTP,
44 URL_SCHEME_GOPHER,
45 URL_SCHEME_MAILTO,
46 URL_SCHEME_NEWS,
47 URL_SCHEME_NNTP,
48 URL_SCHEME_TELNET,
49 URL_SCHEME_WAIS,
50 URL_SCHEME_FILE,
51 URL_SCHEME_MK,
52 URL_SCHEME_HTTPS,
53 URL_SCHEME_SHELL,
54 URL_SCHEME_SNEWS,
55 URL_SCHEME_LOCAL,
56 URL_SCHEME_JAVASCRIPT,
57 URL_SCHEME_VBSCRIPT,
58 URL_SCHEME_ABOUT,
59 URL_SCHEME_RES,
60 URL_SCHEME_MAXVALUE
61 } URL_SCHEME;
63 typedef struct {
64 URL_SCHEME scheme_number;
65 LPCSTR scheme_name;
66 } SHL_2_inet_scheme;
68 static const SHL_2_inet_scheme shlwapi_schemes[] = {
69 {URL_SCHEME_FTP, "ftp"},
70 {URL_SCHEME_HTTP, "http"},
71 {URL_SCHEME_GOPHER, "gopher"},
72 {URL_SCHEME_MAILTO, "mailto"},
73 {URL_SCHEME_NEWS, "news"},
74 {URL_SCHEME_NNTP, "nntp"},
75 {URL_SCHEME_TELNET, "telnet"},
76 {URL_SCHEME_WAIS, "wais"},
77 {URL_SCHEME_FILE, "file"},
78 {URL_SCHEME_MK, "mk"},
79 {URL_SCHEME_HTTPS, "https"},
80 {URL_SCHEME_SHELL, "shell"},
81 {URL_SCHEME_SNEWS, "snews"},
82 {URL_SCHEME_LOCAL, "local"},
83 {URL_SCHEME_JAVASCRIPT, "javascript"},
84 {URL_SCHEME_VBSCRIPT, "vbscript"},
85 {URL_SCHEME_ABOUT, "about"},
86 {URL_SCHEME_RES, "res"},
87 {0, 0}
90 typedef struct {
91 LPCWSTR pScheme; /* [out] start of scheme */
92 DWORD szScheme; /* [out] size of scheme (until colon) */
93 LPCWSTR pUserName; /* [out] start of Username */
94 DWORD szUserName; /* [out] size of Username (until ":" or "@") */
95 LPCWSTR pPassword; /* [out] start of Password */
96 DWORD szPassword; /* [out] size of Password (until "@") */
97 LPCWSTR pHostName; /* [out] start of Hostname */
98 DWORD szHostName; /* [out] size of Hostname (until ":" or "/") */
99 LPCWSTR pPort; /* [out] start of Port */
100 DWORD szPort; /* [out] size of Port (until "/" or eos) */
101 LPCWSTR pQuery; /* [out] start of Query */
102 DWORD szQuery; /* [out] size of Query (until eos) */
103 } WINE_PARSE_URL;
105 typedef enum {
106 SCHEME,
107 HOST,
108 PORT,
109 USERPASS,
110 } WINE_URL_SCAN_TYPE;
112 typedef struct {
113 INT size; /* [in] (always 0x18) */
114 LPCSTR ap1; /* [out] start of scheme */
115 INT sizep1; /* [out] size of scheme (until colon) */
116 LPCSTR ap2; /* [out] pointer following first colon */
117 INT sizep2; /* [out] size of remainder */
118 INT fcncde; /* [out] function match of p1 (0 if unknown) */
119 } UNKNOWN_SHLWAPI_1;
121 typedef struct {
122 INT size; /* [in] (always 0x18) */
123 LPCWSTR ap1; /* [out] start of scheme */
124 INT sizep1; /* [out] size of scheme (until colon) */
125 LPCWSTR ap2; /* [out] pointer following first colon */
126 INT sizep2; /* [out] size of remainder */
127 INT fcncde; /* [out] function match of p1 (0 if unknown) */
128 } UNKNOWN_SHLWAPI_2;
130 static const CHAR hexDigits[] = "0123456789ABCDEF";
132 static const WCHAR fileW[] = {'f','i','l','e','\0'};
134 static const unsigned char HashDataLookup[256] = {
135 0x01, 0x0E, 0x6E, 0x19, 0x61, 0xAE, 0x84, 0x77, 0x8A, 0xAA, 0x7D, 0x76, 0x1B,
136 0xE9, 0x8C, 0x33, 0x57, 0xC5, 0xB1, 0x6B, 0xEA, 0xA9, 0x38, 0x44, 0x1E, 0x07,
137 0xAD, 0x49, 0xBC, 0x28, 0x24, 0x41, 0x31, 0xD5, 0x68, 0xBE, 0x39, 0xD3, 0x94,
138 0xDF, 0x30, 0x73, 0x0F, 0x02, 0x43, 0xBA, 0xD2, 0x1C, 0x0C, 0xB5, 0x67, 0x46,
139 0x16, 0x3A, 0x4B, 0x4E, 0xB7, 0xA7, 0xEE, 0x9D, 0x7C, 0x93, 0xAC, 0x90, 0xB0,
140 0xA1, 0x8D, 0x56, 0x3C, 0x42, 0x80, 0x53, 0x9C, 0xF1, 0x4F, 0x2E, 0xA8, 0xC6,
141 0x29, 0xFE, 0xB2, 0x55, 0xFD, 0xED, 0xFA, 0x9A, 0x85, 0x58, 0x23, 0xCE, 0x5F,
142 0x74, 0xFC, 0xC0, 0x36, 0xDD, 0x66, 0xDA, 0xFF, 0xF0, 0x52, 0x6A, 0x9E, 0xC9,
143 0x3D, 0x03, 0x59, 0x09, 0x2A, 0x9B, 0x9F, 0x5D, 0xA6, 0x50, 0x32, 0x22, 0xAF,
144 0xC3, 0x64, 0x63, 0x1A, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xBD, 0x79, 0x40,
145 0x4D, 0x48, 0xD0, 0xF5, 0x82, 0x7A, 0x8F, 0x37, 0x69, 0x86, 0x1D, 0xA4, 0xB9,
146 0xC2, 0xC1, 0xEF, 0x65, 0xF2, 0x05, 0xAB, 0x7E, 0x0B, 0x4A, 0x3B, 0x89, 0xE4,
147 0x6C, 0xBF, 0xE8, 0x8B, 0x06, 0x18, 0x51, 0x14, 0x7F, 0x11, 0x5B, 0x5C, 0xFB,
148 0x97, 0xE1, 0xCF, 0x15, 0x62, 0x71, 0x70, 0x54, 0xE2, 0x12, 0xD6, 0xC7, 0xBB,
149 0x0D, 0x20, 0x5E, 0xDC, 0xE0, 0xD4, 0xF7, 0xCC, 0xC4, 0x2B, 0xF9, 0xEC, 0x2D,
150 0xF4, 0x6F, 0xB6, 0x99, 0x88, 0x81, 0x5A, 0xD9, 0xCA, 0x13, 0xA5, 0xE7, 0x47,
151 0xE6, 0x8E, 0x60, 0xE3, 0x3E, 0xB3, 0xF6, 0x72, 0xA2, 0x35, 0xA0, 0xD7, 0xCD,
152 0xB4, 0x2F, 0x6D, 0x2C, 0x26, 0x1F, 0x95, 0x87, 0x00, 0xD8, 0x34, 0x3F, 0x17,
153 0x25, 0x45, 0x27, 0x75, 0x92, 0xB8, 0xA3, 0xC8, 0xDE, 0xEB, 0xF8, 0xF3, 0xDB,
154 0x0A, 0x98, 0x83, 0x7B, 0xE5, 0xCB, 0x4C, 0x78, 0xD1 };
156 static BOOL URL_NeedEscapeA(CHAR ch, DWORD dwFlags)
159 if (isalnum(ch))
160 return FALSE;
162 if(dwFlags & URL_ESCAPE_SPACES_ONLY) {
163 if(ch == ' ')
164 return TRUE;
165 else
166 return FALSE;
169 if ((dwFlags & URL_ESCAPE_PERCENT) && (ch == '%'))
170 return TRUE;
172 if (ch <= 31 || ch >= 127)
173 return TRUE;
175 else {
176 switch (ch) {
177 case ' ':
178 case '<':
179 case '>':
180 case '\"':
181 case '{':
182 case '}':
183 case '|':
184 case '\\':
185 case '^':
186 case ']':
187 case '[':
188 case '`':
189 case '&':
190 return TRUE;
192 case '/':
193 case '?':
194 if (dwFlags & URL_ESCAPE_SEGMENT_ONLY) return TRUE;
195 default:
196 return FALSE;
201 static BOOL URL_NeedEscapeW(WCHAR ch, DWORD dwFlags)
204 if (isalnumW(ch))
205 return FALSE;
207 if(dwFlags & URL_ESCAPE_SPACES_ONLY) {
208 if(ch == L' ')
209 return TRUE;
210 else
211 return FALSE;
214 if ((dwFlags & URL_ESCAPE_PERCENT) && (ch == L'%'))
215 return TRUE;
217 if (ch <= 31 || ch >= 127)
218 return TRUE;
220 else {
221 switch (ch) {
222 case L' ':
223 case L'<':
224 case L'>':
225 case L'\"':
226 case L'{':
227 case L'}':
228 case L'|':
229 case L'\\':
230 case L'^':
231 case L']':
232 case L'[':
233 case L'`':
234 case L'&':
235 return TRUE;
237 case L'/':
238 case L'?':
239 if (dwFlags & URL_ESCAPE_SEGMENT_ONLY) return TRUE;
240 default:
241 return FALSE;
246 static BOOL URL_JustLocation(LPCWSTR str)
248 while(*str && (*str == L'/')) str++;
249 if (*str) {
250 while (*str && ((*str == L'-') ||
251 (*str == L'.') ||
252 isalnumW(*str))) str++;
253 if (*str == L'/') return FALSE;
255 return TRUE;
259 /*************************************************************************
260 * @ [SHLWAPI.1]
262 * Identifies the Internet "scheme" in the passed string. ASCII based.
263 * Also determines start and length of item after the ':'
265 DWORD WINAPI SHLWAPI_1 (LPCSTR x, UNKNOWN_SHLWAPI_1 *y)
267 DWORD cnt;
268 const SHL_2_inet_scheme *inet_pro;
270 y->fcncde = URL_SCHEME_INVALID;
271 if (y->size != 0x18) return E_INVALIDARG;
272 /* FIXME: leading white space generates error of 0x80041001 which
273 * is undefined
275 if (*x <= ' ') return 0x80041001;
276 cnt = 0;
277 y->sizep1 = 0;
278 y->ap1 = x;
279 while (*x) {
280 if (*x == ':') {
281 y->sizep1 = cnt;
282 cnt = -1;
283 y->ap2 = x+1;
284 break;
286 x++;
287 cnt++;
290 /* check for no scheme in string start */
291 /* (apparently schemes *must* be larger than a single character) */
292 if ((*x == '\0') || (y->sizep1 <= 1)) {
293 y->ap1 = 0;
294 return 0x80041001;
297 /* found scheme, set length of remainder */
298 y->sizep2 = lstrlenA(y->ap2);
300 /* see if known scheme and return indicator number */
301 y->fcncde = URL_SCHEME_UNKNOWN;
302 inet_pro = shlwapi_schemes;
303 while (inet_pro->scheme_name) {
304 if (!strncasecmp(inet_pro->scheme_name, y->ap1,
305 min(y->sizep1, lstrlenA(inet_pro->scheme_name)))) {
306 y->fcncde = inet_pro->scheme_number;
307 break;
309 inet_pro++;
311 return S_OK;
314 /*************************************************************************
315 * @ [SHLWAPI.2]
317 * Identifies the Internet "scheme" in the passed string. UNICODE based.
318 * Also determines start and length of item after the ':'
320 DWORD WINAPI SHLWAPI_2 (LPCWSTR x, UNKNOWN_SHLWAPI_2 *y)
322 DWORD cnt;
323 const SHL_2_inet_scheme *inet_pro;
324 LPSTR cmpstr;
325 INT len;
327 y->fcncde = URL_SCHEME_INVALID;
328 if (y->size != 0x18) return E_INVALIDARG;
329 /* FIXME: leading white space generates error of 0x80041001 which
330 * is undefined
332 if (*x <= L' ') return 0x80041001;
333 cnt = 0;
334 y->sizep1 = 0;
335 y->ap1 = x;
336 while (*x) {
337 if (*x == L':') {
338 y->sizep1 = cnt;
339 cnt = -1;
340 y->ap2 = x+1;
341 break;
343 x++;
344 cnt++;
347 /* check for no scheme in string start */
348 /* (apparently schemes *must* be larger than a single character) */
349 if ((*x == L'\0') || (y->sizep1 <= 1)) {
350 y->ap1 = 0;
351 return 0x80041001;
354 /* found scheme, set length of remainder */
355 y->sizep2 = lstrlenW(y->ap2);
357 /* see if known scheme and return indicator number */
358 len = WideCharToMultiByte(0, 0, y->ap1, y->sizep1, 0, 0, 0, 0);
359 cmpstr = (LPSTR)HeapAlloc(GetProcessHeap(), 0, len+1);
360 WideCharToMultiByte(0, 0, y->ap1, y->sizep1, cmpstr, len+1, 0, 0);
361 y->fcncde = URL_SCHEME_UNKNOWN;
362 inet_pro = shlwapi_schemes;
363 while (inet_pro->scheme_name) {
364 if (!strncasecmp(inet_pro->scheme_name, cmpstr,
365 min(len, lstrlenA(inet_pro->scheme_name)))) {
366 y->fcncde = inet_pro->scheme_number;
367 break;
369 inet_pro++;
371 HeapFree(GetProcessHeap(), 0, cmpstr);
372 return S_OK;
375 /*************************************************************************
376 * UrlCanonicalizeA [SHLWAPI.@]
378 * Uses the W version to do job.
380 HRESULT WINAPI UrlCanonicalizeA(LPCSTR pszUrl, LPSTR pszCanonicalized,
381 LPDWORD pcchCanonicalized, DWORD dwFlags)
383 LPWSTR base, canonical;
384 DWORD ret, len, len2;
386 TRACE("(%s %p %p 0x%08lx) using W version\n",
387 debugstr_a(pszUrl), pszCanonicalized,
388 pcchCanonicalized, dwFlags);
390 base = (LPWSTR) HeapAlloc(GetProcessHeap(), 0,
391 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
392 canonical = base + INTERNET_MAX_URL_LENGTH;
394 MultiByteToWideChar(0, 0, pszUrl, -1, base, INTERNET_MAX_URL_LENGTH);
395 len = INTERNET_MAX_URL_LENGTH;
397 ret = UrlCanonicalizeW(base, canonical, &len, dwFlags);
398 if (ret != S_OK) {
399 HeapFree(GetProcessHeap(), 0, base);
400 return ret;
403 len2 = WideCharToMultiByte(0, 0, canonical, len, 0, 0, 0, 0);
404 if (len2 > *pcchCanonicalized) {
405 *pcchCanonicalized = len;
406 HeapFree(GetProcessHeap(), 0, base);
407 return E_POINTER;
409 WideCharToMultiByte(0, 0, canonical, len+1, pszCanonicalized,
410 *pcchCanonicalized, 0, 0);
411 *pcchCanonicalized = len2;
412 HeapFree(GetProcessHeap(), 0, base);
413 return S_OK;
416 /*************************************************************************
417 * UrlCanonicalizeW [SHLWAPI.@]
420 * MSDN is wrong (at 10/30/01 - go figure). This should support the
421 * following flags: GLA
422 * URL_DONT_ESCAPE_EXTRA_INFO 0x02000000
423 * URL_ESCAPE_SPACES_ONLY 0x04000000
424 * URL_ESCAPE_PERCENT 0x00001000
425 * URL_ESCAPE_UNSAFE 0x10000000
426 * URL_UNESCAPE 0x10000000
427 * URL_DONT_SIMPLIFY 0x08000000
428 * URL_ESCAPE_SEGMENT_ONLY 0x00002000
430 HRESULT WINAPI UrlCanonicalizeW(LPCWSTR pszUrl, LPWSTR pszCanonicalized,
431 LPDWORD pcchCanonicalized, DWORD dwFlags)
433 HRESULT hr = S_OK;
434 DWORD EscapeFlags;
435 LPWSTR lpszUrlCpy, wk1, wk2, mp, root;
436 INT nLen, nByteLen, state;
438 TRACE("(%s %p %p 0x%08lx)\n", debugstr_w(pszUrl), pszCanonicalized,
439 pcchCanonicalized, dwFlags);
441 nByteLen = (lstrlenW(pszUrl) + 1) * sizeof(WCHAR); /* length in bytes */
442 lpszUrlCpy = HeapAlloc(GetProcessHeap(), 0, nByteLen);
444 if (dwFlags & URL_DONT_SIMPLIFY)
445 memcpy(lpszUrlCpy, pszUrl, nByteLen);
446 else {
449 * state =
450 * 0 initial 1,3
451 * 1 have 2[+] alnum 2,3
452 * 2 have scheme (found :) 4,6,3
453 * 3 failed (no location)
454 * 4 have // 5,3
455 * 5 have 1[+] alnum 6,3
456 * 6 have location (found /) save root location
459 wk1 = (LPWSTR)pszUrl;
460 wk2 = lpszUrlCpy;
461 state = 0;
462 while (*wk1) {
463 switch (state) {
464 case 0:
465 if (!isalnumW(*wk1)) {state = 3; break;}
466 *wk2++ = *wk1++;
467 if (!isalnumW(*wk1)) {state = 3; break;}
468 *wk2++ = *wk1++;
469 state = 1;
470 break;
471 case 1:
472 *wk2++ = *wk1;
473 if (*wk1++ == L':') state = 2;
474 break;
475 case 2:
476 if (*wk1 != L'/') {state = 3; break;}
477 *wk2++ = *wk1++;
478 if (*wk1 != L'/') {state = 6; break;}
479 *wk2++ = *wk1++;
480 state = 4;
481 break;
482 case 3:
483 strcpyW(wk2, wk1);
484 wk1 += strlenW(wk1);
485 wk2 += strlenW(wk2);
486 break;
487 case 4:
488 if (!isalnumW(*wk1) && (*wk1 != L'-')) {state = 3; break;}
489 while(isalnumW(*wk1) || (*wk1 == L'-')) *wk2++ = *wk1++;
490 state = 5;
491 break;
492 case 5:
493 if (*wk1 != L'/') {state = 3; break;}
494 *wk2++ = *wk1++;
495 state = 6;
496 break;
497 case 6:
498 /* Now at root location, cannot back up any more. */
499 /* "root" will point at the '/' */
500 root = wk2-1;
501 while (*wk1) {
502 TRACE("wk1=%c\n", (CHAR)*wk1);
503 mp = strchrW(wk1, L'/');
504 if (!mp) {
505 strcpyW(wk2, wk1);
506 wk1 += strlenW(wk1);
507 wk2 += strlenW(wk2);
508 continue;
510 nLen = mp - wk1 + 1;
511 strncpyW(wk2, wk1, nLen);
512 wk2 += nLen;
513 wk1 += nLen;
514 if (*wk1 == L'.') {
515 TRACE("found '/.'\n");
516 if (*(wk1+1) == L'/') {
517 /* case of /./ -> skip the ./ */
518 wk1 += 2;
520 else if (*(wk1+1) == L'.') {
521 /* found /.. look for next / */
522 TRACE("found '/..'\n");
523 if (*(wk1+2) == L'/') {
524 /* case /../ -> need to backup wk2 */
525 TRACE("found '/../'\n");
526 *(wk2-1) = L'\0'; /* set end of string */
527 mp = strrchrW(root, L'/');
528 if (mp && (mp >= root)) {
529 /* found valid backup point */
530 wk2 = mp + 1;
531 wk1 += 3;
533 else {
534 /* did not find point, restore '/' */
535 *(wk2-1) = L'/';
541 *wk2 = L'\0';
542 break;
543 default:
544 FIXME("how did we get here - state=%d\n", state);
545 return E_INVALIDARG;
548 *wk2 = L'\0';
549 TRACE("Simplified, orig <%s>, simple <%s>\n",
550 debugstr_w(pszUrl), debugstr_w(lpszUrlCpy));
553 if(dwFlags & URL_UNESCAPE)
554 UrlUnescapeW(lpszUrlCpy, NULL, NULL, URL_UNESCAPE_INPLACE);
556 if((EscapeFlags = dwFlags & (URL_ESCAPE_UNSAFE |
557 URL_ESCAPE_SPACES_ONLY |
558 URL_ESCAPE_PERCENT |
559 URL_DONT_ESCAPE_EXTRA_INFO |
560 URL_ESCAPE_SEGMENT_ONLY ))) {
561 EscapeFlags &= ~URL_ESCAPE_UNSAFE;
562 hr = UrlEscapeW(lpszUrlCpy, pszCanonicalized, pcchCanonicalized,
563 EscapeFlags);
564 } else { /* No escaping needed, just copy the string */
565 nLen = lstrlenW(lpszUrlCpy);
566 if(nLen < *pcchCanonicalized)
567 memcpy(pszCanonicalized, lpszUrlCpy, (nLen + 1)*sizeof(WCHAR));
568 else {
569 hr = E_POINTER;
570 nLen++;
572 *pcchCanonicalized = nLen;
575 HeapFree(GetProcessHeap(), 0, lpszUrlCpy);
577 if (hr == S_OK)
578 TRACE("result %s\n", debugstr_w(pszCanonicalized));
580 return hr;
583 /*************************************************************************
584 * UrlCombineA [SHLWAPI.@]
586 * Uses the W version to do job.
588 HRESULT WINAPI UrlCombineA(LPCSTR pszBase, LPCSTR pszRelative,
589 LPSTR pszCombined, LPDWORD pcchCombined,
590 DWORD dwFlags)
592 LPWSTR base, relative, combined;
593 DWORD ret, len, len2;
595 TRACE("(base %s, Relative %s, Combine size %ld, flags %08lx) using W version\n",
596 debugstr_a(pszBase),debugstr_a(pszRelative),
597 *pcchCombined,dwFlags);
599 base = (LPWSTR) HeapAlloc(GetProcessHeap(), 0,
600 (3*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
601 relative = base + INTERNET_MAX_URL_LENGTH;
602 combined = relative + INTERNET_MAX_URL_LENGTH;
604 MultiByteToWideChar(0, 0, pszBase, -1, base, INTERNET_MAX_URL_LENGTH);
605 MultiByteToWideChar(0, 0, pszRelative, -1, relative, INTERNET_MAX_URL_LENGTH);
606 len = INTERNET_MAX_URL_LENGTH;
608 ret = UrlCombineW(base, relative, combined, &len, dwFlags);
609 if (ret != S_OK) {
610 HeapFree(GetProcessHeap(), 0, base);
611 return ret;
614 len2 = WideCharToMultiByte(0, 0, combined, len, 0, 0, 0, 0);
615 if (len2 > *pcchCombined) {
616 *pcchCombined = len2;
617 HeapFree(GetProcessHeap(), 0, base);
618 return E_POINTER;
620 WideCharToMultiByte(0, 0, combined, len+1, pszCombined, *pcchCombined,
621 0, 0);
622 *pcchCombined = len2;
623 HeapFree(GetProcessHeap(), 0, base);
624 return S_OK;
627 /*************************************************************************
628 * UrlCombineW [SHLWAPI.@]
630 HRESULT WINAPI UrlCombineW(LPCWSTR pszBase, LPCWSTR pszRelative,
631 LPWSTR pszCombined, LPDWORD pcchCombined,
632 DWORD dwFlags)
634 UNKNOWN_SHLWAPI_2 base, relative;
635 DWORD myflags, sizeloc = 0;
636 DWORD len, res1, res2, process_case = 0;
637 LPWSTR work, preliminary, mbase, mrelative;
638 WCHAR myfilestr[] = {'f','i','l','e',':','/','/','/','\0'};
639 WCHAR single_slash[] = {'/','\0'};
640 HRESULT ret;
642 TRACE("(base %s, Relative %s, Combine size %ld, flags %08lx)\n",
643 debugstr_w(pszBase),debugstr_w(pszRelative),
644 *pcchCombined,dwFlags);
646 base.size = 24;
647 relative.size = 24;
649 /* Get space for duplicates of the input and the output */
650 preliminary = HeapAlloc(GetProcessHeap(), 0, (3*INTERNET_MAX_URL_LENGTH) *
651 sizeof(WCHAR));
652 mbase = preliminary + INTERNET_MAX_URL_LENGTH;
653 mrelative = mbase + INTERNET_MAX_URL_LENGTH;
654 *preliminary = L'\0';
656 /* Canonicalize the base input prior to looking for the scheme */
657 myflags = dwFlags & (URL_DONT_SIMPLIFY | URL_UNESCAPE);
658 len = INTERNET_MAX_URL_LENGTH;
659 ret = UrlCanonicalizeW(pszBase, mbase, &len, myflags);
661 /* Canonicalize the relative input prior to looking for the scheme */
662 len = INTERNET_MAX_URL_LENGTH;
663 ret = UrlCanonicalizeW(pszRelative, mrelative, &len, myflags);
665 /* See if the base has a scheme */
666 res1 = SHLWAPI_2(mbase, &base);
667 if (res1) {
668 /* if pszBase has no scheme, then return pszRelative */
669 TRACE("no scheme detected in Base\n");
670 process_case = 1;
672 else do {
674 /* get size of location field (if it exists) */
675 work = (LPWSTR)base.ap2;
676 sizeloc = 0;
677 if (*work++ == L'/') {
678 if (*work++ == L'/') {
679 /* At this point have start of location and
680 * it ends at next '/' or end of string.
682 while(*work && (*work != L'/')) work++;
683 sizeloc = work - base.ap2;
687 /* Change .sizep2 to not have the last leaf in it,
688 * Note: we need to start after the location (if it exists)
690 work = strrchrW((base.ap2+sizeloc), L'/');
691 if (work) {
692 len = work - base.ap2 + 1;
693 base.sizep2 = len;
696 * At this point:
697 * .ap2 points to location (starting with '//')
698 * .sizep2 length of location (above) and rest less the last
699 * leaf (if any)
700 * sizeloc length of location (above) up to but not including
701 * the last '/'
704 res2 = SHLWAPI_2(mrelative, &relative);
705 if (res2) {
706 /* no scheme in pszRelative */
707 TRACE("no scheme detected in Relative\n");
708 relative.ap2 = mrelative; /* case 3,4,5 depends on this */
709 relative.sizep2 = strlenW(mrelative);
710 if (*pszRelative == L':') {
711 /* case that is either left alone or uses pszBase */
712 if (dwFlags & URL_PLUGGABLE_PROTOCOL) {
713 process_case = 5;
714 break;
716 process_case = 1;
717 break;
719 if (isalnum(*mrelative) && (*(mrelative + 1) == L':')) {
720 /* case that becomes "file:///" */
721 strcpyW(preliminary, myfilestr);
722 process_case = 1;
723 break;
725 if ((*mrelative == L'/') && (*(mrelative+1) == L'/')) {
726 /* pszRelative has location and rest */
727 process_case = 3;
728 break;
730 if (*mrelative == L'/') {
731 /* case where pszRelative is root to location */
732 process_case = 4;
733 break;
735 process_case = (*base.ap2 == L'/') ? 5 : 3;
736 break;
739 /* handle cases where pszRelative has scheme */
740 if ((base.sizep1 == relative.sizep1) &&
741 (strncmpW(base.ap1, relative.ap1, base.sizep1) == 0)) {
743 /* since the schemes are the same */
744 if ((*relative.ap2 == L'/') && (*(relative.ap2+1) == L'/')) {
745 /* case where pszRelative replaces location and following */
746 process_case = 3;
747 break;
749 if (*relative.ap2 == L'/') {
750 /* case where pszRelative is root to location */
751 process_case = 4;
752 break;
754 /* case where scheme is followed by document path */
755 process_case = 5;
756 break;
758 if ((*relative.ap2 == L'/') && (*(relative.ap2+1) == L'/')) {
759 /* case where pszRelative replaces scheme, location,
760 * and following and handles PLUGGABLE
762 process_case = 2;
763 break;
765 process_case = 1;
766 break;
767 } while(FALSE); /* a litte trick to allow easy exit from nested if's */
770 ret = S_OK;
771 switch (process_case) {
773 case 1: /*
774 * Return pszRelative appended to what ever is in pszCombined,
775 * (which may the string "file:///"
777 len = strlenW(mrelative) + strlenW(preliminary);
778 if (len+1 > *pcchCombined) {
779 *pcchCombined = len;
780 ret = E_POINTER;
781 break;
783 strcatW(preliminary, mrelative);
784 break;
786 case 2: /*
787 * Same as case 1, but if URL_PLUGGABLE_PROTOCOL was specified
788 * and pszRelative starts with "//", then append a "/"
790 len = strlenW(mrelative) + 1;
791 if (len+1 > *pcchCombined) {
792 *pcchCombined = len;
793 ret = E_POINTER;
794 break;
796 strcpyW(preliminary, mrelative);
797 if (!(dwFlags & URL_PLUGGABLE_PROTOCOL) &&
798 URL_JustLocation(relative.ap2))
799 strcatW(preliminary, single_slash);
800 break;
802 case 3: /*
803 * Return the pszBase scheme with pszRelative. Basicly
804 * keeps the scheme and replaces the domain and following.
806 len = base.sizep1 + 1 + relative.sizep2 + 1;
807 if (len+1 > *pcchCombined) {
808 *pcchCombined = len;
809 ret = E_POINTER;
810 break;
812 strncpyW(preliminary, base.ap1, base.sizep1 + 1);
813 work = preliminary + base.sizep1 + 1;
814 strcpyW(work, relative.ap2);
815 if (!(dwFlags & URL_PLUGGABLE_PROTOCOL) &&
816 URL_JustLocation(relative.ap2))
817 strcatW(work, single_slash);
818 break;
820 case 4: /*
821 * Return the pszBase scheme and location but everything
822 * after the location is pszRelative. (Replace document
823 * from root on.)
825 len = base.sizep1 + 1 + sizeloc + relative.sizep2 + 1;
826 if (len+1 > *pcchCombined) {
827 *pcchCombined = len;
828 ret = E_POINTER;
829 break;
831 strncpyW(preliminary, base.ap1, base.sizep1+1+sizeloc);
832 work = preliminary + base.sizep1 + 1 + sizeloc;
833 if (dwFlags & URL_PLUGGABLE_PROTOCOL)
834 *(work++) = L'/';
835 strcpyW(work, relative.ap2);
836 break;
838 case 5: /*
839 * Return the pszBase without its document (if any) and
840 * append pszRelative after its scheme.
842 len = base.sizep1 + 1 + base.sizep2 + relative.sizep2;
843 if (len+1 > *pcchCombined) {
844 *pcchCombined = len;
845 ret = E_POINTER;
846 break;
848 strncpyW(preliminary, base.ap1, base.sizep1+1+base.sizep2);
849 work = preliminary + base.sizep1+1+base.sizep2 - 1;
850 if (*work++ != L'/')
851 *(work++) = L'/';
852 strcpyW(work, relative.ap2);
853 break;
855 default:
856 FIXME("How did we get here????? process_case=%ld\n", process_case);
857 ret = E_INVALIDARG;
860 if (ret == S_OK) {
862 * Now that the combining is done, process the escape options if
863 * necessary, otherwise just copy the string.
865 myflags = dwFlags & (URL_ESCAPE_PERCENT |
866 URL_ESCAPE_SPACES_ONLY |
867 URL_DONT_ESCAPE_EXTRA_INFO |
868 URL_ESCAPE_SEGMENT_ONLY);
869 if (myflags)
870 ret = UrlEscapeW(preliminary, pszCombined,
871 pcchCombined, myflags);
872 else {
873 len = (strlenW(preliminary) + 1) * sizeof(WCHAR);
874 memcpy(pszCombined, preliminary, len);
875 *pcchCombined = strlenW(preliminary);
877 TRACE("return-%ld len=%ld, %s\n",
878 process_case, *pcchCombined, debugstr_w(pszCombined));
880 HeapFree(GetProcessHeap(), 0, preliminary);
881 return ret;
884 /*************************************************************************
885 * UrlEscapeA [SHLWAPI.@]
887 * Converts unsafe characters into their escape sequences.
889 * The converted string is returned in pszEscaped if the buffer size
890 * (which should be supplied in pcchEscaped) is large enough, in this
891 * case the function returns S_OK and pcchEscaped contains the length
892 * of the escaped string. If the buffer is not large enough the
893 * function returns E_POINTER and pcchEscaped contains the required
894 * buffer size (including room for the '\0').
896 * By default the function stops converting at the first '?' or
897 * '#'. [MSDN says differently]. If URL_ESCAPE_SPACES_ONLY flag is set
898 * then only spaces are converted, but the conversion continues past a
899 * '?' or '#'.
901 * BUGS:
902 * Have now implemented the following flags:
903 * URL_ESCAPE_SPACES_ONLY
904 * URL_DONT_ESCAPE_EXTRA_INFO
905 * URL_ESCAPE_SEGMENT_ONLY
906 * URL_ESCAPE_PERCENT
907 * Initial testing seems to indicate that this is now working like
908 * native shlwapi version 5. Note that these functions did not work
909 * well (or at all) in shlwapi version 4.
912 HRESULT WINAPI UrlEscapeA(
913 LPCSTR pszUrl,
914 LPSTR pszEscaped,
915 LPDWORD pcchEscaped,
916 DWORD dwFlags)
918 LPCSTR src;
919 DWORD needed = 0, ret;
920 BOOL stop_escaping = FALSE;
921 char next[3], *dst = pszEscaped;
922 INT len;
924 TRACE("(%s %p %p 0x%08lx)\n", debugstr_a(pszUrl), pszEscaped,
925 pcchEscaped, dwFlags);
927 if(dwFlags & ~(URL_ESCAPE_SPACES_ONLY |
928 URL_ESCAPE_SEGMENT_ONLY |
929 URL_DONT_ESCAPE_EXTRA_INFO |
930 URL_ESCAPE_PERCENT))
931 FIXME("Unimplemented flags: %08lx\n", dwFlags);
933 /* fix up flags */
934 if (dwFlags & URL_ESCAPE_SPACES_ONLY)
935 /* if SPACES_ONLY specified, reset the other controls */
936 dwFlags &= ~(URL_DONT_ESCAPE_EXTRA_INFO |
937 URL_ESCAPE_PERCENT |
938 URL_ESCAPE_SEGMENT_ONLY);
940 else
941 /* if SPACES_ONLY *not* specified then assume DONT_ESCAPE_EXTRA_INFO */
942 dwFlags |= URL_DONT_ESCAPE_EXTRA_INFO;
944 for(src = pszUrl; *src; src++) {
945 if(!(dwFlags & URL_ESCAPE_SEGMENT_ONLY) &&
946 (dwFlags & URL_DONT_ESCAPE_EXTRA_INFO) &&
947 (*src == '#' || *src == '?'))
948 stop_escaping = TRUE;
950 if(URL_NeedEscapeA(*src, dwFlags) && stop_escaping == FALSE) {
951 /* TRACE("escaping %c\n", *src); */
952 next[0] = '%';
953 next[1] = hexDigits[(*src >> 4) & 0xf];
954 next[2] = hexDigits[*src & 0xf];
955 len = 3;
956 } else {
957 /* TRACE("passing %c\n", *src); */
958 next[0] = *src;
959 len = 1;
962 if(needed + len <= *pcchEscaped) {
963 memcpy(dst, next, len);
964 dst += len;
966 needed += len;
969 if(needed < *pcchEscaped) {
970 *dst = '\0';
971 ret = S_OK;
972 } else {
973 needed++; /* add one for the '\0' */
974 ret = E_POINTER;
976 *pcchEscaped = needed;
977 return ret;
980 /*************************************************************************
981 * UrlEscapeW [SHLWAPI.@]
983 * See UrlEscapeA for list of assumptions, bugs, and FIXMEs
985 HRESULT WINAPI UrlEscapeW(
986 LPCWSTR pszUrl,
987 LPWSTR pszEscaped,
988 LPDWORD pcchEscaped,
989 DWORD dwFlags)
991 LPCWSTR src;
992 DWORD needed = 0, ret;
993 BOOL stop_escaping = FALSE;
994 WCHAR next[5], *dst = pszEscaped;
995 INT len;
997 TRACE("(%s %p %p 0x%08lx)\n", debugstr_w(pszUrl), pszEscaped,
998 pcchEscaped, dwFlags);
1000 if(dwFlags & ~(URL_ESCAPE_SPACES_ONLY |
1001 URL_ESCAPE_SEGMENT_ONLY |
1002 URL_DONT_ESCAPE_EXTRA_INFO |
1003 URL_ESCAPE_PERCENT))
1004 FIXME("Unimplemented flags: %08lx\n", dwFlags);
1006 /* fix up flags */
1007 if (dwFlags & URL_ESCAPE_SPACES_ONLY)
1008 /* if SPACES_ONLY specified, reset the other controls */
1009 dwFlags &= ~(URL_DONT_ESCAPE_EXTRA_INFO |
1010 URL_ESCAPE_PERCENT |
1011 URL_ESCAPE_SEGMENT_ONLY);
1013 else
1014 /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
1015 dwFlags |= URL_DONT_ESCAPE_EXTRA_INFO;
1017 for(src = pszUrl; *src; src++) {
1019 * if(!(dwFlags & URL_ESCAPE_SPACES_ONLY) &&
1020 * (*src == L'#' || *src == L'?'))
1021 * stop_escaping = TRUE;
1023 if(!(dwFlags & URL_ESCAPE_SEGMENT_ONLY) &&
1024 (dwFlags & URL_DONT_ESCAPE_EXTRA_INFO) &&
1025 (*src == L'#' || *src == L'?'))
1026 stop_escaping = TRUE;
1028 if(URL_NeedEscapeW(*src, dwFlags) && stop_escaping == FALSE) {
1029 /* TRACE("escaping %c\n", *src); */
1030 next[0] = L'%';
1032 * I would have assumed that the W form would escape
1033 * the character with 4 hex digits (or even 8),
1034 * however, experiments show that native shlwapi escapes
1035 * with only 2 hex digits.
1036 * next[1] = hexDigits[(*src >> 12) & 0xf];
1037 * next[2] = hexDigits[(*src >> 8) & 0xf];
1038 * next[3] = hexDigits[(*src >> 4) & 0xf];
1039 * next[4] = hexDigits[*src & 0xf];
1040 * len = 5;
1042 next[1] = hexDigits[(*src >> 4) & 0xf];
1043 next[2] = hexDigits[*src & 0xf];
1044 len = 3;
1045 } else {
1046 /* TRACE("passing %c\n", *src); */
1047 next[0] = *src;
1048 len = 1;
1051 if(needed + len <= *pcchEscaped) {
1052 memcpy(dst, next, len*sizeof(WCHAR));
1053 dst += len;
1055 needed += len;
1058 if(needed < *pcchEscaped) {
1059 *dst = L'\0';
1060 ret = S_OK;
1061 } else {
1062 needed++; /* add one for the '\0' */
1063 ret = E_POINTER;
1065 *pcchEscaped = needed;
1066 return ret;
1070 /*************************************************************************
1071 * UrlUnescapeA [SHLWAPI.@]
1073 * Converts escape sequences back to ordinary characters.
1075 * If URL_ESCAPE_INPLACE is set in dwFlags then pszUnescaped and
1076 * pcchUnescaped are ignored and the converted string is returned in
1077 * pszUrl, otherwise the string is returned in pszUnescaped.
1078 * pcchUnescaped should contain the size of pszUnescaped on calling
1079 * and will contain the length the the returned string on return if
1080 * the buffer is big enough else it will contain the buffer size
1081 * required (including room for the '\0'). The function returns S_OK
1082 * on success or E_POINTER if the buffer is not large enough. If the
1083 * URL_DONT_ESCAPE_EXTRA_INFO flag is set then the conversion stops at
1084 * the first occurrence of either '?' or '#'.
1087 HRESULT WINAPI UrlUnescapeA(
1088 LPCSTR pszUrl,
1089 LPSTR pszUnescaped,
1090 LPDWORD pcchUnescaped,
1091 DWORD dwFlags)
1093 char *dst, next;
1094 LPCSTR src;
1095 HRESULT ret;
1096 DWORD needed;
1097 BOOL stop_unescaping = FALSE;
1099 TRACE("(%s, %p, %p, 0x%08lx)\n", debugstr_a(pszUrl), pszUnescaped,
1100 pcchUnescaped, dwFlags);
1102 if(dwFlags & URL_UNESCAPE_INPLACE)
1103 dst = (char*)pszUrl;
1104 else
1105 dst = pszUnescaped;
1107 for(src = pszUrl, needed = 0; *src; src++, needed++) {
1108 if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1109 (*src == '#' || *src == '?')) {
1110 stop_unescaping = TRUE;
1111 next = *src;
1112 } else if(*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2))
1113 && stop_unescaping == FALSE) {
1114 INT ih;
1115 char buf[3];
1116 memcpy(buf, src + 1, 2);
1117 buf[2] = '\0';
1118 ih = strtol(buf, NULL, 16);
1119 next = (CHAR) ih;
1120 src += 2; /* Advance to end of escape */
1121 } else
1122 next = *src;
1124 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1125 *dst++ = next;
1128 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1129 *dst = '\0';
1130 ret = S_OK;
1131 } else {
1132 needed++; /* add one for the '\0' */
1133 ret = E_POINTER;
1135 if(!(dwFlags & URL_UNESCAPE_INPLACE))
1136 *pcchUnescaped = needed;
1138 if (ret == S_OK) {
1139 TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1140 debugstr_a(pszUrl) : debugstr_a(pszUnescaped));
1143 return ret;
1146 /*************************************************************************
1147 * UrlUnescapeW [SHLWAPI.@]
1149 * See UrlUnescapeA for list of assumptions, bugs, and FIXMEs
1151 HRESULT WINAPI UrlUnescapeW(
1152 LPCWSTR pszUrl,
1153 LPWSTR pszUnescaped,
1154 LPDWORD pcchUnescaped,
1155 DWORD dwFlags)
1157 WCHAR *dst, next;
1158 LPCWSTR src;
1159 HRESULT ret;
1160 DWORD needed;
1161 BOOL stop_unescaping = FALSE;
1163 TRACE("(%s, %p, %p, 0x%08lx)\n", debugstr_w(pszUrl), pszUnescaped,
1164 pcchUnescaped, dwFlags);
1166 if(dwFlags & URL_UNESCAPE_INPLACE)
1167 dst = (WCHAR*)pszUrl;
1168 else
1169 dst = pszUnescaped;
1171 for(src = pszUrl, needed = 0; *src; src++, needed++) {
1172 if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1173 (*src == L'#' || *src == L'?')) {
1174 stop_unescaping = TRUE;
1175 next = *src;
1176 } else if(*src == L'%' && isxdigitW(*(src + 1)) && isxdigitW(*(src + 2))
1177 && stop_unescaping == FALSE) {
1178 INT ih;
1179 WCHAR buf[3];
1180 memcpy(buf, src + 1, 2*sizeof(WCHAR));
1181 buf[2] = L'\0';
1182 ih = StrToIntW(buf);
1183 next = (WCHAR) ih;
1184 src += 2; /* Advance to end of escape */
1185 } else
1186 next = *src;
1188 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1189 *dst++ = next;
1192 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1193 *dst = L'\0';
1194 ret = S_OK;
1195 } else {
1196 needed++; /* add one for the '\0' */
1197 ret = E_POINTER;
1199 if(!(dwFlags & URL_UNESCAPE_INPLACE))
1200 *pcchUnescaped = needed;
1202 if (ret == S_OK) {
1203 TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1204 debugstr_w(pszUrl) : debugstr_w(pszUnescaped));
1207 return ret;
1210 /*************************************************************************
1211 * UrlGetLocationA [SHLWAPI.@]
1213 * Bugs/Features:
1214 * MSDN (as of 2001-11-01) says that:
1215 * "The location is the segment of the URL starting with a ?
1216 * or # character."
1217 * Neither V4 nor V5 of shlwapi.dll implement the '?' and always return
1218 * a NULL.
1219 * MSDN further states that:
1220 * "If a file URL has a query string, the returned string is
1221 * the query string."
1222 * In all test cases if the scheme starts with "fi" then a NULL is
1223 * returned. V5 gives the following results:
1224 * NULL file://aa/b/cd#hohoh
1225 * #hohoh http://aa/b/cd#hohoh
1226 * NULL fi://aa/b/cd#hohoh
1227 * #hohoh ff://aa/b/cd#hohoh
1229 LPCSTR WINAPI UrlGetLocationA(
1230 LPCSTR pszUrl)
1232 UNKNOWN_SHLWAPI_1 base;
1233 DWORD res1;
1235 base.size = 24;
1236 res1 = SHLWAPI_1(pszUrl, &base);
1237 if (res1) return NULL; /* invalid scheme */
1239 /* if scheme is file: then never return pointer */
1240 if (strncmp(base.ap1, "file", min(4,base.sizep1)) == 0) return NULL;
1242 /* Look for '#' and return its addr */
1243 return strchr(base.ap2, '#');
1246 /*************************************************************************
1247 * UrlGetLocationW [SHLWAPI.@]
1249 * See UrlGetLocationA for list of assumptions, bugs, and FIXMEs
1251 LPCWSTR WINAPI UrlGetLocationW(
1252 LPCWSTR pszUrl)
1254 UNKNOWN_SHLWAPI_2 base;
1255 DWORD res1;
1257 base.size = 24;
1258 res1 = SHLWAPI_2(pszUrl, &base);
1259 if (res1) return NULL; /* invalid scheme */
1261 /* if scheme is file: then never return pointer */
1262 if (strncmpW(base.ap1, fileW, min(4,base.sizep1)) == 0) return NULL;
1264 /* Look for '#' and return its addr */
1265 return strchrW(base.ap2, L'#');
1268 /*************************************************************************
1269 * UrlCompareA [SHLWAPI.@]
1271 INT WINAPI UrlCompareA(
1272 LPCSTR pszUrl1,
1273 LPCSTR pszUrl2,
1274 BOOL fIgnoreSlash)
1276 INT ret, len, len1, len2;
1278 if (!fIgnoreSlash)
1279 return strcmp(pszUrl1, pszUrl2);
1280 len1 = strlen(pszUrl1);
1281 if (pszUrl1[len1-1] == L'/') len1--;
1282 len2 = strlen(pszUrl2);
1283 if (pszUrl2[len2-1] == L'/') len2--;
1284 if (len1 == len2)
1285 return strncmp(pszUrl1, pszUrl2, len1);
1286 len = min(len1, len2);
1287 ret = strncmp(pszUrl1, pszUrl2, len);
1288 if (ret) return ret;
1289 if (len1 > len2) return 1;
1290 return -1;
1293 /*************************************************************************
1294 * UrlCompareW [SHLWAPI.@]
1296 INT WINAPI UrlCompareW(
1297 LPCWSTR pszUrl1,
1298 LPCWSTR pszUrl2,
1299 BOOL fIgnoreSlash)
1301 INT ret, len, len1, len2;
1303 if (!fIgnoreSlash)
1304 return strcmpW(pszUrl1, pszUrl2);
1305 len1 = strlenW(pszUrl1);
1306 if (pszUrl1[len1-1] == L'/') len1--;
1307 len2 = strlenW(pszUrl2);
1308 if (pszUrl2[len2-1] == L'/') len2--;
1309 if (len1 == len2)
1310 return strncmpW(pszUrl1, pszUrl2, len1);
1311 len = min(len1, len2);
1312 ret = strncmpW(pszUrl1, pszUrl2, len);
1313 if (ret) return ret;
1314 if (len1 > len2) return 1;
1315 return -1;
1318 /*************************************************************************
1319 * HashData [SHLWAPI.@]
1321 * Hash an input block into a variable sized digest.
1323 BOOL WINAPI HashData(const unsigned char *lpSrc, INT nSrcLen,
1324 unsigned char *lpDest, INT nDestLen)
1326 INT srcCount = nSrcLen - 1, destCount = nDestLen - 1;
1328 if (IsBadReadPtr(lpSrc, nSrcLen) ||
1329 IsBadWritePtr(lpDest, nDestLen))
1330 return FALSE;
1332 while (destCount >= 0)
1334 lpDest[destCount] = (destCount & 0xff);
1335 destCount--;
1338 while (srcCount >= 0)
1340 destCount = nDestLen - 1;
1341 while (destCount >= 0)
1343 lpDest[destCount] = HashDataLookup[lpSrc[srcCount] ^ lpDest[destCount]];
1344 destCount--;
1346 srcCount--;
1348 return TRUE;
1351 /*************************************************************************
1352 * UrlHashA [SHLWAPI.@]
1354 * Hash an ASCII URL.
1356 HRESULT WINAPI UrlHashA(LPCSTR pszUrl, unsigned char *lpDest, INT nDestLen)
1358 if (IsBadStringPtrA(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1359 return E_INVALIDARG;
1361 HashData(pszUrl, strlen(pszUrl), lpDest, nDestLen);
1362 return S_OK;
1365 /*************************************************************************
1366 * UrlHashW [SHLWAPI.@]
1368 * See UrlHashA.
1370 HRESULT WINAPI UrlHashW(LPCWSTR pszUrl, unsigned char *lpDest, INT nDestLen)
1372 char szUrl[MAX_PATH];
1374 TRACE("(%s,%p,%d)\n",debugstr_w(pszUrl), lpDest, nDestLen);
1376 if (IsBadStringPtrW(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1377 return E_INVALIDARG;
1379 /* Win32 hashes the data as an ASCII string, presumably so that both A+W
1380 * return the same digests for the same URL.
1382 WideCharToMultiByte(0, 0, pszUrl, -1, szUrl, MAX_PATH, 0, 0);
1383 HashData(szUrl, strlen(szUrl), lpDest, nDestLen);
1384 return S_OK;
1387 /*************************************************************************
1388 * UrlApplySchemeA [SHLWAPI.@]
1390 HRESULT WINAPI UrlApplySchemeA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1392 LPWSTR in, out;
1393 DWORD ret, len, len2;
1395 TRACE("(in %s, out size %ld, flags %08lx) using W version\n",
1396 debugstr_a(pszIn), *pcchOut, dwFlags);
1398 in = (LPWSTR) HeapAlloc(GetProcessHeap(), 0,
1399 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
1400 out = in + INTERNET_MAX_URL_LENGTH;
1402 MultiByteToWideChar(0, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
1403 len = INTERNET_MAX_URL_LENGTH;
1405 ret = UrlApplySchemeW(in, out, &len, dwFlags);
1406 if ((ret != S_OK) && (ret != S_FALSE)) {
1407 HeapFree(GetProcessHeap(), 0, in);
1408 return ret;
1411 len2 = WideCharToMultiByte(0, 0, out, len+1, 0, 0, 0, 0);
1412 if (len2 > *pcchOut) {
1413 *pcchOut = len2;
1414 HeapFree(GetProcessHeap(), 0, in);
1415 return E_POINTER;
1417 WideCharToMultiByte(0, 0, out, len+1, pszOut, *pcchOut, 0, 0);
1418 *pcchOut = len2;
1419 HeapFree(GetProcessHeap(), 0, in);
1420 return ret;
1423 HRESULT URL_GuessScheme(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1425 HKEY newkey;
1426 BOOL j;
1427 INT index, i;
1428 DWORD value_len, data_len, dwType;
1429 WCHAR reg_path[MAX_PATH];
1430 WCHAR value[MAX_PATH], data[MAX_PATH];
1431 WCHAR Wxx, Wyy;
1433 MultiByteToWideChar(0, 0,
1434 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes",
1435 -1, reg_path, MAX_PATH);
1436 RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
1437 index = 0;
1438 while(value_len = data_len = MAX_PATH,
1439 RegEnumValueW(newkey, index, value, &value_len,
1440 0, &dwType, (LPVOID)data, &data_len) == 0) {
1441 TRACE("guess %d %s is %s\n",
1442 index, debugstr_w(value), debugstr_w(data));
1444 j = FALSE;
1445 for(i=0; i<value_len; i++) {
1446 Wxx = pszIn[i];
1447 Wyy = value[i];
1448 /* remember that TRUE is not-equal */
1449 j = ChrCmpIW(Wxx, Wyy);
1450 if (j) break;
1452 if ((i == value_len) && !j) {
1453 if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1454 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1455 RegCloseKey(newkey);
1456 return E_POINTER;
1458 strcpyW(pszOut, data);
1459 strcatW(pszOut, pszIn);
1460 *pcchOut = strlenW(pszOut);
1461 TRACE("matched and set to %s\n", debugstr_w(pszOut));
1462 RegCloseKey(newkey);
1463 return S_OK;
1465 index++;
1467 RegCloseKey(newkey);
1468 return -1;
1471 HRESULT URL_ApplyDefault(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1473 HKEY newkey;
1474 DWORD data_len, dwType;
1475 WCHAR reg_path[MAX_PATH];
1476 WCHAR value[MAX_PATH], data[MAX_PATH];
1478 /* get and prepend default */
1479 MultiByteToWideChar(0, 0,
1480 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\DefaultPrefix",
1481 -1, reg_path, MAX_PATH);
1482 RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
1483 data_len = MAX_PATH;
1484 value[0] = L'@';
1485 value[1] = L'\0';
1486 RegQueryValueExW(newkey, value, 0, &dwType, (LPBYTE)data, &data_len);
1487 RegCloseKey(newkey);
1488 if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1489 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1490 return E_POINTER;
1492 strcpyW(pszOut, data);
1493 strcatW(pszOut, pszIn);
1494 *pcchOut = strlenW(pszOut);
1495 TRACE("used default %s\n", debugstr_w(pszOut));
1496 return S_OK;
1499 /*************************************************************************
1500 * UrlApplySchemeW [SHLWAPI.@]
1502 HRESULT WINAPI UrlApplySchemeW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1504 UNKNOWN_SHLWAPI_2 in_scheme;
1505 DWORD res1;
1506 HRESULT ret;
1508 TRACE("(in %s, out size %ld, flags %08lx)\n",
1509 debugstr_w(pszIn), *pcchOut, dwFlags);
1511 if (dwFlags & URL_APPLY_GUESSFILE) {
1512 FIXME("(%s %p %p(%ld) 0x%08lx): stub URL_APPLY_GUESSFILE not implemented\n",
1513 debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwFlags);
1514 strcpyW(pszOut, pszIn);
1515 *pcchOut = strlenW(pszOut);
1516 return S_FALSE;
1519 in_scheme.size = 24;
1520 /* See if the base has a scheme */
1521 res1 = SHLWAPI_2(pszIn, &in_scheme);
1522 if (res1) {
1523 /* no scheme in input, need to see if we need to guess */
1524 if (dwFlags & URL_APPLY_GUESSSCHEME) {
1525 if ((ret = URL_GuessScheme(pszIn, pszOut, pcchOut)) != -1)
1526 return ret;
1529 else {
1530 /* we have a scheme, see if valid (known scheme) */
1531 if (in_scheme.fcncde) {
1532 /* have valid scheme, so just copy and exit */
1533 if (strlenW(pszIn) + 1 > *pcchOut) {
1534 *pcchOut = strlenW(pszIn) + 1;
1535 return E_POINTER;
1537 strcpyW(pszOut, pszIn);
1538 *pcchOut = strlenW(pszOut);
1539 TRACE("valid scheme, returing copy\n");
1540 return S_OK;
1544 /* If we are here, then either invalid scheme,
1545 * or no scheme and can't/failed guess.
1547 if ( ( ((res1 == 0) && (dwFlags & URL_APPLY_FORCEAPPLY)) ||
1548 ((res1 != 0)) ) &&
1549 (dwFlags & URL_APPLY_DEFAULT)) {
1550 /* find and apply default scheme */
1551 return URL_ApplyDefault(pszIn, pszOut, pcchOut);
1554 /* just copy and give proper return code */
1555 if (strlenW(pszIn) + 1 > *pcchOut) {
1556 *pcchOut = strlenW(pszIn) + 1;
1557 return E_POINTER;
1559 strcpyW(pszOut, pszIn);
1560 *pcchOut = strlenW(pszOut);
1561 TRACE("returning copy, left alone\n");
1562 return S_FALSE;
1565 /*************************************************************************
1566 * UrlIsA [SHLWAPI.@]
1568 BOOL WINAPI UrlIsA(LPCSTR pszUrl, URLIS Urlis)
1570 UNKNOWN_SHLWAPI_1 base;
1571 DWORD res1;
1573 switch (Urlis) {
1575 case URLIS_OPAQUE:
1576 base.size = 24;
1577 res1 = SHLWAPI_1(pszUrl, &base);
1578 if (res1) return FALSE; /* invalid scheme */
1579 if ((*base.ap2 == '/') && (*(base.ap2+1) == '/'))
1580 /* has scheme followed by 2 '/' */
1581 return FALSE;
1582 return TRUE;
1584 case URLIS_URL:
1585 case URLIS_NOHISTORY:
1586 case URLIS_FILEURL:
1587 case URLIS_APPLIABLE:
1588 case URLIS_DIRECTORY:
1589 case URLIS_HASQUERY:
1590 default:
1591 FIXME("(%s %d): stub\n", debugstr_a(pszUrl), Urlis);
1593 return FALSE;
1596 /*************************************************************************
1597 * UrlIsW [SHLWAPI.@]
1599 BOOL WINAPI UrlIsW(LPCWSTR pszUrl, URLIS Urlis)
1601 UNKNOWN_SHLWAPI_2 base;
1602 DWORD res1;
1604 switch (Urlis) {
1606 case URLIS_OPAQUE:
1607 base.size = 24;
1608 res1 = SHLWAPI_2(pszUrl, &base);
1609 if (res1) return FALSE; /* invalid scheme */
1610 if ((*base.ap2 == L'/') && (*(base.ap2+1) == L'/'))
1611 /* has scheme followed by 2 '/' */
1612 return FALSE;
1613 return TRUE;
1615 case URLIS_URL:
1616 case URLIS_NOHISTORY:
1617 case URLIS_FILEURL:
1618 case URLIS_APPLIABLE:
1619 case URLIS_DIRECTORY:
1620 case URLIS_HASQUERY:
1621 default:
1622 FIXME("(%s %d): stub\n", debugstr_w(pszUrl), Urlis);
1624 return FALSE;
1627 /*************************************************************************
1628 * UrlIsNoHistoryA [SHLWAPI.@]
1630 BOOL WINAPI UrlIsNoHistoryA(LPCSTR pszUrl)
1632 return UrlIsA(pszUrl, URLIS_NOHISTORY);
1635 /*************************************************************************
1636 * UrlIsNoHistoryW [SHLWAPI.@]
1638 BOOL WINAPI UrlIsNoHistoryW(LPCWSTR pszUrl)
1640 return UrlIsW(pszUrl, URLIS_NOHISTORY);
1643 /*************************************************************************
1644 * UrlIsOpaqueA [SHLWAPI.@]
1646 BOOL WINAPI UrlIsOpaqueA(LPCSTR pszUrl)
1648 return UrlIsA(pszUrl, URLIS_OPAQUE);
1651 /*************************************************************************
1652 * UrlIsOpaqueW [SHLWAPI.@]
1654 BOOL WINAPI UrlIsOpaqueW(LPCWSTR pszUrl)
1656 return UrlIsW(pszUrl, URLIS_OPAQUE);
1659 /*************************************************************************
1660 * Scans for characters of type "type" and when not matching found,
1661 * returns pointer to it and length in size.
1663 * Characters tested based on RFC 1738
1665 LPCWSTR URL_ScanID(LPCWSTR start, LPDWORD size, WINE_URL_SCAN_TYPE type)
1667 static DWORD alwayszero = 0;
1668 BOOL cont = TRUE;
1670 *size = 0;
1672 switch(type){
1674 case SCHEME:
1675 while (cont) {
1676 if ( (islowerW(*start) && isalphaW(*start)) ||
1677 isdigitW(*start) ||
1678 (*start == L'+') ||
1679 (*start == L'-') ||
1680 (*start == L'.')) {
1681 start++;
1682 (*size)++;
1684 else
1685 cont = FALSE;
1687 break;
1689 case USERPASS:
1690 while (cont) {
1691 if ( isalphaW(*start) ||
1692 isdigitW(*start) ||
1693 /* user/password only characters */
1694 (*start == L';') ||
1695 (*start == L'?') ||
1696 (*start == L'&') ||
1697 (*start == L'=') ||
1698 /* *extra* characters */
1699 (*start == L'!') ||
1700 (*start == L'*') ||
1701 (*start == L'\'') ||
1702 (*start == L'(') ||
1703 (*start == L')') ||
1704 (*start == L',') ||
1705 /* *safe* characters */
1706 (*start == L'$') ||
1707 (*start == L'_') ||
1708 (*start == L'+') ||
1709 (*start == L'-') ||
1710 (*start == L'.')) {
1711 start++;
1712 (*size)++;
1713 } else if (*start == L'%') {
1714 if (isxdigitW(*(start+1)) &&
1715 isxdigitW(*(start+2))) {
1716 start += 3;
1717 *size += 3;
1718 } else
1719 cont = FALSE;
1720 } else
1721 cont = FALSE;
1723 break;
1725 case PORT:
1726 while (cont) {
1727 if (isdigitW(*start)) {
1728 start++;
1729 (*size)++;
1731 else
1732 cont = FALSE;
1734 break;
1736 case HOST:
1737 while (cont) {
1738 if (isalnumW(*start) ||
1739 (*start == L'-') ||
1740 (*start == L'.') ) {
1741 start++;
1742 (*size)++;
1744 else
1745 cont = FALSE;
1747 break;
1748 default:
1749 FIXME("unknown type %d\n", type);
1750 return (LPWSTR)&alwayszero;
1752 /* TRACE("scanned %ld characters next char %p<%c>\n",
1753 *size, start, *start); */
1754 return start;
1757 /*************************************************************************
1758 * Attempt to parse URL into pieces.
1760 LONG URL_ParseUrl(LPCWSTR pszUrl, WINE_PARSE_URL *pl)
1762 LPCWSTR work;
1764 memset(pl, 0, sizeof(WINE_PARSE_URL));
1765 pl->pScheme = pszUrl;
1766 work = URL_ScanID(pl->pScheme, &pl->szScheme, SCHEME);
1767 if (!*work || (*work != L':')) goto ERROR;
1768 work++;
1769 if ((*work != L'/') || (*(work+1) != L'/')) goto ERROR;
1770 pl->pUserName = work + 2;
1771 work = URL_ScanID(pl->pUserName, &pl->szUserName, USERPASS);
1772 if (*work == L':' ) {
1773 /* parse password */
1774 work++;
1775 pl->pPassword = work;
1776 work = URL_ScanID(pl->pPassword, &pl->szPassword, USERPASS);
1777 if (*work != L'@') {
1778 /* what we just parsed must be the hostname and port
1779 * so reset pointers and clear then let it parse */
1780 pl->szUserName = pl->szPassword = 0;
1781 work = pl->pUserName - 1;
1782 pl->pUserName = pl->pPassword = 0;
1784 } else if (*work == L'@') {
1785 /* no password */
1786 pl->szPassword = 0;
1787 pl->pPassword = 0;
1788 } else if (!*work || (*work == L'/') || (*work == L'.')) {
1789 /* what was parsed was hostname, so reset pointers and let it parse */
1790 pl->szUserName = pl->szPassword = 0;
1791 work = pl->pUserName - 1;
1792 pl->pUserName = pl->pPassword = 0;
1793 } else goto ERROR;
1795 /* now start parsing hostname or hostnumber */
1796 work++;
1797 pl->pHostName = work;
1798 work = URL_ScanID(pl->pHostName, &pl->szHostName, HOST);
1799 if (*work == L':') {
1800 /* parse port */
1801 work++;
1802 pl->pPort = work;
1803 work = URL_ScanID(pl->pPort, &pl->szPort, PORT);
1805 if (*work == L'/') {
1806 /* see if query string */
1807 pl->pQuery = strchrW(work, L'?');
1808 if (pl->pQuery) pl->szQuery = strlenW(pl->pQuery);
1810 TRACE("parse successful: scheme=%p(%ld), user=%p(%ld), pass=%p(%ld), host=%p(%ld), port=%p(%ld), query=%p(%ld)\n",
1811 pl->pScheme, pl->szScheme,
1812 pl->pUserName, pl->szUserName,
1813 pl->pPassword, pl->szPassword,
1814 pl->pHostName, pl->szHostName,
1815 pl->pPort, pl->szPort,
1816 pl->pQuery, pl->szQuery);
1817 return S_OK;
1818 ERROR:
1819 FIXME("failed to parse %s\n", debugstr_w(pszUrl));
1820 return E_INVALIDARG;
1823 /*************************************************************************
1824 * UrlGetPartA [SHLWAPI.@]
1826 HRESULT WINAPI UrlGetPartA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut,
1827 DWORD dwPart, DWORD dwFlags)
1829 LPWSTR in, out;
1830 DWORD ret, len, len2;
1832 in = (LPWSTR) HeapAlloc(GetProcessHeap(), 0,
1833 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
1834 out = in + INTERNET_MAX_URL_LENGTH;
1836 MultiByteToWideChar(0, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
1838 len = INTERNET_MAX_URL_LENGTH;
1839 ret = UrlGetPartW(in, out, &len, dwPart, dwFlags);
1841 if (ret != S_OK) {
1842 HeapFree(GetProcessHeap(), 0, in);
1843 return ret;
1846 len2 = WideCharToMultiByte(0, 0, out, len, 0, 0, 0, 0);
1847 if (len2 > *pcchOut) {
1848 *pcchOut = len2;
1849 HeapFree(GetProcessHeap(), 0, in);
1850 return E_POINTER;
1852 WideCharToMultiByte(0, 0, out, len+1, pszOut, *pcchOut, 0, 0);
1853 *pcchOut = len2;
1854 HeapFree(GetProcessHeap(), 0, in);
1855 return S_OK;
1858 /*************************************************************************
1859 * UrlGetPartW [SHLWAPI.@]
1861 HRESULT WINAPI UrlGetPartW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut,
1862 DWORD dwPart, DWORD dwFlags)
1864 WINE_PARSE_URL pl;
1865 HRESULT ret;
1866 DWORD size, schsize;
1867 LPCWSTR addr, schaddr;
1868 LPWSTR work;
1870 TRACE("(%s %p %p(%ld) %08lx %08lx)\n",
1871 debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwPart, dwFlags);
1873 ret = URL_ParseUrl(pszIn, &pl);
1874 if (!ret) {
1875 schaddr = pl.pScheme;
1876 schsize = pl.szScheme;
1878 switch (dwPart) {
1879 case URL_PART_SCHEME:
1880 if (!pl.szScheme) return E_INVALIDARG;
1881 addr = pl.pScheme;
1882 size = pl.szScheme;
1883 break;
1885 case URL_PART_HOSTNAME:
1886 if (!pl.szHostName) return E_INVALIDARG;
1887 addr = pl.pHostName;
1888 size = pl.szHostName;
1889 break;
1891 case URL_PART_USERNAME:
1892 if (!pl.szUserName) return E_INVALIDARG;
1893 addr = pl.pUserName;
1894 size = pl.szUserName;
1895 break;
1897 case URL_PART_PASSWORD:
1898 if (!pl.szPassword) return E_INVALIDARG;
1899 addr = pl.pPassword;
1900 size = pl.szPassword;
1901 break;
1903 case URL_PART_PORT:
1904 if (!pl.szPort) return E_INVALIDARG;
1905 addr = pl.pPort;
1906 size = pl.szPort;
1907 break;
1909 case URL_PART_QUERY:
1910 if (!pl.szQuery) return E_INVALIDARG;
1911 addr = pl.pQuery;
1912 size = pl.szQuery;
1913 break;
1915 default:
1916 return E_INVALIDARG;
1919 if (dwFlags == URL_PARTFLAG_KEEPSCHEME) {
1920 if (*pcchOut < size + schsize + 2) {
1921 *pcchOut = size + schsize + 2;
1922 return E_POINTER;
1924 strncpyW(pszOut, schaddr, schsize);
1925 work = pszOut + schsize;
1926 *work = L':';
1927 strncpyW(work+1, addr, size);
1928 *pcchOut = size + schsize + 1;
1929 work += (size + 1);
1930 *work = L'\0';
1932 else {
1933 if (*pcchOut < size + 1) {*pcchOut = size+1; return E_POINTER;}
1934 strncpyW(pszOut, addr, size);
1935 *pcchOut = size;
1936 work = pszOut + size;
1937 *work = L'\0';
1939 TRACE("len=%ld %s\n", *pcchOut, debugstr_w(pszOut));
1941 return ret;
1944 /*************************************************************************
1945 * PathIsURLA [SHLWAPI.@]
1947 * Check if the given path is a URL.
1949 * PARAMS
1950 * lpszPath [I] Path to check.
1952 * RETURNS
1953 * TRUE if lpszPath is a URL.
1954 * FALSE if lpszPath is NULL or not a URL.
1956 BOOL WINAPI PathIsURLA(LPCSTR lpstrPath)
1958 UNKNOWN_SHLWAPI_1 base;
1959 DWORD res1;
1961 if (!lpstrPath || !*lpstrPath) return FALSE;
1963 /* get protocol */
1964 base.size = sizeof(base);
1965 res1 = SHLWAPI_1(lpstrPath, &base);
1966 return (base.fcncde > 0);
1969 /*************************************************************************
1970 * PathIsURLW [SHLWAPI.@]
1972 BOOL WINAPI PathIsURLW(LPCWSTR lpstrPath)
1974 UNKNOWN_SHLWAPI_2 base;
1975 DWORD res1;
1977 if (!lpstrPath || !*lpstrPath) return FALSE;
1979 /* get protocol */
1980 base.size = sizeof(base);
1981 res1 = SHLWAPI_2(lpstrPath, &base);
1982 return (base.fcncde > 0);