include: Add new button control styles.
[wine/multimedia.git] / dlls / shlwapi / url.c
blobadfbb0976fcf622f7e25bfbb7141e13684d96dfd
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
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 "intshcut.h"
37 #include "wine/debug.h"
39 HMODULE WINAPI MLLoadLibraryW(LPCWSTR,HMODULE,DWORD);
40 BOOL WINAPI MLFreeLibrary(HMODULE);
41 HRESULT WINAPI MLBuildResURLW(LPCWSTR,HMODULE,DWORD,LPCWSTR,LPWSTR,DWORD);
43 WINE_DEFAULT_DEBUG_CHANNEL(shell);
45 /* The following schemes were identified in the native version of
46 * SHLWAPI.DLL version 5.50
48 static const struct {
49 URL_SCHEME scheme_number;
50 WCHAR scheme_name[12];
51 } shlwapi_schemes[] = {
52 {URL_SCHEME_FTP, {'f','t','p',0}},
53 {URL_SCHEME_HTTP, {'h','t','t','p',0}},
54 {URL_SCHEME_GOPHER, {'g','o','p','h','e','r',0}},
55 {URL_SCHEME_MAILTO, {'m','a','i','l','t','o',0}},
56 {URL_SCHEME_NEWS, {'n','e','w','s',0}},
57 {URL_SCHEME_NNTP, {'n','n','t','p',0}},
58 {URL_SCHEME_TELNET, {'t','e','l','n','e','t',0}},
59 {URL_SCHEME_WAIS, {'w','a','i','s',0}},
60 {URL_SCHEME_FILE, {'f','i','l','e',0}},
61 {URL_SCHEME_MK, {'m','k',0}},
62 {URL_SCHEME_HTTPS, {'h','t','t','p','s',0}},
63 {URL_SCHEME_SHELL, {'s','h','e','l','l',0}},
64 {URL_SCHEME_SNEWS, {'s','n','e','w','s',0}},
65 {URL_SCHEME_LOCAL, {'l','o','c','a','l',0}},
66 {URL_SCHEME_JAVASCRIPT, {'j','a','v','a','s','c','r','i','p','t',0}},
67 {URL_SCHEME_VBSCRIPT, {'v','b','s','c','r','i','p','t',0}},
68 {URL_SCHEME_ABOUT, {'a','b','o','u','t',0}},
69 {URL_SCHEME_RES, {'r','e','s',0}},
72 typedef struct {
73 LPCWSTR pScheme; /* [out] start of scheme */
74 DWORD szScheme; /* [out] size of scheme (until colon) */
75 LPCWSTR pUserName; /* [out] start of Username */
76 DWORD szUserName; /* [out] size of Username (until ":" or "@") */
77 LPCWSTR pPassword; /* [out] start of Password */
78 DWORD szPassword; /* [out] size of Password (until "@") */
79 LPCWSTR pHostName; /* [out] start of Hostname */
80 DWORD szHostName; /* [out] size of Hostname (until ":" or "/") */
81 LPCWSTR pPort; /* [out] start of Port */
82 DWORD szPort; /* [out] size of Port (until "/" or eos) */
83 LPCWSTR pQuery; /* [out] start of Query */
84 DWORD szQuery; /* [out] size of Query (until eos) */
85 } WINE_PARSE_URL;
87 typedef enum {
88 SCHEME,
89 HOST,
90 PORT,
91 USERPASS,
92 } WINE_URL_SCAN_TYPE;
94 static const CHAR hexDigits[] = "0123456789ABCDEF";
96 static const WCHAR fileW[] = {'f','i','l','e','\0'};
98 static const unsigned char HashDataLookup[256] = {
99 0x01, 0x0E, 0x6E, 0x19, 0x61, 0xAE, 0x84, 0x77, 0x8A, 0xAA, 0x7D, 0x76, 0x1B,
100 0xE9, 0x8C, 0x33, 0x57, 0xC5, 0xB1, 0x6B, 0xEA, 0xA9, 0x38, 0x44, 0x1E, 0x07,
101 0xAD, 0x49, 0xBC, 0x28, 0x24, 0x41, 0x31, 0xD5, 0x68, 0xBE, 0x39, 0xD3, 0x94,
102 0xDF, 0x30, 0x73, 0x0F, 0x02, 0x43, 0xBA, 0xD2, 0x1C, 0x0C, 0xB5, 0x67, 0x46,
103 0x16, 0x3A, 0x4B, 0x4E, 0xB7, 0xA7, 0xEE, 0x9D, 0x7C, 0x93, 0xAC, 0x90, 0xB0,
104 0xA1, 0x8D, 0x56, 0x3C, 0x42, 0x80, 0x53, 0x9C, 0xF1, 0x4F, 0x2E, 0xA8, 0xC6,
105 0x29, 0xFE, 0xB2, 0x55, 0xFD, 0xED, 0xFA, 0x9A, 0x85, 0x58, 0x23, 0xCE, 0x5F,
106 0x74, 0xFC, 0xC0, 0x36, 0xDD, 0x66, 0xDA, 0xFF, 0xF0, 0x52, 0x6A, 0x9E, 0xC9,
107 0x3D, 0x03, 0x59, 0x09, 0x2A, 0x9B, 0x9F, 0x5D, 0xA6, 0x50, 0x32, 0x22, 0xAF,
108 0xC3, 0x64, 0x63, 0x1A, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xBD, 0x79, 0x40,
109 0x4D, 0x48, 0xD0, 0xF5, 0x82, 0x7A, 0x8F, 0x37, 0x69, 0x86, 0x1D, 0xA4, 0xB9,
110 0xC2, 0xC1, 0xEF, 0x65, 0xF2, 0x05, 0xAB, 0x7E, 0x0B, 0x4A, 0x3B, 0x89, 0xE4,
111 0x6C, 0xBF, 0xE8, 0x8B, 0x06, 0x18, 0x51, 0x14, 0x7F, 0x11, 0x5B, 0x5C, 0xFB,
112 0x97, 0xE1, 0xCF, 0x15, 0x62, 0x71, 0x70, 0x54, 0xE2, 0x12, 0xD6, 0xC7, 0xBB,
113 0x0D, 0x20, 0x5E, 0xDC, 0xE0, 0xD4, 0xF7, 0xCC, 0xC4, 0x2B, 0xF9, 0xEC, 0x2D,
114 0xF4, 0x6F, 0xB6, 0x99, 0x88, 0x81, 0x5A, 0xD9, 0xCA, 0x13, 0xA5, 0xE7, 0x47,
115 0xE6, 0x8E, 0x60, 0xE3, 0x3E, 0xB3, 0xF6, 0x72, 0xA2, 0x35, 0xA0, 0xD7, 0xCD,
116 0xB4, 0x2F, 0x6D, 0x2C, 0x26, 0x1F, 0x95, 0x87, 0x00, 0xD8, 0x34, 0x3F, 0x17,
117 0x25, 0x45, 0x27, 0x75, 0x92, 0xB8, 0xA3, 0xC8, 0xDE, 0xEB, 0xF8, 0xF3, 0xDB,
118 0x0A, 0x98, 0x83, 0x7B, 0xE5, 0xCB, 0x4C, 0x78, 0xD1 };
120 static DWORD get_scheme_code(LPCWSTR scheme, DWORD scheme_len)
122 unsigned int i;
124 for(i=0; i < sizeof(shlwapi_schemes)/sizeof(shlwapi_schemes[0]); i++) {
125 if(scheme_len == strlenW(shlwapi_schemes[i].scheme_name)
126 && !memcmp(scheme, shlwapi_schemes[i].scheme_name, scheme_len*sizeof(WCHAR)))
127 return shlwapi_schemes[i].scheme_number;
130 return URL_SCHEME_UNKNOWN;
133 /*************************************************************************
134 * @ [SHLWAPI.1]
136 * Parse a Url into its constituent parts.
138 * PARAMS
139 * x [I] Url to parse
140 * y [O] Undocumented structure holding the parsed information
142 * RETURNS
143 * Success: S_OK. y contains the parsed Url details.
144 * Failure: An HRESULT error code.
146 HRESULT WINAPI ParseURLA(LPCSTR x, PARSEDURLA *y)
148 WCHAR scheme[INTERNET_MAX_SCHEME_LENGTH];
149 const char *ptr = x;
150 int len;
152 TRACE("%s %p\n", debugstr_a(x), y);
154 if(y->cbSize != sizeof(*y))
155 return E_INVALIDARG;
157 while(*ptr && (isalnum(*ptr) || *ptr == '-'))
158 ptr++;
160 if (*ptr != ':' || ptr <= x+1) {
161 y->pszProtocol = NULL;
162 return URL_E_INVALID_SYNTAX;
165 y->pszProtocol = x;
166 y->cchProtocol = ptr-x;
167 y->pszSuffix = ptr+1;
168 y->cchSuffix = strlen(y->pszSuffix);
170 len = MultiByteToWideChar(CP_ACP, 0, x, ptr-x,
171 scheme, sizeof(scheme)/sizeof(WCHAR));
172 y->nScheme = get_scheme_code(scheme, len);
174 return S_OK;
177 /*************************************************************************
178 * @ [SHLWAPI.2]
180 * Unicode version of ParseURLA.
182 HRESULT WINAPI ParseURLW(LPCWSTR x, PARSEDURLW *y)
184 const WCHAR *ptr = x;
186 TRACE("%s %p\n", debugstr_w(x), y);
188 if(y->cbSize != sizeof(*y))
189 return E_INVALIDARG;
191 while(*ptr && (isalnumW(*ptr) || *ptr == '-'))
192 ptr++;
194 if (*ptr != ':' || ptr <= x+1) {
195 y->pszProtocol = NULL;
196 return URL_E_INVALID_SYNTAX;
199 y->pszProtocol = x;
200 y->cchProtocol = ptr-x;
201 y->pszSuffix = ptr+1;
202 y->cchSuffix = strlenW(y->pszSuffix);
203 y->nScheme = get_scheme_code(x, ptr-x);
205 return S_OK;
208 /*************************************************************************
209 * UrlCanonicalizeA [SHLWAPI.@]
211 * Canonicalize a Url.
213 * PARAMS
214 * pszUrl [I] Url to cCanonicalize
215 * pszCanonicalized [O] Destination for converted Url.
216 * pcchCanonicalized [I/O] Length of pszUrl, destination for length of pszCanonicalized
217 * dwFlags [I] Flags controlling the conversion.
219 * RETURNS
220 * Success: S_OK. The pszCanonicalized contains the converted Url.
221 * Failure: E_POINTER, if *pcchCanonicalized is too small.
223 * MSDN incorrectly describes the flags for this function. They should be:
224 *| URL_DONT_ESCAPE_EXTRA_INFO 0x02000000
225 *| URL_ESCAPE_SPACES_ONLY 0x04000000
226 *| URL_ESCAPE_PERCENT 0x00001000
227 *| URL_ESCAPE_UNSAFE 0x10000000
228 *| URL_UNESCAPE 0x10000000
229 *| URL_DONT_SIMPLIFY 0x08000000
230 *| URL_ESCAPE_SEGMENT_ONLY 0x00002000
232 HRESULT WINAPI UrlCanonicalizeA(LPCSTR pszUrl, LPSTR pszCanonicalized,
233 LPDWORD pcchCanonicalized, DWORD dwFlags)
235 LPWSTR url, canonical;
236 HRESULT ret;
237 DWORD len;
239 TRACE("(%s, %p, %p, 0x%08x) *pcchCanonicalized: %d\n", debugstr_a(pszUrl), pszCanonicalized,
240 pcchCanonicalized, dwFlags, pcchCanonicalized ? *pcchCanonicalized : -1);
242 if(!pszUrl || !pszCanonicalized || !pcchCanonicalized || !*pcchCanonicalized)
243 return E_INVALIDARG;
245 len = strlen(pszUrl)+1;
246 url = HeapAlloc(GetProcessHeap(), 0, len*sizeof(WCHAR));
247 canonical = HeapAlloc(GetProcessHeap(), 0, *pcchCanonicalized*sizeof(WCHAR));
248 if(!url || !canonical) {
249 HeapFree(GetProcessHeap(), 0, url);
250 HeapFree(GetProcessHeap(), 0, canonical);
251 return E_OUTOFMEMORY;
254 MultiByteToWideChar(0, 0, pszUrl, -1, url, len);
256 ret = UrlCanonicalizeW(url, canonical, pcchCanonicalized, dwFlags);
257 if(ret == S_OK)
258 WideCharToMultiByte(0, 0, canonical, -1, pszCanonicalized,
259 *pcchCanonicalized+1, 0, 0);
261 HeapFree(GetProcessHeap(), 0, canonical);
262 return ret;
265 /*************************************************************************
266 * UrlCanonicalizeW [SHLWAPI.@]
268 * See UrlCanonicalizeA.
270 HRESULT WINAPI UrlCanonicalizeW(LPCWSTR pszUrl, LPWSTR pszCanonicalized,
271 LPDWORD pcchCanonicalized, DWORD dwFlags)
273 HRESULT hr = S_OK;
274 DWORD EscapeFlags;
275 LPCWSTR wk1, root;
276 LPWSTR lpszUrlCpy, url, wk2, mp, mp2;
277 INT state;
278 DWORD nByteLen, nLen, nWkLen;
279 BOOL is_file_url;
280 WCHAR slash = '\0';
282 static const WCHAR wszFile[] = {'f','i','l','e',':'};
283 static const WCHAR wszRes[] = {'r','e','s',':'};
284 static const WCHAR wszHttp[] = {'h','t','t','p',':'};
285 static const WCHAR wszLocalhost[] = {'l','o','c','a','l','h','o','s','t'};
286 static const WCHAR wszFilePrefix[] = {'f','i','l','e',':','/','/','/'};
288 TRACE("(%s, %p, %p, 0x%08x) *pcchCanonicalized: %d\n", debugstr_w(pszUrl), pszCanonicalized,
289 pcchCanonicalized, dwFlags, pcchCanonicalized ? *pcchCanonicalized : -1);
291 if(!pszUrl || !pszCanonicalized || !pcchCanonicalized || !*pcchCanonicalized)
292 return E_INVALIDARG;
294 if(!*pszUrl) {
295 *pszCanonicalized = 0;
296 return S_OK;
299 /* Remove '\t' characters from URL */
300 nByteLen = (strlenW(pszUrl) + 1) * sizeof(WCHAR); /* length in bytes */
301 url = HeapAlloc(GetProcessHeap(), 0, nByteLen);
302 if(!url)
303 return E_OUTOFMEMORY;
305 wk1 = pszUrl;
306 wk2 = url;
307 do {
308 while(*wk1 == '\t')
309 wk1++;
310 *wk2++ = *wk1;
311 } while(*wk1++);
313 /* Allocate memory for simplified URL (before escaping) */
314 nByteLen = (wk2-url)*sizeof(WCHAR);
315 lpszUrlCpy = HeapAlloc(GetProcessHeap(), 0,
316 nByteLen+sizeof(wszFilePrefix)+sizeof(WCHAR));
317 if(!lpszUrlCpy) {
318 HeapFree(GetProcessHeap(), 0, url);
319 return E_OUTOFMEMORY;
322 is_file_url = !strncmpW(wszFile, url, sizeof(wszFile)/sizeof(WCHAR));
324 if ((nByteLen >= sizeof(wszHttp) &&
325 !memcmp(wszHttp, url, sizeof(wszHttp))) || is_file_url)
326 slash = '/';
328 if((dwFlags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY)) && is_file_url)
329 slash = '\\';
331 if(nByteLen >= sizeof(wszRes) && !memcmp(wszRes, url, sizeof(wszRes))) {
332 dwFlags &= ~URL_FILE_USE_PATHURL;
333 slash = '\0';
337 * state =
338 * 0 initial 1,3
339 * 1 have 2[+] alnum 2,3
340 * 2 have scheme (found :) 4,6,3
341 * 3 failed (no location)
342 * 4 have // 5,3
343 * 5 have 1[+] alnum 6,3
344 * 6 have location (found /) save root location
347 wk1 = url;
348 wk2 = lpszUrlCpy;
349 state = 0;
351 if(url[1] == ':') { /* Assume path */
352 memcpy(wk2, wszFilePrefix, sizeof(wszFilePrefix));
353 wk2 += sizeof(wszFilePrefix)/sizeof(WCHAR);
354 if (dwFlags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY))
356 slash = '\\';
357 --wk2;
359 else
360 dwFlags |= URL_ESCAPE_UNSAFE;
361 state = 5;
362 is_file_url = TRUE;
365 while (*wk1) {
366 switch (state) {
367 case 0:
368 if (!isalnumW(*wk1)) {state = 3; break;}
369 *wk2++ = *wk1++;
370 if (!isalnumW(*wk1)) {state = 3; break;}
371 *wk2++ = *wk1++;
372 state = 1;
373 break;
374 case 1:
375 *wk2++ = *wk1;
376 if (*wk1++ == ':') state = 2;
377 break;
378 case 2:
379 *wk2++ = *wk1++;
380 if (*wk1 != '/') {state = 6; break;}
381 *wk2++ = *wk1++;
382 if((dwFlags & URL_FILE_USE_PATHURL) && nByteLen >= sizeof(wszLocalhost)
383 && is_file_url
384 && !memcmp(wszLocalhost, wk1, sizeof(wszLocalhost))){
385 wk1 += sizeof(wszLocalhost)/sizeof(WCHAR);
386 while(*wk1 == '\\' && (dwFlags & URL_FILE_USE_PATHURL))
387 wk1++;
390 if(*wk1 == '/' && (dwFlags & URL_FILE_USE_PATHURL)){
391 wk1++;
392 }else if(is_file_url){
393 const WCHAR *body = wk1;
395 while(*body == '/')
396 ++body;
398 if(isalnumW(*body) && *(body+1) == ':'){
399 if(!(dwFlags & (URL_WININET_COMPATIBILITY | URL_FILE_USE_PATHURL))){
400 if(slash)
401 *wk2++ = slash;
402 else
403 *wk2++ = '/';
405 }else{
406 if(dwFlags & URL_WININET_COMPATIBILITY){
407 if(*wk1 == '/' && *(wk1+1) != '/'){
408 *wk2++ = '\\';
409 }else{
410 *wk2++ = '\\';
411 *wk2++ = '\\';
413 }else{
414 if(*wk1 == '/' && *(wk1+1) != '/'){
415 if(slash)
416 *wk2++ = slash;
417 else
418 *wk2++ = '/';
422 wk1 = body;
424 state = 4;
425 break;
426 case 3:
427 nWkLen = strlenW(wk1);
428 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
429 mp = wk2;
430 wk1 += nWkLen;
431 wk2 += nWkLen;
433 if(slash) {
434 while(mp < wk2) {
435 if(*mp == '/' || *mp == '\\')
436 *mp = slash;
437 mp++;
440 break;
441 case 4:
442 if (!isalnumW(*wk1) && (*wk1 != '-') && (*wk1 != '.') && (*wk1 != ':'))
443 {state = 3; break;}
444 while(isalnumW(*wk1) || (*wk1 == '-') || (*wk1 == '.') || (*wk1 == ':'))
445 *wk2++ = *wk1++;
446 state = 5;
447 if (!*wk1) {
448 if(slash)
449 *wk2++ = slash;
450 else
451 *wk2++ = '/';
453 break;
454 case 5:
455 if (*wk1 != '/' && *wk1 != '\\') {state = 3; break;}
456 while(*wk1 == '/' || *wk1 == '\\') {
457 if(slash)
458 *wk2++ = slash;
459 else
460 *wk2++ = *wk1;
461 wk1++;
463 state = 6;
464 break;
465 case 6:
466 if(dwFlags & URL_DONT_SIMPLIFY) {
467 state = 3;
468 break;
471 /* Now at root location, cannot back up any more. */
472 /* "root" will point at the '/' */
474 root = wk2-1;
475 while (*wk1) {
476 mp = strchrW(wk1, '/');
477 mp2 = strchrW(wk1, '\\');
478 if(mp2 && (!mp || mp2 < mp))
479 mp = mp2;
480 if (!mp) {
481 nWkLen = strlenW(wk1);
482 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
483 wk1 += nWkLen;
484 wk2 += nWkLen;
485 continue;
487 nLen = mp - wk1;
488 if(nLen) {
489 memcpy(wk2, wk1, nLen * sizeof(WCHAR));
490 wk2 += nLen;
491 wk1 += nLen;
493 if(slash)
494 *wk2++ = slash;
495 else
496 *wk2++ = *wk1;
497 wk1++;
499 while (*wk1 == '.') {
500 TRACE("found '/.'\n");
501 if (wk1[1] == '/' || wk1[1] == '\\') {
502 /* case of /./ -> skip the ./ */
503 wk1 += 2;
505 else if (wk1[1] == '.' && (wk1[2] == '/'
506 || wk1[2] == '\\' || wk1[2] == '?'
507 || wk1[2] == '#' || !wk1[2])) {
508 /* case /../ -> need to backup wk2 */
509 TRACE("found '/../'\n");
510 *(wk2-1) = '\0'; /* set end of string */
511 mp = strrchrW(root, '/');
512 mp2 = strrchrW(root, '\\');
513 if(mp2 && (!mp || mp2 < mp))
514 mp = mp2;
515 if (mp && (mp >= root)) {
516 /* found valid backup point */
517 wk2 = mp + 1;
518 if(wk1[2] != '/' && wk1[2] != '\\')
519 wk1 += 2;
520 else
521 wk1 += 3;
523 else {
524 /* did not find point, restore '/' */
525 *(wk2-1) = slash;
526 break;
529 else
530 break;
533 *wk2 = '\0';
534 break;
535 default:
536 FIXME("how did we get here - state=%d\n", state);
537 HeapFree(GetProcessHeap(), 0, lpszUrlCpy);
538 HeapFree(GetProcessHeap(), 0, url);
539 return E_INVALIDARG;
541 *wk2 = '\0';
542 TRACE("Simplified, orig <%s>, simple <%s>\n",
543 debugstr_w(pszUrl), debugstr_w(lpszUrlCpy));
545 nLen = lstrlenW(lpszUrlCpy);
546 while ((nLen > 0) && ((lpszUrlCpy[nLen-1] <= ' ')))
547 lpszUrlCpy[--nLen]=0;
549 if((dwFlags & URL_UNESCAPE) ||
550 ((dwFlags & URL_FILE_USE_PATHURL) && nByteLen >= sizeof(wszFile)
551 && !memcmp(wszFile, url, sizeof(wszFile))))
552 UrlUnescapeW(lpszUrlCpy, NULL, &nLen, URL_UNESCAPE_INPLACE);
554 if((EscapeFlags = dwFlags & (URL_ESCAPE_UNSAFE |
555 URL_ESCAPE_SPACES_ONLY |
556 URL_ESCAPE_PERCENT |
557 URL_DONT_ESCAPE_EXTRA_INFO |
558 URL_ESCAPE_SEGMENT_ONLY ))) {
559 EscapeFlags &= ~URL_ESCAPE_UNSAFE;
560 hr = UrlEscapeW(lpszUrlCpy, pszCanonicalized, pcchCanonicalized,
561 EscapeFlags);
562 } else { /* No escaping needed, just copy the string */
563 nLen = lstrlenW(lpszUrlCpy);
564 if(nLen < *pcchCanonicalized)
565 memcpy(pszCanonicalized, lpszUrlCpy, (nLen + 1)*sizeof(WCHAR));
566 else {
567 hr = E_POINTER;
568 nLen++;
570 *pcchCanonicalized = nLen;
573 HeapFree(GetProcessHeap(), 0, lpszUrlCpy);
574 HeapFree(GetProcessHeap(), 0, url);
576 if (hr == S_OK)
577 TRACE("result %s\n", debugstr_w(pszCanonicalized));
579 return hr;
582 /*************************************************************************
583 * UrlCombineA [SHLWAPI.@]
585 * Combine two Urls.
587 * PARAMS
588 * pszBase [I] Base Url
589 * pszRelative [I] Url to combine with pszBase
590 * pszCombined [O] Destination for combined Url
591 * pcchCombined [O] Destination for length of pszCombined
592 * dwFlags [I] URL_ flags from "shlwapi.h"
594 * RETURNS
595 * Success: S_OK. pszCombined contains the combined Url, pcchCombined
596 * contains its length.
597 * Failure: An HRESULT error code indicating the error.
599 HRESULT WINAPI UrlCombineA(LPCSTR pszBase, LPCSTR pszRelative,
600 LPSTR pszCombined, LPDWORD pcchCombined,
601 DWORD dwFlags)
603 LPWSTR base, relative, combined;
604 DWORD ret, len, len2;
606 TRACE("(base %s, Relative %s, Combine size %d, flags %08x) using W version\n",
607 debugstr_a(pszBase),debugstr_a(pszRelative),
608 pcchCombined?*pcchCombined:0,dwFlags);
610 if(!pszBase || !pszRelative || !pcchCombined)
611 return E_INVALIDARG;
613 base = HeapAlloc(GetProcessHeap(), 0,
614 (3*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
615 relative = base + INTERNET_MAX_URL_LENGTH;
616 combined = relative + INTERNET_MAX_URL_LENGTH;
618 MultiByteToWideChar(0, 0, pszBase, -1, base, INTERNET_MAX_URL_LENGTH);
619 MultiByteToWideChar(0, 0, pszRelative, -1, relative, INTERNET_MAX_URL_LENGTH);
620 len = *pcchCombined;
622 ret = UrlCombineW(base, relative, pszCombined?combined:NULL, &len, dwFlags);
623 if (ret != S_OK) {
624 *pcchCombined = len;
625 HeapFree(GetProcessHeap(), 0, base);
626 return ret;
629 len2 = WideCharToMultiByte(0, 0, combined, len, 0, 0, 0, 0);
630 if (len2 > *pcchCombined) {
631 *pcchCombined = len2;
632 HeapFree(GetProcessHeap(), 0, base);
633 return E_POINTER;
635 WideCharToMultiByte(0, 0, combined, len+1, pszCombined, (*pcchCombined)+1,
636 0, 0);
637 *pcchCombined = len2;
638 HeapFree(GetProcessHeap(), 0, base);
639 return S_OK;
642 /*************************************************************************
643 * UrlCombineW [SHLWAPI.@]
645 * See UrlCombineA.
647 HRESULT WINAPI UrlCombineW(LPCWSTR pszBase, LPCWSTR pszRelative,
648 LPWSTR pszCombined, LPDWORD pcchCombined,
649 DWORD dwFlags)
651 PARSEDURLW base, relative;
652 DWORD myflags, sizeloc = 0;
653 DWORD len, res1, res2, process_case = 0;
654 LPWSTR work, preliminary, mbase, mrelative;
655 static const WCHAR myfilestr[] = {'f','i','l','e',':','/','/','/','\0'};
656 HRESULT ret;
658 TRACE("(base %s, Relative %s, Combine size %d, flags %08x)\n",
659 debugstr_w(pszBase),debugstr_w(pszRelative),
660 pcchCombined?*pcchCombined:0,dwFlags);
662 if(!pszBase || !pszRelative || !pcchCombined)
663 return E_INVALIDARG;
665 base.cbSize = sizeof(base);
666 relative.cbSize = sizeof(relative);
668 /* Get space for duplicates of the input and the output */
669 preliminary = HeapAlloc(GetProcessHeap(), 0, (3*INTERNET_MAX_URL_LENGTH) *
670 sizeof(WCHAR));
671 mbase = preliminary + INTERNET_MAX_URL_LENGTH;
672 mrelative = mbase + INTERNET_MAX_URL_LENGTH;
673 *preliminary = '\0';
675 /* Canonicalize the base input prior to looking for the scheme */
676 myflags = dwFlags & (URL_DONT_SIMPLIFY | URL_UNESCAPE);
677 len = INTERNET_MAX_URL_LENGTH;
678 ret = UrlCanonicalizeW(pszBase, mbase, &len, myflags);
680 /* Canonicalize the relative input prior to looking for the scheme */
681 len = INTERNET_MAX_URL_LENGTH;
682 ret = UrlCanonicalizeW(pszRelative, mrelative, &len, myflags);
684 /* See if the base has a scheme */
685 res1 = ParseURLW(mbase, &base);
686 if (res1) {
687 /* if pszBase has no scheme, then return pszRelative */
688 TRACE("no scheme detected in Base\n");
689 process_case = 1;
691 else do {
692 BOOL manual_search = FALSE;
694 /* mk is a special case */
695 if(base.nScheme == URL_SCHEME_MK) {
696 static const WCHAR wsz[] = {':',':',0};
698 WCHAR *ptr = strstrW(base.pszSuffix, wsz);
699 if(ptr) {
700 int delta;
702 ptr += 2;
703 delta = ptr-base.pszSuffix;
704 base.cchProtocol += delta;
705 base.pszSuffix += delta;
706 base.cchSuffix -= delta;
708 }else {
709 /* get size of location field (if it exists) */
710 work = (LPWSTR)base.pszSuffix;
711 sizeloc = 0;
712 if (*work++ == '/') {
713 if (*work++ == '/') {
714 /* At this point have start of location and
715 * it ends at next '/' or end of string.
717 while(*work && (*work != '/')) work++;
718 sizeloc = (DWORD)(work - base.pszSuffix);
723 /* If there is a '#' and the characters immediately preceding it are
724 * ".htm[l]", then begin looking for the last leaf starting from
725 * the '#'. Otherwise the '#' is not meaningful and just start
726 * looking from the end. */
727 if ((work = strchrW(base.pszSuffix + sizeloc, '#'))) {
728 const WCHAR htmlW[] = {'.','h','t','m','l',0};
729 const int len_htmlW = 5;
730 const WCHAR htmW[] = {'.','h','t','m',0};
731 const int len_htmW = 4;
733 if (work - base.pszSuffix > len_htmW * sizeof(WCHAR)) {
734 work -= len_htmW;
735 if (strncmpiW(work, htmW, len_htmW) == 0)
736 manual_search = TRUE;
737 work += len_htmW;
740 if (!manual_search &&
741 work - base.pszSuffix > len_htmlW * sizeof(WCHAR)) {
742 work -= len_htmlW;
743 if (strncmpiW(work, htmlW, len_htmlW) == 0)
744 manual_search = TRUE;
745 work += len_htmlW;
749 if (manual_search) {
750 /* search backwards starting from the current position */
751 while (*work != '/' && work > base.pszSuffix + sizeloc)
752 --work;
753 if (work > base.pszSuffix + sizeloc)
754 base.cchSuffix = work - base.pszSuffix + 1;
755 }else {
756 /* search backwards starting from the end of the string */
757 work = strrchrW((base.pszSuffix+sizeloc), '/');
758 if (work) {
759 len = (DWORD)(work - base.pszSuffix + 1);
760 base.cchSuffix = len;
765 * At this point:
766 * .pszSuffix points to location (starting with '//')
767 * .cchSuffix length of location (above) and rest less the last
768 * leaf (if any)
769 * sizeloc length of location (above) up to but not including
770 * the last '/'
773 res2 = ParseURLW(mrelative, &relative);
774 if (res2) {
775 /* no scheme in pszRelative */
776 TRACE("no scheme detected in Relative\n");
777 relative.pszSuffix = mrelative; /* case 3,4,5 depends on this */
778 relative.cchSuffix = strlenW(mrelative);
779 if (*pszRelative == ':') {
780 /* case that is either left alone or uses pszBase */
781 if (dwFlags & URL_PLUGGABLE_PROTOCOL) {
782 process_case = 5;
783 break;
785 process_case = 1;
786 break;
788 if (isalnum(*mrelative) && (*(mrelative + 1) == ':')) {
789 /* case that becomes "file:///" */
790 strcpyW(preliminary, myfilestr);
791 process_case = 1;
792 break;
794 if ((*mrelative == '/') && (*(mrelative+1) == '/')) {
795 /* pszRelative has location and rest */
796 process_case = 3;
797 break;
799 if (*mrelative == '/') {
800 /* case where pszRelative is root to location */
801 process_case = 4;
802 break;
804 process_case = (*base.pszSuffix == '/' || base.nScheme == URL_SCHEME_MK) ? 5 : 3;
805 break;
808 /* handle cases where pszRelative has scheme */
809 if ((base.cchProtocol == relative.cchProtocol) &&
810 (strncmpW(base.pszProtocol, relative.pszProtocol, base.cchProtocol) == 0)) {
812 /* since the schemes are the same */
813 if ((*relative.pszSuffix == '/') && (*(relative.pszSuffix+1) == '/')) {
814 /* case where pszRelative replaces location and following */
815 process_case = 3;
816 break;
818 if (*relative.pszSuffix == '/') {
819 /* case where pszRelative is root to location */
820 process_case = 4;
821 break;
823 /* replace either just location if base's location starts with a
824 * slash or otherwise everything */
825 process_case = (*base.pszSuffix == '/') ? 5 : 1;
826 break;
828 if ((*relative.pszSuffix == '/') && (*(relative.pszSuffix+1) == '/')) {
829 /* case where pszRelative replaces scheme, location,
830 * and following and handles PLUGGABLE
832 process_case = 2;
833 break;
835 process_case = 1;
836 break;
837 } while(FALSE); /* a little trick to allow easy exit from nested if's */
839 ret = S_OK;
840 switch (process_case) {
842 case 1: /*
843 * Return pszRelative appended to what ever is in pszCombined,
844 * (which may the string "file:///"
846 strcatW(preliminary, mrelative);
847 break;
849 case 2: /* case where pszRelative replaces scheme, and location */
850 strcpyW(preliminary, mrelative);
851 break;
853 case 3: /*
854 * Return the pszBase scheme with pszRelative. Basically
855 * keeps the scheme and replaces the domain and following.
857 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1)*sizeof(WCHAR));
858 work = preliminary + base.cchProtocol + 1;
859 strcpyW(work, relative.pszSuffix);
860 break;
862 case 4: /*
863 * Return the pszBase scheme and location but everything
864 * after the location is pszRelative. (Replace document
865 * from root on.)
867 memcpy(preliminary, base.pszProtocol, (base.cchProtocol+1+sizeloc)*sizeof(WCHAR));
868 work = preliminary + base.cchProtocol + 1 + sizeloc;
869 if (dwFlags & URL_PLUGGABLE_PROTOCOL)
870 *(work++) = '/';
871 strcpyW(work, relative.pszSuffix);
872 break;
874 case 5: /*
875 * Return the pszBase without its document (if any) and
876 * append pszRelative after its scheme.
878 memcpy(preliminary, base.pszProtocol,
879 (base.cchProtocol+1+base.cchSuffix)*sizeof(WCHAR));
880 work = preliminary + base.cchProtocol+1+base.cchSuffix - 1;
881 if (*work++ != '/')
882 *(work++) = '/';
883 strcpyW(work, relative.pszSuffix);
884 break;
886 default:
887 FIXME("How did we get here????? process_case=%d\n", process_case);
888 ret = E_INVALIDARG;
891 if (ret == S_OK) {
892 /* Reuse mrelative as temp storage as its already allocated and not needed anymore */
893 if(*pcchCombined == 0)
894 *pcchCombined = 1;
895 ret = UrlCanonicalizeW(preliminary, mrelative, pcchCombined, (dwFlags & ~URL_FILE_USE_PATHURL));
896 if(SUCCEEDED(ret) && pszCombined) {
897 lstrcpyW(pszCombined, mrelative);
899 TRACE("return-%d len=%d, %s\n",
900 process_case, *pcchCombined, debugstr_w(pszCombined));
902 HeapFree(GetProcessHeap(), 0, preliminary);
903 return ret;
906 /*************************************************************************
907 * UrlEscapeA [SHLWAPI.@]
910 HRESULT WINAPI UrlEscapeA(
911 LPCSTR pszUrl,
912 LPSTR pszEscaped,
913 LPDWORD pcchEscaped,
914 DWORD dwFlags)
916 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
917 WCHAR *escapedW = bufW;
918 UNICODE_STRING urlW;
919 HRESULT ret;
920 DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
922 if (!pszEscaped || !pcchEscaped || !*pcchEscaped)
923 return E_INVALIDARG;
925 if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl))
926 return E_INVALIDARG;
927 if((ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags)) == E_POINTER) {
928 escapedW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
929 ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags);
931 if(ret == S_OK) {
932 RtlUnicodeToMultiByteSize(&lenA, escapedW, lenW * sizeof(WCHAR));
933 if(*pcchEscaped > lenA) {
934 RtlUnicodeToMultiByteN(pszEscaped, *pcchEscaped - 1, &lenA, escapedW, lenW * sizeof(WCHAR));
935 pszEscaped[lenA] = 0;
936 *pcchEscaped = lenA;
937 } else {
938 *pcchEscaped = lenA + 1;
939 ret = E_POINTER;
942 if(escapedW != bufW) HeapFree(GetProcessHeap(), 0, escapedW);
943 RtlFreeUnicodeString(&urlW);
944 return ret;
947 #define WINE_URL_BASH_AS_SLASH 0x01
948 #define WINE_URL_COLLAPSE_SLASHES 0x02
949 #define WINE_URL_ESCAPE_SLASH 0x04
950 #define WINE_URL_ESCAPE_HASH 0x08
951 #define WINE_URL_ESCAPE_QUESTION 0x10
952 #define WINE_URL_STOP_ON_HASH 0x20
953 #define WINE_URL_STOP_ON_QUESTION 0x40
955 static inline BOOL URL_NeedEscapeW(WCHAR ch, DWORD dwFlags, DWORD int_flags)
958 if (isalnumW(ch))
959 return FALSE;
961 if(dwFlags & URL_ESCAPE_SPACES_ONLY) {
962 if(ch == ' ')
963 return TRUE;
964 else
965 return FALSE;
968 if ((dwFlags & URL_ESCAPE_PERCENT) && (ch == '%'))
969 return TRUE;
971 if (ch <= 31 || ch >= 127)
972 return TRUE;
974 else {
975 switch (ch) {
976 case ' ':
977 case '<':
978 case '>':
979 case '\"':
980 case '{':
981 case '}':
982 case '|':
983 case '\\':
984 case '^':
985 case ']':
986 case '[':
987 case '`':
988 case '&':
989 return TRUE;
991 case '/':
992 if (int_flags & WINE_URL_ESCAPE_SLASH) return TRUE;
993 return FALSE;
995 case '?':
996 if (int_flags & WINE_URL_ESCAPE_QUESTION) return TRUE;
997 return FALSE;
999 case '#':
1000 if (int_flags & WINE_URL_ESCAPE_HASH) return TRUE;
1001 return FALSE;
1003 default:
1004 return FALSE;
1010 /*************************************************************************
1011 * UrlEscapeW [SHLWAPI.@]
1013 * Converts unsafe characters in a Url into escape sequences.
1015 * PARAMS
1016 * pszUrl [I] Url to modify
1017 * pszEscaped [O] Destination for modified Url
1018 * pcchEscaped [I/O] Length of pszUrl, destination for length of pszEscaped
1019 * dwFlags [I] URL_ flags from "shlwapi.h"
1021 * RETURNS
1022 * Success: S_OK. pszEscaped contains the escaped Url, pcchEscaped
1023 * contains its length.
1024 * Failure: E_POINTER, if pszEscaped is not large enough. In this case
1025 * pcchEscaped is set to the required length.
1027 * Converts unsafe characters into their escape sequences.
1029 * NOTES
1030 * - By default this function stops converting at the first '?' or
1031 * '#' character.
1032 * - If dwFlags contains URL_ESCAPE_SPACES_ONLY then only spaces are
1033 * converted, but the conversion continues past a '?' or '#'.
1034 * - Note that this function did not work well (or at all) in shlwapi version 4.
1036 * BUGS
1037 * Only the following flags are implemented:
1038 *| URL_ESCAPE_SPACES_ONLY
1039 *| URL_DONT_ESCAPE_EXTRA_INFO
1040 *| URL_ESCAPE_SEGMENT_ONLY
1041 *| URL_ESCAPE_PERCENT
1043 HRESULT WINAPI UrlEscapeW(
1044 LPCWSTR pszUrl,
1045 LPWSTR pszEscaped,
1046 LPDWORD pcchEscaped,
1047 DWORD dwFlags)
1049 LPCWSTR src;
1050 DWORD needed = 0, ret;
1051 BOOL stop_escaping = FALSE;
1052 WCHAR next[5], *dst = pszEscaped;
1053 INT len;
1054 PARSEDURLW parsed_url;
1055 DWORD int_flags;
1056 DWORD slashes = 0;
1057 static const WCHAR localhost[] = {'l','o','c','a','l','h','o','s','t',0};
1059 TRACE("(%p(%s) %p %p 0x%08x)\n", pszUrl, debugstr_w(pszUrl),
1060 pszEscaped, pcchEscaped, dwFlags);
1062 if(!pszUrl || !pcchEscaped)
1063 return E_INVALIDARG;
1065 if(dwFlags & ~(URL_ESCAPE_SPACES_ONLY |
1066 URL_ESCAPE_SEGMENT_ONLY |
1067 URL_DONT_ESCAPE_EXTRA_INFO |
1068 URL_ESCAPE_PERCENT))
1069 FIXME("Unimplemented flags: %08x\n", dwFlags);
1071 if(pszUrl == pszEscaped) {
1072 dst = HeapAlloc(GetProcessHeap(), 0, *pcchEscaped*sizeof(WCHAR));
1073 if(!dst)
1074 return E_OUTOFMEMORY;
1077 /* fix up flags */
1078 if (dwFlags & URL_ESCAPE_SPACES_ONLY)
1079 /* if SPACES_ONLY specified, reset the other controls */
1080 dwFlags &= ~(URL_DONT_ESCAPE_EXTRA_INFO |
1081 URL_ESCAPE_PERCENT |
1082 URL_ESCAPE_SEGMENT_ONLY);
1084 else
1085 /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
1086 dwFlags |= URL_DONT_ESCAPE_EXTRA_INFO;
1089 int_flags = 0;
1090 if(dwFlags & URL_ESCAPE_SEGMENT_ONLY) {
1091 int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH;
1092 } else {
1093 parsed_url.cbSize = sizeof(parsed_url);
1094 if(ParseURLW(pszUrl, &parsed_url) != S_OK)
1095 parsed_url.nScheme = URL_SCHEME_INVALID;
1097 TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol));
1099 if(dwFlags & URL_DONT_ESCAPE_EXTRA_INFO)
1100 int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION;
1102 switch(parsed_url.nScheme) {
1103 case URL_SCHEME_FILE:
1104 int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH;
1105 int_flags &= ~WINE_URL_STOP_ON_HASH;
1106 break;
1108 case URL_SCHEME_HTTP:
1109 case URL_SCHEME_HTTPS:
1110 int_flags |= WINE_URL_BASH_AS_SLASH;
1111 if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\')
1112 int_flags |= WINE_URL_ESCAPE_SLASH;
1113 break;
1115 case URL_SCHEME_MAILTO:
1116 int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH;
1117 int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH);
1118 break;
1120 case URL_SCHEME_INVALID:
1121 break;
1123 case URL_SCHEME_FTP:
1124 default:
1125 if(parsed_url.pszSuffix[0] != '/')
1126 int_flags |= WINE_URL_ESCAPE_SLASH;
1127 break;
1131 for(src = pszUrl; *src; ) {
1132 WCHAR cur = *src;
1133 len = 0;
1135 if((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == pszUrl + parsed_url.cchProtocol + 1) {
1136 int localhost_len = sizeof(localhost)/sizeof(WCHAR) - 1;
1137 while(cur == '/' || cur == '\\') {
1138 slashes++;
1139 cur = *++src;
1141 if(slashes == 2 && !strncmpiW(src, localhost, localhost_len)) { /* file://localhost/ -> file:/// */
1142 if(*(src + localhost_len) == '/' || *(src + localhost_len) == '\\')
1143 src += localhost_len + 1;
1144 slashes = 3;
1147 switch(slashes) {
1148 case 1:
1149 case 3:
1150 next[0] = next[1] = next[2] = '/';
1151 len = 3;
1152 break;
1153 case 0:
1154 len = 0;
1155 break;
1156 default:
1157 next[0] = next[1] = '/';
1158 len = 2;
1159 break;
1162 if(len == 0) {
1164 if(cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH))
1165 stop_escaping = TRUE;
1167 if(cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION))
1168 stop_escaping = TRUE;
1170 if(cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/';
1172 if(URL_NeedEscapeW(cur, dwFlags, int_flags) && stop_escaping == FALSE) {
1173 next[0] = '%';
1174 next[1] = hexDigits[(cur >> 4) & 0xf];
1175 next[2] = hexDigits[cur & 0xf];
1176 len = 3;
1177 } else {
1178 next[0] = cur;
1179 len = 1;
1181 src++;
1184 if(needed + len <= *pcchEscaped) {
1185 memcpy(dst, next, len*sizeof(WCHAR));
1186 dst += len;
1188 needed += len;
1191 if(needed < *pcchEscaped) {
1192 *dst = '\0';
1193 if(pszUrl == pszEscaped)
1194 memcpy(pszEscaped, dst-needed, (needed+1)*sizeof(WCHAR));
1196 ret = S_OK;
1197 } else {
1198 needed++; /* add one for the '\0' */
1199 ret = E_POINTER;
1201 *pcchEscaped = needed;
1203 if(pszUrl == pszEscaped)
1204 HeapFree(GetProcessHeap(), 0, dst);
1205 return ret;
1209 /*************************************************************************
1210 * UrlUnescapeA [SHLWAPI.@]
1212 * Converts Url escape sequences back to ordinary characters.
1214 * PARAMS
1215 * pszUrl [I/O] Url to convert
1216 * pszUnescaped [O] Destination for converted Url
1217 * pcchUnescaped [I/O] Size of output string
1218 * dwFlags [I] URL_ESCAPE_ Flags from "shlwapi.h"
1220 * RETURNS
1221 * Success: S_OK. The converted value is in pszUnescaped, or in pszUrl if
1222 * dwFlags includes URL_ESCAPE_INPLACE.
1223 * Failure: E_POINTER if the converted Url is bigger than pcchUnescaped. In
1224 * this case pcchUnescaped is set to the size required.
1225 * NOTES
1226 * If dwFlags includes URL_DONT_ESCAPE_EXTRA_INFO, the conversion stops at
1227 * the first occurrence of either a '?' or '#' character.
1229 HRESULT WINAPI UrlUnescapeA(
1230 LPSTR pszUrl,
1231 LPSTR pszUnescaped,
1232 LPDWORD pcchUnescaped,
1233 DWORD dwFlags)
1235 char *dst, next;
1236 LPCSTR src;
1237 HRESULT ret;
1238 DWORD needed;
1239 BOOL stop_unescaping = FALSE;
1241 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_a(pszUrl), pszUnescaped,
1242 pcchUnescaped, dwFlags);
1244 if (!pszUrl) return E_INVALIDARG;
1246 if(dwFlags & URL_UNESCAPE_INPLACE)
1247 dst = pszUrl;
1248 else
1250 if (!pszUnescaped || !pcchUnescaped) return E_INVALIDARG;
1251 dst = pszUnescaped;
1254 for(src = pszUrl, needed = 0; *src; src++, needed++) {
1255 if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1256 (*src == '#' || *src == '?')) {
1257 stop_unescaping = TRUE;
1258 next = *src;
1259 } else if(*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2))
1260 && stop_unescaping == FALSE) {
1261 INT ih;
1262 char buf[3];
1263 memcpy(buf, src + 1, 2);
1264 buf[2] = '\0';
1265 ih = strtol(buf, NULL, 16);
1266 next = (CHAR) ih;
1267 src += 2; /* Advance to end of escape */
1268 } else
1269 next = *src;
1271 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1272 *dst++ = next;
1275 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1276 *dst = '\0';
1277 ret = S_OK;
1278 } else {
1279 needed++; /* add one for the '\0' */
1280 ret = E_POINTER;
1282 if(!(dwFlags & URL_UNESCAPE_INPLACE))
1283 *pcchUnescaped = needed;
1285 if (ret == S_OK) {
1286 TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1287 debugstr_a(pszUrl) : debugstr_a(pszUnescaped));
1290 return ret;
1293 /*************************************************************************
1294 * UrlUnescapeW [SHLWAPI.@]
1296 * See UrlUnescapeA.
1298 HRESULT WINAPI UrlUnescapeW(
1299 LPWSTR pszUrl,
1300 LPWSTR pszUnescaped,
1301 LPDWORD pcchUnescaped,
1302 DWORD dwFlags)
1304 WCHAR *dst, next;
1305 LPCWSTR src;
1306 HRESULT ret;
1307 DWORD needed;
1308 BOOL stop_unescaping = FALSE;
1310 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszUrl), pszUnescaped,
1311 pcchUnescaped, dwFlags);
1313 if(!pszUrl) return E_INVALIDARG;
1315 if(dwFlags & URL_UNESCAPE_INPLACE)
1316 dst = pszUrl;
1317 else
1319 if (!pszUnescaped || !pcchUnescaped) return E_INVALIDARG;
1320 dst = pszUnescaped;
1323 for(src = pszUrl, needed = 0; *src; src++, needed++) {
1324 if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1325 (*src == '#' || *src == '?')) {
1326 stop_unescaping = TRUE;
1327 next = *src;
1328 } else if(*src == '%' && isxdigitW(*(src + 1)) && isxdigitW(*(src + 2))
1329 && stop_unescaping == FALSE) {
1330 INT ih;
1331 WCHAR buf[5] = {'0','x',0};
1332 memcpy(buf + 2, src + 1, 2*sizeof(WCHAR));
1333 buf[4] = 0;
1334 StrToIntExW(buf, STIF_SUPPORT_HEX, &ih);
1335 next = (WCHAR) ih;
1336 src += 2; /* Advance to end of escape */
1337 } else
1338 next = *src;
1340 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1341 *dst++ = next;
1344 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1345 *dst = '\0';
1346 ret = S_OK;
1347 } else {
1348 needed++; /* add one for the '\0' */
1349 ret = E_POINTER;
1351 if(!(dwFlags & URL_UNESCAPE_INPLACE))
1352 *pcchUnescaped = needed;
1354 if (ret == S_OK) {
1355 TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1356 debugstr_w(pszUrl) : debugstr_w(pszUnescaped));
1359 return ret;
1362 /*************************************************************************
1363 * UrlGetLocationA [SHLWAPI.@]
1365 * Get the location from a Url.
1367 * PARAMS
1368 * pszUrl [I] Url to get the location from
1370 * RETURNS
1371 * A pointer to the start of the location in pszUrl, or NULL if there is
1372 * no location.
1374 * NOTES
1375 * - MSDN erroneously states that "The location is the segment of the Url
1376 * starting with a '?' or '#' character". Neither V4 nor V5 of shlwapi.dll
1377 * stop at '?' and always return a NULL in this case.
1378 * - MSDN also erroneously states that "If a file URL has a query string,
1379 * the returned string is the query string". In all tested cases, if the
1380 * Url starts with "fi" then a NULL is returned. V5 gives the following results:
1381 *| Result Url
1382 *| ------ ---
1383 *| NULL file://aa/b/cd#hohoh
1384 *| #hohoh http://aa/b/cd#hohoh
1385 *| NULL fi://aa/b/cd#hohoh
1386 *| #hohoh ff://aa/b/cd#hohoh
1388 LPCSTR WINAPI UrlGetLocationA(
1389 LPCSTR pszUrl)
1391 PARSEDURLA base;
1392 DWORD res1;
1394 base.cbSize = sizeof(base);
1395 res1 = ParseURLA(pszUrl, &base);
1396 if (res1) return NULL; /* invalid scheme */
1398 /* if scheme is file: then never return pointer */
1399 if (strncmp(base.pszProtocol, "file", min(4,base.cchProtocol)) == 0) return NULL;
1401 /* Look for '#' and return its addr */
1402 return strchr(base.pszSuffix, '#');
1405 /*************************************************************************
1406 * UrlGetLocationW [SHLWAPI.@]
1408 * See UrlGetLocationA.
1410 LPCWSTR WINAPI UrlGetLocationW(
1411 LPCWSTR pszUrl)
1413 PARSEDURLW base;
1414 DWORD res1;
1416 base.cbSize = sizeof(base);
1417 res1 = ParseURLW(pszUrl, &base);
1418 if (res1) return NULL; /* invalid scheme */
1420 /* if scheme is file: then never return pointer */
1421 if (strncmpW(base.pszProtocol, fileW, min(4,base.cchProtocol)) == 0) return NULL;
1423 /* Look for '#' and return its addr */
1424 return strchrW(base.pszSuffix, '#');
1427 /*************************************************************************
1428 * UrlCompareA [SHLWAPI.@]
1430 * Compare two Urls.
1432 * PARAMS
1433 * pszUrl1 [I] First Url to compare
1434 * pszUrl2 [I] Url to compare to pszUrl1
1435 * fIgnoreSlash [I] TRUE = compare only up to a final slash
1437 * RETURNS
1438 * less than zero, zero, or greater than zero indicating pszUrl2 is greater
1439 * than, equal to, or less than pszUrl1 respectively.
1441 INT WINAPI UrlCompareA(
1442 LPCSTR pszUrl1,
1443 LPCSTR pszUrl2,
1444 BOOL fIgnoreSlash)
1446 INT ret, len, len1, len2;
1448 if (!fIgnoreSlash)
1449 return strcmp(pszUrl1, pszUrl2);
1450 len1 = strlen(pszUrl1);
1451 if (pszUrl1[len1-1] == '/') len1--;
1452 len2 = strlen(pszUrl2);
1453 if (pszUrl2[len2-1] == '/') len2--;
1454 if (len1 == len2)
1455 return strncmp(pszUrl1, pszUrl2, len1);
1456 len = min(len1, len2);
1457 ret = strncmp(pszUrl1, pszUrl2, len);
1458 if (ret) return ret;
1459 if (len1 > len2) return 1;
1460 return -1;
1463 /*************************************************************************
1464 * UrlCompareW [SHLWAPI.@]
1466 * See UrlCompareA.
1468 INT WINAPI UrlCompareW(
1469 LPCWSTR pszUrl1,
1470 LPCWSTR pszUrl2,
1471 BOOL fIgnoreSlash)
1473 INT ret;
1474 size_t len, len1, len2;
1476 if (!fIgnoreSlash)
1477 return strcmpW(pszUrl1, pszUrl2);
1478 len1 = strlenW(pszUrl1);
1479 if (pszUrl1[len1-1] == '/') len1--;
1480 len2 = strlenW(pszUrl2);
1481 if (pszUrl2[len2-1] == '/') len2--;
1482 if (len1 == len2)
1483 return strncmpW(pszUrl1, pszUrl2, len1);
1484 len = min(len1, len2);
1485 ret = strncmpW(pszUrl1, pszUrl2, len);
1486 if (ret) return ret;
1487 if (len1 > len2) return 1;
1488 return -1;
1491 /*************************************************************************
1492 * HashData [SHLWAPI.@]
1494 * Hash an input block into a variable sized digest.
1496 * PARAMS
1497 * lpSrc [I] Input block
1498 * nSrcLen [I] Length of lpSrc
1499 * lpDest [I] Output for hash digest
1500 * nDestLen [I] Length of lpDest
1502 * RETURNS
1503 * Success: TRUE. lpDest is filled with the computed hash value.
1504 * Failure: FALSE, if any argument is invalid.
1506 HRESULT WINAPI HashData(const unsigned char *lpSrc, DWORD nSrcLen,
1507 unsigned char *lpDest, DWORD nDestLen)
1509 INT srcCount = nSrcLen - 1, destCount = nDestLen - 1;
1511 if (!lpSrc || !lpDest)
1512 return E_INVALIDARG;
1514 while (destCount >= 0)
1516 lpDest[destCount] = (destCount & 0xff);
1517 destCount--;
1520 while (srcCount >= 0)
1522 destCount = nDestLen - 1;
1523 while (destCount >= 0)
1525 lpDest[destCount] = HashDataLookup[lpSrc[srcCount] ^ lpDest[destCount]];
1526 destCount--;
1528 srcCount--;
1530 return S_OK;
1533 /*************************************************************************
1534 * UrlHashA [SHLWAPI.@]
1536 * Produce a Hash from a Url.
1538 * PARAMS
1539 * pszUrl [I] Url to hash
1540 * lpDest [O] Destinationh for hash
1541 * nDestLen [I] Length of lpDest
1543 * RETURNS
1544 * Success: S_OK. lpDest is filled with the computed hash value.
1545 * Failure: E_INVALIDARG, if any argument is invalid.
1547 HRESULT WINAPI UrlHashA(LPCSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1549 if (IsBadStringPtrA(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1550 return E_INVALIDARG;
1552 HashData((const BYTE*)pszUrl, (int)strlen(pszUrl), lpDest, nDestLen);
1553 return S_OK;
1556 /*************************************************************************
1557 * UrlHashW [SHLWAPI.@]
1559 * See UrlHashA.
1561 HRESULT WINAPI UrlHashW(LPCWSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1563 char szUrl[MAX_PATH];
1565 TRACE("(%s,%p,%d)\n",debugstr_w(pszUrl), lpDest, nDestLen);
1567 if (IsBadStringPtrW(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1568 return E_INVALIDARG;
1570 /* Win32 hashes the data as an ASCII string, presumably so that both A+W
1571 * return the same digests for the same URL.
1573 WideCharToMultiByte(0, 0, pszUrl, -1, szUrl, MAX_PATH, 0, 0);
1574 HashData((const BYTE*)szUrl, (int)strlen(szUrl), lpDest, nDestLen);
1575 return S_OK;
1578 /*************************************************************************
1579 * UrlApplySchemeA [SHLWAPI.@]
1581 * Apply a scheme to a Url.
1583 * PARAMS
1584 * pszIn [I] Url to apply scheme to
1585 * pszOut [O] Destination for modified Url
1586 * pcchOut [I/O] Length of pszOut/destination for length of pszOut
1587 * dwFlags [I] URL_ flags from "shlwapi.h"
1589 * RETURNS
1590 * Success: S_OK: pszOut contains the modified Url, pcchOut contains its length.
1591 * Failure: An HRESULT error code describing the error.
1593 HRESULT WINAPI UrlApplySchemeA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1595 LPWSTR in, out;
1596 HRESULT ret;
1597 DWORD len;
1599 TRACE("(%s, %p, %p:out size %d, 0x%08x)\n", debugstr_a(pszIn),
1600 pszOut, pcchOut, pcchOut ? *pcchOut : 0, dwFlags);
1602 if (!pszIn || !pszOut || !pcchOut) return E_INVALIDARG;
1604 in = HeapAlloc(GetProcessHeap(), 0,
1605 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
1606 out = in + INTERNET_MAX_URL_LENGTH;
1608 MultiByteToWideChar(CP_ACP, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
1609 len = INTERNET_MAX_URL_LENGTH;
1611 ret = UrlApplySchemeW(in, out, &len, dwFlags);
1612 if (ret != S_OK) {
1613 HeapFree(GetProcessHeap(), 0, in);
1614 return ret;
1617 len = WideCharToMultiByte(CP_ACP, 0, out, -1, NULL, 0, NULL, NULL);
1618 if (len > *pcchOut) {
1619 ret = E_POINTER;
1620 goto cleanup;
1623 WideCharToMultiByte(CP_ACP, 0, out, -1, pszOut, *pcchOut, NULL, NULL);
1624 len--;
1626 cleanup:
1627 *pcchOut = len;
1628 HeapFree(GetProcessHeap(), 0, in);
1629 return ret;
1632 static HRESULT URL_GuessScheme(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1634 HKEY newkey;
1635 BOOL j;
1636 INT index;
1637 DWORD value_len, data_len, dwType, i;
1638 WCHAR reg_path[MAX_PATH];
1639 WCHAR value[MAX_PATH], data[MAX_PATH];
1640 WCHAR Wxx, Wyy;
1642 MultiByteToWideChar(0, 0,
1643 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes",
1644 -1, reg_path, MAX_PATH);
1645 RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
1646 index = 0;
1647 while(value_len = data_len = MAX_PATH,
1648 RegEnumValueW(newkey, index, value, &value_len,
1649 0, &dwType, (LPVOID)data, &data_len) == 0) {
1650 TRACE("guess %d %s is %s\n",
1651 index, debugstr_w(value), debugstr_w(data));
1653 j = FALSE;
1654 for(i=0; i<value_len; i++) {
1655 Wxx = pszIn[i];
1656 Wyy = value[i];
1657 /* remember that TRUE is not-equal */
1658 j = ChrCmpIW(Wxx, Wyy);
1659 if (j) break;
1661 if ((i == value_len) && !j) {
1662 if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1663 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1664 RegCloseKey(newkey);
1665 return E_POINTER;
1667 strcpyW(pszOut, data);
1668 strcatW(pszOut, pszIn);
1669 *pcchOut = strlenW(pszOut);
1670 TRACE("matched and set to %s\n", debugstr_w(pszOut));
1671 RegCloseKey(newkey);
1672 return S_OK;
1674 index++;
1676 RegCloseKey(newkey);
1677 return E_FAIL;
1680 static HRESULT URL_ApplyDefault(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1682 HKEY newkey;
1683 DWORD data_len, dwType;
1684 WCHAR data[MAX_PATH];
1686 static const WCHAR prefix_keyW[] =
1687 {'S','o','f','t','w','a','r','e',
1688 '\\','M','i','c','r','o','s','o','f','t',
1689 '\\','W','i','n','d','o','w','s',
1690 '\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n',
1691 '\\','U','R','L',
1692 '\\','D','e','f','a','u','l','t','P','r','e','f','i','x',0};
1694 /* get and prepend default */
1695 RegOpenKeyExW(HKEY_LOCAL_MACHINE, prefix_keyW, 0, 1, &newkey);
1696 data_len = sizeof(data);
1697 RegQueryValueExW(newkey, NULL, 0, &dwType, (LPBYTE)data, &data_len);
1698 RegCloseKey(newkey);
1699 if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1700 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1701 return E_POINTER;
1703 strcpyW(pszOut, data);
1704 strcatW(pszOut, pszIn);
1705 *pcchOut = strlenW(pszOut);
1706 TRACE("used default %s\n", debugstr_w(pszOut));
1707 return S_OK;
1710 /*************************************************************************
1711 * UrlApplySchemeW [SHLWAPI.@]
1713 * See UrlApplySchemeA.
1715 HRESULT WINAPI UrlApplySchemeW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1717 PARSEDURLW in_scheme;
1718 DWORD res1;
1719 HRESULT ret;
1721 TRACE("(%s, %p, %p:out size %d, 0x%08x)\n", debugstr_w(pszIn),
1722 pszOut, pcchOut, pcchOut ? *pcchOut : 0, dwFlags);
1724 if (!pszIn || !pszOut || !pcchOut) return E_INVALIDARG;
1726 if (dwFlags & URL_APPLY_GUESSFILE) {
1727 FIXME("(%s %p %p(%d) 0x%08x): stub URL_APPLY_GUESSFILE not implemented\n",
1728 debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwFlags);
1729 strcpyW(pszOut, pszIn);
1730 *pcchOut = strlenW(pszOut);
1731 return S_FALSE;
1734 in_scheme.cbSize = sizeof(in_scheme);
1735 /* See if the base has a scheme */
1736 res1 = ParseURLW(pszIn, &in_scheme);
1737 if (res1) {
1738 /* no scheme in input, need to see if we need to guess */
1739 if (dwFlags & URL_APPLY_GUESSSCHEME) {
1740 if ((ret = URL_GuessScheme(pszIn, pszOut, pcchOut)) != E_FAIL)
1741 return ret;
1744 else {
1745 /* we have a scheme, see if valid (known scheme) */
1746 if (in_scheme.nScheme) {
1747 /* have valid scheme, so just copy and exit */
1748 if (strlenW(pszIn) + 1 > *pcchOut) {
1749 *pcchOut = strlenW(pszIn) + 1;
1750 return E_POINTER;
1752 strcpyW(pszOut, pszIn);
1753 *pcchOut = strlenW(pszOut);
1754 TRACE("valid scheme, returning copy\n");
1755 return S_OK;
1759 /* If we are here, then either invalid scheme,
1760 * or no scheme and can't/failed guess.
1762 if ( ( ((res1 == 0) && (dwFlags & URL_APPLY_FORCEAPPLY)) ||
1763 ((res1 != 0)) ) &&
1764 (dwFlags & URL_APPLY_DEFAULT)) {
1765 /* find and apply default scheme */
1766 return URL_ApplyDefault(pszIn, pszOut, pcchOut);
1769 return S_FALSE;
1772 /*************************************************************************
1773 * UrlIsA [SHLWAPI.@]
1775 * Determine if a Url is of a certain class.
1777 * PARAMS
1778 * pszUrl [I] Url to check
1779 * Urlis [I] URLIS_ constant from "shlwapi.h"
1781 * RETURNS
1782 * TRUE if pszUrl belongs to the class type in Urlis.
1783 * FALSE Otherwise.
1785 BOOL WINAPI UrlIsA(LPCSTR pszUrl, URLIS Urlis)
1787 PARSEDURLA base;
1788 DWORD res1;
1789 LPCSTR last;
1791 TRACE("(%s %d)\n", debugstr_a(pszUrl), Urlis);
1793 if(!pszUrl)
1794 return FALSE;
1796 switch (Urlis) {
1798 case URLIS_OPAQUE:
1799 base.cbSize = sizeof(base);
1800 res1 = ParseURLA(pszUrl, &base);
1801 if (res1) return FALSE; /* invalid scheme */
1802 switch (base.nScheme)
1804 case URL_SCHEME_MAILTO:
1805 case URL_SCHEME_SHELL:
1806 case URL_SCHEME_JAVASCRIPT:
1807 case URL_SCHEME_VBSCRIPT:
1808 case URL_SCHEME_ABOUT:
1809 return TRUE;
1811 return FALSE;
1813 case URLIS_FILEURL:
1814 return !StrCmpNA("file:", pszUrl, 5);
1816 case URLIS_DIRECTORY:
1817 last = pszUrl + strlen(pszUrl) - 1;
1818 return (last >= pszUrl && (*last == '/' || *last == '\\' ));
1820 case URLIS_URL:
1821 return PathIsURLA(pszUrl);
1823 case URLIS_NOHISTORY:
1824 case URLIS_APPLIABLE:
1825 case URLIS_HASQUERY:
1826 default:
1827 FIXME("(%s %d): stub\n", debugstr_a(pszUrl), Urlis);
1829 return FALSE;
1832 /*************************************************************************
1833 * UrlIsW [SHLWAPI.@]
1835 * See UrlIsA.
1837 BOOL WINAPI UrlIsW(LPCWSTR pszUrl, URLIS Urlis)
1839 static const WCHAR stemp[] = { 'f','i','l','e',':',0 };
1840 PARSEDURLW base;
1841 DWORD res1;
1842 LPCWSTR last;
1844 TRACE("(%s %d)\n", debugstr_w(pszUrl), Urlis);
1846 if(!pszUrl)
1847 return FALSE;
1849 switch (Urlis) {
1851 case URLIS_OPAQUE:
1852 base.cbSize = sizeof(base);
1853 res1 = ParseURLW(pszUrl, &base);
1854 if (res1) return FALSE; /* invalid scheme */
1855 switch (base.nScheme)
1857 case URL_SCHEME_MAILTO:
1858 case URL_SCHEME_SHELL:
1859 case URL_SCHEME_JAVASCRIPT:
1860 case URL_SCHEME_VBSCRIPT:
1861 case URL_SCHEME_ABOUT:
1862 return TRUE;
1864 return FALSE;
1866 case URLIS_FILEURL:
1867 return !strncmpW(stemp, pszUrl, 5);
1869 case URLIS_DIRECTORY:
1870 last = pszUrl + strlenW(pszUrl) - 1;
1871 return (last >= pszUrl && (*last == '/' || *last == '\\'));
1873 case URLIS_URL:
1874 return PathIsURLW(pszUrl);
1876 case URLIS_NOHISTORY:
1877 case URLIS_APPLIABLE:
1878 case URLIS_HASQUERY:
1879 default:
1880 FIXME("(%s %d): stub\n", debugstr_w(pszUrl), Urlis);
1882 return FALSE;
1885 /*************************************************************************
1886 * UrlIsNoHistoryA [SHLWAPI.@]
1888 * Determine if a Url should not be stored in the users history list.
1890 * PARAMS
1891 * pszUrl [I] Url to check
1893 * RETURNS
1894 * TRUE, if pszUrl should be excluded from the history list,
1895 * FALSE otherwise.
1897 BOOL WINAPI UrlIsNoHistoryA(LPCSTR pszUrl)
1899 return UrlIsA(pszUrl, URLIS_NOHISTORY);
1902 /*************************************************************************
1903 * UrlIsNoHistoryW [SHLWAPI.@]
1905 * See UrlIsNoHistoryA.
1907 BOOL WINAPI UrlIsNoHistoryW(LPCWSTR pszUrl)
1909 return UrlIsW(pszUrl, URLIS_NOHISTORY);
1912 /*************************************************************************
1913 * UrlIsOpaqueA [SHLWAPI.@]
1915 * Determine if a Url is opaque.
1917 * PARAMS
1918 * pszUrl [I] Url to check
1920 * RETURNS
1921 * TRUE if pszUrl is opaque,
1922 * FALSE Otherwise.
1924 * NOTES
1925 * An opaque Url is one that does not start with "<protocol>://".
1927 BOOL WINAPI UrlIsOpaqueA(LPCSTR pszUrl)
1929 return UrlIsA(pszUrl, URLIS_OPAQUE);
1932 /*************************************************************************
1933 * UrlIsOpaqueW [SHLWAPI.@]
1935 * See UrlIsOpaqueA.
1937 BOOL WINAPI UrlIsOpaqueW(LPCWSTR pszUrl)
1939 return UrlIsW(pszUrl, URLIS_OPAQUE);
1942 /*************************************************************************
1943 * Scans for characters of type "type" and when not matching found,
1944 * returns pointer to it and length in size.
1946 * Characters tested based on RFC 1738
1948 static LPCWSTR URL_ScanID(LPCWSTR start, LPDWORD size, WINE_URL_SCAN_TYPE type)
1950 static DWORD alwayszero = 0;
1951 BOOL cont = TRUE;
1953 *size = 0;
1955 switch(type){
1957 case SCHEME:
1958 while (cont) {
1959 if ( (islowerW(*start) && isalphaW(*start)) ||
1960 isdigitW(*start) ||
1961 (*start == '+') ||
1962 (*start == '-') ||
1963 (*start == '.')) {
1964 start++;
1965 (*size)++;
1967 else
1968 cont = FALSE;
1971 if(*start != ':')
1972 *size = 0;
1974 break;
1976 case USERPASS:
1977 while (cont) {
1978 if ( isalphaW(*start) ||
1979 isdigitW(*start) ||
1980 /* user/password only characters */
1981 (*start == ';') ||
1982 (*start == '?') ||
1983 (*start == '&') ||
1984 (*start == '=') ||
1985 /* *extra* characters */
1986 (*start == '!') ||
1987 (*start == '*') ||
1988 (*start == '\'') ||
1989 (*start == '(') ||
1990 (*start == ')') ||
1991 (*start == ',') ||
1992 /* *safe* characters */
1993 (*start == '$') ||
1994 (*start == '_') ||
1995 (*start == '+') ||
1996 (*start == '-') ||
1997 (*start == '.') ||
1998 (*start == ' ')) {
1999 start++;
2000 (*size)++;
2001 } else if (*start == '%') {
2002 if (isxdigitW(*(start+1)) &&
2003 isxdigitW(*(start+2))) {
2004 start += 3;
2005 *size += 3;
2006 } else
2007 cont = FALSE;
2008 } else
2009 cont = FALSE;
2011 break;
2013 case PORT:
2014 while (cont) {
2015 if (isdigitW(*start)) {
2016 start++;
2017 (*size)++;
2019 else
2020 cont = FALSE;
2022 break;
2024 case HOST:
2025 while (cont) {
2026 if (isalnumW(*start) ||
2027 (*start == '-') ||
2028 (*start == '.') ||
2029 (*start == ' ') ) {
2030 start++;
2031 (*size)++;
2033 else
2034 cont = FALSE;
2036 break;
2037 default:
2038 FIXME("unknown type %d\n", type);
2039 return (LPWSTR)&alwayszero;
2041 /* TRACE("scanned %d characters next char %p<%c>\n",
2042 *size, start, *start); */
2043 return start;
2046 /*************************************************************************
2047 * Attempt to parse URL into pieces.
2049 static LONG URL_ParseUrl(LPCWSTR pszUrl, WINE_PARSE_URL *pl)
2051 LPCWSTR work;
2053 memset(pl, 0, sizeof(WINE_PARSE_URL));
2054 pl->pScheme = pszUrl;
2055 work = URL_ScanID(pl->pScheme, &pl->szScheme, SCHEME);
2056 if (!*work || (*work != ':')) goto ErrorExit;
2057 work++;
2058 if ((*work != '/') || (*(work+1) != '/')) goto SuccessExit;
2059 pl->pUserName = work + 2;
2060 work = URL_ScanID(pl->pUserName, &pl->szUserName, USERPASS);
2061 if (*work == ':' ) {
2062 /* parse password */
2063 work++;
2064 pl->pPassword = work;
2065 work = URL_ScanID(pl->pPassword, &pl->szPassword, USERPASS);
2066 if (*work != '@') {
2067 /* what we just parsed must be the hostname and port
2068 * so reset pointers and clear then let it parse */
2069 pl->szUserName = pl->szPassword = 0;
2070 work = pl->pUserName - 1;
2071 pl->pUserName = pl->pPassword = 0;
2073 } else if (*work == '@') {
2074 /* no password */
2075 pl->szPassword = 0;
2076 pl->pPassword = 0;
2077 } else if (!*work || (*work == '/') || (*work == '.')) {
2078 /* what was parsed was hostname, so reset pointers and let it parse */
2079 pl->szUserName = pl->szPassword = 0;
2080 work = pl->pUserName - 1;
2081 pl->pUserName = pl->pPassword = 0;
2082 } else goto ErrorExit;
2084 /* now start parsing hostname or hostnumber */
2085 work++;
2086 pl->pHostName = work;
2087 work = URL_ScanID(pl->pHostName, &pl->szHostName, HOST);
2088 if (*work == ':') {
2089 /* parse port */
2090 work++;
2091 pl->pPort = work;
2092 work = URL_ScanID(pl->pPort, &pl->szPort, PORT);
2094 if (*work == '/') {
2095 /* see if query string */
2096 pl->pQuery = strchrW(work, '?');
2097 if (pl->pQuery) pl->szQuery = strlenW(pl->pQuery);
2099 SuccessExit:
2100 TRACE("parse successful: scheme=%p(%d), user=%p(%d), pass=%p(%d), host=%p(%d), port=%p(%d), query=%p(%d)\n",
2101 pl->pScheme, pl->szScheme,
2102 pl->pUserName, pl->szUserName,
2103 pl->pPassword, pl->szPassword,
2104 pl->pHostName, pl->szHostName,
2105 pl->pPort, pl->szPort,
2106 pl->pQuery, pl->szQuery);
2107 return S_OK;
2108 ErrorExit:
2109 FIXME("failed to parse %s\n", debugstr_w(pszUrl));
2110 return E_INVALIDARG;
2113 /*************************************************************************
2114 * UrlGetPartA [SHLWAPI.@]
2116 * Retrieve part of a Url.
2118 * PARAMS
2119 * pszIn [I] Url to parse
2120 * pszOut [O] Destination for part of pszIn requested
2121 * pcchOut [I] Size of pszOut
2122 * [O] length of pszOut string EXCLUDING '\0' if S_OK, otherwise
2123 * needed size of pszOut INCLUDING '\0'.
2124 * dwPart [I] URL_PART_ enum from "shlwapi.h"
2125 * dwFlags [I] URL_ flags from "shlwapi.h"
2127 * RETURNS
2128 * Success: S_OK. pszOut contains the part requested, pcchOut contains its length.
2129 * Failure: An HRESULT error code describing the error.
2131 HRESULT WINAPI UrlGetPartA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut,
2132 DWORD dwPart, DWORD dwFlags)
2134 LPWSTR in, out;
2135 DWORD ret, len, len2;
2137 if(!pszIn || !pszOut || !pcchOut || *pcchOut <= 0)
2138 return E_INVALIDARG;
2140 in = HeapAlloc(GetProcessHeap(), 0,
2141 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
2142 out = in + INTERNET_MAX_URL_LENGTH;
2144 MultiByteToWideChar(0, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
2146 len = INTERNET_MAX_URL_LENGTH;
2147 ret = UrlGetPartW(in, out, &len, dwPart, dwFlags);
2149 if (FAILED(ret)) {
2150 HeapFree(GetProcessHeap(), 0, in);
2151 return ret;
2154 len2 = WideCharToMultiByte(0, 0, out, len, 0, 0, 0, 0);
2155 if (len2 > *pcchOut) {
2156 *pcchOut = len2+1;
2157 HeapFree(GetProcessHeap(), 0, in);
2158 return E_POINTER;
2160 len2 = WideCharToMultiByte(0, 0, out, len+1, pszOut, *pcchOut, 0, 0);
2161 *pcchOut = len2-1;
2162 HeapFree(GetProcessHeap(), 0, in);
2163 return ret;
2166 /*************************************************************************
2167 * UrlGetPartW [SHLWAPI.@]
2169 * See UrlGetPartA.
2171 HRESULT WINAPI UrlGetPartW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut,
2172 DWORD dwPart, DWORD dwFlags)
2174 WINE_PARSE_URL pl;
2175 HRESULT ret;
2176 DWORD scheme, size, schsize;
2177 LPCWSTR addr, schaddr;
2179 TRACE("(%s %p %p(%d) %08x %08x)\n",
2180 debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwPart, dwFlags);
2182 if(!pszIn || !pszOut || !pcchOut || *pcchOut <= 0)
2183 return E_INVALIDARG;
2185 *pszOut = '\0';
2187 addr = strchrW(pszIn, ':');
2188 if(!addr)
2189 scheme = URL_SCHEME_UNKNOWN;
2190 else
2191 scheme = get_scheme_code(pszIn, addr-pszIn);
2193 ret = URL_ParseUrl(pszIn, &pl);
2195 switch (dwPart) {
2196 case URL_PART_SCHEME:
2197 if (!pl.szScheme) {
2198 *pcchOut = 0;
2199 return S_FALSE;
2201 addr = pl.pScheme;
2202 size = pl.szScheme;
2203 break;
2205 case URL_PART_HOSTNAME:
2206 switch(scheme) {
2207 case URL_SCHEME_FTP:
2208 case URL_SCHEME_HTTP:
2209 case URL_SCHEME_GOPHER:
2210 case URL_SCHEME_TELNET:
2211 case URL_SCHEME_FILE:
2212 case URL_SCHEME_HTTPS:
2213 break;
2214 default:
2215 *pcchOut = 0;
2216 return E_FAIL;
2219 if(scheme==URL_SCHEME_FILE && (!pl.szHostName ||
2220 (pl.szHostName==1 && *(pl.pHostName+1)==':'))) {
2221 *pcchOut = 0;
2222 return S_FALSE;
2225 if (!pl.szHostName) {
2226 *pcchOut = 0;
2227 return S_FALSE;
2229 addr = pl.pHostName;
2230 size = pl.szHostName;
2231 break;
2233 case URL_PART_USERNAME:
2234 if (!pl.szUserName) {
2235 *pcchOut = 0;
2236 return S_FALSE;
2238 addr = pl.pUserName;
2239 size = pl.szUserName;
2240 break;
2242 case URL_PART_PASSWORD:
2243 if (!pl.szPassword) {
2244 *pcchOut = 0;
2245 return S_FALSE;
2247 addr = pl.pPassword;
2248 size = pl.szPassword;
2249 break;
2251 case URL_PART_PORT:
2252 if (!pl.szPort) {
2253 *pcchOut = 0;
2254 return S_FALSE;
2256 addr = pl.pPort;
2257 size = pl.szPort;
2258 break;
2260 case URL_PART_QUERY:
2261 if (!pl.szQuery) {
2262 *pcchOut = 0;
2263 return S_FALSE;
2265 addr = pl.pQuery;
2266 size = pl.szQuery;
2267 break;
2269 default:
2270 *pcchOut = 0;
2271 return E_INVALIDARG;
2274 if (dwFlags == URL_PARTFLAG_KEEPSCHEME) {
2275 if(!pl.pScheme || !pl.szScheme) {
2276 *pcchOut = 0;
2277 return E_FAIL;
2279 schaddr = pl.pScheme;
2280 schsize = pl.szScheme;
2281 if (*pcchOut < schsize + size + 2) {
2282 *pcchOut = schsize + size + 2;
2283 return E_POINTER;
2285 memcpy(pszOut, schaddr, schsize*sizeof(WCHAR));
2286 pszOut[schsize] = ':';
2287 memcpy(pszOut+schsize+1, addr, size*sizeof(WCHAR));
2288 pszOut[schsize+1+size] = 0;
2289 *pcchOut = schsize + 1 + size;
2291 else {
2292 if (*pcchOut < size + 1) {*pcchOut = size+1; return E_POINTER;}
2293 memcpy(pszOut, addr, size*sizeof(WCHAR));
2294 pszOut[size] = 0;
2295 *pcchOut = size;
2297 TRACE("len=%d %s\n", *pcchOut, debugstr_w(pszOut));
2299 return ret;
2302 /*************************************************************************
2303 * PathIsURLA [SHLWAPI.@]
2305 * Check if the given path is a Url.
2307 * PARAMS
2308 * lpszPath [I] Path to check.
2310 * RETURNS
2311 * TRUE if lpszPath is a Url.
2312 * FALSE if lpszPath is NULL or not a Url.
2314 BOOL WINAPI PathIsURLA(LPCSTR lpstrPath)
2316 PARSEDURLA base;
2317 HRESULT hres;
2319 TRACE("%s\n", debugstr_a(lpstrPath));
2321 if (!lpstrPath || !*lpstrPath) return FALSE;
2323 /* get protocol */
2324 base.cbSize = sizeof(base);
2325 hres = ParseURLA(lpstrPath, &base);
2326 return hres == S_OK && (base.nScheme != URL_SCHEME_INVALID);
2329 /*************************************************************************
2330 * PathIsURLW [SHLWAPI.@]
2332 * See PathIsURLA.
2334 BOOL WINAPI PathIsURLW(LPCWSTR lpstrPath)
2336 PARSEDURLW base;
2337 HRESULT hres;
2339 TRACE("%s\n", debugstr_w(lpstrPath));
2341 if (!lpstrPath || !*lpstrPath) return FALSE;
2343 /* get protocol */
2344 base.cbSize = sizeof(base);
2345 hres = ParseURLW(lpstrPath, &base);
2346 return hres == S_OK && (base.nScheme != URL_SCHEME_INVALID);
2349 /*************************************************************************
2350 * UrlCreateFromPathA [SHLWAPI.@]
2352 * See UrlCreateFromPathW
2354 HRESULT WINAPI UrlCreateFromPathA(LPCSTR pszPath, LPSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2356 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
2357 WCHAR *urlW = bufW;
2358 UNICODE_STRING pathW;
2359 HRESULT ret;
2360 DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
2362 if(!RtlCreateUnicodeStringFromAsciiz(&pathW, pszPath))
2363 return E_INVALIDARG;
2364 if((ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved)) == E_POINTER) {
2365 urlW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
2366 ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved);
2368 if(ret == S_OK || ret == S_FALSE) {
2369 RtlUnicodeToMultiByteSize(&lenA, urlW, lenW * sizeof(WCHAR));
2370 if(*pcchUrl > lenA) {
2371 RtlUnicodeToMultiByteN(pszUrl, *pcchUrl - 1, &lenA, urlW, lenW * sizeof(WCHAR));
2372 pszUrl[lenA] = 0;
2373 *pcchUrl = lenA;
2374 } else {
2375 *pcchUrl = lenA + 1;
2376 ret = E_POINTER;
2379 if(urlW != bufW) HeapFree(GetProcessHeap(), 0, urlW);
2380 RtlFreeUnicodeString(&pathW);
2381 return ret;
2384 /*************************************************************************
2385 * UrlCreateFromPathW [SHLWAPI.@]
2387 * Create a Url from a file path.
2389 * PARAMS
2390 * pszPath [I] Path to convert
2391 * pszUrl [O] Destination for the converted Url
2392 * pcchUrl [I/O] Length of pszUrl
2393 * dwReserved [I] Reserved, must be 0
2395 * RETURNS
2396 * Success: S_OK pszUrl contains the converted path, S_FALSE if the path is already a Url
2397 * Failure: An HRESULT error code.
2399 HRESULT WINAPI UrlCreateFromPathW(LPCWSTR pszPath, LPWSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2401 DWORD needed;
2402 HRESULT ret;
2403 WCHAR *pszNewUrl;
2404 WCHAR file_colonW[] = {'f','i','l','e',':',0};
2405 WCHAR three_slashesW[] = {'/','/','/',0};
2406 PARSEDURLW parsed_url;
2408 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszPath), pszUrl, pcchUrl, dwReserved);
2410 /* Validate arguments */
2411 if (dwReserved != 0)
2412 return E_INVALIDARG;
2413 if (!pszUrl || !pcchUrl)
2414 return E_INVALIDARG;
2417 parsed_url.cbSize = sizeof(parsed_url);
2418 if(ParseURLW(pszPath, &parsed_url) == S_OK) {
2419 if(parsed_url.nScheme != URL_SCHEME_INVALID && parsed_url.cchProtocol > 1) {
2420 needed = strlenW(pszPath);
2421 if (needed >= *pcchUrl) {
2422 *pcchUrl = needed + 1;
2423 return E_POINTER;
2424 } else {
2425 *pcchUrl = needed;
2426 strcpyW(pszUrl, pszPath);
2427 return S_FALSE;
2432 pszNewUrl = HeapAlloc(GetProcessHeap(), 0, (strlenW(pszPath) + 9) * sizeof(WCHAR)); /* "file:///" + pszPath_len + 1 */
2433 strcpyW(pszNewUrl, file_colonW);
2434 if(isalphaW(pszPath[0]) && pszPath[1] == ':')
2435 strcatW(pszNewUrl, three_slashesW);
2436 strcatW(pszNewUrl, pszPath);
2437 ret = UrlEscapeW(pszNewUrl, pszUrl, pcchUrl, URL_ESCAPE_PERCENT);
2439 HeapFree(GetProcessHeap(), 0, pszNewUrl);
2440 return ret;
2443 /*************************************************************************
2444 * SHAutoComplete [SHLWAPI.@]
2446 * Enable auto-completion for an edit control.
2448 * PARAMS
2449 * hwndEdit [I] Handle of control to enable auto-completion for
2450 * dwFlags [I] SHACF_ flags from "shlwapi.h"
2452 * RETURNS
2453 * Success: S_OK. Auto-completion is enabled for the control.
2454 * Failure: An HRESULT error code indicating the error.
2456 HRESULT WINAPI SHAutoComplete(HWND hwndEdit, DWORD dwFlags)
2458 FIXME("stub\n");
2459 return S_FALSE;
2462 /*************************************************************************
2463 * MLBuildResURLA [SHLWAPI.405]
2465 * Create a Url pointing to a resource in a module.
2467 * PARAMS
2468 * lpszLibName [I] Name of the module containing the resource
2469 * hMod [I] Callers module handle
2470 * dwFlags [I] Undocumented flags for loading the module
2471 * lpszRes [I] Resource name
2472 * lpszDest [O] Destination for resulting Url
2473 * dwDestLen [I] Length of lpszDest
2475 * RETURNS
2476 * Success: S_OK. lpszDest contains the resource Url.
2477 * Failure: E_INVALIDARG, if any argument is invalid, or
2478 * E_FAIL if dwDestLen is too small.
2480 HRESULT WINAPI MLBuildResURLA(LPCSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2481 LPCSTR lpszRes, LPSTR lpszDest, DWORD dwDestLen)
2483 WCHAR szLibName[MAX_PATH], szRes[MAX_PATH], szDest[MAX_PATH];
2484 HRESULT hRet;
2486 if (lpszLibName)
2487 MultiByteToWideChar(CP_ACP, 0, lpszLibName, -1, szLibName, sizeof(szLibName)/sizeof(WCHAR));
2489 if (lpszRes)
2490 MultiByteToWideChar(CP_ACP, 0, lpszRes, -1, szRes, sizeof(szRes)/sizeof(WCHAR));
2492 if (dwDestLen > sizeof(szLibName)/sizeof(WCHAR))
2493 dwDestLen = sizeof(szLibName)/sizeof(WCHAR);
2495 hRet = MLBuildResURLW(lpszLibName ? szLibName : NULL, hMod, dwFlags,
2496 lpszRes ? szRes : NULL, lpszDest ? szDest : NULL, dwDestLen);
2497 if (SUCCEEDED(hRet) && lpszDest)
2498 WideCharToMultiByte(CP_ACP, 0, szDest, -1, lpszDest, dwDestLen, 0, 0);
2500 return hRet;
2503 /*************************************************************************
2504 * MLBuildResURLA [SHLWAPI.406]
2506 * See MLBuildResURLA.
2508 HRESULT WINAPI MLBuildResURLW(LPCWSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2509 LPCWSTR lpszRes, LPWSTR lpszDest, DWORD dwDestLen)
2511 static const WCHAR szRes[] = { 'r','e','s',':','/','/','\0' };
2512 #define szResLen ((sizeof(szRes) - sizeof(WCHAR))/sizeof(WCHAR))
2513 HRESULT hRet = E_FAIL;
2515 TRACE("(%s,%p,0x%08x,%s,%p,%d)\n", debugstr_w(lpszLibName), hMod, dwFlags,
2516 debugstr_w(lpszRes), lpszDest, dwDestLen);
2518 if (!lpszLibName || !hMod || hMod == INVALID_HANDLE_VALUE || !lpszRes ||
2519 !lpszDest || (dwFlags && dwFlags != 2))
2520 return E_INVALIDARG;
2522 if (dwDestLen >= szResLen + 1)
2524 dwDestLen -= (szResLen + 1);
2525 memcpy(lpszDest, szRes, sizeof(szRes));
2527 hMod = MLLoadLibraryW(lpszLibName, hMod, dwFlags);
2529 if (hMod)
2531 WCHAR szBuff[MAX_PATH];
2532 DWORD len;
2534 len = GetModuleFileNameW(hMod, szBuff, sizeof(szBuff)/sizeof(WCHAR));
2535 if (len && len < sizeof(szBuff)/sizeof(WCHAR))
2537 DWORD dwPathLen = strlenW(szBuff) + 1;
2539 if (dwDestLen >= dwPathLen)
2541 DWORD dwResLen;
2543 dwDestLen -= dwPathLen;
2544 memcpy(lpszDest + szResLen, szBuff, dwPathLen * sizeof(WCHAR));
2546 dwResLen = strlenW(lpszRes) + 1;
2547 if (dwDestLen >= dwResLen + 1)
2549 lpszDest[szResLen + dwPathLen-1] = '/';
2550 memcpy(lpszDest + szResLen + dwPathLen, lpszRes, dwResLen * sizeof(WCHAR));
2551 hRet = S_OK;
2555 MLFreeLibrary(hMod);
2558 return hRet;
2561 /***********************************************************************
2562 * UrlFixupW [SHLWAPI.462]
2564 * Checks the scheme part of a URL and attempts to correct misspellings.
2566 * PARAMS
2567 * lpszUrl [I] Pointer to the URL to be corrected
2568 * lpszTranslatedUrl [O] Pointer to a buffer to store corrected URL
2569 * dwMaxChars [I] Maximum size of corrected URL
2571 * RETURNS
2572 * success: S_OK if URL corrected or already correct
2573 * failure: S_FALSE if unable to correct / COM error code if other error
2576 HRESULT WINAPI UrlFixupW(LPCWSTR url, LPWSTR translatedUrl, DWORD maxChars)
2578 DWORD srcLen;
2580 FIXME("(%s,%p,%d) STUB\n", debugstr_w(url), translatedUrl, maxChars);
2582 if (!url)
2583 return E_FAIL;
2585 srcLen = lstrlenW(url) + 1;
2587 /* For now just copy the URL directly */
2588 lstrcpynW(translatedUrl, url, (maxChars < srcLen) ? maxChars : srcLen);
2590 return S_OK;