msxml3/tests: Tests for IMXAttributes::clear().
[wine/multimedia.git] / dlls / shlwapi / url.c
blobd43bea5a791f049696888812db7e0b04cef959e4
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 static inline WCHAR *heap_strdupAtoW(const char *str)
47 LPWSTR ret = NULL;
49 if(str) {
50 DWORD len;
52 len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
53 ret = HeapAlloc(GetProcessHeap(), 0, len*sizeof(WCHAR));
54 MultiByteToWideChar(CP_ACP, 0, str, -1, ret, len);
57 return ret;
60 /* The following schemes were identified in the native version of
61 * SHLWAPI.DLL version 5.50
63 static const struct {
64 URL_SCHEME scheme_number;
65 WCHAR scheme_name[12];
66 } shlwapi_schemes[] = {
67 {URL_SCHEME_FTP, {'f','t','p',0}},
68 {URL_SCHEME_HTTP, {'h','t','t','p',0}},
69 {URL_SCHEME_GOPHER, {'g','o','p','h','e','r',0}},
70 {URL_SCHEME_MAILTO, {'m','a','i','l','t','o',0}},
71 {URL_SCHEME_NEWS, {'n','e','w','s',0}},
72 {URL_SCHEME_NNTP, {'n','n','t','p',0}},
73 {URL_SCHEME_TELNET, {'t','e','l','n','e','t',0}},
74 {URL_SCHEME_WAIS, {'w','a','i','s',0}},
75 {URL_SCHEME_FILE, {'f','i','l','e',0}},
76 {URL_SCHEME_MK, {'m','k',0}},
77 {URL_SCHEME_HTTPS, {'h','t','t','p','s',0}},
78 {URL_SCHEME_SHELL, {'s','h','e','l','l',0}},
79 {URL_SCHEME_SNEWS, {'s','n','e','w','s',0}},
80 {URL_SCHEME_LOCAL, {'l','o','c','a','l',0}},
81 {URL_SCHEME_JAVASCRIPT, {'j','a','v','a','s','c','r','i','p','t',0}},
82 {URL_SCHEME_VBSCRIPT, {'v','b','s','c','r','i','p','t',0}},
83 {URL_SCHEME_ABOUT, {'a','b','o','u','t',0}},
84 {URL_SCHEME_RES, {'r','e','s',0}},
87 typedef struct {
88 LPCWSTR pScheme; /* [out] start of scheme */
89 DWORD szScheme; /* [out] size of scheme (until colon) */
90 LPCWSTR pUserName; /* [out] start of Username */
91 DWORD szUserName; /* [out] size of Username (until ":" or "@") */
92 LPCWSTR pPassword; /* [out] start of Password */
93 DWORD szPassword; /* [out] size of Password (until "@") */
94 LPCWSTR pHostName; /* [out] start of Hostname */
95 DWORD szHostName; /* [out] size of Hostname (until ":" or "/") */
96 LPCWSTR pPort; /* [out] start of Port */
97 DWORD szPort; /* [out] size of Port (until "/" or eos) */
98 LPCWSTR pQuery; /* [out] start of Query */
99 DWORD szQuery; /* [out] size of Query (until eos) */
100 } WINE_PARSE_URL;
102 typedef enum {
103 SCHEME,
104 HOST,
105 PORT,
106 USERPASS,
107 } WINE_URL_SCAN_TYPE;
109 static const CHAR hexDigits[] = "0123456789ABCDEF";
111 static const WCHAR fileW[] = {'f','i','l','e','\0'};
113 static const unsigned char HashDataLookup[256] = {
114 0x01, 0x0E, 0x6E, 0x19, 0x61, 0xAE, 0x84, 0x77, 0x8A, 0xAA, 0x7D, 0x76, 0x1B,
115 0xE9, 0x8C, 0x33, 0x57, 0xC5, 0xB1, 0x6B, 0xEA, 0xA9, 0x38, 0x44, 0x1E, 0x07,
116 0xAD, 0x49, 0xBC, 0x28, 0x24, 0x41, 0x31, 0xD5, 0x68, 0xBE, 0x39, 0xD3, 0x94,
117 0xDF, 0x30, 0x73, 0x0F, 0x02, 0x43, 0xBA, 0xD2, 0x1C, 0x0C, 0xB5, 0x67, 0x46,
118 0x16, 0x3A, 0x4B, 0x4E, 0xB7, 0xA7, 0xEE, 0x9D, 0x7C, 0x93, 0xAC, 0x90, 0xB0,
119 0xA1, 0x8D, 0x56, 0x3C, 0x42, 0x80, 0x53, 0x9C, 0xF1, 0x4F, 0x2E, 0xA8, 0xC6,
120 0x29, 0xFE, 0xB2, 0x55, 0xFD, 0xED, 0xFA, 0x9A, 0x85, 0x58, 0x23, 0xCE, 0x5F,
121 0x74, 0xFC, 0xC0, 0x36, 0xDD, 0x66, 0xDA, 0xFF, 0xF0, 0x52, 0x6A, 0x9E, 0xC9,
122 0x3D, 0x03, 0x59, 0x09, 0x2A, 0x9B, 0x9F, 0x5D, 0xA6, 0x50, 0x32, 0x22, 0xAF,
123 0xC3, 0x64, 0x63, 0x1A, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xBD, 0x79, 0x40,
124 0x4D, 0x48, 0xD0, 0xF5, 0x82, 0x7A, 0x8F, 0x37, 0x69, 0x86, 0x1D, 0xA4, 0xB9,
125 0xC2, 0xC1, 0xEF, 0x65, 0xF2, 0x05, 0xAB, 0x7E, 0x0B, 0x4A, 0x3B, 0x89, 0xE4,
126 0x6C, 0xBF, 0xE8, 0x8B, 0x06, 0x18, 0x51, 0x14, 0x7F, 0x11, 0x5B, 0x5C, 0xFB,
127 0x97, 0xE1, 0xCF, 0x15, 0x62, 0x71, 0x70, 0x54, 0xE2, 0x12, 0xD6, 0xC7, 0xBB,
128 0x0D, 0x20, 0x5E, 0xDC, 0xE0, 0xD4, 0xF7, 0xCC, 0xC4, 0x2B, 0xF9, 0xEC, 0x2D,
129 0xF4, 0x6F, 0xB6, 0x99, 0x88, 0x81, 0x5A, 0xD9, 0xCA, 0x13, 0xA5, 0xE7, 0x47,
130 0xE6, 0x8E, 0x60, 0xE3, 0x3E, 0xB3, 0xF6, 0x72, 0xA2, 0x35, 0xA0, 0xD7, 0xCD,
131 0xB4, 0x2F, 0x6D, 0x2C, 0x26, 0x1F, 0x95, 0x87, 0x00, 0xD8, 0x34, 0x3F, 0x17,
132 0x25, 0x45, 0x27, 0x75, 0x92, 0xB8, 0xA3, 0xC8, 0xDE, 0xEB, 0xF8, 0xF3, 0xDB,
133 0x0A, 0x98, 0x83, 0x7B, 0xE5, 0xCB, 0x4C, 0x78, 0xD1 };
135 static DWORD get_scheme_code(LPCWSTR scheme, DWORD scheme_len)
137 unsigned int i;
139 for(i=0; i < sizeof(shlwapi_schemes)/sizeof(shlwapi_schemes[0]); i++) {
140 if(scheme_len == strlenW(shlwapi_schemes[i].scheme_name)
141 && !memcmp(scheme, shlwapi_schemes[i].scheme_name, scheme_len*sizeof(WCHAR)))
142 return shlwapi_schemes[i].scheme_number;
145 return URL_SCHEME_UNKNOWN;
148 /*************************************************************************
149 * @ [SHLWAPI.1]
151 * Parse a Url into its constituent parts.
153 * PARAMS
154 * x [I] Url to parse
155 * y [O] Undocumented structure holding the parsed information
157 * RETURNS
158 * Success: S_OK. y contains the parsed Url details.
159 * Failure: An HRESULT error code.
161 HRESULT WINAPI ParseURLA(LPCSTR x, PARSEDURLA *y)
163 WCHAR scheme[INTERNET_MAX_SCHEME_LENGTH];
164 const char *ptr = x;
165 int len;
167 TRACE("%s %p\n", debugstr_a(x), y);
169 if(y->cbSize != sizeof(*y))
170 return E_INVALIDARG;
172 while(*ptr && (isalnum(*ptr) || *ptr == '-'))
173 ptr++;
175 if (*ptr != ':' || ptr <= x+1) {
176 y->pszProtocol = NULL;
177 return URL_E_INVALID_SYNTAX;
180 y->pszProtocol = x;
181 y->cchProtocol = ptr-x;
182 y->pszSuffix = ptr+1;
183 y->cchSuffix = strlen(y->pszSuffix);
185 len = MultiByteToWideChar(CP_ACP, 0, x, ptr-x,
186 scheme, sizeof(scheme)/sizeof(WCHAR));
187 y->nScheme = get_scheme_code(scheme, len);
189 return S_OK;
192 /*************************************************************************
193 * @ [SHLWAPI.2]
195 * Unicode version of ParseURLA.
197 HRESULT WINAPI ParseURLW(LPCWSTR x, PARSEDURLW *y)
199 const WCHAR *ptr = x;
201 TRACE("%s %p\n", debugstr_w(x), y);
203 if(y->cbSize != sizeof(*y))
204 return E_INVALIDARG;
206 while(*ptr && (isalnumW(*ptr) || *ptr == '-'))
207 ptr++;
209 if (*ptr != ':' || ptr <= x+1) {
210 y->pszProtocol = NULL;
211 return URL_E_INVALID_SYNTAX;
214 y->pszProtocol = x;
215 y->cchProtocol = ptr-x;
216 y->pszSuffix = ptr+1;
217 y->cchSuffix = strlenW(y->pszSuffix);
218 y->nScheme = get_scheme_code(x, ptr-x);
220 return S_OK;
223 /*************************************************************************
224 * UrlCanonicalizeA [SHLWAPI.@]
226 * Canonicalize a Url.
228 * PARAMS
229 * pszUrl [I] Url to cCanonicalize
230 * pszCanonicalized [O] Destination for converted Url.
231 * pcchCanonicalized [I/O] Length of pszUrl, destination for length of pszCanonicalized
232 * dwFlags [I] Flags controlling the conversion.
234 * RETURNS
235 * Success: S_OK. The pszCanonicalized contains the converted Url.
236 * Failure: E_POINTER, if *pcchCanonicalized is too small.
238 * MSDN incorrectly describes the flags for this function. They should be:
239 *| URL_DONT_ESCAPE_EXTRA_INFO 0x02000000
240 *| URL_ESCAPE_SPACES_ONLY 0x04000000
241 *| URL_ESCAPE_PERCENT 0x00001000
242 *| URL_ESCAPE_UNSAFE 0x10000000
243 *| URL_UNESCAPE 0x10000000
244 *| URL_DONT_SIMPLIFY 0x08000000
245 *| URL_ESCAPE_SEGMENT_ONLY 0x00002000
247 HRESULT WINAPI UrlCanonicalizeA(LPCSTR pszUrl, LPSTR pszCanonicalized,
248 LPDWORD pcchCanonicalized, DWORD dwFlags)
250 LPWSTR url, canonical;
251 HRESULT ret;
253 TRACE("(%s, %p, %p, 0x%08x) *pcchCanonicalized: %d\n", debugstr_a(pszUrl), pszCanonicalized,
254 pcchCanonicalized, dwFlags, pcchCanonicalized ? *pcchCanonicalized : -1);
256 if(!pszUrl || !pszCanonicalized || !pcchCanonicalized || !*pcchCanonicalized)
257 return E_INVALIDARG;
259 url = heap_strdupAtoW(pszUrl);
260 canonical = HeapAlloc(GetProcessHeap(), 0, *pcchCanonicalized*sizeof(WCHAR));
261 if(!url || !canonical) {
262 HeapFree(GetProcessHeap(), 0, url);
263 HeapFree(GetProcessHeap(), 0, canonical);
264 return E_OUTOFMEMORY;
267 ret = UrlCanonicalizeW(url, canonical, pcchCanonicalized, dwFlags);
268 if(ret == S_OK)
269 WideCharToMultiByte(0, 0, canonical, -1, pszCanonicalized,
270 *pcchCanonicalized+1, 0, 0);
272 HeapFree(GetProcessHeap(), 0, url);
273 HeapFree(GetProcessHeap(), 0, canonical);
274 return ret;
277 /*************************************************************************
278 * UrlCanonicalizeW [SHLWAPI.@]
280 * See UrlCanonicalizeA.
282 HRESULT WINAPI UrlCanonicalizeW(LPCWSTR pszUrl, LPWSTR pszCanonicalized,
283 LPDWORD pcchCanonicalized, DWORD dwFlags)
285 HRESULT hr = S_OK;
286 DWORD EscapeFlags;
287 LPCWSTR wk1, root;
288 LPWSTR lpszUrlCpy, url, wk2, mp, mp2;
289 INT state;
290 DWORD nByteLen, nLen, nWkLen;
291 BOOL is_file_url;
292 WCHAR slash = '\0';
294 static const WCHAR wszFile[] = {'f','i','l','e',':'};
295 static const WCHAR wszRes[] = {'r','e','s',':'};
296 static const WCHAR wszHttp[] = {'h','t','t','p',':'};
297 static const WCHAR wszLocalhost[] = {'l','o','c','a','l','h','o','s','t'};
298 static const WCHAR wszFilePrefix[] = {'f','i','l','e',':','/','/','/'};
300 TRACE("(%s, %p, %p, 0x%08x) *pcchCanonicalized: %d\n", debugstr_w(pszUrl), pszCanonicalized,
301 pcchCanonicalized, dwFlags, pcchCanonicalized ? *pcchCanonicalized : -1);
303 if(!pszUrl || !pszCanonicalized || !pcchCanonicalized || !*pcchCanonicalized)
304 return E_INVALIDARG;
306 if(!*pszUrl) {
307 *pszCanonicalized = 0;
308 return S_OK;
311 /* Remove '\t' characters from URL */
312 nByteLen = (strlenW(pszUrl) + 1) * sizeof(WCHAR); /* length in bytes */
313 url = HeapAlloc(GetProcessHeap(), 0, nByteLen);
314 if(!url)
315 return E_OUTOFMEMORY;
317 wk1 = pszUrl;
318 wk2 = url;
319 do {
320 while(*wk1 == '\t')
321 wk1++;
322 *wk2++ = *wk1;
323 } while(*wk1++);
325 /* Allocate memory for simplified URL (before escaping) */
326 nByteLen = (wk2-url)*sizeof(WCHAR);
327 lpszUrlCpy = HeapAlloc(GetProcessHeap(), 0,
328 nByteLen+sizeof(wszFilePrefix)+sizeof(WCHAR));
329 if(!lpszUrlCpy) {
330 HeapFree(GetProcessHeap(), 0, url);
331 return E_OUTOFMEMORY;
334 is_file_url = !strncmpW(wszFile, url, sizeof(wszFile)/sizeof(WCHAR));
336 if ((nByteLen >= sizeof(wszHttp) &&
337 !memcmp(wszHttp, url, sizeof(wszHttp))) || is_file_url)
338 slash = '/';
340 if((dwFlags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY)) && is_file_url)
341 slash = '\\';
343 if(nByteLen >= sizeof(wszRes) && !memcmp(wszRes, url, sizeof(wszRes))) {
344 dwFlags &= ~URL_FILE_USE_PATHURL;
345 slash = '\0';
349 * state =
350 * 0 initial 1,3
351 * 1 have 2[+] alnum 2,3
352 * 2 have scheme (found :) 4,6,3
353 * 3 failed (no location)
354 * 4 have // 5,3
355 * 5 have 1[+] alnum 6,3
356 * 6 have location (found /) save root location
359 wk1 = url;
360 wk2 = lpszUrlCpy;
361 state = 0;
363 if(url[1] == ':') { /* Assume path */
364 memcpy(wk2, wszFilePrefix, sizeof(wszFilePrefix));
365 wk2 += sizeof(wszFilePrefix)/sizeof(WCHAR);
366 if (dwFlags & (URL_FILE_USE_PATHURL | URL_WININET_COMPATIBILITY))
368 slash = '\\';
369 --wk2;
371 else
372 dwFlags |= URL_ESCAPE_UNSAFE;
373 state = 5;
374 is_file_url = TRUE;
377 while (*wk1) {
378 switch (state) {
379 case 0:
380 if (!isalnumW(*wk1)) {state = 3; break;}
381 *wk2++ = *wk1++;
382 if (!isalnumW(*wk1)) {state = 3; break;}
383 *wk2++ = *wk1++;
384 state = 1;
385 break;
386 case 1:
387 *wk2++ = *wk1;
388 if (*wk1++ == ':') state = 2;
389 break;
390 case 2:
391 *wk2++ = *wk1++;
392 if (*wk1 != '/') {state = 6; break;}
393 *wk2++ = *wk1++;
394 if((dwFlags & URL_FILE_USE_PATHURL) && nByteLen >= sizeof(wszLocalhost)
395 && is_file_url
396 && !memcmp(wszLocalhost, wk1, sizeof(wszLocalhost))){
397 wk1 += sizeof(wszLocalhost)/sizeof(WCHAR);
398 while(*wk1 == '\\' && (dwFlags & URL_FILE_USE_PATHURL))
399 wk1++;
402 if(*wk1 == '/' && (dwFlags & URL_FILE_USE_PATHURL)){
403 wk1++;
404 }else if(is_file_url){
405 const WCHAR *body = wk1;
407 while(*body == '/')
408 ++body;
410 if(isalnumW(*body) && *(body+1) == ':'){
411 if(!(dwFlags & (URL_WININET_COMPATIBILITY | URL_FILE_USE_PATHURL))){
412 if(slash)
413 *wk2++ = slash;
414 else
415 *wk2++ = '/';
417 }else{
418 if(dwFlags & URL_WININET_COMPATIBILITY){
419 if(*wk1 == '/' && *(wk1+1) != '/'){
420 *wk2++ = '\\';
421 }else{
422 *wk2++ = '\\';
423 *wk2++ = '\\';
425 }else{
426 if(*wk1 == '/' && *(wk1+1) != '/'){
427 if(slash)
428 *wk2++ = slash;
429 else
430 *wk2++ = '/';
434 wk1 = body;
436 state = 4;
437 break;
438 case 3:
439 nWkLen = strlenW(wk1);
440 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
441 mp = wk2;
442 wk1 += nWkLen;
443 wk2 += nWkLen;
445 if(slash) {
446 while(mp < wk2) {
447 if(*mp == '/' || *mp == '\\')
448 *mp = slash;
449 mp++;
452 break;
453 case 4:
454 if (!isalnumW(*wk1) && (*wk1 != '-') && (*wk1 != '.') && (*wk1 != ':'))
455 {state = 3; break;}
456 while(isalnumW(*wk1) || (*wk1 == '-') || (*wk1 == '.') || (*wk1 == ':'))
457 *wk2++ = *wk1++;
458 state = 5;
459 if (!*wk1) {
460 if(slash)
461 *wk2++ = slash;
462 else
463 *wk2++ = '/';
465 break;
466 case 5:
467 if (*wk1 != '/' && *wk1 != '\\') {state = 3; break;}
468 while(*wk1 == '/' || *wk1 == '\\') {
469 if(slash)
470 *wk2++ = slash;
471 else
472 *wk2++ = *wk1;
473 wk1++;
475 state = 6;
476 break;
477 case 6:
478 if(dwFlags & URL_DONT_SIMPLIFY) {
479 state = 3;
480 break;
483 /* Now at root location, cannot back up any more. */
484 /* "root" will point at the '/' */
486 root = wk2-1;
487 while (*wk1) {
488 mp = strchrW(wk1, '/');
489 mp2 = strchrW(wk1, '\\');
490 if(mp2 && (!mp || mp2 < mp))
491 mp = mp2;
492 if (!mp) {
493 nWkLen = strlenW(wk1);
494 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
495 wk1 += nWkLen;
496 wk2 += nWkLen;
497 continue;
499 nLen = mp - wk1;
500 if(nLen) {
501 memcpy(wk2, wk1, nLen * sizeof(WCHAR));
502 wk2 += nLen;
503 wk1 += nLen;
505 if(slash)
506 *wk2++ = slash;
507 else
508 *wk2++ = *wk1;
509 wk1++;
511 while (*wk1 == '.') {
512 TRACE("found '/.'\n");
513 if (wk1[1] == '/' || wk1[1] == '\\') {
514 /* case of /./ -> skip the ./ */
515 wk1 += 2;
517 else if (wk1[1] == '.' && (wk1[2] == '/'
518 || wk1[2] == '\\' || wk1[2] == '?'
519 || wk1[2] == '#' || !wk1[2])) {
520 /* case /../ -> need to backup wk2 */
521 TRACE("found '/../'\n");
522 *(wk2-1) = '\0'; /* set end of string */
523 mp = strrchrW(root, '/');
524 mp2 = strrchrW(root, '\\');
525 if(mp2 && (!mp || mp2 < mp))
526 mp = mp2;
527 if (mp && (mp >= root)) {
528 /* found valid backup point */
529 wk2 = mp + 1;
530 if(wk1[2] != '/' && wk1[2] != '\\')
531 wk1 += 2;
532 else
533 wk1 += 3;
535 else {
536 /* did not find point, restore '/' */
537 *(wk2-1) = slash;
538 break;
541 else
542 break;
545 *wk2 = '\0';
546 break;
547 default:
548 FIXME("how did we get here - state=%d\n", state);
549 HeapFree(GetProcessHeap(), 0, lpszUrlCpy);
550 HeapFree(GetProcessHeap(), 0, url);
551 return E_INVALIDARG;
553 *wk2 = '\0';
554 TRACE("Simplified, orig <%s>, simple <%s>\n",
555 debugstr_w(pszUrl), debugstr_w(lpszUrlCpy));
557 nLen = lstrlenW(lpszUrlCpy);
558 while ((nLen > 0) && ((lpszUrlCpy[nLen-1] <= ' ')))
559 lpszUrlCpy[--nLen]=0;
561 if((dwFlags & URL_UNESCAPE) ||
562 ((dwFlags & URL_FILE_USE_PATHURL) && nByteLen >= sizeof(wszFile)
563 && !memcmp(wszFile, url, sizeof(wszFile))))
564 UrlUnescapeW(lpszUrlCpy, NULL, &nLen, URL_UNESCAPE_INPLACE);
566 if((EscapeFlags = dwFlags & (URL_ESCAPE_UNSAFE |
567 URL_ESCAPE_SPACES_ONLY |
568 URL_ESCAPE_PERCENT |
569 URL_DONT_ESCAPE_EXTRA_INFO |
570 URL_ESCAPE_SEGMENT_ONLY ))) {
571 EscapeFlags &= ~URL_ESCAPE_UNSAFE;
572 hr = UrlEscapeW(lpszUrlCpy, pszCanonicalized, pcchCanonicalized,
573 EscapeFlags);
574 } else { /* No escaping needed, just copy the string */
575 nLen = lstrlenW(lpszUrlCpy);
576 if(nLen < *pcchCanonicalized)
577 memcpy(pszCanonicalized, lpszUrlCpy, (nLen + 1)*sizeof(WCHAR));
578 else {
579 hr = E_POINTER;
580 nLen++;
582 *pcchCanonicalized = nLen;
585 HeapFree(GetProcessHeap(), 0, lpszUrlCpy);
586 HeapFree(GetProcessHeap(), 0, url);
588 if (hr == S_OK)
589 TRACE("result %s\n", debugstr_w(pszCanonicalized));
591 return hr;
594 /*************************************************************************
595 * UrlCombineA [SHLWAPI.@]
597 * Combine two Urls.
599 * PARAMS
600 * pszBase [I] Base Url
601 * pszRelative [I] Url to combine with pszBase
602 * pszCombined [O] Destination for combined Url
603 * pcchCombined [O] Destination for length of pszCombined
604 * dwFlags [I] URL_ flags from "shlwapi.h"
606 * RETURNS
607 * Success: S_OK. pszCombined contains the combined Url, pcchCombined
608 * contains its length.
609 * Failure: An HRESULT error code indicating the error.
611 HRESULT WINAPI UrlCombineA(LPCSTR pszBase, LPCSTR pszRelative,
612 LPSTR pszCombined, LPDWORD pcchCombined,
613 DWORD dwFlags)
615 LPWSTR base, relative, combined;
616 DWORD ret, len, len2;
618 TRACE("(base %s, Relative %s, Combine size %d, flags %08x) using W version\n",
619 debugstr_a(pszBase),debugstr_a(pszRelative),
620 pcchCombined?*pcchCombined:0,dwFlags);
622 if(!pszBase || !pszRelative || !pcchCombined)
623 return E_INVALIDARG;
625 base = HeapAlloc(GetProcessHeap(), 0,
626 (3*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
627 relative = base + INTERNET_MAX_URL_LENGTH;
628 combined = relative + INTERNET_MAX_URL_LENGTH;
630 MultiByteToWideChar(0, 0, pszBase, -1, base, INTERNET_MAX_URL_LENGTH);
631 MultiByteToWideChar(0, 0, pszRelative, -1, relative, INTERNET_MAX_URL_LENGTH);
632 len = *pcchCombined;
634 ret = UrlCombineW(base, relative, pszCombined?combined:NULL, &len, dwFlags);
635 if (ret != S_OK) {
636 *pcchCombined = len;
637 HeapFree(GetProcessHeap(), 0, base);
638 return ret;
641 len2 = WideCharToMultiByte(0, 0, combined, len, 0, 0, 0, 0);
642 if (len2 > *pcchCombined) {
643 *pcchCombined = len2;
644 HeapFree(GetProcessHeap(), 0, base);
645 return E_POINTER;
647 WideCharToMultiByte(0, 0, combined, len+1, pszCombined, (*pcchCombined)+1,
648 0, 0);
649 *pcchCombined = len2;
650 HeapFree(GetProcessHeap(), 0, base);
651 return S_OK;
654 /*************************************************************************
655 * UrlCombineW [SHLWAPI.@]
657 * See UrlCombineA.
659 HRESULT WINAPI UrlCombineW(LPCWSTR pszBase, LPCWSTR pszRelative,
660 LPWSTR pszCombined, LPDWORD pcchCombined,
661 DWORD dwFlags)
663 PARSEDURLW base, relative;
664 DWORD myflags, sizeloc = 0;
665 DWORD len, res1, res2, process_case = 0;
666 LPWSTR work, preliminary, mbase, mrelative;
667 static const WCHAR myfilestr[] = {'f','i','l','e',':','/','/','/','\0'};
668 HRESULT ret;
670 TRACE("(base %s, Relative %s, Combine size %d, flags %08x)\n",
671 debugstr_w(pszBase),debugstr_w(pszRelative),
672 pcchCombined?*pcchCombined:0,dwFlags);
674 if(!pszBase || !pszRelative || !pcchCombined)
675 return E_INVALIDARG;
677 base.cbSize = sizeof(base);
678 relative.cbSize = sizeof(relative);
680 /* Get space for duplicates of the input and the output */
681 preliminary = HeapAlloc(GetProcessHeap(), 0, (3*INTERNET_MAX_URL_LENGTH) *
682 sizeof(WCHAR));
683 mbase = preliminary + INTERNET_MAX_URL_LENGTH;
684 mrelative = mbase + INTERNET_MAX_URL_LENGTH;
685 *preliminary = '\0';
687 /* Canonicalize the base input prior to looking for the scheme */
688 myflags = dwFlags & (URL_DONT_SIMPLIFY | URL_UNESCAPE);
689 len = INTERNET_MAX_URL_LENGTH;
690 ret = UrlCanonicalizeW(pszBase, mbase, &len, myflags);
692 /* Canonicalize the relative input prior to looking for the scheme */
693 len = INTERNET_MAX_URL_LENGTH;
694 ret = UrlCanonicalizeW(pszRelative, mrelative, &len, myflags);
696 /* See if the base has a scheme */
697 res1 = ParseURLW(mbase, &base);
698 if (res1) {
699 /* if pszBase has no scheme, then return pszRelative */
700 TRACE("no scheme detected in Base\n");
701 process_case = 1;
703 else do {
704 BOOL manual_search = FALSE;
706 /* mk is a special case */
707 if(base.nScheme == URL_SCHEME_MK) {
708 static const WCHAR wsz[] = {':',':',0};
710 WCHAR *ptr = strstrW(base.pszSuffix, wsz);
711 if(ptr) {
712 int delta;
714 ptr += 2;
715 delta = ptr-base.pszSuffix;
716 base.cchProtocol += delta;
717 base.pszSuffix += delta;
718 base.cchSuffix -= delta;
720 }else {
721 /* get size of location field (if it exists) */
722 work = (LPWSTR)base.pszSuffix;
723 sizeloc = 0;
724 if (*work++ == '/') {
725 if (*work++ == '/') {
726 /* At this point have start of location and
727 * it ends at next '/' or end of string.
729 while(*work && (*work != '/')) work++;
730 sizeloc = (DWORD)(work - base.pszSuffix);
735 /* If there is a '#' and the characters immediately preceding it are
736 * ".htm[l]", then begin looking for the last leaf starting from
737 * the '#'. Otherwise the '#' is not meaningful and just start
738 * looking from the end. */
739 if ((work = strchrW(base.pszSuffix + sizeloc, '#'))) {
740 const WCHAR htmlW[] = {'.','h','t','m','l',0};
741 const int len_htmlW = 5;
742 const WCHAR htmW[] = {'.','h','t','m',0};
743 const int len_htmW = 4;
745 if (base.nScheme == URL_SCHEME_HTTP || base.nScheme == URL_SCHEME_HTTPS)
746 manual_search = TRUE;
747 else if (work - base.pszSuffix > len_htmW) {
748 work -= len_htmW;
749 if (strncmpiW(work, htmW, len_htmW) == 0)
750 manual_search = TRUE;
751 work += len_htmW;
754 if (!manual_search &&
755 work - base.pszSuffix > len_htmlW) {
756 work -= len_htmlW;
757 if (strncmpiW(work, htmlW, len_htmlW) == 0)
758 manual_search = TRUE;
759 work += len_htmlW;
763 if (manual_search) {
764 /* search backwards starting from the current position */
765 while (*work != '/' && work > base.pszSuffix + sizeloc)
766 --work;
767 base.cchSuffix = work - base.pszSuffix + 1;
768 }else {
769 /* search backwards starting from the end of the string */
770 work = strrchrW((base.pszSuffix+sizeloc), '/');
771 if (work) {
772 len = (DWORD)(work - base.pszSuffix + 1);
773 base.cchSuffix = len;
774 }else
775 base.cchSuffix = sizeloc;
779 * At this point:
780 * .pszSuffix points to location (starting with '//')
781 * .cchSuffix length of location (above) and rest less the last
782 * leaf (if any)
783 * sizeloc length of location (above) up to but not including
784 * the last '/'
787 res2 = ParseURLW(mrelative, &relative);
788 if (res2) {
789 /* no scheme in pszRelative */
790 TRACE("no scheme detected in Relative\n");
791 relative.pszSuffix = mrelative; /* case 3,4,5 depends on this */
792 relative.cchSuffix = strlenW(mrelative);
793 if (*pszRelative == ':') {
794 /* case that is either left alone or uses pszBase */
795 if (dwFlags & URL_PLUGGABLE_PROTOCOL) {
796 process_case = 5;
797 break;
799 process_case = 1;
800 break;
802 if (isalnum(*mrelative) && (*(mrelative + 1) == ':')) {
803 /* case that becomes "file:///" */
804 strcpyW(preliminary, myfilestr);
805 process_case = 1;
806 break;
808 if ((*mrelative == '/') && (*(mrelative+1) == '/')) {
809 /* pszRelative has location and rest */
810 process_case = 3;
811 break;
813 if (*mrelative == '/') {
814 /* case where pszRelative is root to location */
815 process_case = 4;
816 break;
818 if (*mrelative == '#') {
819 if(!(work = strchrW(base.pszSuffix+base.cchSuffix, '#')))
820 work = (LPWSTR)base.pszSuffix + strlenW(base.pszSuffix);
822 memcpy(preliminary, base.pszProtocol, (work-base.pszProtocol)*sizeof(WCHAR));
823 preliminary[work-base.pszProtocol] = '\0';
824 process_case = 1;
825 break;
827 process_case = (*base.pszSuffix == '/' || base.nScheme == URL_SCHEME_MK) ? 5 : 3;
828 break;
831 /* handle cases where pszRelative has scheme */
832 if ((base.cchProtocol == relative.cchProtocol) &&
833 (strncmpW(base.pszProtocol, relative.pszProtocol, base.cchProtocol) == 0)) {
835 /* since the schemes are the same */
836 if ((*relative.pszSuffix == '/') && (*(relative.pszSuffix+1) == '/')) {
837 /* case where pszRelative replaces location and following */
838 process_case = 3;
839 break;
841 if (*relative.pszSuffix == '/') {
842 /* case where pszRelative is root to location */
843 process_case = 4;
844 break;
846 /* replace either just location if base's location starts with a
847 * slash or otherwise everything */
848 process_case = (*base.pszSuffix == '/') ? 5 : 1;
849 break;
851 if ((*relative.pszSuffix == '/') && (*(relative.pszSuffix+1) == '/')) {
852 /* case where pszRelative replaces scheme, location,
853 * and following and handles PLUGGABLE
855 process_case = 2;
856 break;
858 process_case = 1;
859 break;
860 } while(FALSE); /* a little trick to allow easy exit from nested if's */
862 ret = S_OK;
863 switch (process_case) {
865 case 1: /*
866 * Return pszRelative appended to what ever is in pszCombined,
867 * (which may the string "file:///"
869 strcatW(preliminary, mrelative);
870 break;
872 case 2: /* case where pszRelative replaces scheme, and location */
873 strcpyW(preliminary, mrelative);
874 break;
876 case 3: /*
877 * Return the pszBase scheme with pszRelative. Basically
878 * keeps the scheme and replaces the domain and following.
880 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1)*sizeof(WCHAR));
881 work = preliminary + base.cchProtocol + 1;
882 strcpyW(work, relative.pszSuffix);
883 break;
885 case 4: /*
886 * Return the pszBase scheme and location but everything
887 * after the location is pszRelative. (Replace document
888 * from root on.)
890 memcpy(preliminary, base.pszProtocol, (base.cchProtocol+1+sizeloc)*sizeof(WCHAR));
891 work = preliminary + base.cchProtocol + 1 + sizeloc;
892 if (dwFlags & URL_PLUGGABLE_PROTOCOL)
893 *(work++) = '/';
894 strcpyW(work, relative.pszSuffix);
895 break;
897 case 5: /*
898 * Return the pszBase without its document (if any) and
899 * append pszRelative after its scheme.
901 memcpy(preliminary, base.pszProtocol,
902 (base.cchProtocol+1+base.cchSuffix)*sizeof(WCHAR));
903 work = preliminary + base.cchProtocol+1+base.cchSuffix - 1;
904 if (*work++ != '/')
905 *(work++) = '/';
906 strcpyW(work, relative.pszSuffix);
907 break;
909 default:
910 FIXME("How did we get here????? process_case=%d\n", process_case);
911 ret = E_INVALIDARG;
914 if (ret == S_OK) {
915 /* Reuse mrelative as temp storage as its already allocated and not needed anymore */
916 if(*pcchCombined == 0)
917 *pcchCombined = 1;
918 ret = UrlCanonicalizeW(preliminary, mrelative, pcchCombined, (dwFlags & ~URL_FILE_USE_PATHURL));
919 if(SUCCEEDED(ret) && pszCombined) {
920 lstrcpyW(pszCombined, mrelative);
922 TRACE("return-%d len=%d, %s\n",
923 process_case, *pcchCombined, debugstr_w(pszCombined));
925 HeapFree(GetProcessHeap(), 0, preliminary);
926 return ret;
929 /*************************************************************************
930 * UrlEscapeA [SHLWAPI.@]
933 HRESULT WINAPI UrlEscapeA(
934 LPCSTR pszUrl,
935 LPSTR pszEscaped,
936 LPDWORD pcchEscaped,
937 DWORD dwFlags)
939 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
940 WCHAR *escapedW = bufW;
941 UNICODE_STRING urlW;
942 HRESULT ret;
943 DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
945 if (!pszEscaped || !pcchEscaped || !*pcchEscaped)
946 return E_INVALIDARG;
948 if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl))
949 return E_INVALIDARG;
950 if((ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags)) == E_POINTER) {
951 escapedW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
952 ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags);
954 if(ret == S_OK) {
955 RtlUnicodeToMultiByteSize(&lenA, escapedW, lenW * sizeof(WCHAR));
956 if(*pcchEscaped > lenA) {
957 RtlUnicodeToMultiByteN(pszEscaped, *pcchEscaped - 1, &lenA, escapedW, lenW * sizeof(WCHAR));
958 pszEscaped[lenA] = 0;
959 *pcchEscaped = lenA;
960 } else {
961 *pcchEscaped = lenA + 1;
962 ret = E_POINTER;
965 if(escapedW != bufW) HeapFree(GetProcessHeap(), 0, escapedW);
966 RtlFreeUnicodeString(&urlW);
967 return ret;
970 #define WINE_URL_BASH_AS_SLASH 0x01
971 #define WINE_URL_COLLAPSE_SLASHES 0x02
972 #define WINE_URL_ESCAPE_SLASH 0x04
973 #define WINE_URL_ESCAPE_HASH 0x08
974 #define WINE_URL_ESCAPE_QUESTION 0x10
975 #define WINE_URL_STOP_ON_HASH 0x20
976 #define WINE_URL_STOP_ON_QUESTION 0x40
978 static inline BOOL URL_NeedEscapeW(WCHAR ch, DWORD dwFlags, DWORD int_flags)
981 if (isalnumW(ch))
982 return FALSE;
984 if(dwFlags & URL_ESCAPE_SPACES_ONLY) {
985 if(ch == ' ')
986 return TRUE;
987 else
988 return FALSE;
991 if ((dwFlags & URL_ESCAPE_PERCENT) && (ch == '%'))
992 return TRUE;
994 if (ch <= 31 || ch >= 127)
995 return TRUE;
997 else {
998 switch (ch) {
999 case ' ':
1000 case '<':
1001 case '>':
1002 case '\"':
1003 case '{':
1004 case '}':
1005 case '|':
1006 case '\\':
1007 case '^':
1008 case ']':
1009 case '[':
1010 case '`':
1011 case '&':
1012 return TRUE;
1014 case '/':
1015 if (int_flags & WINE_URL_ESCAPE_SLASH) return TRUE;
1016 return FALSE;
1018 case '?':
1019 if (int_flags & WINE_URL_ESCAPE_QUESTION) return TRUE;
1020 return FALSE;
1022 case '#':
1023 if (int_flags & WINE_URL_ESCAPE_HASH) return TRUE;
1024 return FALSE;
1026 default:
1027 return FALSE;
1033 /*************************************************************************
1034 * UrlEscapeW [SHLWAPI.@]
1036 * Converts unsafe characters in a Url into escape sequences.
1038 * PARAMS
1039 * pszUrl [I] Url to modify
1040 * pszEscaped [O] Destination for modified Url
1041 * pcchEscaped [I/O] Length of pszUrl, destination for length of pszEscaped
1042 * dwFlags [I] URL_ flags from "shlwapi.h"
1044 * RETURNS
1045 * Success: S_OK. pszEscaped contains the escaped Url, pcchEscaped
1046 * contains its length.
1047 * Failure: E_POINTER, if pszEscaped is not large enough. In this case
1048 * pcchEscaped is set to the required length.
1050 * Converts unsafe characters into their escape sequences.
1052 * NOTES
1053 * - By default this function stops converting at the first '?' or
1054 * '#' character.
1055 * - If dwFlags contains URL_ESCAPE_SPACES_ONLY then only spaces are
1056 * converted, but the conversion continues past a '?' or '#'.
1057 * - Note that this function did not work well (or at all) in shlwapi version 4.
1059 * BUGS
1060 * Only the following flags are implemented:
1061 *| URL_ESCAPE_SPACES_ONLY
1062 *| URL_DONT_ESCAPE_EXTRA_INFO
1063 *| URL_ESCAPE_SEGMENT_ONLY
1064 *| URL_ESCAPE_PERCENT
1066 HRESULT WINAPI UrlEscapeW(
1067 LPCWSTR pszUrl,
1068 LPWSTR pszEscaped,
1069 LPDWORD pcchEscaped,
1070 DWORD dwFlags)
1072 LPCWSTR src;
1073 DWORD needed = 0, ret;
1074 BOOL stop_escaping = FALSE;
1075 WCHAR next[5], *dst, *dst_ptr;
1076 INT len;
1077 PARSEDURLW parsed_url;
1078 DWORD int_flags;
1079 DWORD slashes = 0;
1080 static const WCHAR localhost[] = {'l','o','c','a','l','h','o','s','t',0};
1082 TRACE("(%p(%s) %p %p 0x%08x)\n", pszUrl, debugstr_w(pszUrl),
1083 pszEscaped, pcchEscaped, dwFlags);
1085 if(!pszUrl || !pcchEscaped)
1086 return E_INVALIDARG;
1088 if(dwFlags & ~(URL_ESCAPE_SPACES_ONLY |
1089 URL_ESCAPE_SEGMENT_ONLY |
1090 URL_DONT_ESCAPE_EXTRA_INFO |
1091 URL_ESCAPE_PERCENT))
1092 FIXME("Unimplemented flags: %08x\n", dwFlags);
1094 dst_ptr = dst = HeapAlloc(GetProcessHeap(), 0, *pcchEscaped*sizeof(WCHAR));
1095 if(!dst_ptr)
1096 return E_OUTOFMEMORY;
1098 /* fix up flags */
1099 if (dwFlags & URL_ESCAPE_SPACES_ONLY)
1100 /* if SPACES_ONLY specified, reset the other controls */
1101 dwFlags &= ~(URL_DONT_ESCAPE_EXTRA_INFO |
1102 URL_ESCAPE_PERCENT |
1103 URL_ESCAPE_SEGMENT_ONLY);
1105 else
1106 /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
1107 dwFlags |= URL_DONT_ESCAPE_EXTRA_INFO;
1110 int_flags = 0;
1111 if(dwFlags & URL_ESCAPE_SEGMENT_ONLY) {
1112 int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH;
1113 } else {
1114 parsed_url.cbSize = sizeof(parsed_url);
1115 if(ParseURLW(pszUrl, &parsed_url) != S_OK)
1116 parsed_url.nScheme = URL_SCHEME_INVALID;
1118 TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol));
1120 if(dwFlags & URL_DONT_ESCAPE_EXTRA_INFO)
1121 int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION;
1123 switch(parsed_url.nScheme) {
1124 case URL_SCHEME_FILE:
1125 int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH;
1126 int_flags &= ~WINE_URL_STOP_ON_HASH;
1127 break;
1129 case URL_SCHEME_HTTP:
1130 case URL_SCHEME_HTTPS:
1131 int_flags |= WINE_URL_BASH_AS_SLASH;
1132 if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\')
1133 int_flags |= WINE_URL_ESCAPE_SLASH;
1134 break;
1136 case URL_SCHEME_MAILTO:
1137 int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH;
1138 int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH);
1139 break;
1141 case URL_SCHEME_INVALID:
1142 break;
1144 case URL_SCHEME_FTP:
1145 default:
1146 if(parsed_url.pszSuffix[0] != '/')
1147 int_flags |= WINE_URL_ESCAPE_SLASH;
1148 break;
1152 for(src = pszUrl; *src; ) {
1153 WCHAR cur = *src;
1154 len = 0;
1156 if((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == pszUrl + parsed_url.cchProtocol + 1) {
1157 int localhost_len = sizeof(localhost)/sizeof(WCHAR) - 1;
1158 while(cur == '/' || cur == '\\') {
1159 slashes++;
1160 cur = *++src;
1162 if(slashes == 2 && !strncmpiW(src, localhost, localhost_len)) { /* file://localhost/ -> file:/// */
1163 if(*(src + localhost_len) == '/' || *(src + localhost_len) == '\\')
1164 src += localhost_len + 1;
1165 slashes = 3;
1168 switch(slashes) {
1169 case 1:
1170 case 3:
1171 next[0] = next[1] = next[2] = '/';
1172 len = 3;
1173 break;
1174 case 0:
1175 len = 0;
1176 break;
1177 default:
1178 next[0] = next[1] = '/';
1179 len = 2;
1180 break;
1183 if(len == 0) {
1185 if(cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH))
1186 stop_escaping = TRUE;
1188 if(cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION))
1189 stop_escaping = TRUE;
1191 if(cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/';
1193 if(URL_NeedEscapeW(cur, dwFlags, int_flags) && stop_escaping == FALSE) {
1194 next[0] = '%';
1195 next[1] = hexDigits[(cur >> 4) & 0xf];
1196 next[2] = hexDigits[cur & 0xf];
1197 len = 3;
1198 } else {
1199 next[0] = cur;
1200 len = 1;
1202 src++;
1205 if(needed + len <= *pcchEscaped) {
1206 memcpy(dst, next, len*sizeof(WCHAR));
1207 dst += len;
1209 needed += len;
1212 if(needed < *pcchEscaped) {
1213 *dst = '\0';
1214 memcpy(pszEscaped, dst_ptr, (needed+1)*sizeof(WCHAR));
1216 ret = S_OK;
1217 } else {
1218 needed++; /* add one for the '\0' */
1219 ret = E_POINTER;
1221 *pcchEscaped = needed;
1223 HeapFree(GetProcessHeap(), 0, dst_ptr);
1224 return ret;
1228 /*************************************************************************
1229 * UrlUnescapeA [SHLWAPI.@]
1231 * Converts Url escape sequences back to ordinary characters.
1233 * PARAMS
1234 * pszUrl [I/O] Url to convert
1235 * pszUnescaped [O] Destination for converted Url
1236 * pcchUnescaped [I/O] Size of output string
1237 * dwFlags [I] URL_ESCAPE_ Flags from "shlwapi.h"
1239 * RETURNS
1240 * Success: S_OK. The converted value is in pszUnescaped, or in pszUrl if
1241 * dwFlags includes URL_ESCAPE_INPLACE.
1242 * Failure: E_POINTER if the converted Url is bigger than pcchUnescaped. In
1243 * this case pcchUnescaped is set to the size required.
1244 * NOTES
1245 * If dwFlags includes URL_DONT_ESCAPE_EXTRA_INFO, the conversion stops at
1246 * the first occurrence of either a '?' or '#' character.
1248 HRESULT WINAPI UrlUnescapeA(
1249 LPSTR pszUrl,
1250 LPSTR pszUnescaped,
1251 LPDWORD pcchUnescaped,
1252 DWORD dwFlags)
1254 char *dst, next;
1255 LPCSTR src;
1256 HRESULT ret;
1257 DWORD needed;
1258 BOOL stop_unescaping = FALSE;
1260 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_a(pszUrl), pszUnescaped,
1261 pcchUnescaped, dwFlags);
1263 if (!pszUrl) return E_INVALIDARG;
1265 if(dwFlags & URL_UNESCAPE_INPLACE)
1266 dst = pszUrl;
1267 else
1269 if (!pszUnescaped || !pcchUnescaped) return E_INVALIDARG;
1270 dst = pszUnescaped;
1273 for(src = pszUrl, needed = 0; *src; src++, needed++) {
1274 if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1275 (*src == '#' || *src == '?')) {
1276 stop_unescaping = TRUE;
1277 next = *src;
1278 } else if(*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2))
1279 && stop_unescaping == FALSE) {
1280 INT ih;
1281 char buf[3];
1282 memcpy(buf, src + 1, 2);
1283 buf[2] = '\0';
1284 ih = strtol(buf, NULL, 16);
1285 next = (CHAR) ih;
1286 src += 2; /* Advance to end of escape */
1287 } else
1288 next = *src;
1290 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1291 *dst++ = next;
1294 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1295 *dst = '\0';
1296 ret = S_OK;
1297 } else {
1298 needed++; /* add one for the '\0' */
1299 ret = E_POINTER;
1301 if(!(dwFlags & URL_UNESCAPE_INPLACE))
1302 *pcchUnescaped = needed;
1304 if (ret == S_OK) {
1305 TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1306 debugstr_a(pszUrl) : debugstr_a(pszUnescaped));
1309 return ret;
1312 /*************************************************************************
1313 * UrlUnescapeW [SHLWAPI.@]
1315 * See UrlUnescapeA.
1317 HRESULT WINAPI UrlUnescapeW(
1318 LPWSTR pszUrl,
1319 LPWSTR pszUnescaped,
1320 LPDWORD pcchUnescaped,
1321 DWORD dwFlags)
1323 WCHAR *dst, next;
1324 LPCWSTR src;
1325 HRESULT ret;
1326 DWORD needed;
1327 BOOL stop_unescaping = FALSE;
1329 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszUrl), pszUnescaped,
1330 pcchUnescaped, dwFlags);
1332 if(!pszUrl) return E_INVALIDARG;
1334 if(dwFlags & URL_UNESCAPE_INPLACE)
1335 dst = pszUrl;
1336 else
1338 if (!pszUnescaped || !pcchUnescaped) return E_INVALIDARG;
1339 dst = pszUnescaped;
1342 for(src = pszUrl, needed = 0; *src; src++, needed++) {
1343 if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1344 (*src == '#' || *src == '?')) {
1345 stop_unescaping = TRUE;
1346 next = *src;
1347 } else if(*src == '%' && isxdigitW(*(src + 1)) && isxdigitW(*(src + 2))
1348 && stop_unescaping == FALSE) {
1349 INT ih;
1350 WCHAR buf[5] = {'0','x',0};
1351 memcpy(buf + 2, src + 1, 2*sizeof(WCHAR));
1352 buf[4] = 0;
1353 StrToIntExW(buf, STIF_SUPPORT_HEX, &ih);
1354 next = (WCHAR) ih;
1355 src += 2; /* Advance to end of escape */
1356 } else
1357 next = *src;
1359 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1360 *dst++ = next;
1363 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1364 *dst = '\0';
1365 ret = S_OK;
1366 } else {
1367 needed++; /* add one for the '\0' */
1368 ret = E_POINTER;
1370 if(!(dwFlags & URL_UNESCAPE_INPLACE))
1371 *pcchUnescaped = needed;
1373 if (ret == S_OK) {
1374 TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1375 debugstr_w(pszUrl) : debugstr_w(pszUnescaped));
1378 return ret;
1381 /*************************************************************************
1382 * UrlGetLocationA [SHLWAPI.@]
1384 * Get the location from a Url.
1386 * PARAMS
1387 * pszUrl [I] Url to get the location from
1389 * RETURNS
1390 * A pointer to the start of the location in pszUrl, or NULL if there is
1391 * no location.
1393 * NOTES
1394 * - MSDN erroneously states that "The location is the segment of the Url
1395 * starting with a '?' or '#' character". Neither V4 nor V5 of shlwapi.dll
1396 * stop at '?' and always return a NULL in this case.
1397 * - MSDN also erroneously states that "If a file URL has a query string,
1398 * the returned string is the query string". In all tested cases, if the
1399 * Url starts with "fi" then a NULL is returned. V5 gives the following results:
1400 *| Result Url
1401 *| ------ ---
1402 *| NULL file://aa/b/cd#hohoh
1403 *| #hohoh http://aa/b/cd#hohoh
1404 *| NULL fi://aa/b/cd#hohoh
1405 *| #hohoh ff://aa/b/cd#hohoh
1407 LPCSTR WINAPI UrlGetLocationA(
1408 LPCSTR pszUrl)
1410 PARSEDURLA base;
1411 DWORD res1;
1413 base.cbSize = sizeof(base);
1414 res1 = ParseURLA(pszUrl, &base);
1415 if (res1) return NULL; /* invalid scheme */
1417 /* if scheme is file: then never return pointer */
1418 if (strncmp(base.pszProtocol, "file", min(4,base.cchProtocol)) == 0) return NULL;
1420 /* Look for '#' and return its addr */
1421 return strchr(base.pszSuffix, '#');
1424 /*************************************************************************
1425 * UrlGetLocationW [SHLWAPI.@]
1427 * See UrlGetLocationA.
1429 LPCWSTR WINAPI UrlGetLocationW(
1430 LPCWSTR pszUrl)
1432 PARSEDURLW base;
1433 DWORD res1;
1435 base.cbSize = sizeof(base);
1436 res1 = ParseURLW(pszUrl, &base);
1437 if (res1) return NULL; /* invalid scheme */
1439 /* if scheme is file: then never return pointer */
1440 if (strncmpW(base.pszProtocol, fileW, min(4,base.cchProtocol)) == 0) return NULL;
1442 /* Look for '#' and return its addr */
1443 return strchrW(base.pszSuffix, '#');
1446 /*************************************************************************
1447 * UrlCompareA [SHLWAPI.@]
1449 * Compare two Urls.
1451 * PARAMS
1452 * pszUrl1 [I] First Url to compare
1453 * pszUrl2 [I] Url to compare to pszUrl1
1454 * fIgnoreSlash [I] TRUE = compare only up to a final slash
1456 * RETURNS
1457 * less than zero, zero, or greater than zero indicating pszUrl2 is greater
1458 * than, equal to, or less than pszUrl1 respectively.
1460 INT WINAPI UrlCompareA(
1461 LPCSTR pszUrl1,
1462 LPCSTR pszUrl2,
1463 BOOL fIgnoreSlash)
1465 INT ret, len, len1, len2;
1467 if (!fIgnoreSlash)
1468 return strcmp(pszUrl1, pszUrl2);
1469 len1 = strlen(pszUrl1);
1470 if (pszUrl1[len1-1] == '/') len1--;
1471 len2 = strlen(pszUrl2);
1472 if (pszUrl2[len2-1] == '/') len2--;
1473 if (len1 == len2)
1474 return strncmp(pszUrl1, pszUrl2, len1);
1475 len = min(len1, len2);
1476 ret = strncmp(pszUrl1, pszUrl2, len);
1477 if (ret) return ret;
1478 if (len1 > len2) return 1;
1479 return -1;
1482 /*************************************************************************
1483 * UrlCompareW [SHLWAPI.@]
1485 * See UrlCompareA.
1487 INT WINAPI UrlCompareW(
1488 LPCWSTR pszUrl1,
1489 LPCWSTR pszUrl2,
1490 BOOL fIgnoreSlash)
1492 INT ret;
1493 size_t len, len1, len2;
1495 if (!fIgnoreSlash)
1496 return strcmpW(pszUrl1, pszUrl2);
1497 len1 = strlenW(pszUrl1);
1498 if (pszUrl1[len1-1] == '/') len1--;
1499 len2 = strlenW(pszUrl2);
1500 if (pszUrl2[len2-1] == '/') len2--;
1501 if (len1 == len2)
1502 return strncmpW(pszUrl1, pszUrl2, len1);
1503 len = min(len1, len2);
1504 ret = strncmpW(pszUrl1, pszUrl2, len);
1505 if (ret) return ret;
1506 if (len1 > len2) return 1;
1507 return -1;
1510 /*************************************************************************
1511 * HashData [SHLWAPI.@]
1513 * Hash an input block into a variable sized digest.
1515 * PARAMS
1516 * lpSrc [I] Input block
1517 * nSrcLen [I] Length of lpSrc
1518 * lpDest [I] Output for hash digest
1519 * nDestLen [I] Length of lpDest
1521 * RETURNS
1522 * Success: TRUE. lpDest is filled with the computed hash value.
1523 * Failure: FALSE, if any argument is invalid.
1525 HRESULT WINAPI HashData(const unsigned char *lpSrc, DWORD nSrcLen,
1526 unsigned char *lpDest, DWORD nDestLen)
1528 INT srcCount = nSrcLen - 1, destCount = nDestLen - 1;
1530 if (!lpSrc || !lpDest)
1531 return E_INVALIDARG;
1533 while (destCount >= 0)
1535 lpDest[destCount] = (destCount & 0xff);
1536 destCount--;
1539 while (srcCount >= 0)
1541 destCount = nDestLen - 1;
1542 while (destCount >= 0)
1544 lpDest[destCount] = HashDataLookup[lpSrc[srcCount] ^ lpDest[destCount]];
1545 destCount--;
1547 srcCount--;
1549 return S_OK;
1552 /*************************************************************************
1553 * UrlHashA [SHLWAPI.@]
1555 * Produce a Hash from a Url.
1557 * PARAMS
1558 * pszUrl [I] Url to hash
1559 * lpDest [O] Destinationh for hash
1560 * nDestLen [I] Length of lpDest
1562 * RETURNS
1563 * Success: S_OK. lpDest is filled with the computed hash value.
1564 * Failure: E_INVALIDARG, if any argument is invalid.
1566 HRESULT WINAPI UrlHashA(LPCSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1568 if (IsBadStringPtrA(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1569 return E_INVALIDARG;
1571 HashData((const BYTE*)pszUrl, (int)strlen(pszUrl), lpDest, nDestLen);
1572 return S_OK;
1575 /*************************************************************************
1576 * UrlHashW [SHLWAPI.@]
1578 * See UrlHashA.
1580 HRESULT WINAPI UrlHashW(LPCWSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1582 char szUrl[MAX_PATH];
1584 TRACE("(%s,%p,%d)\n",debugstr_w(pszUrl), lpDest, nDestLen);
1586 if (IsBadStringPtrW(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1587 return E_INVALIDARG;
1589 /* Win32 hashes the data as an ASCII string, presumably so that both A+W
1590 * return the same digests for the same URL.
1592 WideCharToMultiByte(0, 0, pszUrl, -1, szUrl, MAX_PATH, 0, 0);
1593 HashData((const BYTE*)szUrl, (int)strlen(szUrl), lpDest, nDestLen);
1594 return S_OK;
1597 /*************************************************************************
1598 * UrlApplySchemeA [SHLWAPI.@]
1600 * Apply a scheme to a Url.
1602 * PARAMS
1603 * pszIn [I] Url to apply scheme to
1604 * pszOut [O] Destination for modified Url
1605 * pcchOut [I/O] Length of pszOut/destination for length of pszOut
1606 * dwFlags [I] URL_ flags from "shlwapi.h"
1608 * RETURNS
1609 * Success: S_OK: pszOut contains the modified Url, pcchOut contains its length.
1610 * Failure: An HRESULT error code describing the error.
1612 HRESULT WINAPI UrlApplySchemeA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1614 LPWSTR in, out;
1615 HRESULT ret;
1616 DWORD len;
1618 TRACE("(%s, %p, %p:out size %d, 0x%08x)\n", debugstr_a(pszIn),
1619 pszOut, pcchOut, pcchOut ? *pcchOut : 0, dwFlags);
1621 if (!pszIn || !pszOut || !pcchOut) return E_INVALIDARG;
1623 in = HeapAlloc(GetProcessHeap(), 0,
1624 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
1625 out = in + INTERNET_MAX_URL_LENGTH;
1627 MultiByteToWideChar(CP_ACP, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
1628 len = INTERNET_MAX_URL_LENGTH;
1630 ret = UrlApplySchemeW(in, out, &len, dwFlags);
1631 if (ret != S_OK) {
1632 HeapFree(GetProcessHeap(), 0, in);
1633 return ret;
1636 len = WideCharToMultiByte(CP_ACP, 0, out, -1, NULL, 0, NULL, NULL);
1637 if (len > *pcchOut) {
1638 ret = E_POINTER;
1639 goto cleanup;
1642 WideCharToMultiByte(CP_ACP, 0, out, -1, pszOut, *pcchOut, NULL, NULL);
1643 len--;
1645 cleanup:
1646 *pcchOut = len;
1647 HeapFree(GetProcessHeap(), 0, in);
1648 return ret;
1651 static HRESULT URL_GuessScheme(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1653 HKEY newkey;
1654 BOOL j;
1655 INT index;
1656 DWORD value_len, data_len, dwType, i;
1657 WCHAR reg_path[MAX_PATH];
1658 WCHAR value[MAX_PATH], data[MAX_PATH];
1659 WCHAR Wxx, Wyy;
1661 MultiByteToWideChar(0, 0,
1662 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes",
1663 -1, reg_path, MAX_PATH);
1664 RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
1665 index = 0;
1666 while(value_len = data_len = MAX_PATH,
1667 RegEnumValueW(newkey, index, value, &value_len,
1668 0, &dwType, (LPVOID)data, &data_len) == 0) {
1669 TRACE("guess %d %s is %s\n",
1670 index, debugstr_w(value), debugstr_w(data));
1672 j = FALSE;
1673 for(i=0; i<value_len; i++) {
1674 Wxx = pszIn[i];
1675 Wyy = value[i];
1676 /* remember that TRUE is not-equal */
1677 j = ChrCmpIW(Wxx, Wyy);
1678 if (j) break;
1680 if ((i == value_len) && !j) {
1681 if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1682 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1683 RegCloseKey(newkey);
1684 return E_POINTER;
1686 strcpyW(pszOut, data);
1687 strcatW(pszOut, pszIn);
1688 *pcchOut = strlenW(pszOut);
1689 TRACE("matched and set to %s\n", debugstr_w(pszOut));
1690 RegCloseKey(newkey);
1691 return S_OK;
1693 index++;
1695 RegCloseKey(newkey);
1696 return E_FAIL;
1699 static HRESULT URL_CreateFromPath(LPCWSTR pszPath, LPWSTR pszUrl, LPDWORD pcchUrl)
1701 DWORD needed;
1702 HRESULT ret = S_OK;
1703 WCHAR *pszNewUrl;
1704 WCHAR file_colonW[] = {'f','i','l','e',':',0};
1705 WCHAR three_slashesW[] = {'/','/','/',0};
1706 PARSEDURLW parsed_url;
1708 parsed_url.cbSize = sizeof(parsed_url);
1709 if(ParseURLW(pszPath, &parsed_url) == S_OK) {
1710 if(parsed_url.nScheme != URL_SCHEME_INVALID && parsed_url.cchProtocol > 1) {
1711 needed = strlenW(pszPath);
1712 if (needed >= *pcchUrl) {
1713 *pcchUrl = needed + 1;
1714 return E_POINTER;
1715 } else {
1716 *pcchUrl = needed;
1717 return S_FALSE;
1722 pszNewUrl = HeapAlloc(GetProcessHeap(), 0, (strlenW(pszPath) + 9) * sizeof(WCHAR)); /* "file:///" + pszPath_len + 1 */
1723 strcpyW(pszNewUrl, file_colonW);
1724 if(isalphaW(pszPath[0]) && pszPath[1] == ':')
1725 strcatW(pszNewUrl, three_slashesW);
1726 strcatW(pszNewUrl, pszPath);
1727 ret = UrlEscapeW(pszNewUrl, pszUrl, pcchUrl, URL_ESCAPE_PERCENT);
1728 HeapFree(GetProcessHeap(), 0, pszNewUrl);
1729 return ret;
1732 static HRESULT URL_ApplyDefault(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1734 HKEY newkey;
1735 DWORD data_len, dwType;
1736 WCHAR data[MAX_PATH];
1738 static const WCHAR prefix_keyW[] =
1739 {'S','o','f','t','w','a','r','e',
1740 '\\','M','i','c','r','o','s','o','f','t',
1741 '\\','W','i','n','d','o','w','s',
1742 '\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n',
1743 '\\','U','R','L',
1744 '\\','D','e','f','a','u','l','t','P','r','e','f','i','x',0};
1746 /* get and prepend default */
1747 RegOpenKeyExW(HKEY_LOCAL_MACHINE, prefix_keyW, 0, 1, &newkey);
1748 data_len = sizeof(data);
1749 RegQueryValueExW(newkey, NULL, 0, &dwType, (LPBYTE)data, &data_len);
1750 RegCloseKey(newkey);
1751 if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1752 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1753 return E_POINTER;
1755 strcpyW(pszOut, data);
1756 strcatW(pszOut, pszIn);
1757 *pcchOut = strlenW(pszOut);
1758 TRACE("used default %s\n", debugstr_w(pszOut));
1759 return S_OK;
1762 /*************************************************************************
1763 * UrlApplySchemeW [SHLWAPI.@]
1765 * See UrlApplySchemeA.
1767 HRESULT WINAPI UrlApplySchemeW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1769 PARSEDURLW in_scheme;
1770 DWORD res1;
1771 HRESULT ret;
1773 TRACE("(%s, %p, %p:out size %d, 0x%08x)\n", debugstr_w(pszIn),
1774 pszOut, pcchOut, pcchOut ? *pcchOut : 0, dwFlags);
1776 if (!pszIn || !pszOut || !pcchOut) return E_INVALIDARG;
1778 if (dwFlags & URL_APPLY_GUESSFILE) {
1779 if (*pcchOut > 1 && ':' == pszIn[1]) {
1780 res1 = *pcchOut;
1781 ret = URL_CreateFromPath(pszIn, pszOut, &res1);
1782 if (ret == S_OK || ret == E_POINTER){
1783 *pcchOut = res1;
1784 return ret;
1786 else if (ret == S_FALSE)
1788 return ret;
1793 in_scheme.cbSize = sizeof(in_scheme);
1794 /* See if the base has a scheme */
1795 res1 = ParseURLW(pszIn, &in_scheme);
1796 if (res1) {
1797 /* no scheme in input, need to see if we need to guess */
1798 if (dwFlags & URL_APPLY_GUESSSCHEME) {
1799 if ((ret = URL_GuessScheme(pszIn, pszOut, pcchOut)) != E_FAIL)
1800 return ret;
1804 /* If we are here, then either invalid scheme,
1805 * or no scheme and can't/failed guess.
1807 if ( ( ((res1 == 0) && (dwFlags & URL_APPLY_FORCEAPPLY)) ||
1808 ((res1 != 0)) ) &&
1809 (dwFlags & URL_APPLY_DEFAULT)) {
1810 /* find and apply default scheme */
1811 return URL_ApplyDefault(pszIn, pszOut, pcchOut);
1814 return S_FALSE;
1817 /*************************************************************************
1818 * UrlIsA [SHLWAPI.@]
1820 * Determine if a Url is of a certain class.
1822 * PARAMS
1823 * pszUrl [I] Url to check
1824 * Urlis [I] URLIS_ constant from "shlwapi.h"
1826 * RETURNS
1827 * TRUE if pszUrl belongs to the class type in Urlis.
1828 * FALSE Otherwise.
1830 BOOL WINAPI UrlIsA(LPCSTR pszUrl, URLIS Urlis)
1832 PARSEDURLA base;
1833 DWORD res1;
1834 LPCSTR last;
1836 TRACE("(%s %d)\n", debugstr_a(pszUrl), Urlis);
1838 if(!pszUrl)
1839 return FALSE;
1841 switch (Urlis) {
1843 case URLIS_OPAQUE:
1844 base.cbSize = sizeof(base);
1845 res1 = ParseURLA(pszUrl, &base);
1846 if (res1) return FALSE; /* invalid scheme */
1847 switch (base.nScheme)
1849 case URL_SCHEME_MAILTO:
1850 case URL_SCHEME_SHELL:
1851 case URL_SCHEME_JAVASCRIPT:
1852 case URL_SCHEME_VBSCRIPT:
1853 case URL_SCHEME_ABOUT:
1854 return TRUE;
1856 return FALSE;
1858 case URLIS_FILEURL:
1859 return !StrCmpNA("file:", pszUrl, 5);
1861 case URLIS_DIRECTORY:
1862 last = pszUrl + strlen(pszUrl) - 1;
1863 return (last >= pszUrl && (*last == '/' || *last == '\\' ));
1865 case URLIS_URL:
1866 return PathIsURLA(pszUrl);
1868 case URLIS_NOHISTORY:
1869 case URLIS_APPLIABLE:
1870 case URLIS_HASQUERY:
1871 default:
1872 FIXME("(%s %d): stub\n", debugstr_a(pszUrl), Urlis);
1874 return FALSE;
1877 /*************************************************************************
1878 * UrlIsW [SHLWAPI.@]
1880 * See UrlIsA.
1882 BOOL WINAPI UrlIsW(LPCWSTR pszUrl, URLIS Urlis)
1884 static const WCHAR stemp[] = { 'f','i','l','e',':',0 };
1885 PARSEDURLW base;
1886 DWORD res1;
1887 LPCWSTR last;
1889 TRACE("(%s %d)\n", debugstr_w(pszUrl), Urlis);
1891 if(!pszUrl)
1892 return FALSE;
1894 switch (Urlis) {
1896 case URLIS_OPAQUE:
1897 base.cbSize = sizeof(base);
1898 res1 = ParseURLW(pszUrl, &base);
1899 if (res1) return FALSE; /* invalid scheme */
1900 switch (base.nScheme)
1902 case URL_SCHEME_MAILTO:
1903 case URL_SCHEME_SHELL:
1904 case URL_SCHEME_JAVASCRIPT:
1905 case URL_SCHEME_VBSCRIPT:
1906 case URL_SCHEME_ABOUT:
1907 return TRUE;
1909 return FALSE;
1911 case URLIS_FILEURL:
1912 return !strncmpW(stemp, pszUrl, 5);
1914 case URLIS_DIRECTORY:
1915 last = pszUrl + strlenW(pszUrl) - 1;
1916 return (last >= pszUrl && (*last == '/' || *last == '\\'));
1918 case URLIS_URL:
1919 return PathIsURLW(pszUrl);
1921 case URLIS_NOHISTORY:
1922 case URLIS_APPLIABLE:
1923 case URLIS_HASQUERY:
1924 default:
1925 FIXME("(%s %d): stub\n", debugstr_w(pszUrl), Urlis);
1927 return FALSE;
1930 /*************************************************************************
1931 * UrlIsNoHistoryA [SHLWAPI.@]
1933 * Determine if a Url should not be stored in the users history list.
1935 * PARAMS
1936 * pszUrl [I] Url to check
1938 * RETURNS
1939 * TRUE, if pszUrl should be excluded from the history list,
1940 * FALSE otherwise.
1942 BOOL WINAPI UrlIsNoHistoryA(LPCSTR pszUrl)
1944 return UrlIsA(pszUrl, URLIS_NOHISTORY);
1947 /*************************************************************************
1948 * UrlIsNoHistoryW [SHLWAPI.@]
1950 * See UrlIsNoHistoryA.
1952 BOOL WINAPI UrlIsNoHistoryW(LPCWSTR pszUrl)
1954 return UrlIsW(pszUrl, URLIS_NOHISTORY);
1957 /*************************************************************************
1958 * UrlIsOpaqueA [SHLWAPI.@]
1960 * Determine if a Url is opaque.
1962 * PARAMS
1963 * pszUrl [I] Url to check
1965 * RETURNS
1966 * TRUE if pszUrl is opaque,
1967 * FALSE Otherwise.
1969 * NOTES
1970 * An opaque Url is one that does not start with "<protocol>://".
1972 BOOL WINAPI UrlIsOpaqueA(LPCSTR pszUrl)
1974 return UrlIsA(pszUrl, URLIS_OPAQUE);
1977 /*************************************************************************
1978 * UrlIsOpaqueW [SHLWAPI.@]
1980 * See UrlIsOpaqueA.
1982 BOOL WINAPI UrlIsOpaqueW(LPCWSTR pszUrl)
1984 return UrlIsW(pszUrl, URLIS_OPAQUE);
1987 /*************************************************************************
1988 * Scans for characters of type "type" and when not matching found,
1989 * returns pointer to it and length in size.
1991 * Characters tested based on RFC 1738
1993 static LPCWSTR URL_ScanID(LPCWSTR start, LPDWORD size, WINE_URL_SCAN_TYPE type)
1995 static DWORD alwayszero = 0;
1996 BOOL cont = TRUE;
1998 *size = 0;
2000 switch(type){
2002 case SCHEME:
2003 while (cont) {
2004 if ( (islowerW(*start) && isalphaW(*start)) ||
2005 isdigitW(*start) ||
2006 (*start == '+') ||
2007 (*start == '-') ||
2008 (*start == '.')) {
2009 start++;
2010 (*size)++;
2012 else
2013 cont = FALSE;
2016 if(*start != ':')
2017 *size = 0;
2019 break;
2021 case USERPASS:
2022 while (cont) {
2023 if ( isalphaW(*start) ||
2024 isdigitW(*start) ||
2025 /* user/password only characters */
2026 (*start == ';') ||
2027 (*start == '?') ||
2028 (*start == '&') ||
2029 (*start == '=') ||
2030 /* *extra* characters */
2031 (*start == '!') ||
2032 (*start == '*') ||
2033 (*start == '\'') ||
2034 (*start == '(') ||
2035 (*start == ')') ||
2036 (*start == ',') ||
2037 /* *safe* characters */
2038 (*start == '$') ||
2039 (*start == '_') ||
2040 (*start == '+') ||
2041 (*start == '-') ||
2042 (*start == '.') ||
2043 (*start == ' ')) {
2044 start++;
2045 (*size)++;
2046 } else if (*start == '%') {
2047 if (isxdigitW(*(start+1)) &&
2048 isxdigitW(*(start+2))) {
2049 start += 3;
2050 *size += 3;
2051 } else
2052 cont = FALSE;
2053 } else
2054 cont = FALSE;
2056 break;
2058 case PORT:
2059 while (cont) {
2060 if (isdigitW(*start)) {
2061 start++;
2062 (*size)++;
2064 else
2065 cont = FALSE;
2067 break;
2069 case HOST:
2070 while (cont) {
2071 if (isalnumW(*start) ||
2072 (*start == '-') ||
2073 (*start == '.') ||
2074 (*start == ' ') ||
2075 (*start == '*') ) {
2076 start++;
2077 (*size)++;
2079 else
2080 cont = FALSE;
2082 break;
2083 default:
2084 FIXME("unknown type %d\n", type);
2085 return (LPWSTR)&alwayszero;
2087 /* TRACE("scanned %d characters next char %p<%c>\n",
2088 *size, start, *start); */
2089 return start;
2092 /*************************************************************************
2093 * Attempt to parse URL into pieces.
2095 static LONG URL_ParseUrl(LPCWSTR pszUrl, WINE_PARSE_URL *pl)
2097 LPCWSTR work;
2099 memset(pl, 0, sizeof(WINE_PARSE_URL));
2100 pl->pScheme = pszUrl;
2101 work = URL_ScanID(pl->pScheme, &pl->szScheme, SCHEME);
2102 if (!*work || (*work != ':')) goto ErrorExit;
2103 work++;
2104 if ((*work != '/') || (*(work+1) != '/')) goto SuccessExit;
2105 pl->pUserName = work + 2;
2106 work = URL_ScanID(pl->pUserName, &pl->szUserName, USERPASS);
2107 if (*work == ':' ) {
2108 /* parse password */
2109 work++;
2110 pl->pPassword = work;
2111 work = URL_ScanID(pl->pPassword, &pl->szPassword, USERPASS);
2112 if (*work != '@') {
2113 /* what we just parsed must be the hostname and port
2114 * so reset pointers and clear then let it parse */
2115 pl->szUserName = pl->szPassword = 0;
2116 work = pl->pUserName - 1;
2117 pl->pUserName = pl->pPassword = 0;
2119 } else if (*work == '@') {
2120 /* no password */
2121 pl->szPassword = 0;
2122 pl->pPassword = 0;
2123 } else if (!*work || (*work == '/') || (*work == '.')) {
2124 /* what was parsed was hostname, so reset pointers and let it parse */
2125 pl->szUserName = pl->szPassword = 0;
2126 work = pl->pUserName - 1;
2127 pl->pUserName = pl->pPassword = 0;
2128 } else goto ErrorExit;
2130 /* now start parsing hostname or hostnumber */
2131 work++;
2132 pl->pHostName = work;
2133 work = URL_ScanID(pl->pHostName, &pl->szHostName, HOST);
2134 if (*work == ':') {
2135 /* parse port */
2136 work++;
2137 pl->pPort = work;
2138 work = URL_ScanID(pl->pPort, &pl->szPort, PORT);
2140 if (*work == '/') {
2141 /* see if query string */
2142 pl->pQuery = strchrW(work, '?');
2143 if (pl->pQuery) pl->szQuery = strlenW(pl->pQuery);
2145 SuccessExit:
2146 TRACE("parse successful: scheme=%p(%d), user=%p(%d), pass=%p(%d), host=%p(%d), port=%p(%d), query=%p(%d)\n",
2147 pl->pScheme, pl->szScheme,
2148 pl->pUserName, pl->szUserName,
2149 pl->pPassword, pl->szPassword,
2150 pl->pHostName, pl->szHostName,
2151 pl->pPort, pl->szPort,
2152 pl->pQuery, pl->szQuery);
2153 return S_OK;
2154 ErrorExit:
2155 FIXME("failed to parse %s\n", debugstr_w(pszUrl));
2156 return E_INVALIDARG;
2159 /*************************************************************************
2160 * UrlGetPartA [SHLWAPI.@]
2162 * Retrieve part of a Url.
2164 * PARAMS
2165 * pszIn [I] Url to parse
2166 * pszOut [O] Destination for part of pszIn requested
2167 * pcchOut [I] Size of pszOut
2168 * [O] length of pszOut string EXCLUDING '\0' if S_OK, otherwise
2169 * needed size of pszOut INCLUDING '\0'.
2170 * dwPart [I] URL_PART_ enum from "shlwapi.h"
2171 * dwFlags [I] URL_ flags from "shlwapi.h"
2173 * RETURNS
2174 * Success: S_OK. pszOut contains the part requested, pcchOut contains its length.
2175 * Failure: An HRESULT error code describing the error.
2177 HRESULT WINAPI UrlGetPartA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut,
2178 DWORD dwPart, DWORD dwFlags)
2180 LPWSTR in, out;
2181 DWORD ret, len, len2;
2183 if(!pszIn || !pszOut || !pcchOut || *pcchOut <= 0)
2184 return E_INVALIDARG;
2186 in = HeapAlloc(GetProcessHeap(), 0,
2187 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
2188 out = in + INTERNET_MAX_URL_LENGTH;
2190 MultiByteToWideChar(0, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
2192 len = INTERNET_MAX_URL_LENGTH;
2193 ret = UrlGetPartW(in, out, &len, dwPart, dwFlags);
2195 if (FAILED(ret)) {
2196 HeapFree(GetProcessHeap(), 0, in);
2197 return ret;
2200 len2 = WideCharToMultiByte(0, 0, out, len, 0, 0, 0, 0);
2201 if (len2 > *pcchOut) {
2202 *pcchOut = len2+1;
2203 HeapFree(GetProcessHeap(), 0, in);
2204 return E_POINTER;
2206 len2 = WideCharToMultiByte(0, 0, out, len+1, pszOut, *pcchOut, 0, 0);
2207 *pcchOut = len2-1;
2208 HeapFree(GetProcessHeap(), 0, in);
2209 return ret;
2212 /*************************************************************************
2213 * UrlGetPartW [SHLWAPI.@]
2215 * See UrlGetPartA.
2217 HRESULT WINAPI UrlGetPartW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut,
2218 DWORD dwPart, DWORD dwFlags)
2220 WINE_PARSE_URL pl;
2221 HRESULT ret;
2222 DWORD scheme, size, schsize;
2223 LPCWSTR addr, schaddr;
2225 TRACE("(%s %p %p(%d) %08x %08x)\n",
2226 debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwPart, dwFlags);
2228 if(!pszIn || !pszOut || !pcchOut || *pcchOut <= 0)
2229 return E_INVALIDARG;
2231 *pszOut = '\0';
2233 addr = strchrW(pszIn, ':');
2234 if(!addr)
2235 scheme = URL_SCHEME_UNKNOWN;
2236 else
2237 scheme = get_scheme_code(pszIn, addr-pszIn);
2239 ret = URL_ParseUrl(pszIn, &pl);
2241 switch (dwPart) {
2242 case URL_PART_SCHEME:
2243 if (!pl.szScheme) {
2244 *pcchOut = 0;
2245 return S_FALSE;
2247 addr = pl.pScheme;
2248 size = pl.szScheme;
2249 break;
2251 case URL_PART_HOSTNAME:
2252 switch(scheme) {
2253 case URL_SCHEME_FTP:
2254 case URL_SCHEME_HTTP:
2255 case URL_SCHEME_GOPHER:
2256 case URL_SCHEME_TELNET:
2257 case URL_SCHEME_FILE:
2258 case URL_SCHEME_HTTPS:
2259 break;
2260 default:
2261 *pcchOut = 0;
2262 return E_FAIL;
2265 if(scheme==URL_SCHEME_FILE && (!pl.szHostName ||
2266 (pl.szHostName==1 && *(pl.pHostName+1)==':'))) {
2267 *pcchOut = 0;
2268 return S_FALSE;
2271 if (!pl.szHostName) {
2272 *pcchOut = 0;
2273 return S_FALSE;
2275 addr = pl.pHostName;
2276 size = pl.szHostName;
2277 break;
2279 case URL_PART_USERNAME:
2280 if (!pl.szUserName) {
2281 *pcchOut = 0;
2282 return S_FALSE;
2284 addr = pl.pUserName;
2285 size = pl.szUserName;
2286 break;
2288 case URL_PART_PASSWORD:
2289 if (!pl.szPassword) {
2290 *pcchOut = 0;
2291 return S_FALSE;
2293 addr = pl.pPassword;
2294 size = pl.szPassword;
2295 break;
2297 case URL_PART_PORT:
2298 if (!pl.szPort) {
2299 *pcchOut = 0;
2300 return S_FALSE;
2302 addr = pl.pPort;
2303 size = pl.szPort;
2304 break;
2306 case URL_PART_QUERY:
2307 if (!pl.szQuery) {
2308 *pcchOut = 0;
2309 return S_FALSE;
2311 addr = pl.pQuery;
2312 size = pl.szQuery;
2313 break;
2315 default:
2316 *pcchOut = 0;
2317 return E_INVALIDARG;
2320 if (dwFlags == URL_PARTFLAG_KEEPSCHEME) {
2321 if(!pl.pScheme || !pl.szScheme) {
2322 *pcchOut = 0;
2323 return E_FAIL;
2325 schaddr = pl.pScheme;
2326 schsize = pl.szScheme;
2327 if (*pcchOut < schsize + size + 2) {
2328 *pcchOut = schsize + size + 2;
2329 return E_POINTER;
2331 memcpy(pszOut, schaddr, schsize*sizeof(WCHAR));
2332 pszOut[schsize] = ':';
2333 memcpy(pszOut+schsize+1, addr, size*sizeof(WCHAR));
2334 pszOut[schsize+1+size] = 0;
2335 *pcchOut = schsize + 1 + size;
2337 else {
2338 if (*pcchOut < size + 1) {*pcchOut = size+1; return E_POINTER;}
2339 memcpy(pszOut, addr, size*sizeof(WCHAR));
2340 pszOut[size] = 0;
2341 *pcchOut = size;
2343 TRACE("len=%d %s\n", *pcchOut, debugstr_w(pszOut));
2345 return ret;
2348 /*************************************************************************
2349 * PathIsURLA [SHLWAPI.@]
2351 * Check if the given path is a Url.
2353 * PARAMS
2354 * lpszPath [I] Path to check.
2356 * RETURNS
2357 * TRUE if lpszPath is a Url.
2358 * FALSE if lpszPath is NULL or not a Url.
2360 BOOL WINAPI PathIsURLA(LPCSTR lpstrPath)
2362 PARSEDURLA base;
2363 HRESULT hres;
2365 TRACE("%s\n", debugstr_a(lpstrPath));
2367 if (!lpstrPath || !*lpstrPath) return FALSE;
2369 /* get protocol */
2370 base.cbSize = sizeof(base);
2371 hres = ParseURLA(lpstrPath, &base);
2372 return hres == S_OK && (base.nScheme != URL_SCHEME_INVALID);
2375 /*************************************************************************
2376 * PathIsURLW [SHLWAPI.@]
2378 * See PathIsURLA.
2380 BOOL WINAPI PathIsURLW(LPCWSTR lpstrPath)
2382 PARSEDURLW base;
2383 HRESULT hres;
2385 TRACE("%s\n", debugstr_w(lpstrPath));
2387 if (!lpstrPath || !*lpstrPath) return FALSE;
2389 /* get protocol */
2390 base.cbSize = sizeof(base);
2391 hres = ParseURLW(lpstrPath, &base);
2392 return hres == S_OK && (base.nScheme != URL_SCHEME_INVALID);
2395 /*************************************************************************
2396 * UrlCreateFromPathA [SHLWAPI.@]
2398 * See UrlCreateFromPathW
2400 HRESULT WINAPI UrlCreateFromPathA(LPCSTR pszPath, LPSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2402 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
2403 WCHAR *urlW = bufW;
2404 UNICODE_STRING pathW;
2405 HRESULT ret;
2406 DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
2408 if(!RtlCreateUnicodeStringFromAsciiz(&pathW, pszPath))
2409 return E_INVALIDARG;
2410 if((ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved)) == E_POINTER) {
2411 urlW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
2412 ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved);
2414 if(ret == S_OK || ret == S_FALSE) {
2415 RtlUnicodeToMultiByteSize(&lenA, urlW, lenW * sizeof(WCHAR));
2416 if(*pcchUrl > lenA) {
2417 RtlUnicodeToMultiByteN(pszUrl, *pcchUrl - 1, &lenA, urlW, lenW * sizeof(WCHAR));
2418 pszUrl[lenA] = 0;
2419 *pcchUrl = lenA;
2420 } else {
2421 *pcchUrl = lenA + 1;
2422 ret = E_POINTER;
2425 if(urlW != bufW) HeapFree(GetProcessHeap(), 0, urlW);
2426 RtlFreeUnicodeString(&pathW);
2427 return ret;
2430 /*************************************************************************
2431 * UrlCreateFromPathW [SHLWAPI.@]
2433 * Create a Url from a file path.
2435 * PARAMS
2436 * pszPath [I] Path to convert
2437 * pszUrl [O] Destination for the converted Url
2438 * pcchUrl [I/O] Length of pszUrl
2439 * dwReserved [I] Reserved, must be 0
2441 * RETURNS
2442 * Success: S_OK pszUrl contains the converted path, S_FALSE if the path is already a Url
2443 * Failure: An HRESULT error code.
2445 HRESULT WINAPI UrlCreateFromPathW(LPCWSTR pszPath, LPWSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2447 HRESULT ret;
2449 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszPath), pszUrl, pcchUrl, dwReserved);
2451 /* Validate arguments */
2452 if (dwReserved != 0)
2453 return E_INVALIDARG;
2454 if (!pszUrl || !pcchUrl)
2455 return E_INVALIDARG;
2457 ret = URL_CreateFromPath(pszPath, pszUrl, pcchUrl);
2459 if (S_FALSE == ret)
2460 strcpyW(pszUrl, pszPath);
2462 return ret;
2465 /*************************************************************************
2466 * SHAutoComplete [SHLWAPI.@]
2468 * Enable auto-completion for an edit control.
2470 * PARAMS
2471 * hwndEdit [I] Handle of control to enable auto-completion for
2472 * dwFlags [I] SHACF_ flags from "shlwapi.h"
2474 * RETURNS
2475 * Success: S_OK. Auto-completion is enabled for the control.
2476 * Failure: An HRESULT error code indicating the error.
2478 HRESULT WINAPI SHAutoComplete(HWND hwndEdit, DWORD dwFlags)
2480 FIXME("stub\n");
2481 return S_FALSE;
2484 /*************************************************************************
2485 * MLBuildResURLA [SHLWAPI.405]
2487 * Create a Url pointing to a resource in a module.
2489 * PARAMS
2490 * lpszLibName [I] Name of the module containing the resource
2491 * hMod [I] Callers module handle
2492 * dwFlags [I] Undocumented flags for loading the module
2493 * lpszRes [I] Resource name
2494 * lpszDest [O] Destination for resulting Url
2495 * dwDestLen [I] Length of lpszDest
2497 * RETURNS
2498 * Success: S_OK. lpszDest contains the resource Url.
2499 * Failure: E_INVALIDARG, if any argument is invalid, or
2500 * E_FAIL if dwDestLen is too small.
2502 HRESULT WINAPI MLBuildResURLA(LPCSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2503 LPCSTR lpszRes, LPSTR lpszDest, DWORD dwDestLen)
2505 WCHAR szLibName[MAX_PATH], szRes[MAX_PATH], szDest[MAX_PATH];
2506 HRESULT hRet;
2508 if (lpszLibName)
2509 MultiByteToWideChar(CP_ACP, 0, lpszLibName, -1, szLibName, sizeof(szLibName)/sizeof(WCHAR));
2511 if (lpszRes)
2512 MultiByteToWideChar(CP_ACP, 0, lpszRes, -1, szRes, sizeof(szRes)/sizeof(WCHAR));
2514 if (dwDestLen > sizeof(szLibName)/sizeof(WCHAR))
2515 dwDestLen = sizeof(szLibName)/sizeof(WCHAR);
2517 hRet = MLBuildResURLW(lpszLibName ? szLibName : NULL, hMod, dwFlags,
2518 lpszRes ? szRes : NULL, lpszDest ? szDest : NULL, dwDestLen);
2519 if (SUCCEEDED(hRet) && lpszDest)
2520 WideCharToMultiByte(CP_ACP, 0, szDest, -1, lpszDest, dwDestLen, 0, 0);
2522 return hRet;
2525 /*************************************************************************
2526 * MLBuildResURLA [SHLWAPI.406]
2528 * See MLBuildResURLA.
2530 HRESULT WINAPI MLBuildResURLW(LPCWSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2531 LPCWSTR lpszRes, LPWSTR lpszDest, DWORD dwDestLen)
2533 static const WCHAR szRes[] = { 'r','e','s',':','/','/','\0' };
2534 #define szResLen ((sizeof(szRes) - sizeof(WCHAR))/sizeof(WCHAR))
2535 HRESULT hRet = E_FAIL;
2537 TRACE("(%s,%p,0x%08x,%s,%p,%d)\n", debugstr_w(lpszLibName), hMod, dwFlags,
2538 debugstr_w(lpszRes), lpszDest, dwDestLen);
2540 if (!lpszLibName || !hMod || hMod == INVALID_HANDLE_VALUE || !lpszRes ||
2541 !lpszDest || (dwFlags && dwFlags != 2))
2542 return E_INVALIDARG;
2544 if (dwDestLen >= szResLen + 1)
2546 dwDestLen -= (szResLen + 1);
2547 memcpy(lpszDest, szRes, sizeof(szRes));
2549 hMod = MLLoadLibraryW(lpszLibName, hMod, dwFlags);
2551 if (hMod)
2553 WCHAR szBuff[MAX_PATH];
2554 DWORD len;
2556 len = GetModuleFileNameW(hMod, szBuff, sizeof(szBuff)/sizeof(WCHAR));
2557 if (len && len < sizeof(szBuff)/sizeof(WCHAR))
2559 DWORD dwPathLen = strlenW(szBuff) + 1;
2561 if (dwDestLen >= dwPathLen)
2563 DWORD dwResLen;
2565 dwDestLen -= dwPathLen;
2566 memcpy(lpszDest + szResLen, szBuff, dwPathLen * sizeof(WCHAR));
2568 dwResLen = strlenW(lpszRes) + 1;
2569 if (dwDestLen >= dwResLen + 1)
2571 lpszDest[szResLen + dwPathLen-1] = '/';
2572 memcpy(lpszDest + szResLen + dwPathLen, lpszRes, dwResLen * sizeof(WCHAR));
2573 hRet = S_OK;
2577 MLFreeLibrary(hMod);
2580 return hRet;
2583 /***********************************************************************
2584 * UrlFixupW [SHLWAPI.462]
2586 * Checks the scheme part of a URL and attempts to correct misspellings.
2588 * PARAMS
2589 * lpszUrl [I] Pointer to the URL to be corrected
2590 * lpszTranslatedUrl [O] Pointer to a buffer to store corrected URL
2591 * dwMaxChars [I] Maximum size of corrected URL
2593 * RETURNS
2594 * success: S_OK if URL corrected or already correct
2595 * failure: S_FALSE if unable to correct / COM error code if other error
2598 HRESULT WINAPI UrlFixupW(LPCWSTR url, LPWSTR translatedUrl, DWORD maxChars)
2600 DWORD srcLen;
2602 FIXME("(%s,%p,%d) STUB\n", debugstr_w(url), translatedUrl, maxChars);
2604 if (!url)
2605 return E_FAIL;
2607 srcLen = lstrlenW(url) + 1;
2609 /* For now just copy the URL directly */
2610 lstrcpynW(translatedUrl, url, (maxChars < srcLen) ? maxChars : srcLen);
2612 return S_OK;