shlwapi: Keep pointer to allocated block to free it later (Valgrind).
[wine.git] / dlls / shlwapi / url.c
blob6b469e208b32a228f915ad5e366bc484f4891995
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 * sizeof(WCHAR)) {
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 * sizeof(WCHAR)) {
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 = pszEscaped, *dst_ptr = NULL;
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 if(pszUrl == pszEscaped) {
1095 dst_ptr = dst = HeapAlloc(GetProcessHeap(), 0, *pcchEscaped*sizeof(WCHAR));
1096 if(!dst)
1097 return E_OUTOFMEMORY;
1100 /* fix up flags */
1101 if (dwFlags & URL_ESCAPE_SPACES_ONLY)
1102 /* if SPACES_ONLY specified, reset the other controls */
1103 dwFlags &= ~(URL_DONT_ESCAPE_EXTRA_INFO |
1104 URL_ESCAPE_PERCENT |
1105 URL_ESCAPE_SEGMENT_ONLY);
1107 else
1108 /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
1109 dwFlags |= URL_DONT_ESCAPE_EXTRA_INFO;
1112 int_flags = 0;
1113 if(dwFlags & URL_ESCAPE_SEGMENT_ONLY) {
1114 int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH;
1115 } else {
1116 parsed_url.cbSize = sizeof(parsed_url);
1117 if(ParseURLW(pszUrl, &parsed_url) != S_OK)
1118 parsed_url.nScheme = URL_SCHEME_INVALID;
1120 TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol));
1122 if(dwFlags & URL_DONT_ESCAPE_EXTRA_INFO)
1123 int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION;
1125 switch(parsed_url.nScheme) {
1126 case URL_SCHEME_FILE:
1127 int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH;
1128 int_flags &= ~WINE_URL_STOP_ON_HASH;
1129 break;
1131 case URL_SCHEME_HTTP:
1132 case URL_SCHEME_HTTPS:
1133 int_flags |= WINE_URL_BASH_AS_SLASH;
1134 if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\')
1135 int_flags |= WINE_URL_ESCAPE_SLASH;
1136 break;
1138 case URL_SCHEME_MAILTO:
1139 int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH;
1140 int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH);
1141 break;
1143 case URL_SCHEME_INVALID:
1144 break;
1146 case URL_SCHEME_FTP:
1147 default:
1148 if(parsed_url.pszSuffix[0] != '/')
1149 int_flags |= WINE_URL_ESCAPE_SLASH;
1150 break;
1154 for(src = pszUrl; *src; ) {
1155 WCHAR cur = *src;
1156 len = 0;
1158 if((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == pszUrl + parsed_url.cchProtocol + 1) {
1159 int localhost_len = sizeof(localhost)/sizeof(WCHAR) - 1;
1160 while(cur == '/' || cur == '\\') {
1161 slashes++;
1162 cur = *++src;
1164 if(slashes == 2 && !strncmpiW(src, localhost, localhost_len)) { /* file://localhost/ -> file:/// */
1165 if(*(src + localhost_len) == '/' || *(src + localhost_len) == '\\')
1166 src += localhost_len + 1;
1167 slashes = 3;
1170 switch(slashes) {
1171 case 1:
1172 case 3:
1173 next[0] = next[1] = next[2] = '/';
1174 len = 3;
1175 break;
1176 case 0:
1177 len = 0;
1178 break;
1179 default:
1180 next[0] = next[1] = '/';
1181 len = 2;
1182 break;
1185 if(len == 0) {
1187 if(cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH))
1188 stop_escaping = TRUE;
1190 if(cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION))
1191 stop_escaping = TRUE;
1193 if(cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/';
1195 if(URL_NeedEscapeW(cur, dwFlags, int_flags) && stop_escaping == FALSE) {
1196 next[0] = '%';
1197 next[1] = hexDigits[(cur >> 4) & 0xf];
1198 next[2] = hexDigits[cur & 0xf];
1199 len = 3;
1200 } else {
1201 next[0] = cur;
1202 len = 1;
1204 src++;
1207 if(needed + len <= *pcchEscaped) {
1208 memcpy(dst, next, len*sizeof(WCHAR));
1209 dst += len;
1211 needed += len;
1214 if(needed < *pcchEscaped) {
1215 *dst = '\0';
1216 if(pszUrl == pszEscaped)
1217 memcpy(pszEscaped, dst-needed, (needed+1)*sizeof(WCHAR));
1219 ret = S_OK;
1220 } else {
1221 needed++; /* add one for the '\0' */
1222 ret = E_POINTER;
1224 *pcchEscaped = needed;
1226 if(pszUrl == pszEscaped)
1227 HeapFree(GetProcessHeap(), 0, dst_ptr);
1228 return ret;
1232 /*************************************************************************
1233 * UrlUnescapeA [SHLWAPI.@]
1235 * Converts Url escape sequences back to ordinary characters.
1237 * PARAMS
1238 * pszUrl [I/O] Url to convert
1239 * pszUnescaped [O] Destination for converted Url
1240 * pcchUnescaped [I/O] Size of output string
1241 * dwFlags [I] URL_ESCAPE_ Flags from "shlwapi.h"
1243 * RETURNS
1244 * Success: S_OK. The converted value is in pszUnescaped, or in pszUrl if
1245 * dwFlags includes URL_ESCAPE_INPLACE.
1246 * Failure: E_POINTER if the converted Url is bigger than pcchUnescaped. In
1247 * this case pcchUnescaped is set to the size required.
1248 * NOTES
1249 * If dwFlags includes URL_DONT_ESCAPE_EXTRA_INFO, the conversion stops at
1250 * the first occurrence of either a '?' or '#' character.
1252 HRESULT WINAPI UrlUnescapeA(
1253 LPSTR pszUrl,
1254 LPSTR pszUnescaped,
1255 LPDWORD pcchUnescaped,
1256 DWORD dwFlags)
1258 char *dst, next;
1259 LPCSTR src;
1260 HRESULT ret;
1261 DWORD needed;
1262 BOOL stop_unescaping = FALSE;
1264 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_a(pszUrl), pszUnescaped,
1265 pcchUnescaped, dwFlags);
1267 if (!pszUrl) return E_INVALIDARG;
1269 if(dwFlags & URL_UNESCAPE_INPLACE)
1270 dst = pszUrl;
1271 else
1273 if (!pszUnescaped || !pcchUnescaped) return E_INVALIDARG;
1274 dst = pszUnescaped;
1277 for(src = pszUrl, needed = 0; *src; src++, needed++) {
1278 if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1279 (*src == '#' || *src == '?')) {
1280 stop_unescaping = TRUE;
1281 next = *src;
1282 } else if(*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2))
1283 && stop_unescaping == FALSE) {
1284 INT ih;
1285 char buf[3];
1286 memcpy(buf, src + 1, 2);
1287 buf[2] = '\0';
1288 ih = strtol(buf, NULL, 16);
1289 next = (CHAR) ih;
1290 src += 2; /* Advance to end of escape */
1291 } else
1292 next = *src;
1294 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1295 *dst++ = next;
1298 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1299 *dst = '\0';
1300 ret = S_OK;
1301 } else {
1302 needed++; /* add one for the '\0' */
1303 ret = E_POINTER;
1305 if(!(dwFlags & URL_UNESCAPE_INPLACE))
1306 *pcchUnescaped = needed;
1308 if (ret == S_OK) {
1309 TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1310 debugstr_a(pszUrl) : debugstr_a(pszUnescaped));
1313 return ret;
1316 /*************************************************************************
1317 * UrlUnescapeW [SHLWAPI.@]
1319 * See UrlUnescapeA.
1321 HRESULT WINAPI UrlUnescapeW(
1322 LPWSTR pszUrl,
1323 LPWSTR pszUnescaped,
1324 LPDWORD pcchUnescaped,
1325 DWORD dwFlags)
1327 WCHAR *dst, next;
1328 LPCWSTR src;
1329 HRESULT ret;
1330 DWORD needed;
1331 BOOL stop_unescaping = FALSE;
1333 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszUrl), pszUnescaped,
1334 pcchUnescaped, dwFlags);
1336 if(!pszUrl) return E_INVALIDARG;
1338 if(dwFlags & URL_UNESCAPE_INPLACE)
1339 dst = pszUrl;
1340 else
1342 if (!pszUnescaped || !pcchUnescaped) return E_INVALIDARG;
1343 dst = pszUnescaped;
1346 for(src = pszUrl, needed = 0; *src; src++, needed++) {
1347 if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1348 (*src == '#' || *src == '?')) {
1349 stop_unescaping = TRUE;
1350 next = *src;
1351 } else if(*src == '%' && isxdigitW(*(src + 1)) && isxdigitW(*(src + 2))
1352 && stop_unescaping == FALSE) {
1353 INT ih;
1354 WCHAR buf[5] = {'0','x',0};
1355 memcpy(buf + 2, src + 1, 2*sizeof(WCHAR));
1356 buf[4] = 0;
1357 StrToIntExW(buf, STIF_SUPPORT_HEX, &ih);
1358 next = (WCHAR) ih;
1359 src += 2; /* Advance to end of escape */
1360 } else
1361 next = *src;
1363 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1364 *dst++ = next;
1367 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1368 *dst = '\0';
1369 ret = S_OK;
1370 } else {
1371 needed++; /* add one for the '\0' */
1372 ret = E_POINTER;
1374 if(!(dwFlags & URL_UNESCAPE_INPLACE))
1375 *pcchUnescaped = needed;
1377 if (ret == S_OK) {
1378 TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1379 debugstr_w(pszUrl) : debugstr_w(pszUnescaped));
1382 return ret;
1385 /*************************************************************************
1386 * UrlGetLocationA [SHLWAPI.@]
1388 * Get the location from a Url.
1390 * PARAMS
1391 * pszUrl [I] Url to get the location from
1393 * RETURNS
1394 * A pointer to the start of the location in pszUrl, or NULL if there is
1395 * no location.
1397 * NOTES
1398 * - MSDN erroneously states that "The location is the segment of the Url
1399 * starting with a '?' or '#' character". Neither V4 nor V5 of shlwapi.dll
1400 * stop at '?' and always return a NULL in this case.
1401 * - MSDN also erroneously states that "If a file URL has a query string,
1402 * the returned string is the query string". In all tested cases, if the
1403 * Url starts with "fi" then a NULL is returned. V5 gives the following results:
1404 *| Result Url
1405 *| ------ ---
1406 *| NULL file://aa/b/cd#hohoh
1407 *| #hohoh http://aa/b/cd#hohoh
1408 *| NULL fi://aa/b/cd#hohoh
1409 *| #hohoh ff://aa/b/cd#hohoh
1411 LPCSTR WINAPI UrlGetLocationA(
1412 LPCSTR pszUrl)
1414 PARSEDURLA base;
1415 DWORD res1;
1417 base.cbSize = sizeof(base);
1418 res1 = ParseURLA(pszUrl, &base);
1419 if (res1) return NULL; /* invalid scheme */
1421 /* if scheme is file: then never return pointer */
1422 if (strncmp(base.pszProtocol, "file", min(4,base.cchProtocol)) == 0) return NULL;
1424 /* Look for '#' and return its addr */
1425 return strchr(base.pszSuffix, '#');
1428 /*************************************************************************
1429 * UrlGetLocationW [SHLWAPI.@]
1431 * See UrlGetLocationA.
1433 LPCWSTR WINAPI UrlGetLocationW(
1434 LPCWSTR pszUrl)
1436 PARSEDURLW base;
1437 DWORD res1;
1439 base.cbSize = sizeof(base);
1440 res1 = ParseURLW(pszUrl, &base);
1441 if (res1) return NULL; /* invalid scheme */
1443 /* if scheme is file: then never return pointer */
1444 if (strncmpW(base.pszProtocol, fileW, min(4,base.cchProtocol)) == 0) return NULL;
1446 /* Look for '#' and return its addr */
1447 return strchrW(base.pszSuffix, '#');
1450 /*************************************************************************
1451 * UrlCompareA [SHLWAPI.@]
1453 * Compare two Urls.
1455 * PARAMS
1456 * pszUrl1 [I] First Url to compare
1457 * pszUrl2 [I] Url to compare to pszUrl1
1458 * fIgnoreSlash [I] TRUE = compare only up to a final slash
1460 * RETURNS
1461 * less than zero, zero, or greater than zero indicating pszUrl2 is greater
1462 * than, equal to, or less than pszUrl1 respectively.
1464 INT WINAPI UrlCompareA(
1465 LPCSTR pszUrl1,
1466 LPCSTR pszUrl2,
1467 BOOL fIgnoreSlash)
1469 INT ret, len, len1, len2;
1471 if (!fIgnoreSlash)
1472 return strcmp(pszUrl1, pszUrl2);
1473 len1 = strlen(pszUrl1);
1474 if (pszUrl1[len1-1] == '/') len1--;
1475 len2 = strlen(pszUrl2);
1476 if (pszUrl2[len2-1] == '/') len2--;
1477 if (len1 == len2)
1478 return strncmp(pszUrl1, pszUrl2, len1);
1479 len = min(len1, len2);
1480 ret = strncmp(pszUrl1, pszUrl2, len);
1481 if (ret) return ret;
1482 if (len1 > len2) return 1;
1483 return -1;
1486 /*************************************************************************
1487 * UrlCompareW [SHLWAPI.@]
1489 * See UrlCompareA.
1491 INT WINAPI UrlCompareW(
1492 LPCWSTR pszUrl1,
1493 LPCWSTR pszUrl2,
1494 BOOL fIgnoreSlash)
1496 INT ret;
1497 size_t len, len1, len2;
1499 if (!fIgnoreSlash)
1500 return strcmpW(pszUrl1, pszUrl2);
1501 len1 = strlenW(pszUrl1);
1502 if (pszUrl1[len1-1] == '/') len1--;
1503 len2 = strlenW(pszUrl2);
1504 if (pszUrl2[len2-1] == '/') len2--;
1505 if (len1 == len2)
1506 return strncmpW(pszUrl1, pszUrl2, len1);
1507 len = min(len1, len2);
1508 ret = strncmpW(pszUrl1, pszUrl2, len);
1509 if (ret) return ret;
1510 if (len1 > len2) return 1;
1511 return -1;
1514 /*************************************************************************
1515 * HashData [SHLWAPI.@]
1517 * Hash an input block into a variable sized digest.
1519 * PARAMS
1520 * lpSrc [I] Input block
1521 * nSrcLen [I] Length of lpSrc
1522 * lpDest [I] Output for hash digest
1523 * nDestLen [I] Length of lpDest
1525 * RETURNS
1526 * Success: TRUE. lpDest is filled with the computed hash value.
1527 * Failure: FALSE, if any argument is invalid.
1529 HRESULT WINAPI HashData(const unsigned char *lpSrc, DWORD nSrcLen,
1530 unsigned char *lpDest, DWORD nDestLen)
1532 INT srcCount = nSrcLen - 1, destCount = nDestLen - 1;
1534 if (!lpSrc || !lpDest)
1535 return E_INVALIDARG;
1537 while (destCount >= 0)
1539 lpDest[destCount] = (destCount & 0xff);
1540 destCount--;
1543 while (srcCount >= 0)
1545 destCount = nDestLen - 1;
1546 while (destCount >= 0)
1548 lpDest[destCount] = HashDataLookup[lpSrc[srcCount] ^ lpDest[destCount]];
1549 destCount--;
1551 srcCount--;
1553 return S_OK;
1556 /*************************************************************************
1557 * UrlHashA [SHLWAPI.@]
1559 * Produce a Hash from a Url.
1561 * PARAMS
1562 * pszUrl [I] Url to hash
1563 * lpDest [O] Destinationh for hash
1564 * nDestLen [I] Length of lpDest
1566 * RETURNS
1567 * Success: S_OK. lpDest is filled with the computed hash value.
1568 * Failure: E_INVALIDARG, if any argument is invalid.
1570 HRESULT WINAPI UrlHashA(LPCSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1572 if (IsBadStringPtrA(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1573 return E_INVALIDARG;
1575 HashData((const BYTE*)pszUrl, (int)strlen(pszUrl), lpDest, nDestLen);
1576 return S_OK;
1579 /*************************************************************************
1580 * UrlHashW [SHLWAPI.@]
1582 * See UrlHashA.
1584 HRESULT WINAPI UrlHashW(LPCWSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1586 char szUrl[MAX_PATH];
1588 TRACE("(%s,%p,%d)\n",debugstr_w(pszUrl), lpDest, nDestLen);
1590 if (IsBadStringPtrW(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1591 return E_INVALIDARG;
1593 /* Win32 hashes the data as an ASCII string, presumably so that both A+W
1594 * return the same digests for the same URL.
1596 WideCharToMultiByte(0, 0, pszUrl, -1, szUrl, MAX_PATH, 0, 0);
1597 HashData((const BYTE*)szUrl, (int)strlen(szUrl), lpDest, nDestLen);
1598 return S_OK;
1601 /*************************************************************************
1602 * UrlApplySchemeA [SHLWAPI.@]
1604 * Apply a scheme to a Url.
1606 * PARAMS
1607 * pszIn [I] Url to apply scheme to
1608 * pszOut [O] Destination for modified Url
1609 * pcchOut [I/O] Length of pszOut/destination for length of pszOut
1610 * dwFlags [I] URL_ flags from "shlwapi.h"
1612 * RETURNS
1613 * Success: S_OK: pszOut contains the modified Url, pcchOut contains its length.
1614 * Failure: An HRESULT error code describing the error.
1616 HRESULT WINAPI UrlApplySchemeA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1618 LPWSTR in, out;
1619 HRESULT ret;
1620 DWORD len;
1622 TRACE("(%s, %p, %p:out size %d, 0x%08x)\n", debugstr_a(pszIn),
1623 pszOut, pcchOut, pcchOut ? *pcchOut : 0, dwFlags);
1625 if (!pszIn || !pszOut || !pcchOut) return E_INVALIDARG;
1627 in = HeapAlloc(GetProcessHeap(), 0,
1628 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
1629 out = in + INTERNET_MAX_URL_LENGTH;
1631 MultiByteToWideChar(CP_ACP, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
1632 len = INTERNET_MAX_URL_LENGTH;
1634 ret = UrlApplySchemeW(in, out, &len, dwFlags);
1635 if (ret != S_OK) {
1636 HeapFree(GetProcessHeap(), 0, in);
1637 return ret;
1640 len = WideCharToMultiByte(CP_ACP, 0, out, -1, NULL, 0, NULL, NULL);
1641 if (len > *pcchOut) {
1642 ret = E_POINTER;
1643 goto cleanup;
1646 WideCharToMultiByte(CP_ACP, 0, out, -1, pszOut, *pcchOut, NULL, NULL);
1647 len--;
1649 cleanup:
1650 *pcchOut = len;
1651 HeapFree(GetProcessHeap(), 0, in);
1652 return ret;
1655 static HRESULT URL_GuessScheme(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1657 HKEY newkey;
1658 BOOL j;
1659 INT index;
1660 DWORD value_len, data_len, dwType, i;
1661 WCHAR reg_path[MAX_PATH];
1662 WCHAR value[MAX_PATH], data[MAX_PATH];
1663 WCHAR Wxx, Wyy;
1665 MultiByteToWideChar(0, 0,
1666 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes",
1667 -1, reg_path, MAX_PATH);
1668 RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
1669 index = 0;
1670 while(value_len = data_len = MAX_PATH,
1671 RegEnumValueW(newkey, index, value, &value_len,
1672 0, &dwType, (LPVOID)data, &data_len) == 0) {
1673 TRACE("guess %d %s is %s\n",
1674 index, debugstr_w(value), debugstr_w(data));
1676 j = FALSE;
1677 for(i=0; i<value_len; i++) {
1678 Wxx = pszIn[i];
1679 Wyy = value[i];
1680 /* remember that TRUE is not-equal */
1681 j = ChrCmpIW(Wxx, Wyy);
1682 if (j) break;
1684 if ((i == value_len) && !j) {
1685 if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1686 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1687 RegCloseKey(newkey);
1688 return E_POINTER;
1690 strcpyW(pszOut, data);
1691 strcatW(pszOut, pszIn);
1692 *pcchOut = strlenW(pszOut);
1693 TRACE("matched and set to %s\n", debugstr_w(pszOut));
1694 RegCloseKey(newkey);
1695 return S_OK;
1697 index++;
1699 RegCloseKey(newkey);
1700 return E_FAIL;
1703 static HRESULT URL_ApplyDefault(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1705 HKEY newkey;
1706 DWORD data_len, dwType;
1707 WCHAR data[MAX_PATH];
1709 static const WCHAR prefix_keyW[] =
1710 {'S','o','f','t','w','a','r','e',
1711 '\\','M','i','c','r','o','s','o','f','t',
1712 '\\','W','i','n','d','o','w','s',
1713 '\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n',
1714 '\\','U','R','L',
1715 '\\','D','e','f','a','u','l','t','P','r','e','f','i','x',0};
1717 /* get and prepend default */
1718 RegOpenKeyExW(HKEY_LOCAL_MACHINE, prefix_keyW, 0, 1, &newkey);
1719 data_len = sizeof(data);
1720 RegQueryValueExW(newkey, NULL, 0, &dwType, (LPBYTE)data, &data_len);
1721 RegCloseKey(newkey);
1722 if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1723 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1724 return E_POINTER;
1726 strcpyW(pszOut, data);
1727 strcatW(pszOut, pszIn);
1728 *pcchOut = strlenW(pszOut);
1729 TRACE("used default %s\n", debugstr_w(pszOut));
1730 return S_OK;
1733 /*************************************************************************
1734 * UrlApplySchemeW [SHLWAPI.@]
1736 * See UrlApplySchemeA.
1738 HRESULT WINAPI UrlApplySchemeW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1740 PARSEDURLW in_scheme;
1741 DWORD res1;
1742 HRESULT ret;
1744 TRACE("(%s, %p, %p:out size %d, 0x%08x)\n", debugstr_w(pszIn),
1745 pszOut, pcchOut, pcchOut ? *pcchOut : 0, dwFlags);
1747 if (!pszIn || !pszOut || !pcchOut) return E_INVALIDARG;
1749 if (dwFlags & URL_APPLY_GUESSFILE) {
1750 FIXME("(%s %p %p(%d) 0x%08x): stub URL_APPLY_GUESSFILE not implemented\n",
1751 debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwFlags);
1752 strcpyW(pszOut, pszIn);
1753 *pcchOut = strlenW(pszOut);
1754 return S_FALSE;
1757 in_scheme.cbSize = sizeof(in_scheme);
1758 /* See if the base has a scheme */
1759 res1 = ParseURLW(pszIn, &in_scheme);
1760 if (res1) {
1761 /* no scheme in input, need to see if we need to guess */
1762 if (dwFlags & URL_APPLY_GUESSSCHEME) {
1763 if ((ret = URL_GuessScheme(pszIn, pszOut, pcchOut)) != E_FAIL)
1764 return ret;
1767 else {
1768 /* we have a scheme, see if valid (known scheme) */
1769 if (in_scheme.nScheme) {
1770 /* have valid scheme, so just copy and exit */
1771 if (strlenW(pszIn) + 1 > *pcchOut) {
1772 *pcchOut = strlenW(pszIn) + 1;
1773 return E_POINTER;
1775 strcpyW(pszOut, pszIn);
1776 *pcchOut = strlenW(pszOut);
1777 TRACE("valid scheme, returning copy\n");
1778 return S_OK;
1782 /* If we are here, then either invalid scheme,
1783 * or no scheme and can't/failed guess.
1785 if ( ( ((res1 == 0) && (dwFlags & URL_APPLY_FORCEAPPLY)) ||
1786 ((res1 != 0)) ) &&
1787 (dwFlags & URL_APPLY_DEFAULT)) {
1788 /* find and apply default scheme */
1789 return URL_ApplyDefault(pszIn, pszOut, pcchOut);
1792 return S_FALSE;
1795 /*************************************************************************
1796 * UrlIsA [SHLWAPI.@]
1798 * Determine if a Url is of a certain class.
1800 * PARAMS
1801 * pszUrl [I] Url to check
1802 * Urlis [I] URLIS_ constant from "shlwapi.h"
1804 * RETURNS
1805 * TRUE if pszUrl belongs to the class type in Urlis.
1806 * FALSE Otherwise.
1808 BOOL WINAPI UrlIsA(LPCSTR pszUrl, URLIS Urlis)
1810 PARSEDURLA base;
1811 DWORD res1;
1812 LPCSTR last;
1814 TRACE("(%s %d)\n", debugstr_a(pszUrl), Urlis);
1816 if(!pszUrl)
1817 return FALSE;
1819 switch (Urlis) {
1821 case URLIS_OPAQUE:
1822 base.cbSize = sizeof(base);
1823 res1 = ParseURLA(pszUrl, &base);
1824 if (res1) return FALSE; /* invalid scheme */
1825 switch (base.nScheme)
1827 case URL_SCHEME_MAILTO:
1828 case URL_SCHEME_SHELL:
1829 case URL_SCHEME_JAVASCRIPT:
1830 case URL_SCHEME_VBSCRIPT:
1831 case URL_SCHEME_ABOUT:
1832 return TRUE;
1834 return FALSE;
1836 case URLIS_FILEURL:
1837 return !StrCmpNA("file:", pszUrl, 5);
1839 case URLIS_DIRECTORY:
1840 last = pszUrl + strlen(pszUrl) - 1;
1841 return (last >= pszUrl && (*last == '/' || *last == '\\' ));
1843 case URLIS_URL:
1844 return PathIsURLA(pszUrl);
1846 case URLIS_NOHISTORY:
1847 case URLIS_APPLIABLE:
1848 case URLIS_HASQUERY:
1849 default:
1850 FIXME("(%s %d): stub\n", debugstr_a(pszUrl), Urlis);
1852 return FALSE;
1855 /*************************************************************************
1856 * UrlIsW [SHLWAPI.@]
1858 * See UrlIsA.
1860 BOOL WINAPI UrlIsW(LPCWSTR pszUrl, URLIS Urlis)
1862 static const WCHAR stemp[] = { 'f','i','l','e',':',0 };
1863 PARSEDURLW base;
1864 DWORD res1;
1865 LPCWSTR last;
1867 TRACE("(%s %d)\n", debugstr_w(pszUrl), Urlis);
1869 if(!pszUrl)
1870 return FALSE;
1872 switch (Urlis) {
1874 case URLIS_OPAQUE:
1875 base.cbSize = sizeof(base);
1876 res1 = ParseURLW(pszUrl, &base);
1877 if (res1) return FALSE; /* invalid scheme */
1878 switch (base.nScheme)
1880 case URL_SCHEME_MAILTO:
1881 case URL_SCHEME_SHELL:
1882 case URL_SCHEME_JAVASCRIPT:
1883 case URL_SCHEME_VBSCRIPT:
1884 case URL_SCHEME_ABOUT:
1885 return TRUE;
1887 return FALSE;
1889 case URLIS_FILEURL:
1890 return !strncmpW(stemp, pszUrl, 5);
1892 case URLIS_DIRECTORY:
1893 last = pszUrl + strlenW(pszUrl) - 1;
1894 return (last >= pszUrl && (*last == '/' || *last == '\\'));
1896 case URLIS_URL:
1897 return PathIsURLW(pszUrl);
1899 case URLIS_NOHISTORY:
1900 case URLIS_APPLIABLE:
1901 case URLIS_HASQUERY:
1902 default:
1903 FIXME("(%s %d): stub\n", debugstr_w(pszUrl), Urlis);
1905 return FALSE;
1908 /*************************************************************************
1909 * UrlIsNoHistoryA [SHLWAPI.@]
1911 * Determine if a Url should not be stored in the users history list.
1913 * PARAMS
1914 * pszUrl [I] Url to check
1916 * RETURNS
1917 * TRUE, if pszUrl should be excluded from the history list,
1918 * FALSE otherwise.
1920 BOOL WINAPI UrlIsNoHistoryA(LPCSTR pszUrl)
1922 return UrlIsA(pszUrl, URLIS_NOHISTORY);
1925 /*************************************************************************
1926 * UrlIsNoHistoryW [SHLWAPI.@]
1928 * See UrlIsNoHistoryA.
1930 BOOL WINAPI UrlIsNoHistoryW(LPCWSTR pszUrl)
1932 return UrlIsW(pszUrl, URLIS_NOHISTORY);
1935 /*************************************************************************
1936 * UrlIsOpaqueA [SHLWAPI.@]
1938 * Determine if a Url is opaque.
1940 * PARAMS
1941 * pszUrl [I] Url to check
1943 * RETURNS
1944 * TRUE if pszUrl is opaque,
1945 * FALSE Otherwise.
1947 * NOTES
1948 * An opaque Url is one that does not start with "<protocol>://".
1950 BOOL WINAPI UrlIsOpaqueA(LPCSTR pszUrl)
1952 return UrlIsA(pszUrl, URLIS_OPAQUE);
1955 /*************************************************************************
1956 * UrlIsOpaqueW [SHLWAPI.@]
1958 * See UrlIsOpaqueA.
1960 BOOL WINAPI UrlIsOpaqueW(LPCWSTR pszUrl)
1962 return UrlIsW(pszUrl, URLIS_OPAQUE);
1965 /*************************************************************************
1966 * Scans for characters of type "type" and when not matching found,
1967 * returns pointer to it and length in size.
1969 * Characters tested based on RFC 1738
1971 static LPCWSTR URL_ScanID(LPCWSTR start, LPDWORD size, WINE_URL_SCAN_TYPE type)
1973 static DWORD alwayszero = 0;
1974 BOOL cont = TRUE;
1976 *size = 0;
1978 switch(type){
1980 case SCHEME:
1981 while (cont) {
1982 if ( (islowerW(*start) && isalphaW(*start)) ||
1983 isdigitW(*start) ||
1984 (*start == '+') ||
1985 (*start == '-') ||
1986 (*start == '.')) {
1987 start++;
1988 (*size)++;
1990 else
1991 cont = FALSE;
1994 if(*start != ':')
1995 *size = 0;
1997 break;
1999 case USERPASS:
2000 while (cont) {
2001 if ( isalphaW(*start) ||
2002 isdigitW(*start) ||
2003 /* user/password only characters */
2004 (*start == ';') ||
2005 (*start == '?') ||
2006 (*start == '&') ||
2007 (*start == '=') ||
2008 /* *extra* characters */
2009 (*start == '!') ||
2010 (*start == '*') ||
2011 (*start == '\'') ||
2012 (*start == '(') ||
2013 (*start == ')') ||
2014 (*start == ',') ||
2015 /* *safe* characters */
2016 (*start == '$') ||
2017 (*start == '_') ||
2018 (*start == '+') ||
2019 (*start == '-') ||
2020 (*start == '.') ||
2021 (*start == ' ')) {
2022 start++;
2023 (*size)++;
2024 } else if (*start == '%') {
2025 if (isxdigitW(*(start+1)) &&
2026 isxdigitW(*(start+2))) {
2027 start += 3;
2028 *size += 3;
2029 } else
2030 cont = FALSE;
2031 } else
2032 cont = FALSE;
2034 break;
2036 case PORT:
2037 while (cont) {
2038 if (isdigitW(*start)) {
2039 start++;
2040 (*size)++;
2042 else
2043 cont = FALSE;
2045 break;
2047 case HOST:
2048 while (cont) {
2049 if (isalnumW(*start) ||
2050 (*start == '-') ||
2051 (*start == '.') ||
2052 (*start == ' ') ) {
2053 start++;
2054 (*size)++;
2056 else
2057 cont = FALSE;
2059 break;
2060 default:
2061 FIXME("unknown type %d\n", type);
2062 return (LPWSTR)&alwayszero;
2064 /* TRACE("scanned %d characters next char %p<%c>\n",
2065 *size, start, *start); */
2066 return start;
2069 /*************************************************************************
2070 * Attempt to parse URL into pieces.
2072 static LONG URL_ParseUrl(LPCWSTR pszUrl, WINE_PARSE_URL *pl)
2074 LPCWSTR work;
2076 memset(pl, 0, sizeof(WINE_PARSE_URL));
2077 pl->pScheme = pszUrl;
2078 work = URL_ScanID(pl->pScheme, &pl->szScheme, SCHEME);
2079 if (!*work || (*work != ':')) goto ErrorExit;
2080 work++;
2081 if ((*work != '/') || (*(work+1) != '/')) goto SuccessExit;
2082 pl->pUserName = work + 2;
2083 work = URL_ScanID(pl->pUserName, &pl->szUserName, USERPASS);
2084 if (*work == ':' ) {
2085 /* parse password */
2086 work++;
2087 pl->pPassword = work;
2088 work = URL_ScanID(pl->pPassword, &pl->szPassword, USERPASS);
2089 if (*work != '@') {
2090 /* what we just parsed must be the hostname and port
2091 * so reset pointers and clear then let it parse */
2092 pl->szUserName = pl->szPassword = 0;
2093 work = pl->pUserName - 1;
2094 pl->pUserName = pl->pPassword = 0;
2096 } else if (*work == '@') {
2097 /* no password */
2098 pl->szPassword = 0;
2099 pl->pPassword = 0;
2100 } else if (!*work || (*work == '/') || (*work == '.')) {
2101 /* what was parsed was hostname, so reset pointers and let it parse */
2102 pl->szUserName = pl->szPassword = 0;
2103 work = pl->pUserName - 1;
2104 pl->pUserName = pl->pPassword = 0;
2105 } else goto ErrorExit;
2107 /* now start parsing hostname or hostnumber */
2108 work++;
2109 pl->pHostName = work;
2110 work = URL_ScanID(pl->pHostName, &pl->szHostName, HOST);
2111 if (*work == ':') {
2112 /* parse port */
2113 work++;
2114 pl->pPort = work;
2115 work = URL_ScanID(pl->pPort, &pl->szPort, PORT);
2117 if (*work == '/') {
2118 /* see if query string */
2119 pl->pQuery = strchrW(work, '?');
2120 if (pl->pQuery) pl->szQuery = strlenW(pl->pQuery);
2122 SuccessExit:
2123 TRACE("parse successful: scheme=%p(%d), user=%p(%d), pass=%p(%d), host=%p(%d), port=%p(%d), query=%p(%d)\n",
2124 pl->pScheme, pl->szScheme,
2125 pl->pUserName, pl->szUserName,
2126 pl->pPassword, pl->szPassword,
2127 pl->pHostName, pl->szHostName,
2128 pl->pPort, pl->szPort,
2129 pl->pQuery, pl->szQuery);
2130 return S_OK;
2131 ErrorExit:
2132 FIXME("failed to parse %s\n", debugstr_w(pszUrl));
2133 return E_INVALIDARG;
2136 /*************************************************************************
2137 * UrlGetPartA [SHLWAPI.@]
2139 * Retrieve part of a Url.
2141 * PARAMS
2142 * pszIn [I] Url to parse
2143 * pszOut [O] Destination for part of pszIn requested
2144 * pcchOut [I] Size of pszOut
2145 * [O] length of pszOut string EXCLUDING '\0' if S_OK, otherwise
2146 * needed size of pszOut INCLUDING '\0'.
2147 * dwPart [I] URL_PART_ enum from "shlwapi.h"
2148 * dwFlags [I] URL_ flags from "shlwapi.h"
2150 * RETURNS
2151 * Success: S_OK. pszOut contains the part requested, pcchOut contains its length.
2152 * Failure: An HRESULT error code describing the error.
2154 HRESULT WINAPI UrlGetPartA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut,
2155 DWORD dwPart, DWORD dwFlags)
2157 LPWSTR in, out;
2158 DWORD ret, len, len2;
2160 if(!pszIn || !pszOut || !pcchOut || *pcchOut <= 0)
2161 return E_INVALIDARG;
2163 in = HeapAlloc(GetProcessHeap(), 0,
2164 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
2165 out = in + INTERNET_MAX_URL_LENGTH;
2167 MultiByteToWideChar(0, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
2169 len = INTERNET_MAX_URL_LENGTH;
2170 ret = UrlGetPartW(in, out, &len, dwPart, dwFlags);
2172 if (FAILED(ret)) {
2173 HeapFree(GetProcessHeap(), 0, in);
2174 return ret;
2177 len2 = WideCharToMultiByte(0, 0, out, len, 0, 0, 0, 0);
2178 if (len2 > *pcchOut) {
2179 *pcchOut = len2+1;
2180 HeapFree(GetProcessHeap(), 0, in);
2181 return E_POINTER;
2183 len2 = WideCharToMultiByte(0, 0, out, len+1, pszOut, *pcchOut, 0, 0);
2184 *pcchOut = len2-1;
2185 HeapFree(GetProcessHeap(), 0, in);
2186 return ret;
2189 /*************************************************************************
2190 * UrlGetPartW [SHLWAPI.@]
2192 * See UrlGetPartA.
2194 HRESULT WINAPI UrlGetPartW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut,
2195 DWORD dwPart, DWORD dwFlags)
2197 WINE_PARSE_URL pl;
2198 HRESULT ret;
2199 DWORD scheme, size, schsize;
2200 LPCWSTR addr, schaddr;
2202 TRACE("(%s %p %p(%d) %08x %08x)\n",
2203 debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwPart, dwFlags);
2205 if(!pszIn || !pszOut || !pcchOut || *pcchOut <= 0)
2206 return E_INVALIDARG;
2208 *pszOut = '\0';
2210 addr = strchrW(pszIn, ':');
2211 if(!addr)
2212 scheme = URL_SCHEME_UNKNOWN;
2213 else
2214 scheme = get_scheme_code(pszIn, addr-pszIn);
2216 ret = URL_ParseUrl(pszIn, &pl);
2218 switch (dwPart) {
2219 case URL_PART_SCHEME:
2220 if (!pl.szScheme) {
2221 *pcchOut = 0;
2222 return S_FALSE;
2224 addr = pl.pScheme;
2225 size = pl.szScheme;
2226 break;
2228 case URL_PART_HOSTNAME:
2229 switch(scheme) {
2230 case URL_SCHEME_FTP:
2231 case URL_SCHEME_HTTP:
2232 case URL_SCHEME_GOPHER:
2233 case URL_SCHEME_TELNET:
2234 case URL_SCHEME_FILE:
2235 case URL_SCHEME_HTTPS:
2236 break;
2237 default:
2238 *pcchOut = 0;
2239 return E_FAIL;
2242 if(scheme==URL_SCHEME_FILE && (!pl.szHostName ||
2243 (pl.szHostName==1 && *(pl.pHostName+1)==':'))) {
2244 *pcchOut = 0;
2245 return S_FALSE;
2248 if (!pl.szHostName) {
2249 *pcchOut = 0;
2250 return S_FALSE;
2252 addr = pl.pHostName;
2253 size = pl.szHostName;
2254 break;
2256 case URL_PART_USERNAME:
2257 if (!pl.szUserName) {
2258 *pcchOut = 0;
2259 return S_FALSE;
2261 addr = pl.pUserName;
2262 size = pl.szUserName;
2263 break;
2265 case URL_PART_PASSWORD:
2266 if (!pl.szPassword) {
2267 *pcchOut = 0;
2268 return S_FALSE;
2270 addr = pl.pPassword;
2271 size = pl.szPassword;
2272 break;
2274 case URL_PART_PORT:
2275 if (!pl.szPort) {
2276 *pcchOut = 0;
2277 return S_FALSE;
2279 addr = pl.pPort;
2280 size = pl.szPort;
2281 break;
2283 case URL_PART_QUERY:
2284 if (!pl.szQuery) {
2285 *pcchOut = 0;
2286 return S_FALSE;
2288 addr = pl.pQuery;
2289 size = pl.szQuery;
2290 break;
2292 default:
2293 *pcchOut = 0;
2294 return E_INVALIDARG;
2297 if (dwFlags == URL_PARTFLAG_KEEPSCHEME) {
2298 if(!pl.pScheme || !pl.szScheme) {
2299 *pcchOut = 0;
2300 return E_FAIL;
2302 schaddr = pl.pScheme;
2303 schsize = pl.szScheme;
2304 if (*pcchOut < schsize + size + 2) {
2305 *pcchOut = schsize + size + 2;
2306 return E_POINTER;
2308 memcpy(pszOut, schaddr, schsize*sizeof(WCHAR));
2309 pszOut[schsize] = ':';
2310 memcpy(pszOut+schsize+1, addr, size*sizeof(WCHAR));
2311 pszOut[schsize+1+size] = 0;
2312 *pcchOut = schsize + 1 + size;
2314 else {
2315 if (*pcchOut < size + 1) {*pcchOut = size+1; return E_POINTER;}
2316 memcpy(pszOut, addr, size*sizeof(WCHAR));
2317 pszOut[size] = 0;
2318 *pcchOut = size;
2320 TRACE("len=%d %s\n", *pcchOut, debugstr_w(pszOut));
2322 return ret;
2325 /*************************************************************************
2326 * PathIsURLA [SHLWAPI.@]
2328 * Check if the given path is a Url.
2330 * PARAMS
2331 * lpszPath [I] Path to check.
2333 * RETURNS
2334 * TRUE if lpszPath is a Url.
2335 * FALSE if lpszPath is NULL or not a Url.
2337 BOOL WINAPI PathIsURLA(LPCSTR lpstrPath)
2339 PARSEDURLA base;
2340 HRESULT hres;
2342 TRACE("%s\n", debugstr_a(lpstrPath));
2344 if (!lpstrPath || !*lpstrPath) return FALSE;
2346 /* get protocol */
2347 base.cbSize = sizeof(base);
2348 hres = ParseURLA(lpstrPath, &base);
2349 return hres == S_OK && (base.nScheme != URL_SCHEME_INVALID);
2352 /*************************************************************************
2353 * PathIsURLW [SHLWAPI.@]
2355 * See PathIsURLA.
2357 BOOL WINAPI PathIsURLW(LPCWSTR lpstrPath)
2359 PARSEDURLW base;
2360 HRESULT hres;
2362 TRACE("%s\n", debugstr_w(lpstrPath));
2364 if (!lpstrPath || !*lpstrPath) return FALSE;
2366 /* get protocol */
2367 base.cbSize = sizeof(base);
2368 hres = ParseURLW(lpstrPath, &base);
2369 return hres == S_OK && (base.nScheme != URL_SCHEME_INVALID);
2372 /*************************************************************************
2373 * UrlCreateFromPathA [SHLWAPI.@]
2375 * See UrlCreateFromPathW
2377 HRESULT WINAPI UrlCreateFromPathA(LPCSTR pszPath, LPSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2379 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
2380 WCHAR *urlW = bufW;
2381 UNICODE_STRING pathW;
2382 HRESULT ret;
2383 DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
2385 if(!RtlCreateUnicodeStringFromAsciiz(&pathW, pszPath))
2386 return E_INVALIDARG;
2387 if((ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved)) == E_POINTER) {
2388 urlW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
2389 ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved);
2391 if(ret == S_OK || ret == S_FALSE) {
2392 RtlUnicodeToMultiByteSize(&lenA, urlW, lenW * sizeof(WCHAR));
2393 if(*pcchUrl > lenA) {
2394 RtlUnicodeToMultiByteN(pszUrl, *pcchUrl - 1, &lenA, urlW, lenW * sizeof(WCHAR));
2395 pszUrl[lenA] = 0;
2396 *pcchUrl = lenA;
2397 } else {
2398 *pcchUrl = lenA + 1;
2399 ret = E_POINTER;
2402 if(urlW != bufW) HeapFree(GetProcessHeap(), 0, urlW);
2403 RtlFreeUnicodeString(&pathW);
2404 return ret;
2407 /*************************************************************************
2408 * UrlCreateFromPathW [SHLWAPI.@]
2410 * Create a Url from a file path.
2412 * PARAMS
2413 * pszPath [I] Path to convert
2414 * pszUrl [O] Destination for the converted Url
2415 * pcchUrl [I/O] Length of pszUrl
2416 * dwReserved [I] Reserved, must be 0
2418 * RETURNS
2419 * Success: S_OK pszUrl contains the converted path, S_FALSE if the path is already a Url
2420 * Failure: An HRESULT error code.
2422 HRESULT WINAPI UrlCreateFromPathW(LPCWSTR pszPath, LPWSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2424 DWORD needed;
2425 HRESULT ret;
2426 WCHAR *pszNewUrl;
2427 WCHAR file_colonW[] = {'f','i','l','e',':',0};
2428 WCHAR three_slashesW[] = {'/','/','/',0};
2429 PARSEDURLW parsed_url;
2431 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszPath), pszUrl, pcchUrl, dwReserved);
2433 /* Validate arguments */
2434 if (dwReserved != 0)
2435 return E_INVALIDARG;
2436 if (!pszUrl || !pcchUrl)
2437 return E_INVALIDARG;
2440 parsed_url.cbSize = sizeof(parsed_url);
2441 if(ParseURLW(pszPath, &parsed_url) == S_OK) {
2442 if(parsed_url.nScheme != URL_SCHEME_INVALID && parsed_url.cchProtocol > 1) {
2443 needed = strlenW(pszPath);
2444 if (needed >= *pcchUrl) {
2445 *pcchUrl = needed + 1;
2446 return E_POINTER;
2447 } else {
2448 *pcchUrl = needed;
2449 strcpyW(pszUrl, pszPath);
2450 return S_FALSE;
2455 pszNewUrl = HeapAlloc(GetProcessHeap(), 0, (strlenW(pszPath) + 9) * sizeof(WCHAR)); /* "file:///" + pszPath_len + 1 */
2456 strcpyW(pszNewUrl, file_colonW);
2457 if(isalphaW(pszPath[0]) && pszPath[1] == ':')
2458 strcatW(pszNewUrl, three_slashesW);
2459 strcatW(pszNewUrl, pszPath);
2460 ret = UrlEscapeW(pszNewUrl, pszUrl, pcchUrl, URL_ESCAPE_PERCENT);
2462 HeapFree(GetProcessHeap(), 0, pszNewUrl);
2463 return ret;
2466 /*************************************************************************
2467 * SHAutoComplete [SHLWAPI.@]
2469 * Enable auto-completion for an edit control.
2471 * PARAMS
2472 * hwndEdit [I] Handle of control to enable auto-completion for
2473 * dwFlags [I] SHACF_ flags from "shlwapi.h"
2475 * RETURNS
2476 * Success: S_OK. Auto-completion is enabled for the control.
2477 * Failure: An HRESULT error code indicating the error.
2479 HRESULT WINAPI SHAutoComplete(HWND hwndEdit, DWORD dwFlags)
2481 FIXME("stub\n");
2482 return S_FALSE;
2485 /*************************************************************************
2486 * MLBuildResURLA [SHLWAPI.405]
2488 * Create a Url pointing to a resource in a module.
2490 * PARAMS
2491 * lpszLibName [I] Name of the module containing the resource
2492 * hMod [I] Callers module handle
2493 * dwFlags [I] Undocumented flags for loading the module
2494 * lpszRes [I] Resource name
2495 * lpszDest [O] Destination for resulting Url
2496 * dwDestLen [I] Length of lpszDest
2498 * RETURNS
2499 * Success: S_OK. lpszDest contains the resource Url.
2500 * Failure: E_INVALIDARG, if any argument is invalid, or
2501 * E_FAIL if dwDestLen is too small.
2503 HRESULT WINAPI MLBuildResURLA(LPCSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2504 LPCSTR lpszRes, LPSTR lpszDest, DWORD dwDestLen)
2506 WCHAR szLibName[MAX_PATH], szRes[MAX_PATH], szDest[MAX_PATH];
2507 HRESULT hRet;
2509 if (lpszLibName)
2510 MultiByteToWideChar(CP_ACP, 0, lpszLibName, -1, szLibName, sizeof(szLibName)/sizeof(WCHAR));
2512 if (lpszRes)
2513 MultiByteToWideChar(CP_ACP, 0, lpszRes, -1, szRes, sizeof(szRes)/sizeof(WCHAR));
2515 if (dwDestLen > sizeof(szLibName)/sizeof(WCHAR))
2516 dwDestLen = sizeof(szLibName)/sizeof(WCHAR);
2518 hRet = MLBuildResURLW(lpszLibName ? szLibName : NULL, hMod, dwFlags,
2519 lpszRes ? szRes : NULL, lpszDest ? szDest : NULL, dwDestLen);
2520 if (SUCCEEDED(hRet) && lpszDest)
2521 WideCharToMultiByte(CP_ACP, 0, szDest, -1, lpszDest, dwDestLen, 0, 0);
2523 return hRet;
2526 /*************************************************************************
2527 * MLBuildResURLA [SHLWAPI.406]
2529 * See MLBuildResURLA.
2531 HRESULT WINAPI MLBuildResURLW(LPCWSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2532 LPCWSTR lpszRes, LPWSTR lpszDest, DWORD dwDestLen)
2534 static const WCHAR szRes[] = { 'r','e','s',':','/','/','\0' };
2535 #define szResLen ((sizeof(szRes) - sizeof(WCHAR))/sizeof(WCHAR))
2536 HRESULT hRet = E_FAIL;
2538 TRACE("(%s,%p,0x%08x,%s,%p,%d)\n", debugstr_w(lpszLibName), hMod, dwFlags,
2539 debugstr_w(lpszRes), lpszDest, dwDestLen);
2541 if (!lpszLibName || !hMod || hMod == INVALID_HANDLE_VALUE || !lpszRes ||
2542 !lpszDest || (dwFlags && dwFlags != 2))
2543 return E_INVALIDARG;
2545 if (dwDestLen >= szResLen + 1)
2547 dwDestLen -= (szResLen + 1);
2548 memcpy(lpszDest, szRes, sizeof(szRes));
2550 hMod = MLLoadLibraryW(lpszLibName, hMod, dwFlags);
2552 if (hMod)
2554 WCHAR szBuff[MAX_PATH];
2555 DWORD len;
2557 len = GetModuleFileNameW(hMod, szBuff, sizeof(szBuff)/sizeof(WCHAR));
2558 if (len && len < sizeof(szBuff)/sizeof(WCHAR))
2560 DWORD dwPathLen = strlenW(szBuff) + 1;
2562 if (dwDestLen >= dwPathLen)
2564 DWORD dwResLen;
2566 dwDestLen -= dwPathLen;
2567 memcpy(lpszDest + szResLen, szBuff, dwPathLen * sizeof(WCHAR));
2569 dwResLen = strlenW(lpszRes) + 1;
2570 if (dwDestLen >= dwResLen + 1)
2572 lpszDest[szResLen + dwPathLen-1] = '/';
2573 memcpy(lpszDest + szResLen + dwPathLen, lpszRes, dwResLen * sizeof(WCHAR));
2574 hRet = S_OK;
2578 MLFreeLibrary(hMod);
2581 return hRet;
2584 /***********************************************************************
2585 * UrlFixupW [SHLWAPI.462]
2587 * Checks the scheme part of a URL and attempts to correct misspellings.
2589 * PARAMS
2590 * lpszUrl [I] Pointer to the URL to be corrected
2591 * lpszTranslatedUrl [O] Pointer to a buffer to store corrected URL
2592 * dwMaxChars [I] Maximum size of corrected URL
2594 * RETURNS
2595 * success: S_OK if URL corrected or already correct
2596 * failure: S_FALSE if unable to correct / COM error code if other error
2599 HRESULT WINAPI UrlFixupW(LPCWSTR url, LPWSTR translatedUrl, DWORD maxChars)
2601 DWORD srcLen;
2603 FIXME("(%s,%p,%d) STUB\n", debugstr_w(url), translatedUrl, maxChars);
2605 if (!url)
2606 return E_FAIL;
2608 srcLen = lstrlenW(url) + 1;
2610 /* For now just copy the URL directly */
2611 lstrcpynW(translatedUrl, url, (maxChars < srcLen) ? maxChars : srcLen);
2613 return S_OK;