winegstreamer: Add better support for unsupported audio/video.
[wine/multimedia.git] / dlls / shlwapi / url.c
blobae2f408320a33c2eb069b5b39e9fc83d6cf80fbc
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 (base.nScheme == URL_SCHEME_HTTP || base.nScheme == URL_SCHEME_HTTPS)
734 manual_search = TRUE;
735 else if (work - base.pszSuffix > len_htmW * sizeof(WCHAR)) {
736 work -= len_htmW;
737 if (strncmpiW(work, htmW, len_htmW) == 0)
738 manual_search = TRUE;
739 work += len_htmW;
742 if (!manual_search &&
743 work - base.pszSuffix > len_htmlW * sizeof(WCHAR)) {
744 work -= len_htmlW;
745 if (strncmpiW(work, htmlW, len_htmlW) == 0)
746 manual_search = TRUE;
747 work += len_htmlW;
751 if (manual_search) {
752 /* search backwards starting from the current position */
753 while (*work != '/' && work > base.pszSuffix + sizeloc)
754 --work;
755 base.cchSuffix = work - base.pszSuffix + 1;
756 }else {
757 /* search backwards starting from the end of the string */
758 work = strrchrW((base.pszSuffix+sizeloc), '/');
759 if (work) {
760 len = (DWORD)(work - base.pszSuffix + 1);
761 base.cchSuffix = len;
762 }else
763 base.cchSuffix = sizeloc;
767 * At this point:
768 * .pszSuffix points to location (starting with '//')
769 * .cchSuffix length of location (above) and rest less the last
770 * leaf (if any)
771 * sizeloc length of location (above) up to but not including
772 * the last '/'
775 res2 = ParseURLW(mrelative, &relative);
776 if (res2) {
777 /* no scheme in pszRelative */
778 TRACE("no scheme detected in Relative\n");
779 relative.pszSuffix = mrelative; /* case 3,4,5 depends on this */
780 relative.cchSuffix = strlenW(mrelative);
781 if (*pszRelative == ':') {
782 /* case that is either left alone or uses pszBase */
783 if (dwFlags & URL_PLUGGABLE_PROTOCOL) {
784 process_case = 5;
785 break;
787 process_case = 1;
788 break;
790 if (isalnum(*mrelative) && (*(mrelative + 1) == ':')) {
791 /* case that becomes "file:///" */
792 strcpyW(preliminary, myfilestr);
793 process_case = 1;
794 break;
796 if ((*mrelative == '/') && (*(mrelative+1) == '/')) {
797 /* pszRelative has location and rest */
798 process_case = 3;
799 break;
801 if (*mrelative == '/') {
802 /* case where pszRelative is root to location */
803 process_case = 4;
804 break;
806 if (*mrelative == '#') {
807 if(!(work = strchrW(base.pszSuffix+base.cchSuffix, '#')))
808 work = (LPWSTR)base.pszSuffix + strlenW(base.pszSuffix);
810 memcpy(preliminary, base.pszProtocol, (work-base.pszProtocol)*sizeof(WCHAR));
811 preliminary[work-base.pszProtocol] = '\0';
812 process_case = 1;
813 break;
815 process_case = (*base.pszSuffix == '/' || base.nScheme == URL_SCHEME_MK) ? 5 : 3;
816 break;
819 /* handle cases where pszRelative has scheme */
820 if ((base.cchProtocol == relative.cchProtocol) &&
821 (strncmpW(base.pszProtocol, relative.pszProtocol, base.cchProtocol) == 0)) {
823 /* since the schemes are the same */
824 if ((*relative.pszSuffix == '/') && (*(relative.pszSuffix+1) == '/')) {
825 /* case where pszRelative replaces location and following */
826 process_case = 3;
827 break;
829 if (*relative.pszSuffix == '/') {
830 /* case where pszRelative is root to location */
831 process_case = 4;
832 break;
834 /* replace either just location if base's location starts with a
835 * slash or otherwise everything */
836 process_case = (*base.pszSuffix == '/') ? 5 : 1;
837 break;
839 if ((*relative.pszSuffix == '/') && (*(relative.pszSuffix+1) == '/')) {
840 /* case where pszRelative replaces scheme, location,
841 * and following and handles PLUGGABLE
843 process_case = 2;
844 break;
846 process_case = 1;
847 break;
848 } while(FALSE); /* a little trick to allow easy exit from nested if's */
850 ret = S_OK;
851 switch (process_case) {
853 case 1: /*
854 * Return pszRelative appended to what ever is in pszCombined,
855 * (which may the string "file:///"
857 strcatW(preliminary, mrelative);
858 break;
860 case 2: /* case where pszRelative replaces scheme, and location */
861 strcpyW(preliminary, mrelative);
862 break;
864 case 3: /*
865 * Return the pszBase scheme with pszRelative. Basically
866 * keeps the scheme and replaces the domain and following.
868 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1)*sizeof(WCHAR));
869 work = preliminary + base.cchProtocol + 1;
870 strcpyW(work, relative.pszSuffix);
871 break;
873 case 4: /*
874 * Return the pszBase scheme and location but everything
875 * after the location is pszRelative. (Replace document
876 * from root on.)
878 memcpy(preliminary, base.pszProtocol, (base.cchProtocol+1+sizeloc)*sizeof(WCHAR));
879 work = preliminary + base.cchProtocol + 1 + sizeloc;
880 if (dwFlags & URL_PLUGGABLE_PROTOCOL)
881 *(work++) = '/';
882 strcpyW(work, relative.pszSuffix);
883 break;
885 case 5: /*
886 * Return the pszBase without its document (if any) and
887 * append pszRelative after its scheme.
889 memcpy(preliminary, base.pszProtocol,
890 (base.cchProtocol+1+base.cchSuffix)*sizeof(WCHAR));
891 work = preliminary + base.cchProtocol+1+base.cchSuffix - 1;
892 if (*work++ != '/')
893 *(work++) = '/';
894 strcpyW(work, relative.pszSuffix);
895 break;
897 default:
898 FIXME("How did we get here????? process_case=%d\n", process_case);
899 ret = E_INVALIDARG;
902 if (ret == S_OK) {
903 /* Reuse mrelative as temp storage as its already allocated and not needed anymore */
904 if(*pcchCombined == 0)
905 *pcchCombined = 1;
906 ret = UrlCanonicalizeW(preliminary, mrelative, pcchCombined, (dwFlags & ~URL_FILE_USE_PATHURL));
907 if(SUCCEEDED(ret) && pszCombined) {
908 lstrcpyW(pszCombined, mrelative);
910 TRACE("return-%d len=%d, %s\n",
911 process_case, *pcchCombined, debugstr_w(pszCombined));
913 HeapFree(GetProcessHeap(), 0, preliminary);
914 return ret;
917 /*************************************************************************
918 * UrlEscapeA [SHLWAPI.@]
921 HRESULT WINAPI UrlEscapeA(
922 LPCSTR pszUrl,
923 LPSTR pszEscaped,
924 LPDWORD pcchEscaped,
925 DWORD dwFlags)
927 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
928 WCHAR *escapedW = bufW;
929 UNICODE_STRING urlW;
930 HRESULT ret;
931 DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
933 if (!pszEscaped || !pcchEscaped || !*pcchEscaped)
934 return E_INVALIDARG;
936 if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl))
937 return E_INVALIDARG;
938 if((ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags)) == E_POINTER) {
939 escapedW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
940 ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags);
942 if(ret == S_OK) {
943 RtlUnicodeToMultiByteSize(&lenA, escapedW, lenW * sizeof(WCHAR));
944 if(*pcchEscaped > lenA) {
945 RtlUnicodeToMultiByteN(pszEscaped, *pcchEscaped - 1, &lenA, escapedW, lenW * sizeof(WCHAR));
946 pszEscaped[lenA] = 0;
947 *pcchEscaped = lenA;
948 } else {
949 *pcchEscaped = lenA + 1;
950 ret = E_POINTER;
953 if(escapedW != bufW) HeapFree(GetProcessHeap(), 0, escapedW);
954 RtlFreeUnicodeString(&urlW);
955 return ret;
958 #define WINE_URL_BASH_AS_SLASH 0x01
959 #define WINE_URL_COLLAPSE_SLASHES 0x02
960 #define WINE_URL_ESCAPE_SLASH 0x04
961 #define WINE_URL_ESCAPE_HASH 0x08
962 #define WINE_URL_ESCAPE_QUESTION 0x10
963 #define WINE_URL_STOP_ON_HASH 0x20
964 #define WINE_URL_STOP_ON_QUESTION 0x40
966 static inline BOOL URL_NeedEscapeW(WCHAR ch, DWORD dwFlags, DWORD int_flags)
969 if (isalnumW(ch))
970 return FALSE;
972 if(dwFlags & URL_ESCAPE_SPACES_ONLY) {
973 if(ch == ' ')
974 return TRUE;
975 else
976 return FALSE;
979 if ((dwFlags & URL_ESCAPE_PERCENT) && (ch == '%'))
980 return TRUE;
982 if (ch <= 31 || ch >= 127)
983 return TRUE;
985 else {
986 switch (ch) {
987 case ' ':
988 case '<':
989 case '>':
990 case '\"':
991 case '{':
992 case '}':
993 case '|':
994 case '\\':
995 case '^':
996 case ']':
997 case '[':
998 case '`':
999 case '&':
1000 return TRUE;
1002 case '/':
1003 if (int_flags & WINE_URL_ESCAPE_SLASH) return TRUE;
1004 return FALSE;
1006 case '?':
1007 if (int_flags & WINE_URL_ESCAPE_QUESTION) return TRUE;
1008 return FALSE;
1010 case '#':
1011 if (int_flags & WINE_URL_ESCAPE_HASH) return TRUE;
1012 return FALSE;
1014 default:
1015 return FALSE;
1021 /*************************************************************************
1022 * UrlEscapeW [SHLWAPI.@]
1024 * Converts unsafe characters in a Url into escape sequences.
1026 * PARAMS
1027 * pszUrl [I] Url to modify
1028 * pszEscaped [O] Destination for modified Url
1029 * pcchEscaped [I/O] Length of pszUrl, destination for length of pszEscaped
1030 * dwFlags [I] URL_ flags from "shlwapi.h"
1032 * RETURNS
1033 * Success: S_OK. pszEscaped contains the escaped Url, pcchEscaped
1034 * contains its length.
1035 * Failure: E_POINTER, if pszEscaped is not large enough. In this case
1036 * pcchEscaped is set to the required length.
1038 * Converts unsafe characters into their escape sequences.
1040 * NOTES
1041 * - By default this function stops converting at the first '?' or
1042 * '#' character.
1043 * - If dwFlags contains URL_ESCAPE_SPACES_ONLY then only spaces are
1044 * converted, but the conversion continues past a '?' or '#'.
1045 * - Note that this function did not work well (or at all) in shlwapi version 4.
1047 * BUGS
1048 * Only the following flags are implemented:
1049 *| URL_ESCAPE_SPACES_ONLY
1050 *| URL_DONT_ESCAPE_EXTRA_INFO
1051 *| URL_ESCAPE_SEGMENT_ONLY
1052 *| URL_ESCAPE_PERCENT
1054 HRESULT WINAPI UrlEscapeW(
1055 LPCWSTR pszUrl,
1056 LPWSTR pszEscaped,
1057 LPDWORD pcchEscaped,
1058 DWORD dwFlags)
1060 LPCWSTR src;
1061 DWORD needed = 0, ret;
1062 BOOL stop_escaping = FALSE;
1063 WCHAR next[5], *dst = pszEscaped;
1064 INT len;
1065 PARSEDURLW parsed_url;
1066 DWORD int_flags;
1067 DWORD slashes = 0;
1068 static const WCHAR localhost[] = {'l','o','c','a','l','h','o','s','t',0};
1070 TRACE("(%p(%s) %p %p 0x%08x)\n", pszUrl, debugstr_w(pszUrl),
1071 pszEscaped, pcchEscaped, dwFlags);
1073 if(!pszUrl || !pcchEscaped)
1074 return E_INVALIDARG;
1076 if(dwFlags & ~(URL_ESCAPE_SPACES_ONLY |
1077 URL_ESCAPE_SEGMENT_ONLY |
1078 URL_DONT_ESCAPE_EXTRA_INFO |
1079 URL_ESCAPE_PERCENT))
1080 FIXME("Unimplemented flags: %08x\n", dwFlags);
1082 if(pszUrl == pszEscaped) {
1083 dst = HeapAlloc(GetProcessHeap(), 0, *pcchEscaped*sizeof(WCHAR));
1084 if(!dst)
1085 return E_OUTOFMEMORY;
1088 /* fix up flags */
1089 if (dwFlags & URL_ESCAPE_SPACES_ONLY)
1090 /* if SPACES_ONLY specified, reset the other controls */
1091 dwFlags &= ~(URL_DONT_ESCAPE_EXTRA_INFO |
1092 URL_ESCAPE_PERCENT |
1093 URL_ESCAPE_SEGMENT_ONLY);
1095 else
1096 /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
1097 dwFlags |= URL_DONT_ESCAPE_EXTRA_INFO;
1100 int_flags = 0;
1101 if(dwFlags & URL_ESCAPE_SEGMENT_ONLY) {
1102 int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH;
1103 } else {
1104 parsed_url.cbSize = sizeof(parsed_url);
1105 if(ParseURLW(pszUrl, &parsed_url) != S_OK)
1106 parsed_url.nScheme = URL_SCHEME_INVALID;
1108 TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol));
1110 if(dwFlags & URL_DONT_ESCAPE_EXTRA_INFO)
1111 int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION;
1113 switch(parsed_url.nScheme) {
1114 case URL_SCHEME_FILE:
1115 int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH;
1116 int_flags &= ~WINE_URL_STOP_ON_HASH;
1117 break;
1119 case URL_SCHEME_HTTP:
1120 case URL_SCHEME_HTTPS:
1121 int_flags |= WINE_URL_BASH_AS_SLASH;
1122 if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\')
1123 int_flags |= WINE_URL_ESCAPE_SLASH;
1124 break;
1126 case URL_SCHEME_MAILTO:
1127 int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH;
1128 int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH);
1129 break;
1131 case URL_SCHEME_INVALID:
1132 break;
1134 case URL_SCHEME_FTP:
1135 default:
1136 if(parsed_url.pszSuffix[0] != '/')
1137 int_flags |= WINE_URL_ESCAPE_SLASH;
1138 break;
1142 for(src = pszUrl; *src; ) {
1143 WCHAR cur = *src;
1144 len = 0;
1146 if((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == pszUrl + parsed_url.cchProtocol + 1) {
1147 int localhost_len = sizeof(localhost)/sizeof(WCHAR) - 1;
1148 while(cur == '/' || cur == '\\') {
1149 slashes++;
1150 cur = *++src;
1152 if(slashes == 2 && !strncmpiW(src, localhost, localhost_len)) { /* file://localhost/ -> file:/// */
1153 if(*(src + localhost_len) == '/' || *(src + localhost_len) == '\\')
1154 src += localhost_len + 1;
1155 slashes = 3;
1158 switch(slashes) {
1159 case 1:
1160 case 3:
1161 next[0] = next[1] = next[2] = '/';
1162 len = 3;
1163 break;
1164 case 0:
1165 len = 0;
1166 break;
1167 default:
1168 next[0] = next[1] = '/';
1169 len = 2;
1170 break;
1173 if(len == 0) {
1175 if(cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH))
1176 stop_escaping = TRUE;
1178 if(cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION))
1179 stop_escaping = TRUE;
1181 if(cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/';
1183 if(URL_NeedEscapeW(cur, dwFlags, int_flags) && stop_escaping == FALSE) {
1184 next[0] = '%';
1185 next[1] = hexDigits[(cur >> 4) & 0xf];
1186 next[2] = hexDigits[cur & 0xf];
1187 len = 3;
1188 } else {
1189 next[0] = cur;
1190 len = 1;
1192 src++;
1195 if(needed + len <= *pcchEscaped) {
1196 memcpy(dst, next, len*sizeof(WCHAR));
1197 dst += len;
1199 needed += len;
1202 if(needed < *pcchEscaped) {
1203 *dst = '\0';
1204 if(pszUrl == pszEscaped)
1205 memcpy(pszEscaped, dst-needed, (needed+1)*sizeof(WCHAR));
1207 ret = S_OK;
1208 } else {
1209 needed++; /* add one for the '\0' */
1210 ret = E_POINTER;
1212 *pcchEscaped = needed;
1214 if(pszUrl == pszEscaped)
1215 HeapFree(GetProcessHeap(), 0, dst);
1216 return ret;
1220 /*************************************************************************
1221 * UrlUnescapeA [SHLWAPI.@]
1223 * Converts Url escape sequences back to ordinary characters.
1225 * PARAMS
1226 * pszUrl [I/O] Url to convert
1227 * pszUnescaped [O] Destination for converted Url
1228 * pcchUnescaped [I/O] Size of output string
1229 * dwFlags [I] URL_ESCAPE_ Flags from "shlwapi.h"
1231 * RETURNS
1232 * Success: S_OK. The converted value is in pszUnescaped, or in pszUrl if
1233 * dwFlags includes URL_ESCAPE_INPLACE.
1234 * Failure: E_POINTER if the converted Url is bigger than pcchUnescaped. In
1235 * this case pcchUnescaped is set to the size required.
1236 * NOTES
1237 * If dwFlags includes URL_DONT_ESCAPE_EXTRA_INFO, the conversion stops at
1238 * the first occurrence of either a '?' or '#' character.
1240 HRESULT WINAPI UrlUnescapeA(
1241 LPSTR pszUrl,
1242 LPSTR pszUnescaped,
1243 LPDWORD pcchUnescaped,
1244 DWORD dwFlags)
1246 char *dst, next;
1247 LPCSTR src;
1248 HRESULT ret;
1249 DWORD needed;
1250 BOOL stop_unescaping = FALSE;
1252 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_a(pszUrl), pszUnescaped,
1253 pcchUnescaped, dwFlags);
1255 if (!pszUrl) return E_INVALIDARG;
1257 if(dwFlags & URL_UNESCAPE_INPLACE)
1258 dst = pszUrl;
1259 else
1261 if (!pszUnescaped || !pcchUnescaped) return E_INVALIDARG;
1262 dst = pszUnescaped;
1265 for(src = pszUrl, needed = 0; *src; src++, needed++) {
1266 if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1267 (*src == '#' || *src == '?')) {
1268 stop_unescaping = TRUE;
1269 next = *src;
1270 } else if(*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2))
1271 && stop_unescaping == FALSE) {
1272 INT ih;
1273 char buf[3];
1274 memcpy(buf, src + 1, 2);
1275 buf[2] = '\0';
1276 ih = strtol(buf, NULL, 16);
1277 next = (CHAR) ih;
1278 src += 2; /* Advance to end of escape */
1279 } else
1280 next = *src;
1282 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1283 *dst++ = next;
1286 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1287 *dst = '\0';
1288 ret = S_OK;
1289 } else {
1290 needed++; /* add one for the '\0' */
1291 ret = E_POINTER;
1293 if(!(dwFlags & URL_UNESCAPE_INPLACE))
1294 *pcchUnescaped = needed;
1296 if (ret == S_OK) {
1297 TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1298 debugstr_a(pszUrl) : debugstr_a(pszUnescaped));
1301 return ret;
1304 /*************************************************************************
1305 * UrlUnescapeW [SHLWAPI.@]
1307 * See UrlUnescapeA.
1309 HRESULT WINAPI UrlUnescapeW(
1310 LPWSTR pszUrl,
1311 LPWSTR pszUnescaped,
1312 LPDWORD pcchUnescaped,
1313 DWORD dwFlags)
1315 WCHAR *dst, next;
1316 LPCWSTR src;
1317 HRESULT ret;
1318 DWORD needed;
1319 BOOL stop_unescaping = FALSE;
1321 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszUrl), pszUnescaped,
1322 pcchUnescaped, dwFlags);
1324 if(!pszUrl) return E_INVALIDARG;
1326 if(dwFlags & URL_UNESCAPE_INPLACE)
1327 dst = pszUrl;
1328 else
1330 if (!pszUnescaped || !pcchUnescaped) return E_INVALIDARG;
1331 dst = pszUnescaped;
1334 for(src = pszUrl, needed = 0; *src; src++, needed++) {
1335 if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1336 (*src == '#' || *src == '?')) {
1337 stop_unescaping = TRUE;
1338 next = *src;
1339 } else if(*src == '%' && isxdigitW(*(src + 1)) && isxdigitW(*(src + 2))
1340 && stop_unescaping == FALSE) {
1341 INT ih;
1342 WCHAR buf[5] = {'0','x',0};
1343 memcpy(buf + 2, src + 1, 2*sizeof(WCHAR));
1344 buf[4] = 0;
1345 StrToIntExW(buf, STIF_SUPPORT_HEX, &ih);
1346 next = (WCHAR) ih;
1347 src += 2; /* Advance to end of escape */
1348 } else
1349 next = *src;
1351 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1352 *dst++ = next;
1355 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1356 *dst = '\0';
1357 ret = S_OK;
1358 } else {
1359 needed++; /* add one for the '\0' */
1360 ret = E_POINTER;
1362 if(!(dwFlags & URL_UNESCAPE_INPLACE))
1363 *pcchUnescaped = needed;
1365 if (ret == S_OK) {
1366 TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1367 debugstr_w(pszUrl) : debugstr_w(pszUnescaped));
1370 return ret;
1373 /*************************************************************************
1374 * UrlGetLocationA [SHLWAPI.@]
1376 * Get the location from a Url.
1378 * PARAMS
1379 * pszUrl [I] Url to get the location from
1381 * RETURNS
1382 * A pointer to the start of the location in pszUrl, or NULL if there is
1383 * no location.
1385 * NOTES
1386 * - MSDN erroneously states that "The location is the segment of the Url
1387 * starting with a '?' or '#' character". Neither V4 nor V5 of shlwapi.dll
1388 * stop at '?' and always return a NULL in this case.
1389 * - MSDN also erroneously states that "If a file URL has a query string,
1390 * the returned string is the query string". In all tested cases, if the
1391 * Url starts with "fi" then a NULL is returned. V5 gives the following results:
1392 *| Result Url
1393 *| ------ ---
1394 *| NULL file://aa/b/cd#hohoh
1395 *| #hohoh http://aa/b/cd#hohoh
1396 *| NULL fi://aa/b/cd#hohoh
1397 *| #hohoh ff://aa/b/cd#hohoh
1399 LPCSTR WINAPI UrlGetLocationA(
1400 LPCSTR pszUrl)
1402 PARSEDURLA base;
1403 DWORD res1;
1405 base.cbSize = sizeof(base);
1406 res1 = ParseURLA(pszUrl, &base);
1407 if (res1) return NULL; /* invalid scheme */
1409 /* if scheme is file: then never return pointer */
1410 if (strncmp(base.pszProtocol, "file", min(4,base.cchProtocol)) == 0) return NULL;
1412 /* Look for '#' and return its addr */
1413 return strchr(base.pszSuffix, '#');
1416 /*************************************************************************
1417 * UrlGetLocationW [SHLWAPI.@]
1419 * See UrlGetLocationA.
1421 LPCWSTR WINAPI UrlGetLocationW(
1422 LPCWSTR pszUrl)
1424 PARSEDURLW base;
1425 DWORD res1;
1427 base.cbSize = sizeof(base);
1428 res1 = ParseURLW(pszUrl, &base);
1429 if (res1) return NULL; /* invalid scheme */
1431 /* if scheme is file: then never return pointer */
1432 if (strncmpW(base.pszProtocol, fileW, min(4,base.cchProtocol)) == 0) return NULL;
1434 /* Look for '#' and return its addr */
1435 return strchrW(base.pszSuffix, '#');
1438 /*************************************************************************
1439 * UrlCompareA [SHLWAPI.@]
1441 * Compare two Urls.
1443 * PARAMS
1444 * pszUrl1 [I] First Url to compare
1445 * pszUrl2 [I] Url to compare to pszUrl1
1446 * fIgnoreSlash [I] TRUE = compare only up to a final slash
1448 * RETURNS
1449 * less than zero, zero, or greater than zero indicating pszUrl2 is greater
1450 * than, equal to, or less than pszUrl1 respectively.
1452 INT WINAPI UrlCompareA(
1453 LPCSTR pszUrl1,
1454 LPCSTR pszUrl2,
1455 BOOL fIgnoreSlash)
1457 INT ret, len, len1, len2;
1459 if (!fIgnoreSlash)
1460 return strcmp(pszUrl1, pszUrl2);
1461 len1 = strlen(pszUrl1);
1462 if (pszUrl1[len1-1] == '/') len1--;
1463 len2 = strlen(pszUrl2);
1464 if (pszUrl2[len2-1] == '/') len2--;
1465 if (len1 == len2)
1466 return strncmp(pszUrl1, pszUrl2, len1);
1467 len = min(len1, len2);
1468 ret = strncmp(pszUrl1, pszUrl2, len);
1469 if (ret) return ret;
1470 if (len1 > len2) return 1;
1471 return -1;
1474 /*************************************************************************
1475 * UrlCompareW [SHLWAPI.@]
1477 * See UrlCompareA.
1479 INT WINAPI UrlCompareW(
1480 LPCWSTR pszUrl1,
1481 LPCWSTR pszUrl2,
1482 BOOL fIgnoreSlash)
1484 INT ret;
1485 size_t len, len1, len2;
1487 if (!fIgnoreSlash)
1488 return strcmpW(pszUrl1, pszUrl2);
1489 len1 = strlenW(pszUrl1);
1490 if (pszUrl1[len1-1] == '/') len1--;
1491 len2 = strlenW(pszUrl2);
1492 if (pszUrl2[len2-1] == '/') len2--;
1493 if (len1 == len2)
1494 return strncmpW(pszUrl1, pszUrl2, len1);
1495 len = min(len1, len2);
1496 ret = strncmpW(pszUrl1, pszUrl2, len);
1497 if (ret) return ret;
1498 if (len1 > len2) return 1;
1499 return -1;
1502 /*************************************************************************
1503 * HashData [SHLWAPI.@]
1505 * Hash an input block into a variable sized digest.
1507 * PARAMS
1508 * lpSrc [I] Input block
1509 * nSrcLen [I] Length of lpSrc
1510 * lpDest [I] Output for hash digest
1511 * nDestLen [I] Length of lpDest
1513 * RETURNS
1514 * Success: TRUE. lpDest is filled with the computed hash value.
1515 * Failure: FALSE, if any argument is invalid.
1517 HRESULT WINAPI HashData(const unsigned char *lpSrc, DWORD nSrcLen,
1518 unsigned char *lpDest, DWORD nDestLen)
1520 INT srcCount = nSrcLen - 1, destCount = nDestLen - 1;
1522 if (!lpSrc || !lpDest)
1523 return E_INVALIDARG;
1525 while (destCount >= 0)
1527 lpDest[destCount] = (destCount & 0xff);
1528 destCount--;
1531 while (srcCount >= 0)
1533 destCount = nDestLen - 1;
1534 while (destCount >= 0)
1536 lpDest[destCount] = HashDataLookup[lpSrc[srcCount] ^ lpDest[destCount]];
1537 destCount--;
1539 srcCount--;
1541 return S_OK;
1544 /*************************************************************************
1545 * UrlHashA [SHLWAPI.@]
1547 * Produce a Hash from a Url.
1549 * PARAMS
1550 * pszUrl [I] Url to hash
1551 * lpDest [O] Destinationh for hash
1552 * nDestLen [I] Length of lpDest
1554 * RETURNS
1555 * Success: S_OK. lpDest is filled with the computed hash value.
1556 * Failure: E_INVALIDARG, if any argument is invalid.
1558 HRESULT WINAPI UrlHashA(LPCSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1560 if (IsBadStringPtrA(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1561 return E_INVALIDARG;
1563 HashData((const BYTE*)pszUrl, (int)strlen(pszUrl), lpDest, nDestLen);
1564 return S_OK;
1567 /*************************************************************************
1568 * UrlHashW [SHLWAPI.@]
1570 * See UrlHashA.
1572 HRESULT WINAPI UrlHashW(LPCWSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1574 char szUrl[MAX_PATH];
1576 TRACE("(%s,%p,%d)\n",debugstr_w(pszUrl), lpDest, nDestLen);
1578 if (IsBadStringPtrW(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1579 return E_INVALIDARG;
1581 /* Win32 hashes the data as an ASCII string, presumably so that both A+W
1582 * return the same digests for the same URL.
1584 WideCharToMultiByte(0, 0, pszUrl, -1, szUrl, MAX_PATH, 0, 0);
1585 HashData((const BYTE*)szUrl, (int)strlen(szUrl), lpDest, nDestLen);
1586 return S_OK;
1589 /*************************************************************************
1590 * UrlApplySchemeA [SHLWAPI.@]
1592 * Apply a scheme to a Url.
1594 * PARAMS
1595 * pszIn [I] Url to apply scheme to
1596 * pszOut [O] Destination for modified Url
1597 * pcchOut [I/O] Length of pszOut/destination for length of pszOut
1598 * dwFlags [I] URL_ flags from "shlwapi.h"
1600 * RETURNS
1601 * Success: S_OK: pszOut contains the modified Url, pcchOut contains its length.
1602 * Failure: An HRESULT error code describing the error.
1604 HRESULT WINAPI UrlApplySchemeA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1606 LPWSTR in, out;
1607 HRESULT ret;
1608 DWORD len;
1610 TRACE("(%s, %p, %p:out size %d, 0x%08x)\n", debugstr_a(pszIn),
1611 pszOut, pcchOut, pcchOut ? *pcchOut : 0, dwFlags);
1613 if (!pszIn || !pszOut || !pcchOut) return E_INVALIDARG;
1615 in = HeapAlloc(GetProcessHeap(), 0,
1616 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
1617 out = in + INTERNET_MAX_URL_LENGTH;
1619 MultiByteToWideChar(CP_ACP, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
1620 len = INTERNET_MAX_URL_LENGTH;
1622 ret = UrlApplySchemeW(in, out, &len, dwFlags);
1623 if (ret != S_OK) {
1624 HeapFree(GetProcessHeap(), 0, in);
1625 return ret;
1628 len = WideCharToMultiByte(CP_ACP, 0, out, -1, NULL, 0, NULL, NULL);
1629 if (len > *pcchOut) {
1630 ret = E_POINTER;
1631 goto cleanup;
1634 WideCharToMultiByte(CP_ACP, 0, out, -1, pszOut, *pcchOut, NULL, NULL);
1635 len--;
1637 cleanup:
1638 *pcchOut = len;
1639 HeapFree(GetProcessHeap(), 0, in);
1640 return ret;
1643 static HRESULT URL_GuessScheme(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1645 HKEY newkey;
1646 BOOL j;
1647 INT index;
1648 DWORD value_len, data_len, dwType, i;
1649 WCHAR reg_path[MAX_PATH];
1650 WCHAR value[MAX_PATH], data[MAX_PATH];
1651 WCHAR Wxx, Wyy;
1653 MultiByteToWideChar(0, 0,
1654 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes",
1655 -1, reg_path, MAX_PATH);
1656 RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
1657 index = 0;
1658 while(value_len = data_len = MAX_PATH,
1659 RegEnumValueW(newkey, index, value, &value_len,
1660 0, &dwType, (LPVOID)data, &data_len) == 0) {
1661 TRACE("guess %d %s is %s\n",
1662 index, debugstr_w(value), debugstr_w(data));
1664 j = FALSE;
1665 for(i=0; i<value_len; i++) {
1666 Wxx = pszIn[i];
1667 Wyy = value[i];
1668 /* remember that TRUE is not-equal */
1669 j = ChrCmpIW(Wxx, Wyy);
1670 if (j) break;
1672 if ((i == value_len) && !j) {
1673 if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1674 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1675 RegCloseKey(newkey);
1676 return E_POINTER;
1678 strcpyW(pszOut, data);
1679 strcatW(pszOut, pszIn);
1680 *pcchOut = strlenW(pszOut);
1681 TRACE("matched and set to %s\n", debugstr_w(pszOut));
1682 RegCloseKey(newkey);
1683 return S_OK;
1685 index++;
1687 RegCloseKey(newkey);
1688 return E_FAIL;
1691 static HRESULT URL_ApplyDefault(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1693 HKEY newkey;
1694 DWORD data_len, dwType;
1695 WCHAR data[MAX_PATH];
1697 static const WCHAR prefix_keyW[] =
1698 {'S','o','f','t','w','a','r','e',
1699 '\\','M','i','c','r','o','s','o','f','t',
1700 '\\','W','i','n','d','o','w','s',
1701 '\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n',
1702 '\\','U','R','L',
1703 '\\','D','e','f','a','u','l','t','P','r','e','f','i','x',0};
1705 /* get and prepend default */
1706 RegOpenKeyExW(HKEY_LOCAL_MACHINE, prefix_keyW, 0, 1, &newkey);
1707 data_len = sizeof(data);
1708 RegQueryValueExW(newkey, NULL, 0, &dwType, (LPBYTE)data, &data_len);
1709 RegCloseKey(newkey);
1710 if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1711 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1712 return E_POINTER;
1714 strcpyW(pszOut, data);
1715 strcatW(pszOut, pszIn);
1716 *pcchOut = strlenW(pszOut);
1717 TRACE("used default %s\n", debugstr_w(pszOut));
1718 return S_OK;
1721 /*************************************************************************
1722 * UrlApplySchemeW [SHLWAPI.@]
1724 * See UrlApplySchemeA.
1726 HRESULT WINAPI UrlApplySchemeW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1728 PARSEDURLW in_scheme;
1729 DWORD res1;
1730 HRESULT ret;
1732 TRACE("(%s, %p, %p:out size %d, 0x%08x)\n", debugstr_w(pszIn),
1733 pszOut, pcchOut, pcchOut ? *pcchOut : 0, dwFlags);
1735 if (!pszIn || !pszOut || !pcchOut) return E_INVALIDARG;
1737 if (dwFlags & URL_APPLY_GUESSFILE) {
1738 FIXME("(%s %p %p(%d) 0x%08x): stub URL_APPLY_GUESSFILE not implemented\n",
1739 debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwFlags);
1740 strcpyW(pszOut, pszIn);
1741 *pcchOut = strlenW(pszOut);
1742 return S_FALSE;
1745 in_scheme.cbSize = sizeof(in_scheme);
1746 /* See if the base has a scheme */
1747 res1 = ParseURLW(pszIn, &in_scheme);
1748 if (res1) {
1749 /* no scheme in input, need to see if we need to guess */
1750 if (dwFlags & URL_APPLY_GUESSSCHEME) {
1751 if ((ret = URL_GuessScheme(pszIn, pszOut, pcchOut)) != E_FAIL)
1752 return ret;
1755 else {
1756 /* we have a scheme, see if valid (known scheme) */
1757 if (in_scheme.nScheme) {
1758 /* have valid scheme, so just copy and exit */
1759 if (strlenW(pszIn) + 1 > *pcchOut) {
1760 *pcchOut = strlenW(pszIn) + 1;
1761 return E_POINTER;
1763 strcpyW(pszOut, pszIn);
1764 *pcchOut = strlenW(pszOut);
1765 TRACE("valid scheme, returning copy\n");
1766 return S_OK;
1770 /* If we are here, then either invalid scheme,
1771 * or no scheme and can't/failed guess.
1773 if ( ( ((res1 == 0) && (dwFlags & URL_APPLY_FORCEAPPLY)) ||
1774 ((res1 != 0)) ) &&
1775 (dwFlags & URL_APPLY_DEFAULT)) {
1776 /* find and apply default scheme */
1777 return URL_ApplyDefault(pszIn, pszOut, pcchOut);
1780 return S_FALSE;
1783 /*************************************************************************
1784 * UrlIsA [SHLWAPI.@]
1786 * Determine if a Url is of a certain class.
1788 * PARAMS
1789 * pszUrl [I] Url to check
1790 * Urlis [I] URLIS_ constant from "shlwapi.h"
1792 * RETURNS
1793 * TRUE if pszUrl belongs to the class type in Urlis.
1794 * FALSE Otherwise.
1796 BOOL WINAPI UrlIsA(LPCSTR pszUrl, URLIS Urlis)
1798 PARSEDURLA base;
1799 DWORD res1;
1800 LPCSTR last;
1802 TRACE("(%s %d)\n", debugstr_a(pszUrl), Urlis);
1804 if(!pszUrl)
1805 return FALSE;
1807 switch (Urlis) {
1809 case URLIS_OPAQUE:
1810 base.cbSize = sizeof(base);
1811 res1 = ParseURLA(pszUrl, &base);
1812 if (res1) return FALSE; /* invalid scheme */
1813 switch (base.nScheme)
1815 case URL_SCHEME_MAILTO:
1816 case URL_SCHEME_SHELL:
1817 case URL_SCHEME_JAVASCRIPT:
1818 case URL_SCHEME_VBSCRIPT:
1819 case URL_SCHEME_ABOUT:
1820 return TRUE;
1822 return FALSE;
1824 case URLIS_FILEURL:
1825 return !StrCmpNA("file:", pszUrl, 5);
1827 case URLIS_DIRECTORY:
1828 last = pszUrl + strlen(pszUrl) - 1;
1829 return (last >= pszUrl && (*last == '/' || *last == '\\' ));
1831 case URLIS_URL:
1832 return PathIsURLA(pszUrl);
1834 case URLIS_NOHISTORY:
1835 case URLIS_APPLIABLE:
1836 case URLIS_HASQUERY:
1837 default:
1838 FIXME("(%s %d): stub\n", debugstr_a(pszUrl), Urlis);
1840 return FALSE;
1843 /*************************************************************************
1844 * UrlIsW [SHLWAPI.@]
1846 * See UrlIsA.
1848 BOOL WINAPI UrlIsW(LPCWSTR pszUrl, URLIS Urlis)
1850 static const WCHAR stemp[] = { 'f','i','l','e',':',0 };
1851 PARSEDURLW base;
1852 DWORD res1;
1853 LPCWSTR last;
1855 TRACE("(%s %d)\n", debugstr_w(pszUrl), Urlis);
1857 if(!pszUrl)
1858 return FALSE;
1860 switch (Urlis) {
1862 case URLIS_OPAQUE:
1863 base.cbSize = sizeof(base);
1864 res1 = ParseURLW(pszUrl, &base);
1865 if (res1) return FALSE; /* invalid scheme */
1866 switch (base.nScheme)
1868 case URL_SCHEME_MAILTO:
1869 case URL_SCHEME_SHELL:
1870 case URL_SCHEME_JAVASCRIPT:
1871 case URL_SCHEME_VBSCRIPT:
1872 case URL_SCHEME_ABOUT:
1873 return TRUE;
1875 return FALSE;
1877 case URLIS_FILEURL:
1878 return !strncmpW(stemp, pszUrl, 5);
1880 case URLIS_DIRECTORY:
1881 last = pszUrl + strlenW(pszUrl) - 1;
1882 return (last >= pszUrl && (*last == '/' || *last == '\\'));
1884 case URLIS_URL:
1885 return PathIsURLW(pszUrl);
1887 case URLIS_NOHISTORY:
1888 case URLIS_APPLIABLE:
1889 case URLIS_HASQUERY:
1890 default:
1891 FIXME("(%s %d): stub\n", debugstr_w(pszUrl), Urlis);
1893 return FALSE;
1896 /*************************************************************************
1897 * UrlIsNoHistoryA [SHLWAPI.@]
1899 * Determine if a Url should not be stored in the users history list.
1901 * PARAMS
1902 * pszUrl [I] Url to check
1904 * RETURNS
1905 * TRUE, if pszUrl should be excluded from the history list,
1906 * FALSE otherwise.
1908 BOOL WINAPI UrlIsNoHistoryA(LPCSTR pszUrl)
1910 return UrlIsA(pszUrl, URLIS_NOHISTORY);
1913 /*************************************************************************
1914 * UrlIsNoHistoryW [SHLWAPI.@]
1916 * See UrlIsNoHistoryA.
1918 BOOL WINAPI UrlIsNoHistoryW(LPCWSTR pszUrl)
1920 return UrlIsW(pszUrl, URLIS_NOHISTORY);
1923 /*************************************************************************
1924 * UrlIsOpaqueA [SHLWAPI.@]
1926 * Determine if a Url is opaque.
1928 * PARAMS
1929 * pszUrl [I] Url to check
1931 * RETURNS
1932 * TRUE if pszUrl is opaque,
1933 * FALSE Otherwise.
1935 * NOTES
1936 * An opaque Url is one that does not start with "<protocol>://".
1938 BOOL WINAPI UrlIsOpaqueA(LPCSTR pszUrl)
1940 return UrlIsA(pszUrl, URLIS_OPAQUE);
1943 /*************************************************************************
1944 * UrlIsOpaqueW [SHLWAPI.@]
1946 * See UrlIsOpaqueA.
1948 BOOL WINAPI UrlIsOpaqueW(LPCWSTR pszUrl)
1950 return UrlIsW(pszUrl, URLIS_OPAQUE);
1953 /*************************************************************************
1954 * Scans for characters of type "type" and when not matching found,
1955 * returns pointer to it and length in size.
1957 * Characters tested based on RFC 1738
1959 static LPCWSTR URL_ScanID(LPCWSTR start, LPDWORD size, WINE_URL_SCAN_TYPE type)
1961 static DWORD alwayszero = 0;
1962 BOOL cont = TRUE;
1964 *size = 0;
1966 switch(type){
1968 case SCHEME:
1969 while (cont) {
1970 if ( (islowerW(*start) && isalphaW(*start)) ||
1971 isdigitW(*start) ||
1972 (*start == '+') ||
1973 (*start == '-') ||
1974 (*start == '.')) {
1975 start++;
1976 (*size)++;
1978 else
1979 cont = FALSE;
1982 if(*start != ':')
1983 *size = 0;
1985 break;
1987 case USERPASS:
1988 while (cont) {
1989 if ( isalphaW(*start) ||
1990 isdigitW(*start) ||
1991 /* user/password only characters */
1992 (*start == ';') ||
1993 (*start == '?') ||
1994 (*start == '&') ||
1995 (*start == '=') ||
1996 /* *extra* characters */
1997 (*start == '!') ||
1998 (*start == '*') ||
1999 (*start == '\'') ||
2000 (*start == '(') ||
2001 (*start == ')') ||
2002 (*start == ',') ||
2003 /* *safe* characters */
2004 (*start == '$') ||
2005 (*start == '_') ||
2006 (*start == '+') ||
2007 (*start == '-') ||
2008 (*start == '.') ||
2009 (*start == ' ')) {
2010 start++;
2011 (*size)++;
2012 } else if (*start == '%') {
2013 if (isxdigitW(*(start+1)) &&
2014 isxdigitW(*(start+2))) {
2015 start += 3;
2016 *size += 3;
2017 } else
2018 cont = FALSE;
2019 } else
2020 cont = FALSE;
2022 break;
2024 case PORT:
2025 while (cont) {
2026 if (isdigitW(*start)) {
2027 start++;
2028 (*size)++;
2030 else
2031 cont = FALSE;
2033 break;
2035 case HOST:
2036 while (cont) {
2037 if (isalnumW(*start) ||
2038 (*start == '-') ||
2039 (*start == '.') ||
2040 (*start == ' ') ) {
2041 start++;
2042 (*size)++;
2044 else
2045 cont = FALSE;
2047 break;
2048 default:
2049 FIXME("unknown type %d\n", type);
2050 return (LPWSTR)&alwayszero;
2052 /* TRACE("scanned %d characters next char %p<%c>\n",
2053 *size, start, *start); */
2054 return start;
2057 /*************************************************************************
2058 * Attempt to parse URL into pieces.
2060 static LONG URL_ParseUrl(LPCWSTR pszUrl, WINE_PARSE_URL *pl)
2062 LPCWSTR work;
2064 memset(pl, 0, sizeof(WINE_PARSE_URL));
2065 pl->pScheme = pszUrl;
2066 work = URL_ScanID(pl->pScheme, &pl->szScheme, SCHEME);
2067 if (!*work || (*work != ':')) goto ErrorExit;
2068 work++;
2069 if ((*work != '/') || (*(work+1) != '/')) goto SuccessExit;
2070 pl->pUserName = work + 2;
2071 work = URL_ScanID(pl->pUserName, &pl->szUserName, USERPASS);
2072 if (*work == ':' ) {
2073 /* parse password */
2074 work++;
2075 pl->pPassword = work;
2076 work = URL_ScanID(pl->pPassword, &pl->szPassword, USERPASS);
2077 if (*work != '@') {
2078 /* what we just parsed must be the hostname and port
2079 * so reset pointers and clear then let it parse */
2080 pl->szUserName = pl->szPassword = 0;
2081 work = pl->pUserName - 1;
2082 pl->pUserName = pl->pPassword = 0;
2084 } else if (*work == '@') {
2085 /* no password */
2086 pl->szPassword = 0;
2087 pl->pPassword = 0;
2088 } else if (!*work || (*work == '/') || (*work == '.')) {
2089 /* what was parsed was hostname, so reset pointers and let it parse */
2090 pl->szUserName = pl->szPassword = 0;
2091 work = pl->pUserName - 1;
2092 pl->pUserName = pl->pPassword = 0;
2093 } else goto ErrorExit;
2095 /* now start parsing hostname or hostnumber */
2096 work++;
2097 pl->pHostName = work;
2098 work = URL_ScanID(pl->pHostName, &pl->szHostName, HOST);
2099 if (*work == ':') {
2100 /* parse port */
2101 work++;
2102 pl->pPort = work;
2103 work = URL_ScanID(pl->pPort, &pl->szPort, PORT);
2105 if (*work == '/') {
2106 /* see if query string */
2107 pl->pQuery = strchrW(work, '?');
2108 if (pl->pQuery) pl->szQuery = strlenW(pl->pQuery);
2110 SuccessExit:
2111 TRACE("parse successful: scheme=%p(%d), user=%p(%d), pass=%p(%d), host=%p(%d), port=%p(%d), query=%p(%d)\n",
2112 pl->pScheme, pl->szScheme,
2113 pl->pUserName, pl->szUserName,
2114 pl->pPassword, pl->szPassword,
2115 pl->pHostName, pl->szHostName,
2116 pl->pPort, pl->szPort,
2117 pl->pQuery, pl->szQuery);
2118 return S_OK;
2119 ErrorExit:
2120 FIXME("failed to parse %s\n", debugstr_w(pszUrl));
2121 return E_INVALIDARG;
2124 /*************************************************************************
2125 * UrlGetPartA [SHLWAPI.@]
2127 * Retrieve part of a Url.
2129 * PARAMS
2130 * pszIn [I] Url to parse
2131 * pszOut [O] Destination for part of pszIn requested
2132 * pcchOut [I] Size of pszOut
2133 * [O] length of pszOut string EXCLUDING '\0' if S_OK, otherwise
2134 * needed size of pszOut INCLUDING '\0'.
2135 * dwPart [I] URL_PART_ enum from "shlwapi.h"
2136 * dwFlags [I] URL_ flags from "shlwapi.h"
2138 * RETURNS
2139 * Success: S_OK. pszOut contains the part requested, pcchOut contains its length.
2140 * Failure: An HRESULT error code describing the error.
2142 HRESULT WINAPI UrlGetPartA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut,
2143 DWORD dwPart, DWORD dwFlags)
2145 LPWSTR in, out;
2146 DWORD ret, len, len2;
2148 if(!pszIn || !pszOut || !pcchOut || *pcchOut <= 0)
2149 return E_INVALIDARG;
2151 in = HeapAlloc(GetProcessHeap(), 0,
2152 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
2153 out = in + INTERNET_MAX_URL_LENGTH;
2155 MultiByteToWideChar(0, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
2157 len = INTERNET_MAX_URL_LENGTH;
2158 ret = UrlGetPartW(in, out, &len, dwPart, dwFlags);
2160 if (FAILED(ret)) {
2161 HeapFree(GetProcessHeap(), 0, in);
2162 return ret;
2165 len2 = WideCharToMultiByte(0, 0, out, len, 0, 0, 0, 0);
2166 if (len2 > *pcchOut) {
2167 *pcchOut = len2+1;
2168 HeapFree(GetProcessHeap(), 0, in);
2169 return E_POINTER;
2171 len2 = WideCharToMultiByte(0, 0, out, len+1, pszOut, *pcchOut, 0, 0);
2172 *pcchOut = len2-1;
2173 HeapFree(GetProcessHeap(), 0, in);
2174 return ret;
2177 /*************************************************************************
2178 * UrlGetPartW [SHLWAPI.@]
2180 * See UrlGetPartA.
2182 HRESULT WINAPI UrlGetPartW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut,
2183 DWORD dwPart, DWORD dwFlags)
2185 WINE_PARSE_URL pl;
2186 HRESULT ret;
2187 DWORD scheme, size, schsize;
2188 LPCWSTR addr, schaddr;
2190 TRACE("(%s %p %p(%d) %08x %08x)\n",
2191 debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwPart, dwFlags);
2193 if(!pszIn || !pszOut || !pcchOut || *pcchOut <= 0)
2194 return E_INVALIDARG;
2196 *pszOut = '\0';
2198 addr = strchrW(pszIn, ':');
2199 if(!addr)
2200 scheme = URL_SCHEME_UNKNOWN;
2201 else
2202 scheme = get_scheme_code(pszIn, addr-pszIn);
2204 ret = URL_ParseUrl(pszIn, &pl);
2206 switch (dwPart) {
2207 case URL_PART_SCHEME:
2208 if (!pl.szScheme) {
2209 *pcchOut = 0;
2210 return S_FALSE;
2212 addr = pl.pScheme;
2213 size = pl.szScheme;
2214 break;
2216 case URL_PART_HOSTNAME:
2217 switch(scheme) {
2218 case URL_SCHEME_FTP:
2219 case URL_SCHEME_HTTP:
2220 case URL_SCHEME_GOPHER:
2221 case URL_SCHEME_TELNET:
2222 case URL_SCHEME_FILE:
2223 case URL_SCHEME_HTTPS:
2224 break;
2225 default:
2226 *pcchOut = 0;
2227 return E_FAIL;
2230 if(scheme==URL_SCHEME_FILE && (!pl.szHostName ||
2231 (pl.szHostName==1 && *(pl.pHostName+1)==':'))) {
2232 *pcchOut = 0;
2233 return S_FALSE;
2236 if (!pl.szHostName) {
2237 *pcchOut = 0;
2238 return S_FALSE;
2240 addr = pl.pHostName;
2241 size = pl.szHostName;
2242 break;
2244 case URL_PART_USERNAME:
2245 if (!pl.szUserName) {
2246 *pcchOut = 0;
2247 return S_FALSE;
2249 addr = pl.pUserName;
2250 size = pl.szUserName;
2251 break;
2253 case URL_PART_PASSWORD:
2254 if (!pl.szPassword) {
2255 *pcchOut = 0;
2256 return S_FALSE;
2258 addr = pl.pPassword;
2259 size = pl.szPassword;
2260 break;
2262 case URL_PART_PORT:
2263 if (!pl.szPort) {
2264 *pcchOut = 0;
2265 return S_FALSE;
2267 addr = pl.pPort;
2268 size = pl.szPort;
2269 break;
2271 case URL_PART_QUERY:
2272 if (!pl.szQuery) {
2273 *pcchOut = 0;
2274 return S_FALSE;
2276 addr = pl.pQuery;
2277 size = pl.szQuery;
2278 break;
2280 default:
2281 *pcchOut = 0;
2282 return E_INVALIDARG;
2285 if (dwFlags == URL_PARTFLAG_KEEPSCHEME) {
2286 if(!pl.pScheme || !pl.szScheme) {
2287 *pcchOut = 0;
2288 return E_FAIL;
2290 schaddr = pl.pScheme;
2291 schsize = pl.szScheme;
2292 if (*pcchOut < schsize + size + 2) {
2293 *pcchOut = schsize + size + 2;
2294 return E_POINTER;
2296 memcpy(pszOut, schaddr, schsize*sizeof(WCHAR));
2297 pszOut[schsize] = ':';
2298 memcpy(pszOut+schsize+1, addr, size*sizeof(WCHAR));
2299 pszOut[schsize+1+size] = 0;
2300 *pcchOut = schsize + 1 + size;
2302 else {
2303 if (*pcchOut < size + 1) {*pcchOut = size+1; return E_POINTER;}
2304 memcpy(pszOut, addr, size*sizeof(WCHAR));
2305 pszOut[size] = 0;
2306 *pcchOut = size;
2308 TRACE("len=%d %s\n", *pcchOut, debugstr_w(pszOut));
2310 return ret;
2313 /*************************************************************************
2314 * PathIsURLA [SHLWAPI.@]
2316 * Check if the given path is a Url.
2318 * PARAMS
2319 * lpszPath [I] Path to check.
2321 * RETURNS
2322 * TRUE if lpszPath is a Url.
2323 * FALSE if lpszPath is NULL or not a Url.
2325 BOOL WINAPI PathIsURLA(LPCSTR lpstrPath)
2327 PARSEDURLA base;
2328 HRESULT hres;
2330 TRACE("%s\n", debugstr_a(lpstrPath));
2332 if (!lpstrPath || !*lpstrPath) return FALSE;
2334 /* get protocol */
2335 base.cbSize = sizeof(base);
2336 hres = ParseURLA(lpstrPath, &base);
2337 return hres == S_OK && (base.nScheme != URL_SCHEME_INVALID);
2340 /*************************************************************************
2341 * PathIsURLW [SHLWAPI.@]
2343 * See PathIsURLA.
2345 BOOL WINAPI PathIsURLW(LPCWSTR lpstrPath)
2347 PARSEDURLW base;
2348 HRESULT hres;
2350 TRACE("%s\n", debugstr_w(lpstrPath));
2352 if (!lpstrPath || !*lpstrPath) return FALSE;
2354 /* get protocol */
2355 base.cbSize = sizeof(base);
2356 hres = ParseURLW(lpstrPath, &base);
2357 return hres == S_OK && (base.nScheme != URL_SCHEME_INVALID);
2360 /*************************************************************************
2361 * UrlCreateFromPathA [SHLWAPI.@]
2363 * See UrlCreateFromPathW
2365 HRESULT WINAPI UrlCreateFromPathA(LPCSTR pszPath, LPSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2367 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
2368 WCHAR *urlW = bufW;
2369 UNICODE_STRING pathW;
2370 HRESULT ret;
2371 DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
2373 if(!RtlCreateUnicodeStringFromAsciiz(&pathW, pszPath))
2374 return E_INVALIDARG;
2375 if((ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved)) == E_POINTER) {
2376 urlW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
2377 ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved);
2379 if(ret == S_OK || ret == S_FALSE) {
2380 RtlUnicodeToMultiByteSize(&lenA, urlW, lenW * sizeof(WCHAR));
2381 if(*pcchUrl > lenA) {
2382 RtlUnicodeToMultiByteN(pszUrl, *pcchUrl - 1, &lenA, urlW, lenW * sizeof(WCHAR));
2383 pszUrl[lenA] = 0;
2384 *pcchUrl = lenA;
2385 } else {
2386 *pcchUrl = lenA + 1;
2387 ret = E_POINTER;
2390 if(urlW != bufW) HeapFree(GetProcessHeap(), 0, urlW);
2391 RtlFreeUnicodeString(&pathW);
2392 return ret;
2395 /*************************************************************************
2396 * UrlCreateFromPathW [SHLWAPI.@]
2398 * Create a Url from a file path.
2400 * PARAMS
2401 * pszPath [I] Path to convert
2402 * pszUrl [O] Destination for the converted Url
2403 * pcchUrl [I/O] Length of pszUrl
2404 * dwReserved [I] Reserved, must be 0
2406 * RETURNS
2407 * Success: S_OK pszUrl contains the converted path, S_FALSE if the path is already a Url
2408 * Failure: An HRESULT error code.
2410 HRESULT WINAPI UrlCreateFromPathW(LPCWSTR pszPath, LPWSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2412 DWORD needed;
2413 HRESULT ret;
2414 WCHAR *pszNewUrl;
2415 WCHAR file_colonW[] = {'f','i','l','e',':',0};
2416 WCHAR three_slashesW[] = {'/','/','/',0};
2417 PARSEDURLW parsed_url;
2419 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszPath), pszUrl, pcchUrl, dwReserved);
2421 /* Validate arguments */
2422 if (dwReserved != 0)
2423 return E_INVALIDARG;
2424 if (!pszUrl || !pcchUrl)
2425 return E_INVALIDARG;
2428 parsed_url.cbSize = sizeof(parsed_url);
2429 if(ParseURLW(pszPath, &parsed_url) == S_OK) {
2430 if(parsed_url.nScheme != URL_SCHEME_INVALID && parsed_url.cchProtocol > 1) {
2431 needed = strlenW(pszPath);
2432 if (needed >= *pcchUrl) {
2433 *pcchUrl = needed + 1;
2434 return E_POINTER;
2435 } else {
2436 *pcchUrl = needed;
2437 strcpyW(pszUrl, pszPath);
2438 return S_FALSE;
2443 pszNewUrl = HeapAlloc(GetProcessHeap(), 0, (strlenW(pszPath) + 9) * sizeof(WCHAR)); /* "file:///" + pszPath_len + 1 */
2444 strcpyW(pszNewUrl, file_colonW);
2445 if(isalphaW(pszPath[0]) && pszPath[1] == ':')
2446 strcatW(pszNewUrl, three_slashesW);
2447 strcatW(pszNewUrl, pszPath);
2448 ret = UrlEscapeW(pszNewUrl, pszUrl, pcchUrl, URL_ESCAPE_PERCENT);
2450 HeapFree(GetProcessHeap(), 0, pszNewUrl);
2451 return ret;
2454 /*************************************************************************
2455 * SHAutoComplete [SHLWAPI.@]
2457 * Enable auto-completion for an edit control.
2459 * PARAMS
2460 * hwndEdit [I] Handle of control to enable auto-completion for
2461 * dwFlags [I] SHACF_ flags from "shlwapi.h"
2463 * RETURNS
2464 * Success: S_OK. Auto-completion is enabled for the control.
2465 * Failure: An HRESULT error code indicating the error.
2467 HRESULT WINAPI SHAutoComplete(HWND hwndEdit, DWORD dwFlags)
2469 FIXME("stub\n");
2470 return S_FALSE;
2473 /*************************************************************************
2474 * MLBuildResURLA [SHLWAPI.405]
2476 * Create a Url pointing to a resource in a module.
2478 * PARAMS
2479 * lpszLibName [I] Name of the module containing the resource
2480 * hMod [I] Callers module handle
2481 * dwFlags [I] Undocumented flags for loading the module
2482 * lpszRes [I] Resource name
2483 * lpszDest [O] Destination for resulting Url
2484 * dwDestLen [I] Length of lpszDest
2486 * RETURNS
2487 * Success: S_OK. lpszDest contains the resource Url.
2488 * Failure: E_INVALIDARG, if any argument is invalid, or
2489 * E_FAIL if dwDestLen is too small.
2491 HRESULT WINAPI MLBuildResURLA(LPCSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2492 LPCSTR lpszRes, LPSTR lpszDest, DWORD dwDestLen)
2494 WCHAR szLibName[MAX_PATH], szRes[MAX_PATH], szDest[MAX_PATH];
2495 HRESULT hRet;
2497 if (lpszLibName)
2498 MultiByteToWideChar(CP_ACP, 0, lpszLibName, -1, szLibName, sizeof(szLibName)/sizeof(WCHAR));
2500 if (lpszRes)
2501 MultiByteToWideChar(CP_ACP, 0, lpszRes, -1, szRes, sizeof(szRes)/sizeof(WCHAR));
2503 if (dwDestLen > sizeof(szLibName)/sizeof(WCHAR))
2504 dwDestLen = sizeof(szLibName)/sizeof(WCHAR);
2506 hRet = MLBuildResURLW(lpszLibName ? szLibName : NULL, hMod, dwFlags,
2507 lpszRes ? szRes : NULL, lpszDest ? szDest : NULL, dwDestLen);
2508 if (SUCCEEDED(hRet) && lpszDest)
2509 WideCharToMultiByte(CP_ACP, 0, szDest, -1, lpszDest, dwDestLen, 0, 0);
2511 return hRet;
2514 /*************************************************************************
2515 * MLBuildResURLA [SHLWAPI.406]
2517 * See MLBuildResURLA.
2519 HRESULT WINAPI MLBuildResURLW(LPCWSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2520 LPCWSTR lpszRes, LPWSTR lpszDest, DWORD dwDestLen)
2522 static const WCHAR szRes[] = { 'r','e','s',':','/','/','\0' };
2523 #define szResLen ((sizeof(szRes) - sizeof(WCHAR))/sizeof(WCHAR))
2524 HRESULT hRet = E_FAIL;
2526 TRACE("(%s,%p,0x%08x,%s,%p,%d)\n", debugstr_w(lpszLibName), hMod, dwFlags,
2527 debugstr_w(lpszRes), lpszDest, dwDestLen);
2529 if (!lpszLibName || !hMod || hMod == INVALID_HANDLE_VALUE || !lpszRes ||
2530 !lpszDest || (dwFlags && dwFlags != 2))
2531 return E_INVALIDARG;
2533 if (dwDestLen >= szResLen + 1)
2535 dwDestLen -= (szResLen + 1);
2536 memcpy(lpszDest, szRes, sizeof(szRes));
2538 hMod = MLLoadLibraryW(lpszLibName, hMod, dwFlags);
2540 if (hMod)
2542 WCHAR szBuff[MAX_PATH];
2543 DWORD len;
2545 len = GetModuleFileNameW(hMod, szBuff, sizeof(szBuff)/sizeof(WCHAR));
2546 if (len && len < sizeof(szBuff)/sizeof(WCHAR))
2548 DWORD dwPathLen = strlenW(szBuff) + 1;
2550 if (dwDestLen >= dwPathLen)
2552 DWORD dwResLen;
2554 dwDestLen -= dwPathLen;
2555 memcpy(lpszDest + szResLen, szBuff, dwPathLen * sizeof(WCHAR));
2557 dwResLen = strlenW(lpszRes) + 1;
2558 if (dwDestLen >= dwResLen + 1)
2560 lpszDest[szResLen + dwPathLen-1] = '/';
2561 memcpy(lpszDest + szResLen + dwPathLen, lpszRes, dwResLen * sizeof(WCHAR));
2562 hRet = S_OK;
2566 MLFreeLibrary(hMod);
2569 return hRet;
2572 /***********************************************************************
2573 * UrlFixupW [SHLWAPI.462]
2575 * Checks the scheme part of a URL and attempts to correct misspellings.
2577 * PARAMS
2578 * lpszUrl [I] Pointer to the URL to be corrected
2579 * lpszTranslatedUrl [O] Pointer to a buffer to store corrected URL
2580 * dwMaxChars [I] Maximum size of corrected URL
2582 * RETURNS
2583 * success: S_OK if URL corrected or already correct
2584 * failure: S_FALSE if unable to correct / COM error code if other error
2587 HRESULT WINAPI UrlFixupW(LPCWSTR url, LPWSTR translatedUrl, DWORD maxChars)
2589 DWORD srcLen;
2591 FIXME("(%s,%p,%d) STUB\n", debugstr_w(url), translatedUrl, maxChars);
2593 if (!url)
2594 return E_FAIL;
2596 srcLen = lstrlenW(url) + 1;
2598 /* For now just copy the URL directly */
2599 lstrcpynW(translatedUrl, url, (maxChars < srcLen) ? maxChars : srcLen);
2601 return S_OK;