Commit 1/2
[reactos.git] / reactos / dll / win32 / shlwapi / url.c
1 /*
2 * Url functions
3 *
4 * Copyright 2000 Huw D M Davies for CodeWeavers.
5 *
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.
10 *
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.
15 *
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
19 */
20
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 "wine/debug.h"
37
38 HMODULE WINAPI MLLoadLibraryW(LPCWSTR,HMODULE,DWORD);
39 BOOL WINAPI MLFreeLibrary(HMODULE);
40 HRESULT WINAPI MLBuildResURLW(LPCWSTR,HMODULE,DWORD,LPCWSTR,LPWSTR,DWORD);
41
42 WINE_DEFAULT_DEBUG_CHANNEL(shell);
43
44 /* The following schemes were identified in the native version of
45 * SHLWAPI.DLL version 5.50
46 */
47 static const struct {
48 URL_SCHEME scheme_number;
49 WCHAR scheme_name[12];
50 } shlwapi_schemes[] = {
51 {URL_SCHEME_FTP, {'f','t','p',0}},
52 {URL_SCHEME_HTTP, {'h','t','t','p',0}},
53 {URL_SCHEME_GOPHER, {'g','o','p','h','e','r',0}},
54 {URL_SCHEME_MAILTO, {'m','a','i','l','t','o',0}},
55 {URL_SCHEME_NEWS, {'n','e','w','s',0}},
56 {URL_SCHEME_NNTP, {'n','n','t','p',0}},
57 {URL_SCHEME_TELNET, {'t','e','l','n','e','t',0}},
58 {URL_SCHEME_WAIS, {'w','a','i','s',0}},
59 {URL_SCHEME_FILE, {'f','i','l','e',0}},
60 {URL_SCHEME_MK, {'m','k',0}},
61 {URL_SCHEME_HTTPS, {'h','t','t','p','s',0}},
62 {URL_SCHEME_SHELL, {'s','h','e','l','l',0}},
63 {URL_SCHEME_SNEWS, {'s','n','e','w','s',0}},
64 {URL_SCHEME_LOCAL, {'l','o','c','a','l',0}},
65 {URL_SCHEME_JAVASCRIPT, {'j','a','v','a','s','c','r','i','p','t',0}},
66 {URL_SCHEME_VBSCRIPT, {'v','b','s','c','r','i','p','t',0}},
67 {URL_SCHEME_ABOUT, {'a','b','o','u','t',0}},
68 {URL_SCHEME_RES, {'r','e','s',0}},
69 };
70
71 typedef struct {
72 LPCWSTR pScheme; /* [out] start of scheme */
73 DWORD szScheme; /* [out] size of scheme (until colon) */
74 LPCWSTR pUserName; /* [out] start of Username */
75 DWORD szUserName; /* [out] size of Username (until ":" or "@") */
76 LPCWSTR pPassword; /* [out] start of Password */
77 DWORD szPassword; /* [out] size of Password (until "@") */
78 LPCWSTR pHostName; /* [out] start of Hostname */
79 DWORD szHostName; /* [out] size of Hostname (until ":" or "/") */
80 LPCWSTR pPort; /* [out] start of Port */
81 DWORD szPort; /* [out] size of Port (until "/" or eos) */
82 LPCWSTR pQuery; /* [out] start of Query */
83 DWORD szQuery; /* [out] size of Query (until eos) */
84 } WINE_PARSE_URL;
85
86 typedef enum {
87 SCHEME,
88 HOST,
89 PORT,
90 USERPASS,
91 } WINE_URL_SCAN_TYPE;
92
93 static const CHAR hexDigits[] = "0123456789ABCDEF";
94
95 static const WCHAR fileW[] = {'f','i','l','e','\0'};
96
97 static const unsigned char HashDataLookup[256] = {
98 0x01, 0x0E, 0x6E, 0x19, 0x61, 0xAE, 0x84, 0x77, 0x8A, 0xAA, 0x7D, 0x76, 0x1B,
99 0xE9, 0x8C, 0x33, 0x57, 0xC5, 0xB1, 0x6B, 0xEA, 0xA9, 0x38, 0x44, 0x1E, 0x07,
100 0xAD, 0x49, 0xBC, 0x28, 0x24, 0x41, 0x31, 0xD5, 0x68, 0xBE, 0x39, 0xD3, 0x94,
101 0xDF, 0x30, 0x73, 0x0F, 0x02, 0x43, 0xBA, 0xD2, 0x1C, 0x0C, 0xB5, 0x67, 0x46,
102 0x16, 0x3A, 0x4B, 0x4E, 0xB7, 0xA7, 0xEE, 0x9D, 0x7C, 0x93, 0xAC, 0x90, 0xB0,
103 0xA1, 0x8D, 0x56, 0x3C, 0x42, 0x80, 0x53, 0x9C, 0xF1, 0x4F, 0x2E, 0xA8, 0xC6,
104 0x29, 0xFE, 0xB2, 0x55, 0xFD, 0xED, 0xFA, 0x9A, 0x85, 0x58, 0x23, 0xCE, 0x5F,
105 0x74, 0xFC, 0xC0, 0x36, 0xDD, 0x66, 0xDA, 0xFF, 0xF0, 0x52, 0x6A, 0x9E, 0xC9,
106 0x3D, 0x03, 0x59, 0x09, 0x2A, 0x9B, 0x9F, 0x5D, 0xA6, 0x50, 0x32, 0x22, 0xAF,
107 0xC3, 0x64, 0x63, 0x1A, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xBD, 0x79, 0x40,
108 0x4D, 0x48, 0xD0, 0xF5, 0x82, 0x7A, 0x8F, 0x37, 0x69, 0x86, 0x1D, 0xA4, 0xB9,
109 0xC2, 0xC1, 0xEF, 0x65, 0xF2, 0x05, 0xAB, 0x7E, 0x0B, 0x4A, 0x3B, 0x89, 0xE4,
110 0x6C, 0xBF, 0xE8, 0x8B, 0x06, 0x18, 0x51, 0x14, 0x7F, 0x11, 0x5B, 0x5C, 0xFB,
111 0x97, 0xE1, 0xCF, 0x15, 0x62, 0x71, 0x70, 0x54, 0xE2, 0x12, 0xD6, 0xC7, 0xBB,
112 0x0D, 0x20, 0x5E, 0xDC, 0xE0, 0xD4, 0xF7, 0xCC, 0xC4, 0x2B, 0xF9, 0xEC, 0x2D,
113 0xF4, 0x6F, 0xB6, 0x99, 0x88, 0x81, 0x5A, 0xD9, 0xCA, 0x13, 0xA5, 0xE7, 0x47,
114 0xE6, 0x8E, 0x60, 0xE3, 0x3E, 0xB3, 0xF6, 0x72, 0xA2, 0x35, 0xA0, 0xD7, 0xCD,
115 0xB4, 0x2F, 0x6D, 0x2C, 0x26, 0x1F, 0x95, 0x87, 0x00, 0xD8, 0x34, 0x3F, 0x17,
116 0x25, 0x45, 0x27, 0x75, 0x92, 0xB8, 0xA3, 0xC8, 0xDE, 0xEB, 0xF8, 0xF3, 0xDB,
117 0x0A, 0x98, 0x83, 0x7B, 0xE5, 0xCB, 0x4C, 0x78, 0xD1 };
118
119 static DWORD get_scheme_code(LPCWSTR scheme, DWORD scheme_len)
120 {
121 int i;
122
123 for(i=0; i < sizeof(shlwapi_schemes)/sizeof(shlwapi_schemes[0]); i++) {
124 if(scheme_len == strlenW(shlwapi_schemes[i].scheme_name)
125 && !memcmp(scheme, shlwapi_schemes[i].scheme_name, scheme_len*sizeof(WCHAR)))
126 return shlwapi_schemes[i].scheme_number;
127 }
128
129 return URL_SCHEME_UNKNOWN;
130 }
131
132 static BOOL URL_JustLocation(LPCWSTR str)
133 {
134 while(*str && (*str == '/')) str++;
135 if (*str) {
136 while (*str && ((*str == '-') ||
137 (*str == '.') ||
138 isalnumW(*str))) str++;
139 if (*str == '/') return FALSE;
140 }
141 return TRUE;
142 }
143
144
145 /*************************************************************************
146 * @ [SHLWAPI.1]
147 *
148 * Parse a Url into its constituent parts.
149 *
150 * PARAMS
151 * x [I] Url to parse
152 * y [O] Undocumented structure holding the parsed information
153 *
154 * RETURNS
155 * Success: S_OK. y contains the parsed Url details.
156 * Failure: An HRESULT error code.
157 */
158 HRESULT WINAPI ParseURLA(LPCSTR x, PARSEDURLA *y)
159 {
160 WCHAR scheme[INTERNET_MAX_SCHEME_LENGTH];
161 DWORD cnt, len;
162
163 y->nScheme = URL_SCHEME_INVALID;
164 if (y->cbSize != sizeof(*y)) return E_INVALIDARG;
165 /* FIXME: leading white space generates error of 0x80041001 which
166 * is undefined
167 */
168 if (*x <= ' ') return 0x80041001;
169 cnt = 0;
170 y->cchProtocol = 0;
171 y->pszProtocol = x;
172 while (*x) {
173 if (*x == ':') {
174 y->cchProtocol = cnt;
175 cnt = -1;
176 y->pszSuffix = x+1;
177 break;
178 }
179 x++;
180 cnt++;
181 }
182
183 /* check for no scheme in string start */
184 /* (apparently schemes *must* be larger than a single character) */
185 if ((*x == '\0') || (y->cchProtocol <= 1)) {
186 y->pszProtocol = NULL;
187 return 0x80041001;
188 }
189
190 /* found scheme, set length of remainder */
191 y->cchSuffix = lstrlenA(y->pszSuffix);
192
193 len = MultiByteToWideChar(CP_ACP, 0, y->pszProtocol, y->cchProtocol,
194 scheme, sizeof(scheme)/sizeof(WCHAR));
195 y->nScheme = get_scheme_code(scheme, len);
196
197 return S_OK;
198 }
199
200 /*************************************************************************
201 * @ [SHLWAPI.2]
202 *
203 * Unicode version of ParseURLA.
204 */
205 HRESULT WINAPI ParseURLW(LPCWSTR x, PARSEDURLW *y)
206 {
207 DWORD cnt;
208
209 y->nScheme = URL_SCHEME_INVALID;
210 if (y->cbSize != sizeof(*y)) return E_INVALIDARG;
211 /* FIXME: leading white space generates error of 0x80041001 which
212 * is undefined
213 */
214 if (*x <= ' ') return 0x80041001;
215 cnt = 0;
216 y->cchProtocol = 0;
217 y->pszProtocol = x;
218 while (*x) {
219 if (*x == ':') {
220 y->cchProtocol = cnt;
221 cnt = -1;
222 y->pszSuffix = x+1;
223 break;
224 }
225 x++;
226 cnt++;
227 }
228
229 /* check for no scheme in string start */
230 /* (apparently schemes *must* be larger than a single character) */
231 if ((*x == '\0') || (y->cchProtocol <= 1)) {
232 y->pszProtocol = NULL;
233 return 0x80041001;
234 }
235
236 /* found scheme, set length of remainder */
237 y->cchSuffix = lstrlenW(y->pszSuffix);
238 y->nScheme = get_scheme_code(y->pszProtocol, y->cchProtocol);
239
240 return S_OK;
241 }
242
243 /*************************************************************************
244 * UrlCanonicalizeA [SHLWAPI.@]
245 *
246 * Canonicalize a Url.
247 *
248 * PARAMS
249 * pszUrl [I] Url to cCanonicalize
250 * pszCanonicalized [O] Destination for converted Url.
251 * pcchCanonicalized [I/O] Length of pszUrl, destination for length of pszCanonicalized
252 * dwFlags [I] Flags controlling the conversion.
253 *
254 * RETURNS
255 * Success: S_OK. The pszCanonicalized contains the converted Url.
256 * Failure: E_POINTER, if *pcchCanonicalized is too small.
257 *
258 * MSDN incorrectly describes the flags for this function. They should be:
259 *| URL_DONT_ESCAPE_EXTRA_INFO 0x02000000
260 *| URL_ESCAPE_SPACES_ONLY 0x04000000
261 *| URL_ESCAPE_PERCENT 0x00001000
262 *| URL_ESCAPE_UNSAFE 0x10000000
263 *| URL_UNESCAPE 0x10000000
264 *| URL_DONT_SIMPLIFY 0x08000000
265 *| URL_ESCAPE_SEGMENT_ONLY 0x00002000
266 */
267 HRESULT WINAPI UrlCanonicalizeA(LPCSTR pszUrl, LPSTR pszCanonicalized,
268 LPDWORD pcchCanonicalized, DWORD dwFlags)
269 {
270 LPWSTR base, canonical;
271 HRESULT ret;
272 DWORD len, len2;
273
274 TRACE("(%s, %p, %p, 0x%08x) *pcchCanonicalized: %d\n", debugstr_a(pszUrl), pszCanonicalized,
275 pcchCanonicalized, dwFlags, pcchCanonicalized ? *pcchCanonicalized : -1);
276
277 if(!pszUrl || !pszCanonicalized || !pcchCanonicalized)
278 return E_INVALIDARG;
279
280 base = HeapAlloc(GetProcessHeap(), 0,
281 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
282 canonical = base + INTERNET_MAX_URL_LENGTH;
283
284 MultiByteToWideChar(0, 0, pszUrl, -1, base, INTERNET_MAX_URL_LENGTH);
285 len = INTERNET_MAX_URL_LENGTH;
286
287 ret = UrlCanonicalizeW(base, canonical, &len, dwFlags);
288 if (ret != S_OK) {
289 *pcchCanonicalized = len * 2;
290 HeapFree(GetProcessHeap(), 0, base);
291 return ret;
292 }
293
294 len2 = WideCharToMultiByte(0, 0, canonical, -1, 0, 0, 0, 0);
295 if (len2 > *pcchCanonicalized) {
296 *pcchCanonicalized = len2;
297 HeapFree(GetProcessHeap(), 0, base);
298 return E_POINTER;
299 }
300 WideCharToMultiByte(0, 0, canonical, -1, pszCanonicalized, *pcchCanonicalized, 0, 0);
301 *pcchCanonicalized = len;
302 HeapFree(GetProcessHeap(), 0, base);
303 return S_OK;
304 }
305
306 /*************************************************************************
307 * UrlCanonicalizeW [SHLWAPI.@]
308 *
309 * See UrlCanonicalizeA.
310 */
311 HRESULT WINAPI UrlCanonicalizeW(LPCWSTR pszUrl, LPWSTR pszCanonicalized,
312 LPDWORD pcchCanonicalized, DWORD dwFlags)
313 {
314 HRESULT hr = S_OK;
315 DWORD EscapeFlags;
316 LPWSTR lpszUrlCpy, wk1, wk2, mp, mp2, root;
317 INT nByteLen, state;
318 DWORD nLen, nWkLen;
319 WCHAR slash = '/';
320
321 static const WCHAR wszFile[] = {'f','i','l','e',':'};
322 static const WCHAR wszLocalhost[] = {'l','o','c','a','l','h','o','s','t'};
323
324 TRACE("(%s, %p, %p, 0x%08x) *pcchCanonicalized: %d\n", debugstr_w(pszUrl), pszCanonicalized,
325 pcchCanonicalized, dwFlags, pcchCanonicalized ? *pcchCanonicalized : -1);
326
327 if(!pszUrl || !pszCanonicalized || !pcchCanonicalized)
328 return E_INVALIDARG;
329
330 if(!*pszUrl) {
331 *pszCanonicalized = 0;
332 return S_OK;
333 }
334
335 nByteLen = (lstrlenW(pszUrl) + 1) * sizeof(WCHAR); /* length in bytes */
336 lpszUrlCpy = HeapAlloc(GetProcessHeap(), 0,
337 INTERNET_MAX_URL_LENGTH * sizeof(WCHAR));
338
339 if((dwFlags & URL_FILE_USE_PATHURL) && nByteLen >= sizeof(wszFile)
340 && !memcmp(wszFile, pszUrl, sizeof(wszFile)))
341 slash = '\\';
342
343 /*
344 * state =
345 * 0 initial 1,3
346 * 1 have 2[+] alnum 2,3
347 * 2 have scheme (found :) 4,6,3
348 * 3 failed (no location)
349 * 4 have // 5,3
350 * 5 have 1[+] alnum 6,3
351 * 6 have location (found /) save root location
352 */
353
354 wk1 = (LPWSTR)pszUrl;
355 wk2 = lpszUrlCpy;
356 state = 0;
357
358 if(pszUrl[1] == ':') { /* Assume path */
359 static const WCHAR wszFilePrefix[] = {'f','i','l','e',':','/','/','/'};
360
361 memcpy(wk2, wszFilePrefix, sizeof(wszFilePrefix));
362 wk2 += sizeof(wszFilePrefix)/sizeof(WCHAR);
363 if (dwFlags & URL_FILE_USE_PATHURL)
364 {
365 slash = '\\';
366 --wk2;
367 }
368 else
369 dwFlags |= URL_ESCAPE_UNSAFE;
370 state = 5;
371 }
372
373 while (*wk1) {
374 switch (state) {
375 case 0:
376 if (!isalnumW(*wk1)) {state = 3; break;}
377 *wk2++ = *wk1++;
378 if (!isalnumW(*wk1)) {state = 3; break;}
379 *wk2++ = *wk1++;
380 state = 1;
381 break;
382 case 1:
383 *wk2++ = *wk1;
384 if (*wk1++ == ':') state = 2;
385 break;
386 case 2:
387 *wk2++ = *wk1++;
388 if (*wk1 != '/') {state = 6; break;}
389 *wk2++ = *wk1++;
390 if((dwFlags & URL_FILE_USE_PATHURL) && nByteLen >= sizeof(wszLocalhost)
391 && !memcmp(wszLocalhost, wk1, sizeof(wszLocalhost))){
392 wk1 += sizeof(wszLocalhost)/sizeof(WCHAR);
393 while(*wk1 == '\\' && (dwFlags & URL_FILE_USE_PATHURL))
394 wk1++;
395 }
396 if(*wk1 == '/' && (dwFlags & URL_FILE_USE_PATHURL))
397 wk1++;
398 state = 4;
399 break;
400 case 3:
401 nWkLen = strlenW(wk1);
402 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
403 mp = wk2;
404 wk1 += nWkLen;
405 wk2 += nWkLen;
406
407 while(mp < wk2) {
408 if(*mp == '/' || *mp == '\\')
409 *mp = slash;
410 mp++;
411 }
412 break;
413 case 4:
414 if (!isalnumW(*wk1) && (*wk1 != '-') && (*wk1 != '.') && (*wk1 != ':'))
415 {state = 3; break;}
416 while(isalnumW(*wk1) || (*wk1 == '-') || (*wk1 == '.') || (*wk1 == ':'))
417 *wk2++ = *wk1++;
418 state = 5;
419 if (!*wk1)
420 *wk2++ = slash;
421 break;
422 case 5:
423 if (*wk1 != '/' && *wk1 != '\\') {state = 3; break;}
424 while(*wk1 == '/' || *wk1 == '\\') {
425 *wk2++ = slash;
426 wk1++;
427 }
428 state = 6;
429 break;
430 case 6:
431 if(dwFlags & URL_DONT_SIMPLIFY) {
432 state = 3;
433 break;
434 }
435
436 /* Now at root location, cannot back up any more. */
437 /* "root" will point at the '/' */
438
439 root = wk2-1;
440 while (*wk1) {
441 mp = strchrW(wk1, '/');
442 mp2 = strchrW(wk1, '\\');
443 if(mp2 && (!mp || mp2 < mp))
444 mp = mp2;
445 if (!mp) {
446 nWkLen = strlenW(wk1);
447 memcpy(wk2, wk1, (nWkLen + 1) * sizeof(WCHAR));
448 wk1 += nWkLen;
449 wk2 += nWkLen;
450 continue;
451 }
452 nLen = mp - wk1;
453 if(nLen) {
454 memcpy(wk2, wk1, nLen * sizeof(WCHAR));
455 wk2 += nLen;
456 wk1 += nLen;
457 }
458 *wk2++ = slash;
459 wk1++;
460
461 if (*wk1 == '.') {
462 TRACE("found '/.'\n");
463 if (wk1[1] == '/' || wk1[1] == '\\') {
464 /* case of /./ -> skip the ./ */
465 wk1 += 2;
466 }
467 else if (wk1[1] == '.') {
468 /* found /.. look for next / */
469 TRACE("found '/..'\n");
470 if (wk1[2] == '/' || wk1[2] == '\\' ||wk1[2] == '?'
471 || wk1[2] == '#' || !wk1[2]) {
472 /* case /../ -> need to backup wk2 */
473 TRACE("found '/../'\n");
474 *(wk2-1) = '\0'; /* set end of string */
475 mp = strrchrW(root, slash);
476 if (mp && (mp >= root)) {
477 /* found valid backup point */
478 wk2 = mp + 1;
479 if(wk1[2] != '/' && wk1[2] != '\\')
480 wk1 += 2;
481 else
482 wk1 += 3;
483 }
484 else {
485 /* did not find point, restore '/' */
486 *(wk2-1) = slash;
487 }
488 }
489 }
490 }
491 }
492 *wk2 = '\0';
493 break;
494 default:
495 FIXME("how did we get here - state=%d\n", state);
496 HeapFree(GetProcessHeap(), 0, lpszUrlCpy);
497 return E_INVALIDARG;
498 }
499 *wk2 = '\0';
500 TRACE("Simplified, orig <%s>, simple <%s>\n",
501 debugstr_w(pszUrl), debugstr_w(lpszUrlCpy));
502 }
503 nLen = lstrlenW(lpszUrlCpy);
504 while ((nLen > 0) && ((lpszUrlCpy[nLen-1] <= ' ')))
505 lpszUrlCpy[--nLen]=0;
506
507 if(dwFlags & (URL_UNESCAPE | URL_FILE_USE_PATHURL))
508 UrlUnescapeW(lpszUrlCpy, NULL, &nLen, URL_UNESCAPE_INPLACE);
509
510 if((EscapeFlags = dwFlags & (URL_ESCAPE_UNSAFE |
511 URL_ESCAPE_SPACES_ONLY |
512 URL_ESCAPE_PERCENT |
513 URL_DONT_ESCAPE_EXTRA_INFO |
514 URL_ESCAPE_SEGMENT_ONLY ))) {
515 EscapeFlags &= ~URL_ESCAPE_UNSAFE;
516 hr = UrlEscapeW(lpszUrlCpy, pszCanonicalized, pcchCanonicalized,
517 EscapeFlags);
518 } else { /* No escaping needed, just copy the string */
519 nLen = lstrlenW(lpszUrlCpy);
520 if(nLen < *pcchCanonicalized)
521 memcpy(pszCanonicalized, lpszUrlCpy, (nLen + 1)*sizeof(WCHAR));
522 else {
523 hr = E_POINTER;
524 nLen++;
525 }
526 *pcchCanonicalized = nLen;
527 }
528
529 HeapFree(GetProcessHeap(), 0, lpszUrlCpy);
530
531 if (hr == S_OK)
532 TRACE("result %s\n", debugstr_w(pszCanonicalized));
533
534 return hr;
535 }
536
537 /*************************************************************************
538 * UrlCombineA [SHLWAPI.@]
539 *
540 * Combine two Urls.
541 *
542 * PARAMS
543 * pszBase [I] Base Url
544 * pszRelative [I] Url to combine with pszBase
545 * pszCombined [O] Destination for combined Url
546 * pcchCombined [O] Destination for length of pszCombined
547 * dwFlags [I] URL_ flags from "shlwapi.h"
548 *
549 * RETURNS
550 * Success: S_OK. pszCombined contains the combined Url, pcchCombined
551 * contains its length.
552 * Failure: An HRESULT error code indicating the error.
553 */
554 HRESULT WINAPI UrlCombineA(LPCSTR pszBase, LPCSTR pszRelative,
555 LPSTR pszCombined, LPDWORD pcchCombined,
556 DWORD dwFlags)
557 {
558 LPWSTR base, relative, combined;
559 DWORD ret, len, len2;
560
561 TRACE("(base %s, Relative %s, Combine size %d, flags %08x) using W version\n",
562 debugstr_a(pszBase),debugstr_a(pszRelative),
563 pcchCombined?*pcchCombined:0,dwFlags);
564
565 if(!pszBase || !pszRelative || !pcchCombined)
566 return E_INVALIDARG;
567
568 base = HeapAlloc(GetProcessHeap(), 0,
569 (3*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
570 relative = base + INTERNET_MAX_URL_LENGTH;
571 combined = relative + INTERNET_MAX_URL_LENGTH;
572
573 MultiByteToWideChar(0, 0, pszBase, -1, base, INTERNET_MAX_URL_LENGTH);
574 MultiByteToWideChar(0, 0, pszRelative, -1, relative, INTERNET_MAX_URL_LENGTH);
575 len = *pcchCombined;
576
577 ret = UrlCombineW(base, relative, pszCombined?combined:NULL, &len, dwFlags);
578 if (ret != S_OK) {
579 *pcchCombined = len;
580 HeapFree(GetProcessHeap(), 0, base);
581 return ret;
582 }
583
584 len2 = WideCharToMultiByte(0, 0, combined, len, 0, 0, 0, 0);
585 if (len2 > *pcchCombined) {
586 *pcchCombined = len2;
587 HeapFree(GetProcessHeap(), 0, base);
588 return E_POINTER;
589 }
590 WideCharToMultiByte(0, 0, combined, len+1, pszCombined, (*pcchCombined)+1,
591 0, 0);
592 *pcchCombined = len2;
593 HeapFree(GetProcessHeap(), 0, base);
594 return S_OK;
595 }
596
597 /*************************************************************************
598 * UrlCombineW [SHLWAPI.@]
599 *
600 * See UrlCombineA.
601 */
602 HRESULT WINAPI UrlCombineW(LPCWSTR pszBase, LPCWSTR pszRelative,
603 LPWSTR pszCombined, LPDWORD pcchCombined,
604 DWORD dwFlags)
605 {
606 PARSEDURLW base, relative;
607 DWORD myflags, sizeloc = 0;
608 DWORD len, res1, res2, process_case = 0;
609 LPWSTR work, preliminary, mbase, mrelative;
610 static const WCHAR myfilestr[] = {'f','i','l','e',':','/','/','/','\0'};
611 static const WCHAR single_slash[] = {'/','\0'};
612 HRESULT ret;
613
614 TRACE("(base %s, Relative %s, Combine size %d, flags %08x)\n",
615 debugstr_w(pszBase),debugstr_w(pszRelative),
616 pcchCombined?*pcchCombined:0,dwFlags);
617
618 if(!pszBase || !pszRelative || !pcchCombined)
619 return E_INVALIDARG;
620
621 base.cbSize = sizeof(base);
622 relative.cbSize = sizeof(relative);
623
624 /* Get space for duplicates of the input and the output */
625 preliminary = HeapAlloc(GetProcessHeap(), 0, (3*INTERNET_MAX_URL_LENGTH) *
626 sizeof(WCHAR));
627 mbase = preliminary + INTERNET_MAX_URL_LENGTH;
628 mrelative = mbase + INTERNET_MAX_URL_LENGTH;
629 *preliminary = '\0';
630
631 /* Canonicalize the base input prior to looking for the scheme */
632 myflags = dwFlags & (URL_DONT_SIMPLIFY | URL_UNESCAPE);
633 len = INTERNET_MAX_URL_LENGTH;
634 ret = UrlCanonicalizeW(pszBase, mbase, &len, myflags);
635
636 /* Canonicalize the relative input prior to looking for the scheme */
637 len = INTERNET_MAX_URL_LENGTH;
638 ret = UrlCanonicalizeW(pszRelative, mrelative, &len, myflags);
639
640 /* See if the base has a scheme */
641 res1 = ParseURLW(mbase, &base);
642 if (res1) {
643 /* if pszBase has no scheme, then return pszRelative */
644 TRACE("no scheme detected in Base\n");
645 process_case = 1;
646 }
647 else do {
648 /* mk is a special case */
649 if(base.nScheme == URL_SCHEME_MK) {
650 static const WCHAR wsz[] = {':',':',0};
651
652 WCHAR *ptr = strstrW(base.pszSuffix, wsz);
653 if(ptr) {
654 int delta;
655
656 ptr += 2;
657 delta = ptr-base.pszSuffix;
658 base.cchProtocol += delta;
659 base.pszSuffix += delta;
660 base.cchSuffix -= delta;
661 }
662 }else {
663 /* get size of location field (if it exists) */
664 work = (LPWSTR)base.pszSuffix;
665 sizeloc = 0;
666 if (*work++ == '/') {
667 if (*work++ == '/') {
668 /* At this point have start of location and
669 * it ends at next '/' or end of string.
670 */
671 while(*work && (*work != '/')) work++;
672 sizeloc = (DWORD)(work - base.pszSuffix);
673 }
674 }
675 }
676
677 /* Change .sizep2 to not have the last leaf in it,
678 * Note: we need to start after the location (if it exists)
679 */
680 work = strrchrW((base.pszSuffix+sizeloc), '/');
681 if (work) {
682 len = (DWORD)(work - base.pszSuffix + 1);
683 base.cchSuffix = len;
684 }
685
686 /*
687 * At this point:
688 * .pszSuffix points to location (starting with '//')
689 * .cchSuffix length of location (above) and rest less the last
690 * leaf (if any)
691 * sizeloc length of location (above) up to but not including
692 * the last '/'
693 */
694
695 res2 = ParseURLW(mrelative, &relative);
696 if (res2) {
697 /* no scheme in pszRelative */
698 TRACE("no scheme detected in Relative\n");
699 relative.pszSuffix = mrelative; /* case 3,4,5 depends on this */
700 relative.cchSuffix = strlenW(mrelative);
701 if (*pszRelative == ':') {
702 /* case that is either left alone or uses pszBase */
703 if (dwFlags & URL_PLUGGABLE_PROTOCOL) {
704 process_case = 5;
705 break;
706 }
707 process_case = 1;
708 break;
709 }
710 if (isalnum(*mrelative) && (*(mrelative + 1) == ':')) {
711 /* case that becomes "file:///" */
712 strcpyW(preliminary, myfilestr);
713 process_case = 1;
714 break;
715 }
716 if ((*mrelative == '/') && (*(mrelative+1) == '/')) {
717 /* pszRelative has location and rest */
718 process_case = 3;
719 break;
720 }
721 if (*mrelative == '/') {
722 /* case where pszRelative is root to location */
723 process_case = 4;
724 break;
725 }
726 process_case = (*base.pszSuffix == '/' || base.nScheme == URL_SCHEME_MK) ? 5 : 3;
727 break;
728 }
729
730 /* handle cases where pszRelative has scheme */
731 if ((base.cchProtocol == relative.cchProtocol) &&
732 (strncmpW(base.pszProtocol, relative.pszProtocol, base.cchProtocol) == 0)) {
733
734 /* since the schemes are the same */
735 if ((*relative.pszSuffix == '/') && (*(relative.pszSuffix+1) == '/')) {
736 /* case where pszRelative replaces location and following */
737 process_case = 3;
738 break;
739 }
740 if (*relative.pszSuffix == '/') {
741 /* case where pszRelative is root to location */
742 process_case = 4;
743 break;
744 }
745 /* replace either just location if base's location starts with a
746 * slash or otherwise everything */
747 process_case = (*base.pszSuffix == '/') ? 5 : 1;
748 break;
749 }
750 if ((*relative.pszSuffix == '/') && (*(relative.pszSuffix+1) == '/')) {
751 /* case where pszRelative replaces scheme, location,
752 * and following and handles PLUGGABLE
753 */
754 process_case = 2;
755 break;
756 }
757 process_case = 1;
758 break;
759 } while(FALSE); /* a little trick to allow easy exit from nested if's */
760
761 ret = S_OK;
762 switch (process_case) {
763
764 case 1: /*
765 * Return pszRelative appended to what ever is in pszCombined,
766 * (which may the string "file:///"
767 */
768 strcatW(preliminary, mrelative);
769 break;
770
771 case 2: /*
772 * Same as case 1, but if URL_PLUGGABLE_PROTOCOL was specified
773 * and pszRelative starts with "//", then append a "/"
774 */
775 strcpyW(preliminary, mrelative);
776 if (!(dwFlags & URL_PLUGGABLE_PROTOCOL) &&
777 URL_JustLocation(relative.pszSuffix))
778 strcatW(preliminary, single_slash);
779 break;
780
781 case 3: /*
782 * Return the pszBase scheme with pszRelative. Basically
783 * keeps the scheme and replaces the domain and following.
784 */
785 memcpy(preliminary, base.pszProtocol, (base.cchProtocol + 1)*sizeof(WCHAR));
786 work = preliminary + base.cchProtocol + 1;
787 strcpyW(work, relative.pszSuffix);
788 break;
789
790 case 4: /*
791 * Return the pszBase scheme and location but everything
792 * after the location is pszRelative. (Replace document
793 * from root on.)
794 */
795 memcpy(preliminary, base.pszProtocol, (base.cchProtocol+1+sizeloc)*sizeof(WCHAR));
796 work = preliminary + base.cchProtocol + 1 + sizeloc;
797 if (dwFlags & URL_PLUGGABLE_PROTOCOL)
798 *(work++) = '/';
799 strcpyW(work, relative.pszSuffix);
800 break;
801
802 case 5: /*
803 * Return the pszBase without its document (if any) and
804 * append pszRelative after its scheme.
805 */
806 memcpy(preliminary, base.pszProtocol,
807 (base.cchProtocol+1+base.cchSuffix)*sizeof(WCHAR));
808 work = preliminary + base.cchProtocol+1+base.cchSuffix - 1;
809 if (*work++ != '/')
810 *(work++) = '/';
811 strcpyW(work, relative.pszSuffix);
812 break;
813
814 default:
815 FIXME("How did we get here????? process_case=%d\n", process_case);
816 ret = E_INVALIDARG;
817 }
818
819 if (ret == S_OK) {
820 /* Reuse mrelative as temp storage as its already allocated and not needed anymore */
821 ret = UrlCanonicalizeW(preliminary, mrelative, pcchCombined, (dwFlags & ~URL_FILE_USE_PATHURL));
822 if(SUCCEEDED(ret) && pszCombined) {
823 lstrcpyW(pszCombined, mrelative);
824 }
825 TRACE("return-%d len=%d, %s\n",
826 process_case, *pcchCombined, debugstr_w(pszCombined));
827 }
828 HeapFree(GetProcessHeap(), 0, preliminary);
829 return ret;
830 }
831
832 /*************************************************************************
833 * UrlEscapeA [SHLWAPI.@]
834 */
835
836 HRESULT WINAPI UrlEscapeA(
837 LPCSTR pszUrl,
838 LPSTR pszEscaped,
839 LPDWORD pcchEscaped,
840 DWORD dwFlags)
841 {
842 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
843 WCHAR *escapedW = bufW;
844 UNICODE_STRING urlW;
845 HRESULT ret;
846 DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
847
848 if (!pszEscaped || !pcchEscaped || !*pcchEscaped)
849 return E_INVALIDARG;
850
851 if(!RtlCreateUnicodeStringFromAsciiz(&urlW, pszUrl))
852 return E_INVALIDARG;
853 if((ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags)) == E_POINTER) {
854 escapedW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
855 ret = UrlEscapeW(urlW.Buffer, escapedW, &lenW, dwFlags);
856 }
857 if(ret == S_OK) {
858 RtlUnicodeToMultiByteSize(&lenA, escapedW, lenW * sizeof(WCHAR));
859 if(*pcchEscaped > lenA) {
860 RtlUnicodeToMultiByteN(pszEscaped, *pcchEscaped - 1, &lenA, escapedW, lenW * sizeof(WCHAR));
861 pszEscaped[lenA] = 0;
862 *pcchEscaped = lenA;
863 } else {
864 *pcchEscaped = lenA + 1;
865 ret = E_POINTER;
866 }
867 }
868 if(escapedW != bufW) HeapFree(GetProcessHeap(), 0, escapedW);
869 RtlFreeUnicodeString(&urlW);
870 return ret;
871 }
872
873 #define WINE_URL_BASH_AS_SLASH 0x01
874 #define WINE_URL_COLLAPSE_SLASHES 0x02
875 #define WINE_URL_ESCAPE_SLASH 0x04
876 #define WINE_URL_ESCAPE_HASH 0x08
877 #define WINE_URL_ESCAPE_QUESTION 0x10
878 #define WINE_URL_STOP_ON_HASH 0x20
879 #define WINE_URL_STOP_ON_QUESTION 0x40
880
881 static inline BOOL URL_NeedEscapeW(WCHAR ch, DWORD dwFlags, DWORD int_flags)
882 {
883
884 if (isalnumW(ch))
885 return FALSE;
886
887 if(dwFlags & URL_ESCAPE_SPACES_ONLY) {
888 if(ch == ' ')
889 return TRUE;
890 else
891 return FALSE;
892 }
893
894 if ((dwFlags & URL_ESCAPE_PERCENT) && (ch == '%'))
895 return TRUE;
896
897 if (ch <= 31 || ch >= 127)
898 return TRUE;
899
900 else {
901 switch (ch) {
902 case ' ':
903 case '<':
904 case '>':
905 case '\"':
906 case '{':
907 case '}':
908 case '|':
909 case '\\':
910 case '^':
911 case ']':
912 case '[':
913 case '`':
914 case '&':
915 return TRUE;
916
917 case '/':
918 if (int_flags & WINE_URL_ESCAPE_SLASH) return TRUE;
919 return FALSE;
920
921 case '?':
922 if (int_flags & WINE_URL_ESCAPE_QUESTION) return TRUE;
923 return FALSE;
924
925 case '#':
926 if (int_flags & WINE_URL_ESCAPE_HASH) return TRUE;
927 return FALSE;
928
929 default:
930 return FALSE;
931 }
932 }
933 }
934
935
936 /*************************************************************************
937 * UrlEscapeW [SHLWAPI.@]
938 *
939 * Converts unsafe characters in a Url into escape sequences.
940 *
941 * PARAMS
942 * pszUrl [I] Url to modify
943 * pszEscaped [O] Destination for modified Url
944 * pcchEscaped [I/O] Length of pszUrl, destination for length of pszEscaped
945 * dwFlags [I] URL_ flags from "shlwapi.h"
946 *
947 * RETURNS
948 * Success: S_OK. pszEscaped contains the escaped Url, pcchEscaped
949 * contains its length.
950 * Failure: E_POINTER, if pszEscaped is not large enough. In this case
951 * pcchEscaped is set to the required length.
952 *
953 * Converts unsafe characters into their escape sequences.
954 *
955 * NOTES
956 * - By default this function stops converting at the first '?' or
957 * '#' character.
958 * - If dwFlags contains URL_ESCAPE_SPACES_ONLY then only spaces are
959 * converted, but the conversion continues past a '?' or '#'.
960 * - Note that this function did not work well (or at all) in shlwapi version 4.
961 *
962 * BUGS
963 * Only the following flags are implemented:
964 *| URL_ESCAPE_SPACES_ONLY
965 *| URL_DONT_ESCAPE_EXTRA_INFO
966 *| URL_ESCAPE_SEGMENT_ONLY
967 *| URL_ESCAPE_PERCENT
968 */
969 HRESULT WINAPI UrlEscapeW(
970 LPCWSTR pszUrl,
971 LPWSTR pszEscaped,
972 LPDWORD pcchEscaped,
973 DWORD dwFlags)
974 {
975 LPCWSTR src;
976 DWORD needed = 0, ret;
977 BOOL stop_escaping = FALSE;
978 WCHAR next[5], *dst = pszEscaped;
979 INT len;
980 PARSEDURLW parsed_url;
981 DWORD int_flags;
982 DWORD slashes = 0;
983 static const WCHAR localhost[] = {'l','o','c','a','l','h','o','s','t',0};
984
985 TRACE("(%s %p %p 0x%08x)\n", debugstr_w(pszUrl), pszEscaped,
986 pcchEscaped, dwFlags);
987
988 if(!pszUrl || !pcchEscaped)
989 return E_INVALIDARG;
990
991 if(dwFlags & ~(URL_ESCAPE_SPACES_ONLY |
992 URL_ESCAPE_SEGMENT_ONLY |
993 URL_DONT_ESCAPE_EXTRA_INFO |
994 URL_ESCAPE_PERCENT))
995 FIXME("Unimplemented flags: %08x\n", dwFlags);
996
997 /* fix up flags */
998 if (dwFlags & URL_ESCAPE_SPACES_ONLY)
999 /* if SPACES_ONLY specified, reset the other controls */
1000 dwFlags &= ~(URL_DONT_ESCAPE_EXTRA_INFO |
1001 URL_ESCAPE_PERCENT |
1002 URL_ESCAPE_SEGMENT_ONLY);
1003
1004 else
1005 /* if SPACES_ONLY *not* specified the assume DONT_ESCAPE_EXTRA_INFO */
1006 dwFlags |= URL_DONT_ESCAPE_EXTRA_INFO;
1007
1008
1009 int_flags = 0;
1010 if(dwFlags & URL_ESCAPE_SEGMENT_ONLY) {
1011 int_flags = WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH | WINE_URL_ESCAPE_SLASH;
1012 } else {
1013 parsed_url.cbSize = sizeof(parsed_url);
1014 if(ParseURLW(pszUrl, &parsed_url) != S_OK)
1015 parsed_url.nScheme = URL_SCHEME_INVALID;
1016
1017 TRACE("scheme = %d (%s)\n", parsed_url.nScheme, debugstr_wn(parsed_url.pszProtocol, parsed_url.cchProtocol));
1018
1019 if(dwFlags & URL_DONT_ESCAPE_EXTRA_INFO)
1020 int_flags = WINE_URL_STOP_ON_HASH | WINE_URL_STOP_ON_QUESTION;
1021
1022 switch(parsed_url.nScheme) {
1023 case URL_SCHEME_FILE:
1024 int_flags |= WINE_URL_BASH_AS_SLASH | WINE_URL_COLLAPSE_SLASHES | WINE_URL_ESCAPE_HASH;
1025 int_flags &= ~WINE_URL_STOP_ON_HASH;
1026 break;
1027
1028 case URL_SCHEME_HTTP:
1029 case URL_SCHEME_HTTPS:
1030 int_flags |= WINE_URL_BASH_AS_SLASH;
1031 if(parsed_url.pszSuffix[0] != '/' && parsed_url.pszSuffix[0] != '\\')
1032 int_flags |= WINE_URL_ESCAPE_SLASH;
1033 break;
1034
1035 case URL_SCHEME_MAILTO:
1036 int_flags |= WINE_URL_ESCAPE_SLASH | WINE_URL_ESCAPE_QUESTION | WINE_URL_ESCAPE_HASH;
1037 int_flags &= ~(WINE_URL_STOP_ON_QUESTION | WINE_URL_STOP_ON_HASH);
1038 break;
1039
1040 case URL_SCHEME_INVALID:
1041 break;
1042
1043 case URL_SCHEME_FTP:
1044 default:
1045 if(parsed_url.pszSuffix[0] != '/')
1046 int_flags |= WINE_URL_ESCAPE_SLASH;
1047 break;
1048 }
1049 }
1050
1051 for(src = pszUrl; *src; ) {
1052 WCHAR cur = *src;
1053 len = 0;
1054
1055 if((int_flags & WINE_URL_COLLAPSE_SLASHES) && src == pszUrl + parsed_url.cchProtocol + 1) {
1056 int localhost_len = sizeof(localhost)/sizeof(WCHAR) - 1;
1057 while(cur == '/' || cur == '\\') {
1058 slashes++;
1059 cur = *++src;
1060 }
1061 if(slashes == 2 && !strncmpiW(src, localhost, localhost_len)) { /* file://localhost/ -> file:/// */
1062 if(*(src + localhost_len) == '/' || *(src + localhost_len) == '\\')
1063 src += localhost_len + 1;
1064 slashes = 3;
1065 }
1066
1067 switch(slashes) {
1068 case 1:
1069 case 3:
1070 next[0] = next[1] = next[2] = '/';
1071 len = 3;
1072 break;
1073 case 0:
1074 len = 0;
1075 break;
1076 default:
1077 next[0] = next[1] = '/';
1078 len = 2;
1079 break;
1080 }
1081 }
1082 if(len == 0) {
1083
1084 if(cur == '#' && (int_flags & WINE_URL_STOP_ON_HASH))
1085 stop_escaping = TRUE;
1086
1087 if(cur == '?' && (int_flags & WINE_URL_STOP_ON_QUESTION))
1088 stop_escaping = TRUE;
1089
1090 if(cur == '\\' && (int_flags & WINE_URL_BASH_AS_SLASH) && !stop_escaping) cur = '/';
1091
1092 if(URL_NeedEscapeW(cur, dwFlags, int_flags) && stop_escaping == FALSE) {
1093 next[0] = '%';
1094 next[1] = hexDigits[(cur >> 4) & 0xf];
1095 next[2] = hexDigits[cur & 0xf];
1096 len = 3;
1097 } else {
1098 next[0] = cur;
1099 len = 1;
1100 }
1101 src++;
1102 }
1103
1104 if(needed + len <= *pcchEscaped) {
1105 memcpy(dst, next, len*sizeof(WCHAR));
1106 dst += len;
1107 }
1108 needed += len;
1109 }
1110
1111 if(needed < *pcchEscaped) {
1112 *dst = '\0';
1113 ret = S_OK;
1114 } else {
1115 needed++; /* add one for the '\0' */
1116 ret = E_POINTER;
1117 }
1118 *pcchEscaped = needed;
1119 return ret;
1120 }
1121
1122
1123 /*************************************************************************
1124 * UrlUnescapeA [SHLWAPI.@]
1125 *
1126 * Converts Url escape sequences back to ordinary characters.
1127 *
1128 * PARAMS
1129 * pszUrl [I/O] Url to convert
1130 * pszUnescaped [O] Destination for converted Url
1131 * pcchUnescaped [I/O] Size of output string
1132 * dwFlags [I] URL_ESCAPE_ Flags from "shlwapi.h"
1133 *
1134 * RETURNS
1135 * Success: S_OK. The converted value is in pszUnescaped, or in pszUrl if
1136 * dwFlags includes URL_ESCAPE_INPLACE.
1137 * Failure: E_POINTER if the converted Url is bigger than pcchUnescaped. In
1138 * this case pcchUnescaped is set to the size required.
1139 * NOTES
1140 * If dwFlags includes URL_DONT_ESCAPE_EXTRA_INFO, the conversion stops at
1141 * the first occurrence of either a '?' or '#' character.
1142 */
1143 HRESULT WINAPI UrlUnescapeA(
1144 LPSTR pszUrl,
1145 LPSTR pszUnescaped,
1146 LPDWORD pcchUnescaped,
1147 DWORD dwFlags)
1148 {
1149 char *dst, next;
1150 LPCSTR src;
1151 HRESULT ret;
1152 DWORD needed;
1153 BOOL stop_unescaping = FALSE;
1154
1155 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_a(pszUrl), pszUnescaped,
1156 pcchUnescaped, dwFlags);
1157
1158 if (!pszUrl) return E_INVALIDARG;
1159
1160 if(dwFlags & URL_UNESCAPE_INPLACE)
1161 dst = pszUrl;
1162 else
1163 {
1164 if (!pszUnescaped || !pcchUnescaped) return E_INVALIDARG;
1165 dst = pszUnescaped;
1166 }
1167
1168 for(src = pszUrl, needed = 0; *src; src++, needed++) {
1169 if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1170 (*src == '#' || *src == '?')) {
1171 stop_unescaping = TRUE;
1172 next = *src;
1173 } else if(*src == '%' && isxdigit(*(src + 1)) && isxdigit(*(src + 2))
1174 && stop_unescaping == FALSE) {
1175 INT ih;
1176 char buf[3];
1177 memcpy(buf, src + 1, 2);
1178 buf[2] = '\0';
1179 ih = strtol(buf, NULL, 16);
1180 next = (CHAR) ih;
1181 src += 2; /* Advance to end of escape */
1182 } else
1183 next = *src;
1184
1185 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1186 *dst++ = next;
1187 }
1188
1189 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1190 *dst = '\0';
1191 ret = S_OK;
1192 } else {
1193 needed++; /* add one for the '\0' */
1194 ret = E_POINTER;
1195 }
1196 if(!(dwFlags & URL_UNESCAPE_INPLACE))
1197 *pcchUnescaped = needed;
1198
1199 if (ret == S_OK) {
1200 TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1201 debugstr_a(pszUrl) : debugstr_a(pszUnescaped));
1202 }
1203
1204 return ret;
1205 }
1206
1207 /*************************************************************************
1208 * UrlUnescapeW [SHLWAPI.@]
1209 *
1210 * See UrlUnescapeA.
1211 */
1212 HRESULT WINAPI UrlUnescapeW(
1213 LPWSTR pszUrl,
1214 LPWSTR pszUnescaped,
1215 LPDWORD pcchUnescaped,
1216 DWORD dwFlags)
1217 {
1218 WCHAR *dst, next;
1219 LPCWSTR src;
1220 HRESULT ret;
1221 DWORD needed;
1222 BOOL stop_unescaping = FALSE;
1223
1224 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszUrl), pszUnescaped,
1225 pcchUnescaped, dwFlags);
1226
1227 if(!pszUrl) return E_INVALIDARG;
1228
1229 if(dwFlags & URL_UNESCAPE_INPLACE)
1230 dst = pszUrl;
1231 else
1232 {
1233 if (!pszUnescaped || !pcchUnescaped) return E_INVALIDARG;
1234 dst = pszUnescaped;
1235 }
1236
1237 for(src = pszUrl, needed = 0; *src; src++, needed++) {
1238 if(dwFlags & URL_DONT_UNESCAPE_EXTRA_INFO &&
1239 (*src == '#' || *src == '?')) {
1240 stop_unescaping = TRUE;
1241 next = *src;
1242 } else if(*src == '%' && isxdigitW(*(src + 1)) && isxdigitW(*(src + 2))
1243 && stop_unescaping == FALSE) {
1244 INT ih;
1245 WCHAR buf[5] = {'0','x',0};
1246 memcpy(buf + 2, src + 1, 2*sizeof(WCHAR));
1247 buf[4] = 0;
1248 StrToIntExW(buf, STIF_SUPPORT_HEX, &ih);
1249 next = (WCHAR) ih;
1250 src += 2; /* Advance to end of escape */
1251 } else
1252 next = *src;
1253
1254 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped)
1255 *dst++ = next;
1256 }
1257
1258 if(dwFlags & URL_UNESCAPE_INPLACE || needed < *pcchUnescaped) {
1259 *dst = '\0';
1260 ret = S_OK;
1261 } else {
1262 needed++; /* add one for the '\0' */
1263 ret = E_POINTER;
1264 }
1265 if(!(dwFlags & URL_UNESCAPE_INPLACE))
1266 *pcchUnescaped = needed;
1267
1268 if (ret == S_OK) {
1269 TRACE("result %s\n", (dwFlags & URL_UNESCAPE_INPLACE) ?
1270 debugstr_w(pszUrl) : debugstr_w(pszUnescaped));
1271 }
1272
1273 return ret;
1274 }
1275
1276 /*************************************************************************
1277 * UrlGetLocationA [SHLWAPI.@]
1278 *
1279 * Get the location from a Url.
1280 *
1281 * PARAMS
1282 * pszUrl [I] Url to get the location from
1283 *
1284 * RETURNS
1285 * A pointer to the start of the location in pszUrl, or NULL if there is
1286 * no location.
1287 *
1288 * NOTES
1289 * - MSDN erroneously states that "The location is the segment of the Url
1290 * starting with a '?' or '#' character". Neither V4 nor V5 of shlwapi.dll
1291 * stop at '?' and always return a NULL in this case.
1292 * - MSDN also erroneously states that "If a file URL has a query string,
1293 * the returned string is the query string". In all tested cases, if the
1294 * Url starts with "fi" then a NULL is returned. V5 gives the following results:
1295 *| Result Url
1296 *| ------ ---
1297 *| NULL file://aa/b/cd#hohoh
1298 *| #hohoh http://aa/b/cd#hohoh
1299 *| NULL fi://aa/b/cd#hohoh
1300 *| #hohoh ff://aa/b/cd#hohoh
1301 */
1302 LPCSTR WINAPI UrlGetLocationA(
1303 LPCSTR pszUrl)
1304 {
1305 PARSEDURLA base;
1306 DWORD res1;
1307
1308 base.cbSize = sizeof(base);
1309 res1 = ParseURLA(pszUrl, &base);
1310 if (res1) return NULL; /* invalid scheme */
1311
1312 /* if scheme is file: then never return pointer */
1313 if (strncmp(base.pszProtocol, "file", min(4,base.cchProtocol)) == 0) return NULL;
1314
1315 /* Look for '#' and return its addr */
1316 return strchr(base.pszSuffix, '#');
1317 }
1318
1319 /*************************************************************************
1320 * UrlGetLocationW [SHLWAPI.@]
1321 *
1322 * See UrlGetLocationA.
1323 */
1324 LPCWSTR WINAPI UrlGetLocationW(
1325 LPCWSTR pszUrl)
1326 {
1327 PARSEDURLW base;
1328 DWORD res1;
1329
1330 base.cbSize = sizeof(base);
1331 res1 = ParseURLW(pszUrl, &base);
1332 if (res1) return NULL; /* invalid scheme */
1333
1334 /* if scheme is file: then never return pointer */
1335 if (strncmpW(base.pszProtocol, fileW, min(4,base.cchProtocol)) == 0) return NULL;
1336
1337 /* Look for '#' and return its addr */
1338 return strchrW(base.pszSuffix, '#');
1339 }
1340
1341 /*************************************************************************
1342 * UrlCompareA [SHLWAPI.@]
1343 *
1344 * Compare two Urls.
1345 *
1346 * PARAMS
1347 * pszUrl1 [I] First Url to compare
1348 * pszUrl2 [I] Url to compare to pszUrl1
1349 * fIgnoreSlash [I] TRUE = compare only up to a final slash
1350 *
1351 * RETURNS
1352 * less than zero, zero, or greater than zero indicating pszUrl2 is greater
1353 * than, equal to, or less than pszUrl1 respectively.
1354 */
1355 INT WINAPI UrlCompareA(
1356 LPCSTR pszUrl1,
1357 LPCSTR pszUrl2,
1358 BOOL fIgnoreSlash)
1359 {
1360 INT ret, len, len1, len2;
1361
1362 if (!fIgnoreSlash)
1363 return strcmp(pszUrl1, pszUrl2);
1364 len1 = strlen(pszUrl1);
1365 if (pszUrl1[len1-1] == '/') len1--;
1366 len2 = strlen(pszUrl2);
1367 if (pszUrl2[len2-1] == '/') len2--;
1368 if (len1 == len2)
1369 return strncmp(pszUrl1, pszUrl2, len1);
1370 len = min(len1, len2);
1371 ret = strncmp(pszUrl1, pszUrl2, len);
1372 if (ret) return ret;
1373 if (len1 > len2) return 1;
1374 return -1;
1375 }
1376
1377 /*************************************************************************
1378 * UrlCompareW [SHLWAPI.@]
1379 *
1380 * See UrlCompareA.
1381 */
1382 INT WINAPI UrlCompareW(
1383 LPCWSTR pszUrl1,
1384 LPCWSTR pszUrl2,
1385 BOOL fIgnoreSlash)
1386 {
1387 INT ret;
1388 size_t len, len1, len2;
1389
1390 if (!fIgnoreSlash)
1391 return strcmpW(pszUrl1, pszUrl2);
1392 len1 = strlenW(pszUrl1);
1393 if (pszUrl1[len1-1] == '/') len1--;
1394 len2 = strlenW(pszUrl2);
1395 if (pszUrl2[len2-1] == '/') len2--;
1396 if (len1 == len2)
1397 return strncmpW(pszUrl1, pszUrl2, len1);
1398 len = min(len1, len2);
1399 ret = strncmpW(pszUrl1, pszUrl2, len);
1400 if (ret) return ret;
1401 if (len1 > len2) return 1;
1402 return -1;
1403 }
1404
1405 /*************************************************************************
1406 * HashData [SHLWAPI.@]
1407 *
1408 * Hash an input block into a variable sized digest.
1409 *
1410 * PARAMS
1411 * lpSrc [I] Input block
1412 * nSrcLen [I] Length of lpSrc
1413 * lpDest [I] Output for hash digest
1414 * nDestLen [I] Length of lpDest
1415 *
1416 * RETURNS
1417 * Success: TRUE. lpDest is filled with the computed hash value.
1418 * Failure: FALSE, if any argument is invalid.
1419 */
1420 HRESULT WINAPI HashData(const unsigned char *lpSrc, DWORD nSrcLen,
1421 unsigned char *lpDest, DWORD nDestLen)
1422 {
1423 INT srcCount = nSrcLen - 1, destCount = nDestLen - 1;
1424
1425 if (IsBadReadPtr(lpSrc, nSrcLen) ||
1426 IsBadWritePtr(lpDest, nDestLen))
1427 return E_INVALIDARG;
1428
1429 while (destCount >= 0)
1430 {
1431 lpDest[destCount] = (destCount & 0xff);
1432 destCount--;
1433 }
1434
1435 while (srcCount >= 0)
1436 {
1437 destCount = nDestLen - 1;
1438 while (destCount >= 0)
1439 {
1440 lpDest[destCount] = HashDataLookup[lpSrc[srcCount] ^ lpDest[destCount]];
1441 destCount--;
1442 }
1443 srcCount--;
1444 }
1445 return S_OK;
1446 }
1447
1448 /*************************************************************************
1449 * UrlHashA [SHLWAPI.@]
1450 *
1451 * Produce a Hash from a Url.
1452 *
1453 * PARAMS
1454 * pszUrl [I] Url to hash
1455 * lpDest [O] Destinationh for hash
1456 * nDestLen [I] Length of lpDest
1457 *
1458 * RETURNS
1459 * Success: S_OK. lpDest is filled with the computed hash value.
1460 * Failure: E_INVALIDARG, if any argument is invalid.
1461 */
1462 HRESULT WINAPI UrlHashA(LPCSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1463 {
1464 if (IsBadStringPtrA(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1465 return E_INVALIDARG;
1466
1467 HashData((const BYTE*)pszUrl, (int)strlen(pszUrl), lpDest, nDestLen);
1468 return S_OK;
1469 }
1470
1471 /*************************************************************************
1472 * UrlHashW [SHLWAPI.@]
1473 *
1474 * See UrlHashA.
1475 */
1476 HRESULT WINAPI UrlHashW(LPCWSTR pszUrl, unsigned char *lpDest, DWORD nDestLen)
1477 {
1478 char szUrl[MAX_PATH];
1479
1480 TRACE("(%s,%p,%d)\n",debugstr_w(pszUrl), lpDest, nDestLen);
1481
1482 if (IsBadStringPtrW(pszUrl, -1) || IsBadWritePtr(lpDest, nDestLen))
1483 return E_INVALIDARG;
1484
1485 /* Win32 hashes the data as an ASCII string, presumably so that both A+W
1486 * return the same digests for the same URL.
1487 */
1488 WideCharToMultiByte(0, 0, pszUrl, -1, szUrl, MAX_PATH, 0, 0);
1489 HashData((const BYTE*)szUrl, (int)strlen(szUrl), lpDest, nDestLen);
1490 return S_OK;
1491 }
1492
1493 /*************************************************************************
1494 * UrlApplySchemeA [SHLWAPI.@]
1495 *
1496 * Apply a scheme to a Url.
1497 *
1498 * PARAMS
1499 * pszIn [I] Url to apply scheme to
1500 * pszOut [O] Destination for modified Url
1501 * pcchOut [I/O] Length of pszOut/destination for length of pszOut
1502 * dwFlags [I] URL_ flags from "shlwapi.h"
1503 *
1504 * RETURNS
1505 * Success: S_OK: pszOut contains the modified Url, pcchOut contains its length.
1506 * Failure: An HRESULT error code describing the error.
1507 */
1508 HRESULT WINAPI UrlApplySchemeA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1509 {
1510 LPWSTR in, out;
1511 HRESULT ret;
1512 DWORD len;
1513
1514 TRACE("(%s, %p, %p:out size %d, 0x%08x)\n", debugstr_a(pszIn),
1515 pszOut, pcchOut, pcchOut ? *pcchOut : 0, dwFlags);
1516
1517 if (!pszIn || !pszOut || !pcchOut) return E_INVALIDARG;
1518
1519 in = HeapAlloc(GetProcessHeap(), 0,
1520 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
1521 out = in + INTERNET_MAX_URL_LENGTH;
1522
1523 MultiByteToWideChar(CP_ACP, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
1524 len = INTERNET_MAX_URL_LENGTH;
1525
1526 ret = UrlApplySchemeW(in, out, &len, dwFlags);
1527 if (ret != S_OK) {
1528 HeapFree(GetProcessHeap(), 0, in);
1529 return ret;
1530 }
1531
1532 len = WideCharToMultiByte(CP_ACP, 0, out, -1, NULL, 0, NULL, NULL);
1533 if (len > *pcchOut) {
1534 ret = E_POINTER;
1535 goto cleanup;
1536 }
1537
1538 WideCharToMultiByte(CP_ACP, 0, out, -1, pszOut, *pcchOut, NULL, NULL);
1539 len--;
1540
1541 cleanup:
1542 *pcchOut = len;
1543 HeapFree(GetProcessHeap(), 0, in);
1544 return ret;
1545 }
1546
1547 static HRESULT URL_GuessScheme(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1548 {
1549 HKEY newkey;
1550 BOOL j;
1551 INT index;
1552 DWORD value_len, data_len, dwType, i;
1553 WCHAR reg_path[MAX_PATH];
1554 WCHAR value[MAX_PATH], data[MAX_PATH];
1555 WCHAR Wxx, Wyy;
1556
1557 MultiByteToWideChar(0, 0,
1558 "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes",
1559 -1, reg_path, MAX_PATH);
1560 RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, 1, &newkey);
1561 index = 0;
1562 while(value_len = data_len = MAX_PATH,
1563 RegEnumValueW(newkey, index, value, &value_len,
1564 0, &dwType, (LPVOID)data, &data_len) == 0) {
1565 TRACE("guess %d %s is %s\n",
1566 index, debugstr_w(value), debugstr_w(data));
1567
1568 j = FALSE;
1569 for(i=0; i<value_len; i++) {
1570 Wxx = pszIn[i];
1571 Wyy = value[i];
1572 /* remember that TRUE is not-equal */
1573 j = ChrCmpIW(Wxx, Wyy);
1574 if (j) break;
1575 }
1576 if ((i == value_len) && !j) {
1577 if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1578 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1579 RegCloseKey(newkey);
1580 return E_POINTER;
1581 }
1582 strcpyW(pszOut, data);
1583 strcatW(pszOut, pszIn);
1584 *pcchOut = strlenW(pszOut);
1585 TRACE("matched and set to %s\n", debugstr_w(pszOut));
1586 RegCloseKey(newkey);
1587 return S_OK;
1588 }
1589 index++;
1590 }
1591 RegCloseKey(newkey);
1592 return -1;
1593 }
1594
1595 static HRESULT URL_ApplyDefault(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut)
1596 {
1597 HKEY newkey;
1598 DWORD data_len, dwType;
1599 WCHAR data[MAX_PATH];
1600
1601 static const WCHAR prefix_keyW[] =
1602 {'S','o','f','t','w','a','r','e',
1603 '\\','M','i','c','r','o','s','o','f','t',
1604 '\\','W','i','n','d','o','w','s',
1605 '\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n',
1606 '\\','U','R','L',
1607 '\\','D','e','f','a','u','l','t','P','r','e','f','i','x',0};
1608
1609 /* get and prepend default */
1610 RegOpenKeyExW(HKEY_LOCAL_MACHINE, prefix_keyW, 0, 1, &newkey);
1611 data_len = sizeof(data);
1612 RegQueryValueExW(newkey, NULL, 0, &dwType, (LPBYTE)data, &data_len);
1613 RegCloseKey(newkey);
1614 if (strlenW(data) + strlenW(pszIn) + 1 > *pcchOut) {
1615 *pcchOut = strlenW(data) + strlenW(pszIn) + 1;
1616 return E_POINTER;
1617 }
1618 strcpyW(pszOut, data);
1619 strcatW(pszOut, pszIn);
1620 *pcchOut = strlenW(pszOut);
1621 TRACE("used default %s\n", debugstr_w(pszOut));
1622 return S_OK;
1623 }
1624
1625 /*************************************************************************
1626 * UrlApplySchemeW [SHLWAPI.@]
1627 *
1628 * See UrlApplySchemeA.
1629 */
1630 HRESULT WINAPI UrlApplySchemeW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut, DWORD dwFlags)
1631 {
1632 PARSEDURLW in_scheme;
1633 DWORD res1;
1634 HRESULT ret;
1635
1636 TRACE("(%s, %p, %p:out size %d, 0x%08x)\n", debugstr_w(pszIn),
1637 pszOut, pcchOut, pcchOut ? *pcchOut : 0, dwFlags);
1638
1639 if (!pszIn || !pszOut || !pcchOut) return E_INVALIDARG;
1640
1641 if (dwFlags & URL_APPLY_GUESSFILE) {
1642 FIXME("(%s %p %p(%d) 0x%08x): stub URL_APPLY_GUESSFILE not implemented\n",
1643 debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwFlags);
1644 strcpyW(pszOut, pszIn);
1645 *pcchOut = strlenW(pszOut);
1646 return S_FALSE;
1647 }
1648
1649 in_scheme.cbSize = sizeof(in_scheme);
1650 /* See if the base has a scheme */
1651 res1 = ParseURLW(pszIn, &in_scheme);
1652 if (res1) {
1653 /* no scheme in input, need to see if we need to guess */
1654 if (dwFlags & URL_APPLY_GUESSSCHEME) {
1655 if ((ret = URL_GuessScheme(pszIn, pszOut, pcchOut)) != -1)
1656 return ret;
1657 }
1658 }
1659 else {
1660 /* we have a scheme, see if valid (known scheme) */
1661 if (in_scheme.nScheme) {
1662 /* have valid scheme, so just copy and exit */
1663 if (strlenW(pszIn) + 1 > *pcchOut) {
1664 *pcchOut = strlenW(pszIn) + 1;
1665 return E_POINTER;
1666 }
1667 strcpyW(pszOut, pszIn);
1668 *pcchOut = strlenW(pszOut);
1669 TRACE("valid scheme, returning copy\n");
1670 return S_OK;
1671 }
1672 }
1673
1674 /* If we are here, then either invalid scheme,
1675 * or no scheme and can't/failed guess.
1676 */
1677 if ( ( ((res1 == 0) && (dwFlags & URL_APPLY_FORCEAPPLY)) ||
1678 ((res1 != 0)) ) &&
1679 (dwFlags & URL_APPLY_DEFAULT)) {
1680 /* find and apply default scheme */
1681 return URL_ApplyDefault(pszIn, pszOut, pcchOut);
1682 }
1683
1684 return S_FALSE;
1685 }
1686
1687 /*************************************************************************
1688 * UrlIsA [SHLWAPI.@]
1689 *
1690 * Determine if a Url is of a certain class.
1691 *
1692 * PARAMS
1693 * pszUrl [I] Url to check
1694 * Urlis [I] URLIS_ constant from "shlwapi.h"
1695 *
1696 * RETURNS
1697 * TRUE if pszUrl belongs to the class type in Urlis.
1698 * FALSE Otherwise.
1699 */
1700 BOOL WINAPI UrlIsA(LPCSTR pszUrl, URLIS Urlis)
1701 {
1702 PARSEDURLA base;
1703 DWORD res1;
1704 LPCSTR last;
1705
1706 TRACE("(%s %d)\n", debugstr_a(pszUrl), Urlis);
1707
1708 switch (Urlis) {
1709
1710 case URLIS_OPAQUE:
1711 base.cbSize = sizeof(base);
1712 res1 = ParseURLA(pszUrl, &base);
1713 if (res1) return FALSE; /* invalid scheme */
1714 switch (base.nScheme)
1715 {
1716 case URL_SCHEME_MAILTO:
1717 case URL_SCHEME_SHELL:
1718 case URL_SCHEME_JAVASCRIPT:
1719 case URL_SCHEME_VBSCRIPT:
1720 case URL_SCHEME_ABOUT:
1721 return TRUE;
1722 }
1723 return FALSE;
1724
1725 case URLIS_FILEURL:
1726 return !StrCmpNA("file:", pszUrl, 5);
1727
1728 case URLIS_DIRECTORY:
1729 last = pszUrl + strlen(pszUrl) - 1;
1730 return (last >= pszUrl && (*last == '/' || *last == '\\' ));
1731
1732 case URLIS_URL:
1733 return PathIsURLA(pszUrl);
1734
1735 case URLIS_NOHISTORY:
1736 case URLIS_APPLIABLE:
1737 case URLIS_HASQUERY:
1738 default:
1739 FIXME("(%s %d): stub\n", debugstr_a(pszUrl), Urlis);
1740 }
1741 return FALSE;
1742 }
1743
1744 /*************************************************************************
1745 * UrlIsW [SHLWAPI.@]
1746 *
1747 * See UrlIsA.
1748 */
1749 BOOL WINAPI UrlIsW(LPCWSTR pszUrl, URLIS Urlis)
1750 {
1751 static const WCHAR stemp[] = { 'f','i','l','e',':',0 };
1752 PARSEDURLW base;
1753 DWORD res1;
1754 LPCWSTR last;
1755
1756 TRACE("(%s %d)\n", debugstr_w(pszUrl), Urlis);
1757
1758 switch (Urlis) {
1759
1760 case URLIS_OPAQUE:
1761 base.cbSize = sizeof(base);
1762 res1 = ParseURLW(pszUrl, &base);
1763 if (res1) return FALSE; /* invalid scheme */
1764 switch (base.nScheme)
1765 {
1766 case URL_SCHEME_MAILTO:
1767 case URL_SCHEME_SHELL:
1768 case URL_SCHEME_JAVASCRIPT:
1769 case URL_SCHEME_VBSCRIPT:
1770 case URL_SCHEME_ABOUT:
1771 return TRUE;
1772 }
1773 return FALSE;
1774
1775 case URLIS_FILEURL:
1776 return !strncmpW(stemp, pszUrl, 5);
1777
1778 case URLIS_DIRECTORY:
1779 last = pszUrl + strlenW(pszUrl) - 1;
1780 return (last >= pszUrl && (*last == '/' || *last == '\\'));
1781
1782 case URLIS_URL:
1783 return PathIsURLW(pszUrl);
1784
1785 case URLIS_NOHISTORY:
1786 case URLIS_APPLIABLE:
1787 case URLIS_HASQUERY:
1788 default:
1789 FIXME("(%s %d): stub\n", debugstr_w(pszUrl), Urlis);
1790 }
1791 return FALSE;
1792 }
1793
1794 /*************************************************************************
1795 * UrlIsNoHistoryA [SHLWAPI.@]
1796 *
1797 * Determine if a Url should not be stored in the users history list.
1798 *
1799 * PARAMS
1800 * pszUrl [I] Url to check
1801 *
1802 * RETURNS
1803 * TRUE, if pszUrl should be excluded from the history list,
1804 * FALSE otherwise.
1805 */
1806 BOOL WINAPI UrlIsNoHistoryA(LPCSTR pszUrl)
1807 {
1808 return UrlIsA(pszUrl, URLIS_NOHISTORY);
1809 }
1810
1811 /*************************************************************************
1812 * UrlIsNoHistoryW [SHLWAPI.@]
1813 *
1814 * See UrlIsNoHistoryA.
1815 */
1816 BOOL WINAPI UrlIsNoHistoryW(LPCWSTR pszUrl)
1817 {
1818 return UrlIsW(pszUrl, URLIS_NOHISTORY);
1819 }
1820
1821 /*************************************************************************
1822 * UrlIsOpaqueA [SHLWAPI.@]
1823 *
1824 * Determine if a Url is opaque.
1825 *
1826 * PARAMS
1827 * pszUrl [I] Url to check
1828 *
1829 * RETURNS
1830 * TRUE if pszUrl is opaque,
1831 * FALSE Otherwise.
1832 *
1833 * NOTES
1834 * An opaque Url is one that does not start with "<protocol>://".
1835 */
1836 BOOL WINAPI UrlIsOpaqueA(LPCSTR pszUrl)
1837 {
1838 return UrlIsA(pszUrl, URLIS_OPAQUE);
1839 }
1840
1841 /*************************************************************************
1842 * UrlIsOpaqueW [SHLWAPI.@]
1843 *
1844 * See UrlIsOpaqueA.
1845 */
1846 BOOL WINAPI UrlIsOpaqueW(LPCWSTR pszUrl)
1847 {
1848 return UrlIsW(pszUrl, URLIS_OPAQUE);
1849 }
1850
1851 /*************************************************************************
1852 * Scans for characters of type "type" and when not matching found,
1853 * returns pointer to it and length in size.
1854 *
1855 * Characters tested based on RFC 1738
1856 */
1857 static LPCWSTR URL_ScanID(LPCWSTR start, LPDWORD size, WINE_URL_SCAN_TYPE type)
1858 {
1859 static DWORD alwayszero = 0;
1860 BOOL cont = TRUE;
1861
1862 *size = 0;
1863
1864 switch(type){
1865
1866 case SCHEME:
1867 while (cont) {
1868 if ( (islowerW(*start) && isalphaW(*start)) ||
1869 isdigitW(*start) ||
1870 (*start == '+') ||
1871 (*start == '-') ||
1872 (*start == '.')) {
1873 start++;
1874 (*size)++;
1875 }
1876 else
1877 cont = FALSE;
1878 }
1879 break;
1880
1881 case USERPASS:
1882 while (cont) {
1883 if ( isalphaW(*start) ||
1884 isdigitW(*start) ||
1885 /* user/password only characters */
1886 (*start == ';') ||
1887 (*start == '?') ||
1888 (*start == '&') ||
1889 (*start == '=') ||
1890 /* *extra* characters */
1891 (*start == '!') ||
1892 (*start == '*') ||
1893 (*start == '\'') ||
1894 (*start == '(') ||
1895 (*start == ')') ||
1896 (*start == ',') ||
1897 /* *safe* characters */
1898 (*start == '$') ||
1899 (*start == '_') ||
1900 (*start == '+') ||
1901 (*start == '-') ||
1902 (*start == '.')) {
1903 start++;
1904 (*size)++;
1905 } else if (*start == '%') {
1906 if (isxdigitW(*(start+1)) &&
1907 isxdigitW(*(start+2))) {
1908 start += 3;
1909 *size += 3;
1910 } else
1911 cont = FALSE;
1912 } else
1913 cont = FALSE;
1914 }
1915 break;
1916
1917 case PORT:
1918 while (cont) {
1919 if (isdigitW(*start)) {
1920 start++;
1921 (*size)++;
1922 }
1923 else
1924 cont = FALSE;
1925 }
1926 break;
1927
1928 case HOST:
1929 while (cont) {
1930 if (isalnumW(*start) ||
1931 (*start == '-') ||
1932 (*start == '.') ) {
1933 start++;
1934 (*size)++;
1935 }
1936 else
1937 cont = FALSE;
1938 }
1939 break;
1940 default:
1941 FIXME("unknown type %d\n", type);
1942 return (LPWSTR)&alwayszero;
1943 }
1944 /* TRACE("scanned %d characters next char %p<%c>\n",
1945 *size, start, *start); */
1946 return start;
1947 }
1948
1949 /*************************************************************************
1950 * Attempt to parse URL into pieces.
1951 */
1952 static LONG URL_ParseUrl(LPCWSTR pszUrl, WINE_PARSE_URL *pl)
1953 {
1954 LPCWSTR work;
1955
1956 memset(pl, 0, sizeof(WINE_PARSE_URL));
1957 pl->pScheme = pszUrl;
1958 work = URL_ScanID(pl->pScheme, &pl->szScheme, SCHEME);
1959 if (!*work || (*work != ':')) goto ErrorExit;
1960 work++;
1961 if ((*work != '/') || (*(work+1) != '/')) goto ErrorExit;
1962 pl->pUserName = work + 2;
1963 work = URL_ScanID(pl->pUserName, &pl->szUserName, USERPASS);
1964 if (*work == ':' ) {
1965 /* parse password */
1966 work++;
1967 pl->pPassword = work;
1968 work = URL_ScanID(pl->pPassword, &pl->szPassword, USERPASS);
1969 if (*work != '@') {
1970 /* what we just parsed must be the hostname and port
1971 * so reset pointers and clear then let it parse */
1972 pl->szUserName = pl->szPassword = 0;
1973 work = pl->pUserName - 1;
1974 pl->pUserName = pl->pPassword = 0;
1975 }
1976 } else if (*work == '@') {
1977 /* no password */
1978 pl->szPassword = 0;
1979 pl->pPassword = 0;
1980 } else if (!*work || (*work == '/') || (*work == '.')) {
1981 /* what was parsed was hostname, so reset pointers and let it parse */
1982 pl->szUserName = pl->szPassword = 0;
1983 work = pl->pUserName - 1;
1984 pl->pUserName = pl->pPassword = 0;
1985 } else goto ErrorExit;
1986
1987 /* now start parsing hostname or hostnumber */
1988 work++;
1989 pl->pHostName = work;
1990 work = URL_ScanID(pl->pHostName, &pl->szHostName, HOST);
1991 if (*work == ':') {
1992 /* parse port */
1993 work++;
1994 pl->pPort = work;
1995 work = URL_ScanID(pl->pPort, &pl->szPort, PORT);
1996 }
1997 if (*work == '/') {
1998 /* see if query string */
1999 pl->pQuery = strchrW(work, '?');
2000 if (pl->pQuery) pl->szQuery = strlenW(pl->pQuery);
2001 }
2002 TRACE("parse successful: scheme=%p(%d), user=%p(%d), pass=%p(%d), host=%p(%d), port=%p(%d), query=%p(%d)\n",
2003 pl->pScheme, pl->szScheme,
2004 pl->pUserName, pl->szUserName,
2005 pl->pPassword, pl->szPassword,
2006 pl->pHostName, pl->szHostName,
2007 pl->pPort, pl->szPort,
2008 pl->pQuery, pl->szQuery);
2009 return S_OK;
2010 ErrorExit:
2011 FIXME("failed to parse %s\n", debugstr_w(pszUrl));
2012 return E_INVALIDARG;
2013 }
2014
2015 /*************************************************************************
2016 * UrlGetPartA [SHLWAPI.@]
2017 *
2018 * Retrieve part of a Url.
2019 *
2020 * PARAMS
2021 * pszIn [I] Url to parse
2022 * pszOut [O] Destination for part of pszIn requested
2023 * pcchOut [I] Size of pszOut
2024 * [O] length of pszOut string EXCLUDING '\0' if S_OK, otherwise
2025 * needed size of pszOut INCLUDING '\0'.
2026 * dwPart [I] URL_PART_ enum from "shlwapi.h"
2027 * dwFlags [I] URL_ flags from "shlwapi.h"
2028 *
2029 * RETURNS
2030 * Success: S_OK. pszOut contains the part requested, pcchOut contains its length.
2031 * Failure: An HRESULT error code describing the error.
2032 */
2033 HRESULT WINAPI UrlGetPartA(LPCSTR pszIn, LPSTR pszOut, LPDWORD pcchOut,
2034 DWORD dwPart, DWORD dwFlags)
2035 {
2036 LPWSTR in, out;
2037 DWORD ret, len, len2;
2038
2039 in = HeapAlloc(GetProcessHeap(), 0,
2040 (2*INTERNET_MAX_URL_LENGTH) * sizeof(WCHAR));
2041 out = in + INTERNET_MAX_URL_LENGTH;
2042
2043 MultiByteToWideChar(0, 0, pszIn, -1, in, INTERNET_MAX_URL_LENGTH);
2044
2045 len = INTERNET_MAX_URL_LENGTH;
2046 ret = UrlGetPartW(in, out, &len, dwPart, dwFlags);
2047
2048 if (ret != S_OK) {
2049 HeapFree(GetProcessHeap(), 0, in);
2050 return ret;
2051 }
2052
2053 len2 = WideCharToMultiByte(0, 0, out, len, 0, 0, 0, 0);
2054 if (len2 > *pcchOut) {
2055 *pcchOut = len2;
2056 HeapFree(GetProcessHeap(), 0, in);
2057 return E_POINTER;
2058 }
2059 WideCharToMultiByte(0, 0, out, len+1, pszOut, *pcchOut, 0, 0);
2060 *pcchOut = len2;
2061 HeapFree(GetProcessHeap(), 0, in);
2062 return S_OK;
2063 }
2064
2065 /*************************************************************************
2066 * UrlGetPartW [SHLWAPI.@]
2067 *
2068 * See UrlGetPartA.
2069 */
2070 HRESULT WINAPI UrlGetPartW(LPCWSTR pszIn, LPWSTR pszOut, LPDWORD pcchOut,
2071 DWORD dwPart, DWORD dwFlags)
2072 {
2073 WINE_PARSE_URL pl;
2074 HRESULT ret;
2075 DWORD size, schsize;
2076 LPCWSTR addr, schaddr;
2077
2078 TRACE("(%s %p %p(%d) %08x %08x)\n",
2079 debugstr_w(pszIn), pszOut, pcchOut, *pcchOut, dwPart, dwFlags);
2080
2081 ret = URL_ParseUrl(pszIn, &pl);
2082 if (ret == S_OK) {
2083 schaddr = pl.pScheme;
2084 schsize = pl.szScheme;
2085
2086 switch (dwPart) {
2087 case URL_PART_SCHEME:
2088 if (!pl.szScheme) return E_INVALIDARG;
2089 addr = pl.pScheme;
2090 size = pl.szScheme;
2091 break;
2092
2093 case URL_PART_HOSTNAME:
2094 if (!pl.szHostName) return E_INVALIDARG;
2095 addr = pl.pHostName;
2096 size = pl.szHostName;
2097 break;
2098
2099 case URL_PART_USERNAME:
2100 if (!pl.szUserName) return E_INVALIDARG;
2101 addr = pl.pUserName;
2102 size = pl.szUserName;
2103 break;
2104
2105 case URL_PART_PASSWORD:
2106 if (!pl.szPassword) return E_INVALIDARG;
2107 addr = pl.pPassword;
2108 size = pl.szPassword;
2109 break;
2110
2111 case URL_PART_PORT:
2112 if (!pl.szPort) return E_INVALIDARG;
2113 addr = pl.pPort;
2114 size = pl.szPort;
2115 break;
2116
2117 case URL_PART_QUERY:
2118 if (!pl.szQuery) return E_INVALIDARG;
2119 addr = pl.pQuery;
2120 size = pl.szQuery;
2121 break;
2122
2123 default:
2124 return E_INVALIDARG;
2125 }
2126
2127 if (dwFlags == URL_PARTFLAG_KEEPSCHEME) {
2128 if (*pcchOut < schsize + size + 2) {
2129 *pcchOut = schsize + size + 2;
2130 return E_POINTER;
2131 }
2132 memcpy(pszOut, schaddr, schsize*sizeof(WCHAR));
2133 pszOut[schsize] = ':';
2134 memcpy(pszOut+schsize+1, addr, size*sizeof(WCHAR));
2135 pszOut[schsize+1+size] = 0;
2136 *pcchOut = schsize + 1 + size;
2137 }
2138 else {
2139 if (*pcchOut < size + 1) {*pcchOut = size+1; return E_POINTER;}
2140 memcpy(pszOut, addr, size*sizeof(WCHAR));
2141 pszOut[size] = 0;
2142 *pcchOut = size;
2143 }
2144 TRACE("len=%d %s\n", *pcchOut, debugstr_w(pszOut));
2145 }
2146 return ret;
2147 }
2148
2149 /*************************************************************************
2150 * PathIsURLA [SHLWAPI.@]
2151 *
2152 * Check if the given path is a Url.
2153 *
2154 * PARAMS
2155 * lpszPath [I] Path to check.
2156 *
2157 * RETURNS
2158 * TRUE if lpszPath is a Url.
2159 * FALSE if lpszPath is NULL or not a Url.
2160 */
2161 BOOL WINAPI PathIsURLA(LPCSTR lpstrPath)
2162 {
2163 PARSEDURLA base;
2164
2165 TRACE("%s\n", debugstr_a(lpstrPath));
2166
2167 if (!lpstrPath || !*lpstrPath) return FALSE;
2168
2169 /* get protocol */
2170 base.cbSize = sizeof(base);
2171 ParseURLA(lpstrPath, &base);
2172 return (base.nScheme != URL_SCHEME_INVALID);
2173 }
2174
2175 /*************************************************************************
2176 * PathIsURLW [SHLWAPI.@]
2177 *
2178 * See PathIsURLA.
2179 */
2180 BOOL WINAPI PathIsURLW(LPCWSTR lpstrPath)
2181 {
2182 PARSEDURLW base;
2183
2184 TRACE("%s\n", debugstr_w(lpstrPath));
2185
2186 if (!lpstrPath || !*lpstrPath) return FALSE;
2187
2188 /* get protocol */
2189 base.cbSize = sizeof(base);
2190 ParseURLW(lpstrPath, &base);
2191 return (base.nScheme != URL_SCHEME_INVALID);
2192 }
2193
2194 /*************************************************************************
2195 * UrlCreateFromPathA [SHLWAPI.@]
2196 *
2197 * See UrlCreateFromPathW
2198 */
2199 HRESULT WINAPI UrlCreateFromPathA(LPCSTR pszPath, LPSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2200 {
2201 WCHAR bufW[INTERNET_MAX_URL_LENGTH];
2202 WCHAR *urlW = bufW;
2203 UNICODE_STRING pathW;
2204 HRESULT ret;
2205 DWORD lenW = sizeof(bufW)/sizeof(WCHAR), lenA;
2206
2207 if(!RtlCreateUnicodeStringFromAsciiz(&pathW, pszPath))
2208 return E_INVALIDARG;
2209 if((ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved)) == E_POINTER) {
2210 urlW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR));
2211 ret = UrlCreateFromPathW(pathW.Buffer, urlW, &lenW, dwReserved);
2212 }
2213 if(ret == S_OK || ret == S_FALSE) {
2214 RtlUnicodeToMultiByteSize(&lenA, urlW, lenW * sizeof(WCHAR));
2215 if(*pcchUrl > lenA) {
2216 RtlUnicodeToMultiByteN(pszUrl, *pcchUrl - 1, &lenA, urlW, lenW * sizeof(WCHAR));
2217 pszUrl[lenA] = 0;
2218 *pcchUrl = lenA;
2219 } else {
2220 *pcchUrl = lenA + 1;
2221 ret = E_POINTER;
2222 }
2223 }
2224 if(urlW != bufW) HeapFree(GetProcessHeap(), 0, urlW);
2225 RtlFreeUnicodeString(&pathW);
2226 return ret;
2227 }
2228
2229 /*************************************************************************
2230 * UrlCreateFromPathW [SHLWAPI.@]
2231 *
2232 * Create a Url from a file path.
2233 *
2234 * PARAMS
2235 * pszPath [I] Path to convert
2236 * pszUrl [O] Destination for the converted Url
2237 * pcchUrl [I/O] Length of pszUrl
2238 * dwReserved [I] Reserved, must be 0
2239 *
2240 * RETURNS
2241 * Success: S_OK pszUrl contains the converted path, S_FALSE if the path is already a Url
2242 * Failure: An HRESULT error code.
2243 */
2244 HRESULT WINAPI UrlCreateFromPathW(LPCWSTR pszPath, LPWSTR pszUrl, LPDWORD pcchUrl, DWORD dwReserved)
2245 {
2246 DWORD needed;
2247 HRESULT ret;
2248 WCHAR *pszNewUrl;
2249 WCHAR file_colonW[] = {'f','i','l','e',':',0};
2250 WCHAR three_slashesW[] = {'/','/','/',0};
2251 PARSEDURLW parsed_url;
2252
2253 TRACE("(%s, %p, %p, 0x%08x)\n", debugstr_w(pszPath), pszUrl, pcchUrl, dwReserved);
2254
2255 /* Validate arguments */
2256 if (dwReserved != 0)
2257 return E_INVALIDARG;
2258 if (!pszUrl || !pcchUrl)
2259 return E_INVALIDARG;
2260
2261
2262 parsed_url.cbSize = sizeof(parsed_url);
2263 if(ParseURLW(pszPath, &parsed_url) == S_OK) {
2264 if(parsed_url.nScheme != URL_SCHEME_INVALID && parsed_url.cchProtocol > 1) {
2265 needed = strlenW(pszPath);
2266 if (needed >= *pcchUrl) {
2267 *pcchUrl = needed + 1;
2268 return E_POINTER;
2269 } else {
2270 *pcchUrl = needed;
2271 strcpyW(pszUrl, pszPath);
2272 return S_FALSE;
2273 }
2274 }
2275 }
2276
2277 pszNewUrl = HeapAlloc(GetProcessHeap(), 0, (strlenW(pszPath) + 9) * sizeof(WCHAR)); /* "file:///" + pszPath_len + 1 */
2278 strcpyW(pszNewUrl, file_colonW);
2279 if(isalphaW(pszPath[0]) && pszPath[1] == ':')
2280 strcatW(pszNewUrl, three_slashesW);
2281 strcatW(pszNewUrl, pszPath);
2282 ret = UrlEscapeW(pszNewUrl, pszUrl, pcchUrl, URL_ESCAPE_PERCENT);
2283
2284 HeapFree(GetProcessHeap(), 0, pszNewUrl);
2285 return ret;
2286 }
2287
2288 /*************************************************************************
2289 * SHAutoComplete [SHLWAPI.@]
2290 *
2291 * Enable auto-completion for an edit control.
2292 *
2293 * PARAMS
2294 * hwndEdit [I] Handle of control to enable auto-completion for
2295 * dwFlags [I] SHACF_ flags from "shlwapi.h"
2296 *
2297 * RETURNS
2298 * Success: S_OK. Auto-completion is enabled for the control.
2299 * Failure: An HRESULT error code indicating the error.
2300 */
2301 HRESULT WINAPI SHAutoComplete(HWND hwndEdit, DWORD dwFlags)
2302 {
2303 FIXME("SHAutoComplete stub\n");
2304 return S_FALSE;
2305 }
2306
2307 /*************************************************************************
2308 * MLBuildResURLA [SHLWAPI.405]
2309 *
2310 * Create a Url pointing to a resource in a module.
2311 *
2312 * PARAMS
2313 * lpszLibName [I] Name of the module containing the resource
2314 * hMod [I] Callers module handle
2315 * dwFlags [I] Undocumented flags for loading the module
2316 * lpszRes [I] Resource name
2317 * lpszDest [O] Destination for resulting Url
2318 * dwDestLen [I] Length of lpszDest
2319 *
2320 * RETURNS
2321 * Success: S_OK. lpszDest contains the resource Url.
2322 * Failure: E_INVALIDARG, if any argument is invalid, or
2323 * E_FAIL if dwDestLen is too small.
2324 */
2325 HRESULT WINAPI MLBuildResURLA(LPCSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2326 LPCSTR lpszRes, LPSTR lpszDest, DWORD dwDestLen)
2327 {
2328 WCHAR szLibName[MAX_PATH], szRes[MAX_PATH], szDest[MAX_PATH];
2329 HRESULT hRet;
2330
2331 if (lpszLibName)
2332 MultiByteToWideChar(CP_ACP, 0, lpszLibName, -1, szLibName, sizeof(szLibName)/sizeof(WCHAR));
2333
2334 if (lpszRes)
2335 MultiByteToWideChar(CP_ACP, 0, lpszRes, -1, szRes, sizeof(szRes)/sizeof(WCHAR));
2336
2337 if (dwDestLen > sizeof(szLibName)/sizeof(WCHAR))
2338 dwDestLen = sizeof(szLibName)/sizeof(WCHAR);
2339
2340 hRet = MLBuildResURLW(lpszLibName ? szLibName : NULL, hMod, dwFlags,
2341 lpszRes ? szRes : NULL, lpszDest ? szDest : NULL, dwDestLen);
2342 if (SUCCEEDED(hRet) && lpszDest)
2343 WideCharToMultiByte(CP_ACP, 0, szDest, -1, lpszDest, dwDestLen, 0, 0);
2344
2345 return hRet;
2346 }
2347
2348 /*************************************************************************
2349 * MLBuildResURLA [SHLWAPI.406]
2350 *
2351 * See MLBuildResURLA.
2352 */
2353 HRESULT WINAPI MLBuildResURLW(LPCWSTR lpszLibName, HMODULE hMod, DWORD dwFlags,
2354 LPCWSTR lpszRes, LPWSTR lpszDest, DWORD dwDestLen)
2355 {
2356 static const WCHAR szRes[] = { 'r','e','s',':','/','/','\0' };
2357 #define szResLen ((sizeof(szRes) - sizeof(WCHAR))/sizeof(WCHAR))
2358 HRESULT hRet = E_FAIL;
2359
2360 TRACE("(%s,%p,0x%08x,%s,%p,%d)\n", debugstr_w(lpszLibName), hMod, dwFlags,
2361 debugstr_w(lpszRes), lpszDest, dwDestLen);
2362
2363 if (!lpszLibName || !hMod || hMod == INVALID_HANDLE_VALUE || !lpszRes ||
2364 !lpszDest || (dwFlags && dwFlags != 2))
2365 return E_INVALIDARG;
2366
2367 if (dwDestLen >= szResLen + 1)
2368 {
2369 dwDestLen -= (szResLen + 1);
2370 memcpy(lpszDest, szRes, sizeof(szRes));
2371
2372 hMod = MLLoadLibraryW(lpszLibName, hMod, dwFlags);
2373
2374 if (hMod)
2375 {
2376 WCHAR szBuff[MAX_PATH];
2377 DWORD len;
2378
2379 len = GetModuleFileNameW(hMod, szBuff, sizeof(szBuff)/sizeof(WCHAR));
2380 if (len && len < sizeof(szBuff)/sizeof(WCHAR))
2381 {
2382 DWORD dwPathLen = strlenW(szBuff) + 1;
2383
2384 if (dwDestLen >= dwPathLen)
2385 {
2386 DWORD dwResLen;
2387
2388 dwDestLen -= dwPathLen;
2389 memcpy(lpszDest + szResLen, szBuff, dwPathLen * sizeof(WCHAR));
2390
2391 dwResLen = strlenW(lpszRes) + 1;
2392 if (dwDestLen >= dwResLen + 1)
2393 {
2394 lpszDest[szResLen + dwPathLen + dwResLen] = '/';
2395 memcpy(lpszDest + szResLen + dwPathLen, lpszRes, dwResLen * sizeof(WCHAR));
2396 hRet = S_OK;
2397 }
2398 }
2399 }
2400 MLFreeLibrary(hMod);
2401 }
2402 }
2403 return hRet;
2404 }