| /* | |
| * Copyright 2010 Jacek Caban for CodeWeavers | |
| * Copyright 2010 Thomas Mullaly | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | |
| */ | |
| #include <limits.h> | |
| #include "urlmon_main.h" | |
| #include "wine/debug.h" | |
| #define NO_SHLWAPI_REG | |
| #include "shlwapi.h" | |
| #include "strsafe.h" | |
| #define URI_DISPLAY_NO_ABSOLUTE_URI 0x1 | |
| #define URI_DISPLAY_NO_DEFAULT_PORT_AUTH 0x2 | |
| #define ALLOW_NULL_TERM_SCHEME 0x01 | |
| #define ALLOW_NULL_TERM_USER_NAME 0x02 | |
| #define ALLOW_NULL_TERM_PASSWORD 0x04 | |
| #define ALLOW_BRACKETLESS_IP_LITERAL 0x08 | |
| #define SKIP_IP_FUTURE_CHECK 0x10 | |
| #define IGNORE_PORT_DELIMITER 0x20 | |
| #define RAW_URI_FORCE_PORT_DISP 0x1 | |
| #define RAW_URI_CONVERT_TO_DOS_PATH 0x2 | |
| #define COMBINE_URI_FORCE_FLAG_USE 0x1 | |
| WINE_DEFAULT_DEBUG_CHANNEL(urlmon); | |
| static const IID IID_IUriObj = {0x4b364760,0x9f51,0x11df,{0x98,0x1c,0x08,0x00,0x20,0x0c,0x9a,0x66}}; | |
| typedef struct { | |
| IUri IUri_iface; | |
| IUriBuilderFactory IUriBuilderFactory_iface; | |
| IPersistStream IPersistStream_iface; | |
| IMarshal IMarshal_iface; | |
| LONG ref; | |
| BSTR raw_uri; | |
| /* Information about the canonicalized URI's buffer. */ | |
| WCHAR *canon_uri; | |
| DWORD canon_size; | |
| DWORD canon_len; | |
| BOOL display_modifiers; | |
| DWORD create_flags; | |
| INT scheme_start; | |
| DWORD scheme_len; | |
| URL_SCHEME scheme_type; | |
| INT userinfo_start; | |
| DWORD userinfo_len; | |
| INT userinfo_split; | |
| INT host_start; | |
| DWORD host_len; | |
| Uri_HOST_TYPE host_type; | |
| INT port_offset; | |
| DWORD port; | |
| BOOL has_port; | |
| INT authority_start; | |
| DWORD authority_len; | |
| INT domain_offset; | |
| INT path_start; | |
| DWORD path_len; | |
| INT extension_offset; | |
| INT query_start; | |
| DWORD query_len; | |
| INT fragment_start; | |
| DWORD fragment_len; | |
| } Uri; | |
| typedef struct { | |
| IUriBuilder IUriBuilder_iface; | |
| LONG ref; | |
| Uri *uri; | |
| DWORD modified_props; | |
| WCHAR *fragment; | |
| DWORD fragment_len; | |
| WCHAR *host; | |
| DWORD host_len; | |
| WCHAR *password; | |
| DWORD password_len; | |
| WCHAR *path; | |
| DWORD path_len; | |
| BOOL has_port; | |
| DWORD port; | |
| WCHAR *query; | |
| DWORD query_len; | |
| WCHAR *scheme; | |
| DWORD scheme_len; | |
| WCHAR *username; | |
| DWORD username_len; | |
| } UriBuilder; | |
| typedef struct { | |
| const WCHAR *str; | |
| DWORD len; | |
| } h16; | |
| typedef struct { | |
| /* IPv6 addresses can hold up to 8 h16 components. */ | |
| h16 components[8]; | |
| DWORD h16_count; | |
| /* An IPv6 can have 1 elision ("::"). */ | |
| const WCHAR *elision; | |
| /* An IPv6 can contain 1 IPv4 address as the last 32bits of the address. */ | |
| const WCHAR *ipv4; | |
| DWORD ipv4_len; | |
| INT components_size; | |
| INT elision_size; | |
| } ipv6_address; | |
| typedef struct { | |
| BSTR uri; | |
| BOOL is_relative; | |
| BOOL is_opaque; | |
| BOOL has_implicit_scheme; | |
| BOOL has_implicit_ip; | |
| UINT implicit_ipv4; | |
| BOOL must_have_path; | |
| const WCHAR *scheme; | |
| DWORD scheme_len; | |
| URL_SCHEME scheme_type; | |
| const WCHAR *username; | |
| DWORD username_len; | |
| const WCHAR *password; | |
| DWORD password_len; | |
| const WCHAR *host; | |
| DWORD host_len; | |
| Uri_HOST_TYPE host_type; | |
| BOOL has_ipv6; | |
| ipv6_address ipv6_address; | |
| BOOL has_port; | |
| const WCHAR *port; | |
| DWORD port_len; | |
| DWORD port_value; | |
| const WCHAR *path; | |
| DWORD path_len; | |
| const WCHAR *query; | |
| DWORD query_len; | |
| const WCHAR *fragment; | |
| DWORD fragment_len; | |
| } parse_data; | |
| static const CHAR hexDigits[] = "0123456789ABCDEF"; | |
| /* List of scheme types/scheme names that are recognized by the IUri interface as of IE 7. */ | |
| static const struct { | |
| URL_SCHEME scheme; | |
| WCHAR scheme_name[16]; | |
| } recognized_schemes[] = { | |
| {URL_SCHEME_FTP, {'f','t','p',0}}, | |
| {URL_SCHEME_HTTP, {'h','t','t','p',0}}, | |
| {URL_SCHEME_GOPHER, {'g','o','p','h','e','r',0}}, | |
| {URL_SCHEME_MAILTO, {'m','a','i','l','t','o',0}}, | |
| {URL_SCHEME_NEWS, {'n','e','w','s',0}}, | |
| {URL_SCHEME_NNTP, {'n','n','t','p',0}}, | |
| {URL_SCHEME_TELNET, {'t','e','l','n','e','t',0}}, | |
| {URL_SCHEME_WAIS, {'w','a','i','s',0}}, | |
| {URL_SCHEME_FILE, {'f','i','l','e',0}}, | |
| {URL_SCHEME_MK, {'m','k',0}}, | |
| {URL_SCHEME_HTTPS, {'h','t','t','p','s',0}}, | |
| {URL_SCHEME_SHELL, {'s','h','e','l','l',0}}, | |
| {URL_SCHEME_SNEWS, {'s','n','e','w','s',0}}, | |
| {URL_SCHEME_LOCAL, {'l','o','c','a','l',0}}, | |
| {URL_SCHEME_JAVASCRIPT, {'j','a','v','a','s','c','r','i','p','t',0}}, | |
| {URL_SCHEME_VBSCRIPT, {'v','b','s','c','r','i','p','t',0}}, | |
| {URL_SCHEME_ABOUT, {'a','b','o','u','t',0}}, | |
| {URL_SCHEME_RES, {'r','e','s',0}}, | |
| {URL_SCHEME_MSSHELLROOTED, {'m','s','-','s','h','e','l','l','-','r','o','o','t','e','d',0}}, | |
| {URL_SCHEME_MSSHELLIDLIST, {'m','s','-','s','h','e','l','l','-','i','d','l','i','s','t',0}}, | |
| {URL_SCHEME_MSHELP, {'h','c','p',0}}, | |
| {URL_SCHEME_WILDCARD, {'*',0}} | |
| }; | |
| /* List of default ports Windows recognizes. */ | |
| static const struct { | |
| URL_SCHEME scheme; | |
| USHORT port; | |
| } default_ports[] = { | |
| {URL_SCHEME_FTP, 21}, | |
| {URL_SCHEME_HTTP, 80}, | |
| {URL_SCHEME_GOPHER, 70}, | |
| {URL_SCHEME_NNTP, 119}, | |
| {URL_SCHEME_TELNET, 23}, | |
| {URL_SCHEME_WAIS, 210}, | |
| {URL_SCHEME_HTTPS, 443}, | |
| }; | |
| /* List of 3-character top level domain names Windows seems to recognize. | |
| * There might be more, but, these are the only ones I've found so far. | |
| */ | |
| static const struct { | |
| WCHAR tld_name[4]; | |
| } recognized_tlds[] = { | |
| {{'c','o','m',0}}, | |
| {{'e','d','u',0}}, | |
| {{'g','o','v',0}}, | |
| {{'i','n','t',0}}, | |
| {{'m','i','l',0}}, | |
| {{'n','e','t',0}}, | |
| {{'o','r','g',0}} | |
| }; | |
| static Uri *get_uri_obj(IUri *uri) | |
| { | |
| Uri *ret; | |
| HRESULT hres; | |
| hres = IUri_QueryInterface(uri, &IID_IUriObj, (void**)&ret); | |
| return SUCCEEDED(hres) ? ret : NULL; | |
| } | |
| static inline BOOL is_alpha(WCHAR val) { | |
| return ((val >= 'a' && val <= 'z') || (val >= 'A' && val <= 'Z')); | |
| } | |
| static inline BOOL is_num(WCHAR val) { | |
| return (val >= '0' && val <= '9'); | |
| } | |
| static inline BOOL is_drive_path(const WCHAR *str) { | |
| return (is_alpha(str[0]) && (str[1] == ':' || str[1] == '|')); | |
| } | |
| static inline BOOL is_unc_path(const WCHAR *str) { | |
| return (str[0] == '\\' && str[1] == '\\'); | |
| } | |
| static inline BOOL is_forbidden_dos_path_char(WCHAR val) { | |
| return (val == '>' || val == '<' || val == '\"'); | |
| } | |
| /* A URI is implicitly a file path if it begins with | |
| * a drive letter (e.g. X:) or starts with "\\" (UNC path). | |
| */ | |
| static inline BOOL is_implicit_file_path(const WCHAR *str) { | |
| return (is_unc_path(str) || (is_alpha(str[0]) && str[1] == ':')); | |
| } | |
| /* Checks if the URI is a hierarchical URI. A hierarchical | |
| * URI is one that has "//" after the scheme. | |
| */ | |
| static BOOL check_hierarchical(const WCHAR **ptr) { | |
| const WCHAR *start = *ptr; | |
| if(**ptr != '/') | |
| return FALSE; | |
| ++(*ptr); | |
| if(**ptr != '/') { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| ++(*ptr); | |
| return TRUE; | |
| } | |
| /* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" */ | |
| static inline BOOL is_unreserved(WCHAR val) { | |
| return (is_alpha(val) || is_num(val) || val == '-' || val == '.' || | |
| val == '_' || val == '~'); | |
| } | |
| /* sub-delims = "!" / "$" / "&" / "'" / "(" / ")" | |
| * / "*" / "+" / "," / ";" / "=" | |
| */ | |
| static inline BOOL is_subdelim(WCHAR val) { | |
| return (val == '!' || val == '$' || val == '&' || | |
| val == '\'' || val == '(' || val == ')' || | |
| val == '*' || val == '+' || val == ',' || | |
| val == ';' || val == '='); | |
| } | |
| /* gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" */ | |
| static inline BOOL is_gendelim(WCHAR val) { | |
| return (val == ':' || val == '/' || val == '?' || | |
| val == '#' || val == '[' || val == ']' || | |
| val == '@'); | |
| } | |
| /* Characters that delimit the end of the authority | |
| * section of a URI. Sometimes a '\\' is considered | |
| * an authority delimiter. | |
| */ | |
| static inline BOOL is_auth_delim(WCHAR val, BOOL acceptSlash) { | |
| return (val == '#' || val == '/' || val == '?' || | |
| val == '\0' || (acceptSlash && val == '\\')); | |
| } | |
| /* reserved = gen-delims / sub-delims */ | |
| static inline BOOL is_reserved(WCHAR val) { | |
| return (is_subdelim(val) || is_gendelim(val)); | |
| } | |
| static inline BOOL is_hexdigit(WCHAR val) { | |
| return ((val >= 'a' && val <= 'f') || | |
| (val >= 'A' && val <= 'F') || | |
| (val >= '0' && val <= '9')); | |
| } | |
| static inline BOOL is_path_delim(URL_SCHEME scheme, WCHAR val) { | |
| return (!val || (val == '#' && scheme != URL_SCHEME_FILE) || val == '?'); | |
| } | |
| static inline BOOL is_slash(WCHAR c) | |
| { | |
| return c == '/' || c == '\\'; | |
| } | |
| static BOOL is_default_port(URL_SCHEME scheme, DWORD port) { | |
| DWORD i; | |
| for(i = 0; i < sizeof(default_ports)/sizeof(default_ports[0]); ++i) { | |
| if(default_ports[i].scheme == scheme && default_ports[i].port) | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| /* List of schemes types Windows seems to expect to be hierarchical. */ | |
| static inline BOOL is_hierarchical_scheme(URL_SCHEME type) { | |
| return(type == URL_SCHEME_HTTP || type == URL_SCHEME_FTP || | |
| type == URL_SCHEME_GOPHER || type == URL_SCHEME_NNTP || | |
| type == URL_SCHEME_TELNET || type == URL_SCHEME_WAIS || | |
| type == URL_SCHEME_FILE || type == URL_SCHEME_HTTPS || | |
| type == URL_SCHEME_RES); | |
| } | |
| /* Checks if 'flags' contains an invalid combination of Uri_CREATE flags. */ | |
| static inline BOOL has_invalid_flag_combination(DWORD flags) { | |
| return((flags & Uri_CREATE_DECODE_EXTRA_INFO && flags & Uri_CREATE_NO_DECODE_EXTRA_INFO) || | |
| (flags & Uri_CREATE_CANONICALIZE && flags & Uri_CREATE_NO_CANONICALIZE) || | |
| (flags & Uri_CREATE_CRACK_UNKNOWN_SCHEMES && flags & Uri_CREATE_NO_CRACK_UNKNOWN_SCHEMES) || | |
| (flags & Uri_CREATE_PRE_PROCESS_HTML_URI && flags & Uri_CREATE_NO_PRE_PROCESS_HTML_URI) || | |
| (flags & Uri_CREATE_IE_SETTINGS && flags & Uri_CREATE_NO_IE_SETTINGS)); | |
| } | |
| /* Applies each default Uri_CREATE flags to 'flags' if it | |
| * doesn't cause a flag conflict. | |
| */ | |
| static void apply_default_flags(DWORD *flags) { | |
| if(!(*flags & Uri_CREATE_NO_CANONICALIZE)) | |
| *flags |= Uri_CREATE_CANONICALIZE; | |
| if(!(*flags & Uri_CREATE_NO_DECODE_EXTRA_INFO)) | |
| *flags |= Uri_CREATE_DECODE_EXTRA_INFO; | |
| if(!(*flags & Uri_CREATE_NO_CRACK_UNKNOWN_SCHEMES)) | |
| *flags |= Uri_CREATE_CRACK_UNKNOWN_SCHEMES; | |
| if(!(*flags & Uri_CREATE_NO_PRE_PROCESS_HTML_URI)) | |
| *flags |= Uri_CREATE_PRE_PROCESS_HTML_URI; | |
| if(!(*flags & Uri_CREATE_IE_SETTINGS)) | |
| *flags |= Uri_CREATE_NO_IE_SETTINGS; | |
| } | |
| /* Determines if the URI is hierarchical using the information already parsed into | |
| * data and using the current location of parsing in the URI string. | |
| * | |
| * Windows considers a URI hierarchical if one of the following is true: | |
| * A.) It's a wildcard scheme. | |
| * B.) It's an implicit file scheme. | |
| * C.) It's a known hierarchical scheme and it has two '\\' after the scheme name. | |
| * (the '\\' will be converted into "//" during canonicalization). | |
| * D.) "//" appears after the scheme name (or at the beginning if no scheme is given). | |
| */ | |
| static inline BOOL is_hierarchical_uri(const WCHAR **ptr, const parse_data *data) { | |
| const WCHAR *start = *ptr; | |
| if(data->scheme_type == URL_SCHEME_WILDCARD) | |
| return TRUE; | |
| else if(data->scheme_type == URL_SCHEME_FILE && data->has_implicit_scheme) | |
| return TRUE; | |
| else if(is_hierarchical_scheme(data->scheme_type) && (*ptr)[0] == '\\' && (*ptr)[1] == '\\') { | |
| *ptr += 2; | |
| return TRUE; | |
| } else if(data->scheme_type != URL_SCHEME_MAILTO && check_hierarchical(ptr)) | |
| return TRUE; | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| /* Computes the size of the given IPv6 address. | |
| * Each h16 component is 16 bits. If there is an IPv4 address, it's | |
| * 32 bits. If there's an elision it can be 16 to 128 bits, depending | |
| * on the number of other components. | |
| * | |
| * Modeled after google-url's CheckIPv6ComponentsSize function | |
| */ | |
| static void compute_ipv6_comps_size(ipv6_address *address) { | |
| address->components_size = address->h16_count * 2; | |
| if(address->ipv4) | |
| /* IPv4 address is 4 bytes. */ | |
| address->components_size += 4; | |
| if(address->elision) { | |
| /* An elision can be anywhere from 2 bytes up to 16 bytes. | |
| * Its size depends on the size of the h16 and IPv4 components. | |
| */ | |
| address->elision_size = 16 - address->components_size; | |
| if(address->elision_size < 2) | |
| address->elision_size = 2; | |
| } else | |
| address->elision_size = 0; | |
| } | |
| /* Taken from dlls/jscript/lex.c */ | |
| static int hex_to_int(WCHAR val) { | |
| if(val >= '0' && val <= '9') | |
| return val - '0'; | |
| else if(val >= 'a' && val <= 'f') | |
| return val - 'a' + 10; | |
| else if(val >= 'A' && val <= 'F') | |
| return val - 'A' + 10; | |
| return -1; | |
| } | |
| /* Helper function for converting a percent encoded string | |
| * representation of a WCHAR value into its actual WCHAR value. If | |
| * the two characters following the '%' aren't valid hex values then | |
| * this function returns the NULL character. | |
| * | |
| * E.g. | |
| * "%2E" will result in '.' being returned by this function. | |
| */ | |
| static WCHAR decode_pct_val(const WCHAR *ptr) { | |
| WCHAR ret = '\0'; | |
| if(*ptr == '%' && is_hexdigit(*(ptr + 1)) && is_hexdigit(*(ptr + 2))) { | |
| INT a = hex_to_int(*(ptr + 1)); | |
| INT b = hex_to_int(*(ptr + 2)); | |
| ret = a << 4; | |
| ret += b; | |
| } | |
| return ret; | |
| } | |
| /* Helper function for percent encoding a given character | |
| * and storing the encoded value into a given buffer (dest). | |
| * | |
| * It's up to the calling function to ensure that there is | |
| * at least enough space in 'dest' for the percent encoded | |
| * value to be stored (so dest + 3 spaces available). | |
| */ | |
| static inline void pct_encode_val(WCHAR val, WCHAR *dest) { | |
| dest[0] = '%'; | |
| dest[1] = hexDigits[(val >> 4) & 0xf]; | |
| dest[2] = hexDigits[val & 0xf]; | |
| } | |
| /* Attempts to parse the domain name from the host. | |
| * | |
| * This function also includes the Top-level Domain (TLD) name | |
| * of the host when it tries to find the domain name. If it finds | |
| * a valid domain name it will assign 'domain_start' the offset | |
| * into 'host' where the domain name starts. | |
| * | |
| * It's implied that if there is a domain name its range is: | |
| * [host+domain_start, host+host_len). | |
| */ | |
| void find_domain_name(const WCHAR *host, DWORD host_len, | |
| INT *domain_start) { | |
| const WCHAR *last_tld, *sec_last_tld, *end; | |
| end = host+host_len-1; | |
| *domain_start = -1; | |
| /* There has to be at least enough room for a '.' followed by a | |
| * 3-character TLD for a domain to even exist in the host name. | |
| */ | |
| if(host_len < 4) | |
| return; | |
| last_tld = memrchrW(host, '.', host_len); | |
| if(!last_tld) | |
| /* http://hostname -> has no domain name. */ | |
| return; | |
| sec_last_tld = memrchrW(host, '.', last_tld-host); | |
| if(!sec_last_tld) { | |
| /* If the '.' is at the beginning of the host there | |
| * has to be at least 3 characters in the TLD for it | |
| * to be valid. | |
| * Ex: .com -> .com as the domain name. | |
| * .co -> has no domain name. | |
| */ | |
| if(last_tld-host == 0) { | |
| if(end-(last_tld-1) < 3) | |
| return; | |
| } else if(last_tld-host == 3) { | |
| DWORD i; | |
| /* If there are three characters in front of last_tld and | |
| * they are on the list of recognized TLDs, then this | |
| * host doesn't have a domain (since the host only contains | |
| * a TLD name. | |
| * Ex: edu.uk -> has no domain name. | |
| * foo.uk -> foo.uk as the domain name. | |
| */ | |
| for(i = 0; i < sizeof(recognized_tlds)/sizeof(recognized_tlds[0]); ++i) { | |
| if(!StrCmpNIW(host, recognized_tlds[i].tld_name, 3)) | |
| return; | |
| } | |
| } else if(last_tld-host < 3) | |
| /* Anything less than 3 characters is considered part | |
| * of the TLD name. | |
| * Ex: ak.uk -> Has no domain name. | |
| */ | |
| return; | |
| /* Otherwise the domain name is the whole host name. */ | |
| *domain_start = 0; | |
| } else if(end+1-last_tld > 3) { | |
| /* If the last_tld has more than 3 characters, then it's automatically | |
| * considered the TLD of the domain name. | |
| * Ex: www.winehq.org.uk.test -> uk.test as the domain name. | |
| */ | |
| *domain_start = (sec_last_tld+1)-host; | |
| } else if(last_tld - (sec_last_tld+1) < 4) { | |
| DWORD i; | |
| /* If the sec_last_tld is 3 characters long it HAS to be on the list of | |
| * recognized to still be considered part of the TLD name, otherwise | |
| * it's considered the domain name. | |
| * Ex: www.google.com.uk -> google.com.uk as the domain name. | |
| * www.google.foo.uk -> foo.uk as the domain name. | |
| */ | |
| if(last_tld - (sec_last_tld+1) == 3) { | |
| for(i = 0; i < sizeof(recognized_tlds)/sizeof(recognized_tlds[0]); ++i) { | |
| if(!StrCmpNIW(sec_last_tld+1, recognized_tlds[i].tld_name, 3)) { | |
| const WCHAR *domain = memrchrW(host, '.', sec_last_tld-host); | |
| if(!domain) | |
| *domain_start = 0; | |
| else | |
| *domain_start = (domain+1) - host; | |
| TRACE("Found domain name %s\n", debugstr_wn(host+*domain_start, | |
| (host+host_len)-(host+*domain_start))); | |
| return; | |
| } | |
| } | |
| *domain_start = (sec_last_tld+1)-host; | |
| } else { | |
| /* Since the sec_last_tld is less than 3 characters it's considered | |
| * part of the TLD. | |
| * Ex: www.google.fo.uk -> google.fo.uk as the domain name. | |
| */ | |
| const WCHAR *domain = memrchrW(host, '.', sec_last_tld-host); | |
| if(!domain) | |
| *domain_start = 0; | |
| else | |
| *domain_start = (domain+1) - host; | |
| } | |
| } else { | |
| /* The second to last TLD has more than 3 characters making it | |
| * the domain name. | |
| * Ex: www.google.test.us -> test.us as the domain name. | |
| */ | |
| *domain_start = (sec_last_tld+1)-host; | |
| } | |
| TRACE("Found domain name %s\n", debugstr_wn(host+*domain_start, | |
| (host+host_len)-(host+*domain_start))); | |
| } | |
| /* Removes the dot segments from a hierarchical URIs path component. This | |
| * function performs the removal in place. | |
| * | |
| * This function returns the new length of the path string. | |
| */ | |
| static DWORD remove_dot_segments(WCHAR *path, DWORD path_len) { | |
| WCHAR *out = path; | |
| const WCHAR *in = out; | |
| const WCHAR *end = out + path_len; | |
| DWORD len; | |
| while(in < end) { | |
| /* Move the first path segment in the input buffer to the end of | |
| * the output buffer, and any subsequent characters up to, including | |
| * the next "/" character (if any) or the end of the input buffer. | |
| */ | |
| while(in < end && !is_slash(*in)) | |
| *out++ = *in++; | |
| if(in == end) | |
| break; | |
| *out++ = *in++; | |
| while(in < end) { | |
| if(*in != '.') | |
| break; | |
| /* Handle ending "/." */ | |
| if(in + 1 == end) { | |
| ++in; | |
| break; | |
| } | |
| /* Handle "/./" */ | |
| if(is_slash(in[1])) { | |
| in += 2; | |
| continue; | |
| } | |
| /* If we don't have "/../" or ending "/.." */ | |
| if(in[1] != '.' || (in + 2 != end && !is_slash(in[2]))) | |
| break; | |
| /* Find the slash preceding out pointer and move out pointer to it */ | |
| if(out > path+1 && is_slash(*--out)) | |
| --out; | |
| while(out > path && !is_slash(*(--out))); | |
| if(is_slash(*out)) | |
| ++out; | |
| in += 2; | |
| if(in != end) | |
| ++in; | |
| } | |
| } | |
| len = out - path; | |
| TRACE("(%p %d): Path after dot segments removed %s len=%d\n", path, path_len, | |
| debugstr_wn(path, len), len); | |
| return len; | |
| } | |
| /* Attempts to find the file extension in a given path. */ | |
| static INT find_file_extension(const WCHAR *path, DWORD path_len) { | |
| const WCHAR *end; | |
| for(end = path+path_len-1; end >= path && *end != '/' && *end != '\\'; --end) { | |
| if(*end == '.') | |
| return end-path; | |
| } | |
| return -1; | |
| } | |
| /* Computes the location where the elision should occur in the IPv6 | |
| * address using the numerical values of each component stored in | |
| * 'values'. If the address shouldn't contain an elision then 'index' | |
| * is assigned -1 as its value. Otherwise 'index' will contain the | |
| * starting index (into values) where the elision should be, and 'count' | |
| * will contain the number of cells the elision covers. | |
| * | |
| * NOTES: | |
| * Windows will expand an elision if the elision only represents one h16 | |
| * component of the address. | |
| * | |
| * Ex: [1::2:3:4:5:6:7] -> [1:0:2:3:4:5:6:7] | |
| * | |
| * If the IPv6 address contains an IPv4 address, the IPv4 address is also | |
| * considered for being included as part of an elision if all its components | |
| * are zeros. | |
| * | |
| * Ex: [1:2:3:4:5:6:0.0.0.0] -> [1:2:3:4:5:6::] | |
| */ | |
| static void compute_elision_location(const ipv6_address *address, const USHORT values[8], | |
| INT *index, DWORD *count) { | |
| DWORD i, max_len, cur_len; | |
| INT max_index, cur_index; | |
| max_len = cur_len = 0; | |
| max_index = cur_index = -1; | |
| for(i = 0; i < 8; ++i) { | |
| BOOL check_ipv4 = (address->ipv4 && i == 6); | |
| BOOL is_end = (check_ipv4 || i == 7); | |
| if(check_ipv4) { | |
| /* Check if the IPv4 address contains only zeros. */ | |
| if(values[i] == 0 && values[i+1] == 0) { | |
| if(cur_index == -1) | |
| cur_index = i; | |
| cur_len += 2; | |
| ++i; | |
| } | |
| } else if(values[i] == 0) { | |
| if(cur_index == -1) | |
| cur_index = i; | |
| ++cur_len; | |
| } | |
| if(is_end || values[i] != 0) { | |
| /* We only consider it for an elision if it's | |
| * more than 1 component long. | |
| */ | |
| if(cur_len > 1 && cur_len > max_len) { | |
| /* Found the new elision location. */ | |
| max_len = cur_len; | |
| max_index = cur_index; | |
| } | |
| /* Reset the current range for the next range of zeros. */ | |
| cur_index = -1; | |
| cur_len = 0; | |
| } | |
| } | |
| *index = max_index; | |
| *count = max_len; | |
| } | |
| /* Removes all the leading and trailing white spaces or | |
| * control characters from the URI and removes all control | |
| * characters inside of the URI string. | |
| */ | |
| static BSTR pre_process_uri(LPCWSTR uri) { | |
| const WCHAR *start, *end, *ptr; | |
| WCHAR *ptr2; | |
| DWORD len; | |
| BSTR ret; | |
| start = uri; | |
| /* Skip leading controls and whitespace. */ | |
| while(*start && (iscntrlW(*start) || isspaceW(*start))) ++start; | |
| /* URI consisted only of control/whitespace. */ | |
| if(!*start) | |
| return SysAllocStringLen(NULL, 0); | |
| end = start + strlenW(start); | |
| while(--end > start && (iscntrlW(*end) || isspaceW(*end))); | |
| len = ++end - start; | |
| for(ptr = start; ptr < end; ptr++) { | |
| if(iscntrlW(*ptr)) | |
| len--; | |
| } | |
| ret = SysAllocStringLen(NULL, len); | |
| if(!ret) | |
| return NULL; | |
| for(ptr = start, ptr2=ret; ptr < end; ptr++) { | |
| if(!iscntrlW(*ptr)) | |
| *ptr2++ = *ptr; | |
| } | |
| return ret; | |
| } | |
| /* Converts the specified IPv4 address into an uint value. | |
| * | |
| * This function assumes that the IPv4 address has already been validated. | |
| */ | |
| static UINT ipv4toui(const WCHAR *ip, DWORD len) { | |
| UINT ret = 0; | |
| DWORD comp_value = 0; | |
| const WCHAR *ptr; | |
| for(ptr = ip; ptr < ip+len; ++ptr) { | |
| if(*ptr == '.') { | |
| ret <<= 8; | |
| ret += comp_value; | |
| comp_value = 0; | |
| } else | |
| comp_value = comp_value*10 + (*ptr-'0'); | |
| } | |
| ret <<= 8; | |
| ret += comp_value; | |
| return ret; | |
| } | |
| /* Converts an IPv4 address in numerical form into its fully qualified | |
| * string form. This function returns the number of characters written | |
| * to 'dest'. If 'dest' is NULL this function will return the number of | |
| * characters that would have been written. | |
| * | |
| * It's up to the caller to ensure there's enough space in 'dest' for the | |
| * address. | |
| */ | |
| static DWORD ui2ipv4(WCHAR *dest, UINT address) { | |
| static const WCHAR formatW[] = | |
| {'%','u','.','%','u','.','%','u','.','%','u',0}; | |
| DWORD ret = 0; | |
| UCHAR digits[4]; | |
| digits[0] = (address >> 24) & 0xff; | |
| digits[1] = (address >> 16) & 0xff; | |
| digits[2] = (address >> 8) & 0xff; | |
| digits[3] = address & 0xff; | |
| if(!dest) { | |
| WCHAR tmp[16]; | |
| ret = sprintfW(tmp, formatW, digits[0], digits[1], digits[2], digits[3]); | |
| } else | |
| ret = sprintfW(dest, formatW, digits[0], digits[1], digits[2], digits[3]); | |
| return ret; | |
| } | |
| static DWORD ui2str(WCHAR *dest, UINT value) { | |
| static const WCHAR formatW[] = {'%','u',0}; | |
| DWORD ret = 0; | |
| if(!dest) { | |
| WCHAR tmp[11]; | |
| ret = sprintfW(tmp, formatW, value); | |
| } else | |
| ret = sprintfW(dest, formatW, value); | |
| return ret; | |
| } | |
| /* Converts a h16 component (from an IPv6 address) into its | |
| * numerical value. | |
| * | |
| * This function assumes that the h16 component has already been validated. | |
| */ | |
| static USHORT h16tous(h16 component) { | |
| DWORD i; | |
| USHORT ret = 0; | |
| for(i = 0; i < component.len; ++i) { | |
| ret <<= 4; | |
| ret += hex_to_int(component.str[i]); | |
| } | |
| return ret; | |
| } | |
| /* Converts an IPv6 address into its 128 bits (16 bytes) numerical value. | |
| * | |
| * This function assumes that the ipv6_address has already been validated. | |
| */ | |
| static BOOL ipv6_to_number(const ipv6_address *address, USHORT number[8]) { | |
| DWORD i, cur_component = 0; | |
| BOOL already_passed_elision = FALSE; | |
| for(i = 0; i < address->h16_count; ++i) { | |
| if(address->elision) { | |
| if(address->components[i].str > address->elision && !already_passed_elision) { | |
| /* Means we just passed the elision and need to add its values to | |
| * 'number' before we do anything else. | |
| */ | |
| INT j; | |
| for(j = 0; j < address->elision_size; j+=2) | |
| number[cur_component++] = 0; | |
| already_passed_elision = TRUE; | |
| } | |
| } | |
| number[cur_component++] = h16tous(address->components[i]); | |
| } | |
| /* Case when the elision appears after the h16 components. */ | |
| if(!already_passed_elision && address->elision) { | |
| INT j; | |
| for(j = 0; j < address->elision_size; j+=2) | |
| number[cur_component++] = 0; | |
| } | |
| if(address->ipv4) { | |
| UINT value = ipv4toui(address->ipv4, address->ipv4_len); | |
| if(cur_component != 6) { | |
| ERR("(%p %p): Failed sanity check with %d\n", address, number, cur_component); | |
| return FALSE; | |
| } | |
| number[cur_component++] = (value >> 16) & 0xffff; | |
| number[cur_component] = value & 0xffff; | |
| } | |
| return TRUE; | |
| } | |
| /* Checks if the characters pointed to by 'ptr' are | |
| * a percent encoded data octet. | |
| * | |
| * pct-encoded = "%" HEXDIG HEXDIG | |
| */ | |
| static BOOL check_pct_encoded(const WCHAR **ptr) { | |
| const WCHAR *start = *ptr; | |
| if(**ptr != '%') | |
| return FALSE; | |
| ++(*ptr); | |
| if(!is_hexdigit(**ptr)) { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| ++(*ptr); | |
| if(!is_hexdigit(**ptr)) { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| ++(*ptr); | |
| return TRUE; | |
| } | |
| /* dec-octet = DIGIT ; 0-9 | |
| * / %x31-39 DIGIT ; 10-99 | |
| * / "1" 2DIGIT ; 100-199 | |
| * / "2" %x30-34 DIGIT ; 200-249 | |
| * / "25" %x30-35 ; 250-255 | |
| */ | |
| static BOOL check_dec_octet(const WCHAR **ptr) { | |
| const WCHAR *c1, *c2, *c3; | |
| c1 = *ptr; | |
| /* A dec-octet must be at least 1 digit long. */ | |
| if(*c1 < '0' || *c1 > '9') | |
| return FALSE; | |
| ++(*ptr); | |
| c2 = *ptr; | |
| /* Since the 1-digit requirement was met, it doesn't | |
| * matter if this is a DIGIT value, it's considered a | |
| * dec-octet. | |
| */ | |
| if(*c2 < '0' || *c2 > '9') | |
| return TRUE; | |
| ++(*ptr); | |
| c3 = *ptr; | |
| /* Same explanation as above. */ | |
| if(*c3 < '0' || *c3 > '9') | |
| return TRUE; | |
| /* Anything > 255 isn't a valid IP dec-octet. */ | |
| if(*c1 >= '2' && *c2 >= '5' && *c3 >= '5') { | |
| *ptr = c1; | |
| return FALSE; | |
| } | |
| ++(*ptr); | |
| return TRUE; | |
| } | |
| /* Checks if there is an implicit IPv4 address in the host component of the URI. | |
| * The max value of an implicit IPv4 address is UINT_MAX. | |
| * | |
| * Ex: | |
| * "234567" would be considered an implicit IPv4 address. | |
| */ | |
| static BOOL check_implicit_ipv4(const WCHAR **ptr, UINT *val) { | |
| const WCHAR *start = *ptr; | |
| ULONGLONG ret = 0; | |
| *val = 0; | |
| while(is_num(**ptr)) { | |
| ret = ret*10 + (**ptr - '0'); | |
| if(ret > UINT_MAX) { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| ++(*ptr); | |
| } | |
| if(*ptr == start) | |
| return FALSE; | |
| *val = ret; | |
| return TRUE; | |
| } | |
| /* Checks if the string contains an IPv4 address. | |
| * | |
| * This function has a strict mode or a non-strict mode of operation | |
| * When 'strict' is set to FALSE this function will return TRUE if | |
| * the string contains at least 'dec-octet "." dec-octet' since partial | |
| * IPv4 addresses will be normalized out into full IPv4 addresses. When | |
| * 'strict' is set this function expects there to be a full IPv4 address. | |
| * | |
| * IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet | |
| */ | |
| static BOOL check_ipv4address(const WCHAR **ptr, BOOL strict) { | |
| const WCHAR *start = *ptr; | |
| if(!check_dec_octet(ptr)) { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| if(**ptr != '.') { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| ++(*ptr); | |
| if(!check_dec_octet(ptr)) { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| if(**ptr != '.') { | |
| if(strict) { | |
| *ptr = start; | |
| return FALSE; | |
| } else | |
| return TRUE; | |
| } | |
| ++(*ptr); | |
| if(!check_dec_octet(ptr)) { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| if(**ptr != '.') { | |
| if(strict) { | |
| *ptr = start; | |
| return FALSE; | |
| } else | |
| return TRUE; | |
| } | |
| ++(*ptr); | |
| if(!check_dec_octet(ptr)) { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| /* Found a four digit ip address. */ | |
| return TRUE; | |
| } | |
| /* Tries to parse the scheme name of the URI. | |
| * | |
| * scheme = ALPHA *(ALPHA | NUM | '+' | '-' | '.') as defined by RFC 3896. | |
| * NOTE: Windows accepts a number as the first character of a scheme. | |
| */ | |
| static BOOL parse_scheme_name(const WCHAR **ptr, parse_data *data, DWORD extras) { | |
| const WCHAR *start = *ptr; | |
| data->scheme = NULL; | |
| data->scheme_len = 0; | |
| while(**ptr) { | |
| if(**ptr == '*' && *ptr == start) { | |
| /* Might have found a wildcard scheme. If it is the next | |
| * char has to be a ':' for it to be a valid URI | |
| */ | |
| ++(*ptr); | |
| break; | |
| } else if(!is_num(**ptr) && !is_alpha(**ptr) && **ptr != '+' && | |
| **ptr != '-' && **ptr != '.') | |
| break; | |
| (*ptr)++; | |
| } | |
| if(*ptr == start) | |
| return FALSE; | |
| /* Schemes must end with a ':' */ | |
| if(**ptr != ':' && !((extras & ALLOW_NULL_TERM_SCHEME) && !**ptr)) { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| data->scheme = start; | |
| data->scheme_len = *ptr - start; | |
| ++(*ptr); | |
| return TRUE; | |
| } | |
| /* Tries to deduce the corresponding URL_SCHEME for the given URI. Stores | |
| * the deduced URL_SCHEME in data->scheme_type. | |
| */ | |
| static BOOL parse_scheme_type(parse_data *data) { | |
| /* If there's scheme data then see if it's a recognized scheme. */ | |
| if(data->scheme && data->scheme_len) { | |
| DWORD i; | |
| for(i = 0; i < sizeof(recognized_schemes)/sizeof(recognized_schemes[0]); ++i) { | |
| if(lstrlenW(recognized_schemes[i].scheme_name) == data->scheme_len) { | |
| /* Has to be a case insensitive compare. */ | |
| if(!StrCmpNIW(recognized_schemes[i].scheme_name, data->scheme, data->scheme_len)) { | |
| data->scheme_type = recognized_schemes[i].scheme; | |
| return TRUE; | |
| } | |
| } | |
| } | |
| /* If we get here it means it's not a recognized scheme. */ | |
| data->scheme_type = URL_SCHEME_UNKNOWN; | |
| return TRUE; | |
| } else if(data->is_relative) { | |
| /* Relative URI's have no scheme. */ | |
| data->scheme_type = URL_SCHEME_UNKNOWN; | |
| return TRUE; | |
| } else { | |
| /* Should never reach here! what happened... */ | |
| FIXME("(%p): Unable to determine scheme type for URI %s\n", data, debugstr_w(data->uri)); | |
| return FALSE; | |
| } | |
| } | |
| /* Tries to parse (or deduce) the scheme_name of a URI. If it can't | |
| * parse a scheme from the URI it will try to deduce the scheme_name and scheme_type | |
| * using the flags specified in 'flags' (if any). Flags that affect how this function | |
| * operates are the Uri_CREATE_ALLOW_* flags. | |
| * | |
| * All parsed/deduced information will be stored in 'data' when the function returns. | |
| * | |
| * Returns TRUE if it was able to successfully parse the information. | |
| */ | |
| static BOOL parse_scheme(const WCHAR **ptr, parse_data *data, DWORD flags, DWORD extras) { | |
| static const WCHAR fileW[] = {'f','i','l','e',0}; | |
| static const WCHAR wildcardW[] = {'*',0}; | |
| /* First check to see if the uri could implicitly be a file path. */ | |
| if(is_implicit_file_path(*ptr)) { | |
| if(flags & Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME) { | |
| data->scheme = fileW; | |
| data->scheme_len = lstrlenW(fileW); | |
| data->has_implicit_scheme = TRUE; | |
| TRACE("(%p %p %x): URI is an implicit file path.\n", ptr, data, flags); | |
| } else { | |
| /* Windows does not consider anything that can implicitly be a file | |
| * path to be a valid URI if the ALLOW_IMPLICIT_FILE_SCHEME flag is not set... | |
| */ | |
| TRACE("(%p %p %x): URI is implicitly a file path, but, the ALLOW_IMPLICIT_FILE_SCHEME flag wasn't set.\n", | |
| ptr, data, flags); | |
| return FALSE; | |
| } | |
| } else if(!parse_scheme_name(ptr, data, extras)) { | |
| /* No scheme was found, this means it could be: | |
| * a) an implicit Wildcard scheme | |
| * b) a relative URI | |
| * c) an invalid URI. | |
| */ | |
| if(flags & Uri_CREATE_ALLOW_IMPLICIT_WILDCARD_SCHEME) { | |
| data->scheme = wildcardW; | |
| data->scheme_len = lstrlenW(wildcardW); | |
| data->has_implicit_scheme = TRUE; | |
| TRACE("(%p %p %x): URI is an implicit wildcard scheme.\n", ptr, data, flags); | |
| } else if (flags & Uri_CREATE_ALLOW_RELATIVE) { | |
| data->is_relative = TRUE; | |
| TRACE("(%p %p %x): URI is relative.\n", ptr, data, flags); | |
| } else { | |
| TRACE("(%p %p %x): Malformed URI found. Unable to deduce scheme name.\n", ptr, data, flags); | |
| return FALSE; | |
| } | |
| } | |
| if(!data->is_relative) | |
| TRACE("(%p %p %x): Found scheme=%s scheme_len=%d\n", ptr, data, flags, | |
| debugstr_wn(data->scheme, data->scheme_len), data->scheme_len); | |
| if(!parse_scheme_type(data)) | |
| return FALSE; | |
| TRACE("(%p %p %x): Assigned %d as the URL_SCHEME.\n", ptr, data, flags, data->scheme_type); | |
| return TRUE; | |
| } | |
| static BOOL parse_username(const WCHAR **ptr, parse_data *data, DWORD flags, DWORD extras) { | |
| data->username = *ptr; | |
| while(**ptr != ':' && **ptr != '@') { | |
| if(**ptr == '%') { | |
| if(!check_pct_encoded(ptr)) { | |
| if(data->scheme_type != URL_SCHEME_UNKNOWN) { | |
| *ptr = data->username; | |
| data->username = NULL; | |
| return FALSE; | |
| } | |
| } else | |
| continue; | |
| } else if(extras & ALLOW_NULL_TERM_USER_NAME && !**ptr) | |
| break; | |
| else if(is_auth_delim(**ptr, data->scheme_type != URL_SCHEME_UNKNOWN)) { | |
| *ptr = data->username; | |
| data->username = NULL; | |
| return FALSE; | |
| } | |
| ++(*ptr); | |
| } | |
| data->username_len = *ptr - data->username; | |
| return TRUE; | |
| } | |
| static BOOL parse_password(const WCHAR **ptr, parse_data *data, DWORD flags, DWORD extras) { | |
| data->password = *ptr; | |
| while(**ptr != '@') { | |
| if(**ptr == '%') { | |
| if(!check_pct_encoded(ptr)) { | |
| if(data->scheme_type != URL_SCHEME_UNKNOWN) { | |
| *ptr = data->password; | |
| data->password = NULL; | |
| return FALSE; | |
| } | |
| } else | |
| continue; | |
| } else if(extras & ALLOW_NULL_TERM_PASSWORD && !**ptr) | |
| break; | |
| else if(is_auth_delim(**ptr, data->scheme_type != URL_SCHEME_UNKNOWN)) { | |
| *ptr = data->password; | |
| data->password = NULL; | |
| return FALSE; | |
| } | |
| ++(*ptr); | |
| } | |
| data->password_len = *ptr - data->password; | |
| return TRUE; | |
| } | |
| /* Parses the userinfo part of the URI (if it exists). The userinfo field of | |
| * a URI can consist of "username:password@", or just "username@". | |
| * | |
| * RFC def: | |
| * userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) | |
| * | |
| * NOTES: | |
| * 1) If there is more than one ':' in the userinfo part of the URI Windows | |
| * uses the first occurrence of ':' to delimit the username and password | |
| * components. | |
| * | |
| * ex: | |
| * ftp://user:pass:word@winehq.org | |
| * | |
| * would yield "user" as the username and "pass:word" as the password. | |
| * | |
| * 2) Windows allows any character to appear in the "userinfo" part of | |
| * a URI, as long as it's not an authority delimiter character set. | |
| */ | |
| static void parse_userinfo(const WCHAR **ptr, parse_data *data, DWORD flags) { | |
| const WCHAR *start = *ptr; | |
| if(!parse_username(ptr, data, flags, 0)) { | |
| TRACE("(%p %p %x): URI contained no userinfo.\n", ptr, data, flags); | |
| return; | |
| } | |
| if(**ptr == ':') { | |
| ++(*ptr); | |
| if(!parse_password(ptr, data, flags, 0)) { | |
| *ptr = start; | |
| data->username = NULL; | |
| data->username_len = 0; | |
| TRACE("(%p %p %x): URI contained no userinfo.\n", ptr, data, flags); | |
| return; | |
| } | |
| } | |
| if(**ptr != '@') { | |
| *ptr = start; | |
| data->username = NULL; | |
| data->username_len = 0; | |
| data->password = NULL; | |
| data->password_len = 0; | |
| TRACE("(%p %p %x): URI contained no userinfo.\n", ptr, data, flags); | |
| return; | |
| } | |
| if(data->username) | |
| TRACE("(%p %p %x): Found username %s len=%d.\n", ptr, data, flags, | |
| debugstr_wn(data->username, data->username_len), data->username_len); | |
| if(data->password) | |
| TRACE("(%p %p %x): Found password %s len=%d.\n", ptr, data, flags, | |
| debugstr_wn(data->password, data->password_len), data->password_len); | |
| ++(*ptr); | |
| } | |
| /* Attempts to parse a port from the URI. | |
| * | |
| * NOTES: | |
| * Windows seems to have a cap on what the maximum value | |
| * for a port can be. The max value is USHORT_MAX. | |
| * | |
| * port = *DIGIT | |
| */ | |
| static BOOL parse_port(const WCHAR **ptr, parse_data *data, DWORD flags) { | |
| UINT port = 0; | |
| data->port = *ptr; | |
| while(!is_auth_delim(**ptr, data->scheme_type != URL_SCHEME_UNKNOWN)) { | |
| if(!is_num(**ptr)) { | |
| *ptr = data->port; | |
| data->port = NULL; | |
| return FALSE; | |
| } | |
| port = port*10 + (**ptr-'0'); | |
| if(port > USHRT_MAX) { | |
| *ptr = data->port; | |
| data->port = NULL; | |
| return FALSE; | |
| } | |
| ++(*ptr); | |
| } | |
| data->has_port = TRUE; | |
| data->port_value = port; | |
| data->port_len = *ptr - data->port; | |
| TRACE("(%p %p %x): Found port %s len=%d value=%u\n", ptr, data, flags, | |
| debugstr_wn(data->port, data->port_len), data->port_len, data->port_value); | |
| return TRUE; | |
| } | |
| /* Attempts to parse a IPv4 address from the URI. | |
| * | |
| * NOTES: | |
| * Windows normalizes IPv4 addresses, This means there are three | |
| * possibilities for the URI to contain an IPv4 address. | |
| * 1) A well formed address (ex. 192.2.2.2). | |
| * 2) A partially formed address. For example "192.0" would | |
| * normalize to "192.0.0.0" during canonicalization. | |
| * 3) An implicit IPv4 address. For example "256" would | |
| * normalize to "0.0.1.0" during canonicalization. Also | |
| * note that the maximum value for an implicit IP address | |
| * is UINT_MAX, if the value in the URI exceeds this then | |
| * it is not considered an IPv4 address. | |
| */ | |
| static BOOL parse_ipv4address(const WCHAR **ptr, parse_data *data, DWORD flags) { | |
| const BOOL is_unknown = data->scheme_type == URL_SCHEME_UNKNOWN; | |
| data->host = *ptr; | |
| if(!check_ipv4address(ptr, FALSE)) { | |
| if(!check_implicit_ipv4(ptr, &data->implicit_ipv4)) { | |
| TRACE("(%p %p %x): URI didn't contain anything looking like an IPv4 address.\n", | |
| ptr, data, flags); | |
| *ptr = data->host; | |
| data->host = NULL; | |
| return FALSE; | |
| } else | |
| data->has_implicit_ip = TRUE; | |
| } | |
| data->host_len = *ptr - data->host; | |
| data->host_type = Uri_HOST_IPV4; | |
| /* Check if what we found is the only part of the host name (if it isn't | |
| * we don't have an IPv4 address). | |
| */ | |
| if(**ptr == ':') { | |
| ++(*ptr); | |
| if(!parse_port(ptr, data, flags)) { | |
| *ptr = data->host; | |
| data->host = NULL; | |
| return FALSE; | |
| } | |
| } else if(!is_auth_delim(**ptr, !is_unknown)) { | |
| /* Found more data which belongs to the host, so this isn't an IPv4. */ | |
| *ptr = data->host; | |
| data->host = NULL; | |
| data->has_implicit_ip = FALSE; | |
| return FALSE; | |
| } | |
| TRACE("(%p %p %x): IPv4 address found. host=%s host_len=%d host_type=%d\n", | |
| ptr, data, flags, debugstr_wn(data->host, data->host_len), | |
| data->host_len, data->host_type); | |
| return TRUE; | |
| } | |
| /* Attempts to parse the reg-name from the URI. | |
| * | |
| * Because of the way Windows handles ':' this function also | |
| * handles parsing the port. | |
| * | |
| * reg-name = *( unreserved / pct-encoded / sub-delims ) | |
| * | |
| * NOTE: | |
| * Windows allows everything, but, the characters in "auth_delims" and ':' | |
| * to appear in a reg-name, unless it's an unknown scheme type then ':' is | |
| * allowed to appear (even if a valid port isn't after it). | |
| * | |
| * Windows doesn't like host names which start with '[' and end with ']' | |
| * and don't contain a valid IP literal address in between them. | |
| * | |
| * On Windows if a '[' is encountered in the host name the ':' no longer | |
| * counts as a delimiter until you reach the next ']' or an "authority delimiter". | |
| * | |
| * A reg-name CAN be empty. | |
| */ | |
| static BOOL parse_reg_name(const WCHAR **ptr, parse_data *data, DWORD flags, DWORD extras) { | |
| const BOOL has_start_bracket = **ptr == '['; | |
| const BOOL known_scheme = data->scheme_type != URL_SCHEME_UNKNOWN; | |
| const BOOL is_res = data->scheme_type == URL_SCHEME_RES; | |
| BOOL inside_brackets = has_start_bracket; | |
| /* res URIs don't have ports. */ | |
| BOOL ignore_col = (extras & IGNORE_PORT_DELIMITER) || is_res; | |
| /* We have to be careful with file schemes. */ | |
| if(data->scheme_type == URL_SCHEME_FILE) { | |
| /* This is because an implicit file scheme could be "C:\\test" and it | |
| * would trick this function into thinking the host is "C", when after | |
| * canonicalization the host would end up being an empty string. A drive | |
| * path can also have a '|' instead of a ':' after the drive letter. | |
| */ | |
| if(is_drive_path(*ptr)) { | |
| /* Regular old drive paths have no host type (or host name). */ | |
| data->host_type = Uri_HOST_UNKNOWN; | |
| data->host = *ptr; | |
| data->host_len = 0; | |
| return TRUE; | |
| } else if(is_unc_path(*ptr)) | |
| /* Skip past the "\\" of a UNC path. */ | |
| *ptr += 2; | |
| } | |
| data->host = *ptr; | |
| /* For res URIs, everything before the first '/' is | |
| * considered the host. | |
| */ | |
| while((!is_res && !is_auth_delim(**ptr, known_scheme)) || | |
| (is_res && **ptr && **ptr != '/')) { | |
| if(**ptr == ':' && !ignore_col) { | |
| /* We can ignore ':' if we are inside brackets.*/ | |
| if(!inside_brackets) { | |
| const WCHAR *tmp = (*ptr)++; | |
| /* Attempt to parse the port. */ | |
| if(!parse_port(ptr, data, flags)) { | |
| /* Windows expects there to be a valid port for known scheme types. */ | |
| if(data->scheme_type != URL_SCHEME_UNKNOWN) { | |
| *ptr = data->host; | |
| data->host = NULL; | |
| TRACE("(%p %p %x %x): Expected valid port\n", ptr, data, flags, extras); | |
| return FALSE; | |
| } else | |
| /* Windows gives up on trying to parse a port when it | |
| * encounters an invalid port. | |
| */ | |
| ignore_col = TRUE; | |
| } else { | |
| data->host_len = tmp - data->host; | |
| break; | |
| } | |
| } | |
| } else if(**ptr == '%' && (known_scheme && !is_res)) { | |
| /* Has to be a legit % encoded value. */ | |
| if(!check_pct_encoded(ptr)) { | |
| *ptr = data->host; | |
| data->host = NULL; | |
| return FALSE; | |
| } else | |
| continue; | |
| } else if(is_res && is_forbidden_dos_path_char(**ptr)) { | |
| *ptr = data->host; | |
| data->host = NULL; | |
| return FALSE; | |
| } else if(**ptr == ']') | |
| inside_brackets = FALSE; | |
| else if(**ptr == '[') | |
| inside_brackets = TRUE; | |
| ++(*ptr); | |
| } | |
| if(has_start_bracket) { | |
| /* Make sure the last character of the host wasn't a ']'. */ | |
| if(*(*ptr-1) == ']') { | |
| TRACE("(%p %p %x %x): Expected an IP literal inside of the host\n", | |
| ptr, data, flags, extras); | |
| *ptr = data->host; | |
| data->host = NULL; | |
| return FALSE; | |
| } | |
| } | |
| /* Don't overwrite our length if we found a port earlier. */ | |
| if(!data->port) | |
| data->host_len = *ptr - data->host; | |
| /* If the host is empty, then it's an unknown host type. */ | |
| if(data->host_len == 0 || is_res) | |
| data->host_type = Uri_HOST_UNKNOWN; | |
| else | |
| data->host_type = Uri_HOST_DNS; | |
| TRACE("(%p %p %x %x): Parsed reg-name. host=%s len=%d\n", ptr, data, flags, extras, | |
| debugstr_wn(data->host, data->host_len), data->host_len); | |
| return TRUE; | |
| } | |
| /* Attempts to parse an IPv6 address out of the URI. | |
| * | |
| * IPv6address = 6( h16 ":" ) ls32 | |
| * / "::" 5( h16 ":" ) ls32 | |
| * / [ h16 ] "::" 4( h16 ":" ) ls32 | |
| * / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 | |
| * / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 | |
| * / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 | |
| * / [ *4( h16 ":" ) h16 ] "::" ls32 | |
| * / [ *5( h16 ":" ) h16 ] "::" h16 | |
| * / [ *6( h16 ":" ) h16 ] "::" | |
| * | |
| * ls32 = ( h16 ":" h16 ) / IPv4address | |
| * ; least-significant 32 bits of address. | |
| * | |
| * h16 = 1*4HEXDIG | |
| * ; 16 bits of address represented in hexadecimal. | |
| * | |
| * Modeled after google-url's 'DoParseIPv6' function. | |
| */ | |
| static BOOL parse_ipv6address(const WCHAR **ptr, parse_data *data, DWORD flags) { | |
| const WCHAR *start, *cur_start; | |
| ipv6_address ip; | |
| start = cur_start = *ptr; | |
| memset(&ip, 0, sizeof(ipv6_address)); | |
| for(;; ++(*ptr)) { | |
| /* Check if we're on the last character of the host. */ | |
| BOOL is_end = (is_auth_delim(**ptr, data->scheme_type != URL_SCHEME_UNKNOWN) | |
| || **ptr == ']'); | |
| BOOL is_split = (**ptr == ':'); | |
| BOOL is_elision = (is_split && !is_end && *(*ptr+1) == ':'); | |
| /* Check if we're at the end of a component, or | |
| * if we're at the end of the IPv6 address. | |
| */ | |
| if(is_split || is_end) { | |
| DWORD cur_len = 0; | |
| cur_len = *ptr - cur_start; | |
| /* h16 can't have a length > 4. */ | |
| if(cur_len > 4) { | |
| *ptr = start; | |
| TRACE("(%p %p %x): h16 component to long.\n", | |
| ptr, data, flags); | |
| return FALSE; | |
| } | |
| if(cur_len == 0) { | |
| /* An h16 component can't have the length of 0 unless | |
| * the elision is at the beginning of the address, or | |
| * at the end of the address. | |
| */ | |
| if(!((*ptr == start && is_elision) || | |
| (is_end && (*ptr-2) == ip.elision))) { | |
| *ptr = start; | |
| TRACE("(%p %p %x): IPv6 component cannot have a length of 0.\n", | |
| ptr, data, flags); | |
| return FALSE; | |
| } | |
| } | |
| if(cur_len > 0) { | |
| /* An IPv6 address can have no more than 8 h16 components. */ | |
| if(ip.h16_count >= 8) { | |
| *ptr = start; | |
| TRACE("(%p %p %x): Not a IPv6 address, too many h16 components.\n", | |
| ptr, data, flags); | |
| return FALSE; | |
| } | |
| ip.components[ip.h16_count].str = cur_start; | |
| ip.components[ip.h16_count].len = cur_len; | |
| TRACE("(%p %p %x): Found h16 component %s, len=%d, h16_count=%d\n", | |
| ptr, data, flags, debugstr_wn(cur_start, cur_len), cur_len, | |
| ip.h16_count); | |
| ++ip.h16_count; | |
| } | |
| } | |
| if(is_end) | |
| break; | |
| if(is_elision) { | |
| /* A IPv6 address can only have 1 elision ('::'). */ | |
| if(ip.elision) { | |
| *ptr = start; | |
| TRACE("(%p %p %x): IPv6 address cannot have 2 elisions.\n", | |
| ptr, data, flags); | |
| return FALSE; | |
| } | |
| ip.elision = *ptr; | |
| ++(*ptr); | |
| } | |
| if(is_split) | |
| cur_start = *ptr+1; | |
| else { | |
| if(!check_ipv4address(ptr, TRUE)) { | |
| if(!is_hexdigit(**ptr)) { | |
| /* Not a valid character for an IPv6 address. */ | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| } else { | |
| /* Found an IPv4 address. */ | |
| ip.ipv4 = cur_start; | |
| ip.ipv4_len = *ptr - cur_start; | |
| TRACE("(%p %p %x): Found an attached IPv4 address %s len=%d.\n", | |
| ptr, data, flags, debugstr_wn(ip.ipv4, ip.ipv4_len), | |
| ip.ipv4_len); | |
| /* IPv4 addresses can only appear at the end of a IPv6. */ | |
| break; | |
| } | |
| } | |
| } | |
| compute_ipv6_comps_size(&ip); | |
| /* Make sure the IPv6 address adds up to 16 bytes. */ | |
| if(ip.components_size + ip.elision_size != 16) { | |
| *ptr = start; | |
| TRACE("(%p %p %x): Invalid IPv6 address, did not add up to 16 bytes.\n", | |
| ptr, data, flags); | |
| return FALSE; | |
| } | |
| if(ip.elision_size == 2) { | |
| /* For some reason on Windows if an elision that represents | |
| * only one h16 component is encountered at the very begin or | |
| * end of an IPv6 address, Windows does not consider it a | |
| * valid IPv6 address. | |
| * | |
| * Ex: [::2:3:4:5:6:7] is not valid, even though the sum | |
| * of all the components == 128bits. | |
| */ | |
| if(ip.elision < ip.components[0].str || | |
| ip.elision > ip.components[ip.h16_count-1].str) { | |
| *ptr = start; | |
| TRACE("(%p %p %x): Invalid IPv6 address. Detected elision of 2 bytes at the beginning or end of the address.\n", | |
| ptr, data, flags); | |
| return FALSE; | |
| } | |
| } | |
| data->host_type = Uri_HOST_IPV6; | |
| data->has_ipv6 = TRUE; | |
| data->ipv6_address = ip; | |
| TRACE("(%p %p %x): Found valid IPv6 literal %s len=%d\n", | |
| ptr, data, flags, debugstr_wn(start, *ptr-start), | |
| (int)(*ptr-start)); | |
| return TRUE; | |
| } | |
| /* IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) */ | |
| static BOOL parse_ipvfuture(const WCHAR **ptr, parse_data *data, DWORD flags) { | |
| const WCHAR *start = *ptr; | |
| /* IPvFuture has to start with a 'v' or 'V'. */ | |
| if(**ptr != 'v' && **ptr != 'V') | |
| return FALSE; | |
| /* Following the v there must be at least 1 hex digit. */ | |
| ++(*ptr); | |
| if(!is_hexdigit(**ptr)) { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| ++(*ptr); | |
| while(is_hexdigit(**ptr)) | |
| ++(*ptr); | |
| /* End of the hexdigit sequence must be a '.' */ | |
| if(**ptr != '.') { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| ++(*ptr); | |
| if(!is_unreserved(**ptr) && !is_subdelim(**ptr) && **ptr != ':') { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| ++(*ptr); | |
| while(is_unreserved(**ptr) || is_subdelim(**ptr) || **ptr == ':') | |
| ++(*ptr); | |
| data->host_type = Uri_HOST_UNKNOWN; | |
| TRACE("(%p %p %x): Parsed IPvFuture address %s len=%d\n", ptr, data, flags, | |
| debugstr_wn(start, *ptr-start), (int)(*ptr-start)); | |
| return TRUE; | |
| } | |
| /* IP-literal = "[" ( IPv6address / IPvFuture ) "]" */ | |
| static BOOL parse_ip_literal(const WCHAR **ptr, parse_data *data, DWORD flags, DWORD extras) { | |
| data->host = *ptr; | |
| if(**ptr != '[' && !(extras & ALLOW_BRACKETLESS_IP_LITERAL)) { | |
| data->host = NULL; | |
| return FALSE; | |
| } else if(**ptr == '[') | |
| ++(*ptr); | |
| if(!parse_ipv6address(ptr, data, flags)) { | |
| if(extras & SKIP_IP_FUTURE_CHECK || !parse_ipvfuture(ptr, data, flags)) { | |
| *ptr = data->host; | |
| data->host = NULL; | |
| return FALSE; | |
| } | |
| } | |
| if(**ptr != ']' && !(extras & ALLOW_BRACKETLESS_IP_LITERAL)) { | |
| *ptr = data->host; | |
| data->host = NULL; | |
| return FALSE; | |
| } else if(!**ptr && extras & ALLOW_BRACKETLESS_IP_LITERAL) { | |
| /* The IP literal didn't contain brackets and was followed by | |
| * a NULL terminator, so no reason to even check the port. | |
| */ | |
| data->host_len = *ptr - data->host; | |
| return TRUE; | |
| } | |
| ++(*ptr); | |
| if(**ptr == ':') { | |
| ++(*ptr); | |
| /* If a valid port is not found, then let it trickle down to | |
| * parse_reg_name. | |
| */ | |
| if(!parse_port(ptr, data, flags)) { | |
| *ptr = data->host; | |
| data->host = NULL; | |
| return FALSE; | |
| } | |
| } else | |
| data->host_len = *ptr - data->host; | |
| return TRUE; | |
| } | |
| /* Parses the host information from the URI. | |
| * | |
| * host = IP-literal / IPv4address / reg-name | |
| */ | |
| static BOOL parse_host(const WCHAR **ptr, parse_data *data, DWORD flags, DWORD extras) { | |
| if(!parse_ip_literal(ptr, data, flags, extras)) { | |
| if(!parse_ipv4address(ptr, data, flags)) { | |
| if(!parse_reg_name(ptr, data, flags, extras)) { | |
| TRACE("(%p %p %x %x): Malformed URI, Unknown host type.\n", | |
| ptr, data, flags, extras); | |
| return FALSE; | |
| } | |
| } | |
| } | |
| return TRUE; | |
| } | |
| /* Parses the authority information from the URI. | |
| * | |
| * authority = [ userinfo "@" ] host [ ":" port ] | |
| */ | |
| static BOOL parse_authority(const WCHAR **ptr, parse_data *data, DWORD flags) { | |
| parse_userinfo(ptr, data, flags); | |
| /* Parsing the port will happen during one of the host parsing | |
| * routines (if the URI has a port). | |
| */ | |
| if(!parse_host(ptr, data, flags, 0)) | |
| return FALSE; | |
| return TRUE; | |
| } | |
| /* Attempts to parse the path information of a hierarchical URI. */ | |
| static BOOL parse_path_hierarchical(const WCHAR **ptr, parse_data *data, DWORD flags) { | |
| const WCHAR *start = *ptr; | |
| static const WCHAR slash[] = {'/',0}; | |
| const BOOL is_file = data->scheme_type == URL_SCHEME_FILE; | |
| if(is_path_delim(data->scheme_type, **ptr)) { | |
| if(data->scheme_type == URL_SCHEME_WILDCARD && !data->must_have_path) { | |
| data->path = NULL; | |
| data->path_len = 0; | |
| } else if(!(flags & Uri_CREATE_NO_CANONICALIZE)) { | |
| /* If the path component is empty, then a '/' is added. */ | |
| data->path = slash; | |
| data->path_len = 1; | |
| } | |
| } else { | |
| while(!is_path_delim(data->scheme_type, **ptr)) { | |
| if(**ptr == '%' && data->scheme_type != URL_SCHEME_UNKNOWN && !is_file) { | |
| if(!check_pct_encoded(ptr)) { | |
| *ptr = start; | |
| return FALSE; | |
| } else | |
| continue; | |
| } else if(is_forbidden_dos_path_char(**ptr) && is_file && | |
| (flags & Uri_CREATE_FILE_USE_DOS_PATH)) { | |
| /* File schemes with USE_DOS_PATH set aren't allowed to have | |
| * a '<' or '>' or '\"' appear in them. | |
| */ | |
| *ptr = start; | |
| return FALSE; | |
| } else if(**ptr == '\\') { | |
| /* Not allowed to have a backslash if NO_CANONICALIZE is set | |
| * and the scheme is known type (but not a file scheme). | |
| */ | |
| if(flags & Uri_CREATE_NO_CANONICALIZE) { | |
| if(data->scheme_type != URL_SCHEME_FILE && | |
| data->scheme_type != URL_SCHEME_UNKNOWN) { | |
| *ptr = start; | |
| return FALSE; | |
| } | |
| } | |
| } | |
| ++(*ptr); | |
| } | |
| /* The only time a URI doesn't have a path is when | |
| * the NO_CANONICALIZE flag is set and the raw URI | |
| * didn't contain one. | |
| */ | |
| if(*ptr == start) { | |
| data->path = NULL; | |
| data->path_len = 0; | |
| } else { | |
| data->path = start; | |
| data->path_len = *ptr - start; | |
| } | |
| } | |
| if(data->path) | |
| TRACE("(%p %p %x): Parsed path %s len=%d\n", ptr, data, flags, | |
| debugstr_wn(data->path, data->path_len), data->path_len); | |
| else | |
| TRACE("(%p %p %x): The URI contained no path\n", ptr, data, flags); | |
| return TRUE; | |
| } | |
| /* Parses the path of an opaque URI (much less strict than the parser | |
| * for a hierarchical URI). | |
| * | |
| * NOTE: | |
| * Windows allows invalid % encoded data to appear in opaque URI paths | |
| * for unknown scheme types. | |
| * | |
| * File schemes with USE_DOS_PATH set aren't allowed to have '<', '>', or '\"' | |
| * appear in them. | |
| */ | |
| static BOOL parse_path_opaque(const WCHAR **ptr, parse_data *data, DWORD flags) { | |
| const BOOL known_scheme = data->scheme_type != URL_SCHEME_UNKNOWN; | |
| const BOOL is_file = data->scheme_type == URL_SCHEME_FILE; | |
| const BOOL is_mailto = data->scheme_type == URL_SCHEME_MAILTO; | |
| if (is_mailto && (*ptr)[0] == '/' && (*ptr)[1] == '/') | |
| { | |
| if ((*ptr)[2]) data->path = *ptr + 2; | |
| else data->path = NULL; | |
| } | |
| else | |
| data->path = *ptr; | |
| while(!is_path_delim(data->scheme_type, **ptr)) { | |
| if(**ptr == '%' && known_scheme) { | |
| if(!check_pct_encoded(ptr)) { | |
| *ptr = data->path; | |
| data->path = NULL; | |
| return FALSE; | |
| } else | |
| continue; | |
| } else if(is_forbidden_dos_path_char(**ptr) && is_file && | |
| (flags & Uri_CREATE_FILE_USE_DOS_PATH)) { | |
| *ptr = data->path; | |
| data->path = NULL; | |
| return FALSE; | |
| } | |
| ++(*ptr); | |
| } | |
| if (data->path) data->path_len = *ptr - data->path; | |
| TRACE("(%p %p %x): Parsed opaque URI path %s len=%d\n", ptr, data, flags, | |
| debugstr_wn(data->path, data->path_len), data->path_len); | |
| return TRUE; | |
| } | |
| /* Determines how the URI should be parsed after the scheme information. | |
| * | |
| * If the scheme is followed by "//", then it is treated as a hierarchical URI | |
| * which then the authority and path information will be parsed out. Otherwise, the | |
| * URI will be treated as an opaque URI which the authority information is not parsed | |
| * out. | |
| * | |
| * RFC 3896 definition of hier-part: | |
| * | |
| * hier-part = "//" authority path-abempty | |
| * / path-absolute | |
| * / path-rootless | |
| * / path-empty | |
| * | |
| * MSDN opaque URI definition: | |
| * scheme ":" path [ "#" fragment ] | |
| * | |
| * NOTES: | |
| * If the URI is of an unknown scheme type and has a "//" following the scheme then it | |
| * is treated as a hierarchical URI, but, if the CREATE_NO_CRACK_UNKNOWN_SCHEMES flag is | |
| * set then it is considered an opaque URI regardless of what follows the scheme information | |
| * (per MSDN documentation). | |
| */ | |
| static BOOL parse_hierpart(const WCHAR **ptr, parse_data *data, DWORD flags) { | |
| const WCHAR *start = *ptr; | |
| data->must_have_path = FALSE; | |
| /* For javascript: URIs, simply set everything as a path */ | |
| if(data->scheme_type == URL_SCHEME_JAVASCRIPT) { | |
| data->path = *ptr; | |
| data->path_len = strlenW(*ptr); | |
| data->is_opaque = TRUE; | |
| *ptr += data->path_len; | |
| return TRUE; | |
| } | |
| /* Checks if the authority information needs to be parsed. */ | |
| if(is_hierarchical_uri(ptr, data)) { | |
| /* Only treat it as a hierarchical URI if the scheme_type is known or | |
| * the Uri_CREATE_NO_CRACK_UNKNOWN_SCHEMES flag is not set. | |
| */ | |
| if(data->scheme_type != URL_SCHEME_UNKNOWN || | |
| !(flags & Uri_CREATE_NO_CRACK_UNKNOWN_SCHEMES)) { | |
| TRACE("(%p %p %x): Treating URI as an hierarchical URI.\n", ptr, data, flags); | |
| data->is_opaque = FALSE; | |
| if(data->scheme_type == URL_SCHEME_WILDCARD && !data->has_implicit_scheme) { | |
| if(**ptr == '/' && *(*ptr+1) == '/') { | |
| data->must_have_path = TRUE; | |
| *ptr += 2; | |
| } | |
| } | |
| /* TODO: Handle hierarchical URI's, parse authority then parse the path. */ | |
| if(!parse_authority(ptr, data, flags)) | |
| return FALSE; | |
| return parse_path_hierarchical(ptr, data, flags); | |
| } else | |
| /* Reset ptr to its starting position so opaque path parsing | |
| * begins at the correct location. | |
| */ | |
| *ptr = start; | |
| } | |
| /* If it reaches here, then the URI will be treated as an opaque | |
| * URI. | |
| */ | |
| TRACE("(%p %p %x): Treating URI as an opaque URI.\n", ptr, data, flags); | |
| data->is_opaque = TRUE; | |
| if(!parse_path_opaque(ptr, data, flags)) | |
| return FALSE; | |
| return TRUE; | |
| } | |
| /* Attempts to parse the query string from the URI. | |
| * | |
| * NOTES: | |
| * If NO_DECODE_EXTRA_INFO flag is set, then invalid percent encoded | |
| * data is allowed to appear in the query string. For unknown scheme types | |
| * invalid percent encoded data is allowed to appear regardless. | |
| */ | |
| static BOOL parse_query(const WCHAR **ptr, parse_data *data, DWORD flags) { | |
| const BOOL known_scheme = data->scheme_type != URL_SCHEME_UNKNOWN; | |
| if(**ptr != '?') { | |
| TRACE("(%p %p %x): URI didn't contain a query string.\n", ptr, data, flags); | |
| return TRUE; | |
| } | |
| data->query = *ptr; | |
| ++(*ptr); | |
| while(**ptr && **ptr != '#') { | |
| if(**ptr == '%' && known_scheme && | |
| !(flags & Uri_CREATE_NO_DECODE_EXTRA_INFO)) { | |
| if(!check_pct_encoded(ptr)) { | |
| *ptr = data->query; | |
| data->query = NULL; | |
| return FALSE; | |
| } else | |
| continue; | |
| } | |
| ++(*ptr); | |
| } | |
| data->query_len = *ptr - data->query; | |
| TRACE("(%p %p %x): Parsed query string %s len=%d\n", ptr, data, flags, | |
| debugstr_wn(data->query, data->query_len), data->query_len); | |
| return TRUE; | |
| } | |
| /* Attempts to parse the fragment from the URI. | |
| * | |
| * NOTES: | |
| * If NO_DECODE_EXTRA_INFO flag is set, then invalid percent encoded | |
| * data is allowed to appear in the query string. For unknown scheme types | |
| * invalid percent encoded data is allowed to appear regardless. | |
| */ | |
| static BOOL parse_fragment(const WCHAR **ptr, parse_data *data, DWORD flags) { | |
| const BOOL known_scheme = data->scheme_type != URL_SCHEME_UNKNOWN; | |
| if(**ptr != '#') { | |
| TRACE("(%p %p %x): URI didn't contain a fragment.\n", ptr, data, flags); | |
| return TRUE; | |
| } | |
| data->fragment = *ptr; | |
| ++(*ptr); | |
| while(**ptr) { | |
| if(**ptr == '%' && known_scheme && | |
| !(flags & Uri_CREATE_NO_DECODE_EXTRA_INFO)) { | |
| if(!check_pct_encoded(ptr)) { | |
| *ptr = data->fragment; | |
| data->fragment = NULL; | |
| return FALSE; | |
| } else | |
| continue; | |
| } | |
| ++(*ptr); | |
| } | |
| data->fragment_len = *ptr - data->fragment; | |
| TRACE("(%p %p %x): Parsed fragment %s len=%d\n", ptr, data, flags, | |
| debugstr_wn(data->fragment, data->fragment_len), data->fragment_len); | |
| return TRUE; | |
| } | |
| /* Parses and validates the components of the specified by data->uri | |
| * and stores the information it parses into 'data'. | |
| * | |
| * Returns TRUE if it successfully parsed the URI. False otherwise. | |
| */ | |
| static BOOL parse_uri(parse_data *data, DWORD flags) { | |
| const WCHAR *ptr; | |
| const WCHAR **pptr; | |
| ptr = data->uri; | |
| pptr = &ptr; | |
| TRACE("(%p %x): BEGINNING TO PARSE URI %s.\n", data, flags, debugstr_w(data->uri)); | |
| if(!parse_scheme(pptr, data, flags, 0)) | |
| return FALSE; | |
| if(!parse_hierpart(pptr, data, flags)) | |
| return FALSE; | |
| if(!parse_query(pptr, data, flags)) | |
| return FALSE; | |
| if(!parse_fragment(pptr, data, flags)) | |
| return FALSE; | |
| TRACE("(%p %x): FINISHED PARSING URI.\n", data, flags); | |
| return TRUE; | |
| } | |
| static BOOL canonicalize_username(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { | |
| const WCHAR *ptr; | |
| if(!data->username) { | |
| uri->userinfo_start = -1; | |
| return TRUE; | |
| } | |
| uri->userinfo_start = uri->canon_len; | |
| for(ptr = data->username; ptr < data->username+data->username_len; ++ptr) { | |
| if(*ptr == '%') { | |
| /* Only decode % encoded values for known scheme types. */ | |
| if(data->scheme_type != URL_SCHEME_UNKNOWN) { | |
| /* See if the value really needs decoding. */ | |
| WCHAR val = decode_pct_val(ptr); | |
| if(is_unreserved(val)) { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = val; | |
| ++uri->canon_len; | |
| /* Move pass the hex characters. */ | |
| ptr += 2; | |
| continue; | |
| } | |
| } | |
| } else if(!is_reserved(*ptr) && !is_unreserved(*ptr) && *ptr != '\\') { | |
| /* Only percent encode forbidden characters if the NO_ENCODE_FORBIDDEN_CHARACTERS flag | |
| * is NOT set. | |
| */ | |
| if(!(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS)) { | |
| if(!computeOnly) | |
| pct_encode_val(*ptr, uri->canon_uri + uri->canon_len); | |
| uri->canon_len += 3; | |
| continue; | |
| } | |
| } | |
| if(!computeOnly) | |
| /* Nothing special, so just copy the character over. */ | |
| uri->canon_uri[uri->canon_len] = *ptr; | |
| ++uri->canon_len; | |
| } | |
| return TRUE; | |
| } | |
| static BOOL canonicalize_password(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { | |
| const WCHAR *ptr; | |
| if(!data->password) { | |
| uri->userinfo_split = -1; | |
| return TRUE; | |
| } | |
| if(uri->userinfo_start == -1) | |
| /* Has a password, but, doesn't have a username. */ | |
| uri->userinfo_start = uri->canon_len; | |
| uri->userinfo_split = uri->canon_len - uri->userinfo_start; | |
| /* Add the ':' to the userinfo component. */ | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = ':'; | |
| ++uri->canon_len; | |
| for(ptr = data->password; ptr < data->password+data->password_len; ++ptr) { | |
| if(*ptr == '%') { | |
| /* Only decode % encoded values for known scheme types. */ | |
| if(data->scheme_type != URL_SCHEME_UNKNOWN) { | |
| /* See if the value really needs decoding. */ | |
| WCHAR val = decode_pct_val(ptr); | |
| if(is_unreserved(val)) { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = val; | |
| ++uri->canon_len; | |
| /* Move pass the hex characters. */ | |
| ptr += 2; | |
| continue; | |
| } | |
| } | |
| } else if(!is_reserved(*ptr) && !is_unreserved(*ptr) && *ptr != '\\') { | |
| /* Only percent encode forbidden characters if the NO_ENCODE_FORBIDDEN_CHARACTERS flag | |
| * is NOT set. | |
| */ | |
| if(!(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS)) { | |
| if(!computeOnly) | |
| pct_encode_val(*ptr, uri->canon_uri + uri->canon_len); | |
| uri->canon_len += 3; | |
| continue; | |
| } | |
| } | |
| if(!computeOnly) | |
| /* Nothing special, so just copy the character over. */ | |
| uri->canon_uri[uri->canon_len] = *ptr; | |
| ++uri->canon_len; | |
| } | |
| return TRUE; | |
| } | |
| /* Canonicalizes the userinfo of the URI represented by the parse_data. | |
| * | |
| * Canonicalization of the userinfo is a simple process. If there are any percent | |
| * encoded characters that fall in the "unreserved" character set, they are decoded | |
| * to their actual value. If a character is not in the "unreserved" or "reserved" sets | |
| * then it is percent encoded. Other than that the characters are copied over without | |
| * change. | |
| */ | |
| static BOOL canonicalize_userinfo(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { | |
| uri->userinfo_start = uri->userinfo_split = -1; | |
| uri->userinfo_len = 0; | |
| if(!data->username && !data->password) | |
| /* URI doesn't have userinfo, so nothing to do here. */ | |
| return TRUE; | |
| if(!canonicalize_username(data, uri, flags, computeOnly)) | |
| return FALSE; | |
| if(!canonicalize_password(data, uri, flags, computeOnly)) | |
| return FALSE; | |
| uri->userinfo_len = uri->canon_len - uri->userinfo_start; | |
| if(!computeOnly) | |
| TRACE("(%p %p %x %d): Canonicalized userinfo, userinfo_start=%d, userinfo=%s, userinfo_split=%d userinfo_len=%d.\n", | |
| data, uri, flags, computeOnly, uri->userinfo_start, debugstr_wn(uri->canon_uri + uri->userinfo_start, uri->userinfo_len), | |
| uri->userinfo_split, uri->userinfo_len); | |
| /* Now insert the '@' after the userinfo. */ | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = '@'; | |
| ++uri->canon_len; | |
| return TRUE; | |
| } | |
| /* Attempts to canonicalize a reg_name. | |
| * | |
| * Things that happen: | |
| * 1) If Uri_CREATE_NO_CANONICALIZE flag is not set, then the reg_name is | |
| * lower cased. Unless it's an unknown scheme type, which case it's | |
| * no lower cased regardless. | |
| * | |
| * 2) Unreserved % encoded characters are decoded for known | |
| * scheme types. | |
| * | |
| * 3) Forbidden characters are % encoded as long as | |
| * Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS flag is not set and | |
| * it isn't an unknown scheme type. | |
| * | |
| * 4) If it's a file scheme and the host is "localhost" it's removed. | |
| * | |
| * 5) If it's a file scheme and Uri_CREATE_FILE_USE_DOS_PATH is set, | |
| * then the UNC path characters are added before the host name. | |
| */ | |
| static BOOL canonicalize_reg_name(const parse_data *data, Uri *uri, | |
| DWORD flags, BOOL computeOnly) { | |
| static const WCHAR localhostW[] = | |
| {'l','o','c','a','l','h','o','s','t',0}; | |
| const WCHAR *ptr; | |
| const BOOL known_scheme = data->scheme_type != URL_SCHEME_UNKNOWN; | |
| if(data->scheme_type == URL_SCHEME_FILE && | |
| data->host_len == lstrlenW(localhostW)) { | |
| if(!StrCmpNIW(data->host, localhostW, data->host_len)) { | |
| uri->host_start = -1; | |
| uri->host_len = 0; | |
| uri->host_type = Uri_HOST_UNKNOWN; | |
| return TRUE; | |
| } | |
| } | |
| if(data->scheme_type == URL_SCHEME_FILE && flags & Uri_CREATE_FILE_USE_DOS_PATH) { | |
| if(!computeOnly) { | |
| uri->canon_uri[uri->canon_len] = '\\'; | |
| uri->canon_uri[uri->canon_len+1] = '\\'; | |
| } | |
| uri->canon_len += 2; | |
| uri->authority_start = uri->canon_len; | |
| } | |
| uri->host_start = uri->canon_len; | |
| for(ptr = data->host; ptr < data->host+data->host_len; ++ptr) { | |
| if(*ptr == '%' && known_scheme) { | |
| WCHAR val = decode_pct_val(ptr); | |
| if(is_unreserved(val)) { | |
| /* If NO_CANONICALIZE is not set, then windows lower cases the | |
| * decoded value. | |
| */ | |
| if(!(flags & Uri_CREATE_NO_CANONICALIZE) && isupperW(val)) { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = tolowerW(val); | |
| } else { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = val; | |
| } | |
| ++uri->canon_len; | |
| /* Skip past the % encoded character. */ | |
| ptr += 2; | |
| continue; | |
| } else { | |
| /* Just copy the % over. */ | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = *ptr; | |
| ++uri->canon_len; | |
| } | |
| } else if(*ptr == '\\') { | |
| /* Only unknown scheme types could have made it here with a '\\' in the host name. */ | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = *ptr; | |
| ++uri->canon_len; | |
| } else if(!(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS) && | |
| !is_unreserved(*ptr) && !is_reserved(*ptr) && known_scheme) { | |
| if(!computeOnly) { | |
| pct_encode_val(*ptr, uri->canon_uri+uri->canon_len); | |
| /* The percent encoded value gets lower cased also. */ | |
| if(!(flags & Uri_CREATE_NO_CANONICALIZE)) { | |
| uri->canon_uri[uri->canon_len+1] = tolowerW(uri->canon_uri[uri->canon_len+1]); | |
| uri->canon_uri[uri->canon_len+2] = tolowerW(uri->canon_uri[uri->canon_len+2]); | |
| } | |
| } | |
| uri->canon_len += 3; | |
| } else { | |
| if(!computeOnly) { | |
| if(!(flags & Uri_CREATE_NO_CANONICALIZE) && known_scheme) | |
| uri->canon_uri[uri->canon_len] = tolowerW(*ptr); | |
| else | |
| uri->canon_uri[uri->canon_len] = *ptr; | |
| } | |
| ++uri->canon_len; | |
| } | |
| } | |
| uri->host_len = uri->canon_len - uri->host_start; | |
| if(!computeOnly) | |
| TRACE("(%p %p %x %d): Canonicalize reg_name=%s len=%d\n", data, uri, flags, | |
| computeOnly, debugstr_wn(uri->canon_uri+uri->host_start, uri->host_len), | |
| uri->host_len); | |
| if(!computeOnly) | |
| find_domain_name(uri->canon_uri+uri->host_start, uri->host_len, | |
| &(uri->domain_offset)); | |
| return TRUE; | |
| } | |
| /* Attempts to canonicalize an implicit IPv4 address. */ | |
| static BOOL canonicalize_implicit_ipv4address(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { | |
| uri->host_start = uri->canon_len; | |
| TRACE("%u\n", data->implicit_ipv4); | |
| /* For unknown scheme types Windows doesn't convert | |
| * the value into an IP address, but it still considers | |
| * it an IPv4 address. | |
| */ | |
| if(data->scheme_type == URL_SCHEME_UNKNOWN) { | |
| if(!computeOnly) | |
| memcpy(uri->canon_uri+uri->canon_len, data->host, data->host_len*sizeof(WCHAR)); | |
| uri->canon_len += data->host_len; | |
| } else { | |
| if(!computeOnly) | |
| uri->canon_len += ui2ipv4(uri->canon_uri+uri->canon_len, data->implicit_ipv4); | |
| else | |
| uri->canon_len += ui2ipv4(NULL, data->implicit_ipv4); | |
| } | |
| uri->host_len = uri->canon_len - uri->host_start; | |
| uri->host_type = Uri_HOST_IPV4; | |
| if(!computeOnly) | |
| TRACE("%p %p %x %d): Canonicalized implicit IP address=%s len=%d\n", | |
| data, uri, flags, computeOnly, | |
| debugstr_wn(uri->canon_uri+uri->host_start, uri->host_len), | |
| uri->host_len); | |
| return TRUE; | |
| } | |
| /* Attempts to canonicalize an IPv4 address. | |
| * | |
| * If the parse_data represents a URI that has an implicit IPv4 address | |
| * (ex. http://256/, this function will convert 256 into 0.0.1.0). If | |
| * the implicit IP address exceeds the value of UINT_MAX (maximum value | |
| * for an IPv4 address) it's canonicalized as if it were a reg-name. | |
| * | |
| * If the parse_data contains a partial or full IPv4 address it normalizes it. | |
| * A partial IPv4 address is something like "192.0" and would be normalized to | |
| * "192.0.0.0". With a full (or partial) IPv4 address like "192.002.01.003" would | |
| * be normalized to "192.2.1.3". | |
| * | |
| * NOTES: | |
| * Windows ONLY normalizes IPv4 address for known scheme types (one that isn't | |
| * URL_SCHEME_UNKNOWN). For unknown scheme types, it simply copies the data from | |
| * the original URI into the canonicalized URI, but, it still recognizes URI's | |
| * host type as HOST_IPV4. | |
| */ | |
| static BOOL canonicalize_ipv4address(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { | |
| if(data->has_implicit_ip) | |
| return canonicalize_implicit_ipv4address(data, uri, flags, computeOnly); | |
| else { | |
| uri->host_start = uri->canon_len; | |
| /* Windows only normalizes for known scheme types. */ | |
| if(data->scheme_type != URL_SCHEME_UNKNOWN) { | |
| /* parse_data contains a partial or full IPv4 address, so normalize it. */ | |
| DWORD i, octetDigitCount = 0, octetCount = 0; | |
| BOOL octetHasDigit = FALSE; | |
| for(i = 0; i < data->host_len; ++i) { | |
| if(data->host[i] == '0' && !octetHasDigit) { | |
| /* Can ignore leading zeros if: | |
| * 1) It isn't the last digit of the octet. | |
| * 2) i+1 != data->host_len | |
| * 3) i+1 != '.' | |
| */ | |
| if(octetDigitCount == 2 || | |
| i+1 == data->host_len || | |
| data->host[i+1] == '.') { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = data->host[i]; | |
| ++uri->canon_len; | |
| TRACE("Adding zero\n"); | |
| } | |
| } else if(data->host[i] == '.') { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = data->host[i]; | |
| ++uri->canon_len; | |
| octetDigitCount = 0; | |
| octetHasDigit = FALSE; | |
| ++octetCount; | |
| } else { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = data->host[i]; | |
| ++uri->canon_len; | |
| ++octetDigitCount; | |
| octetHasDigit = TRUE; | |
| } | |
| } | |
| /* Make sure the canonicalized IP address has 4 dec-octets. | |
| * If doesn't add "0" ones until there is 4; | |
| */ | |
| for( ; octetCount < 3; ++octetCount) { | |
| if(!computeOnly) { | |
| uri->canon_uri[uri->canon_len] = '.'; | |
| uri->canon_uri[uri->canon_len+1] = '0'; | |
| } | |
| uri->canon_len += 2; | |
| } | |
| } else { | |
| /* Windows doesn't normalize addresses in unknown schemes. */ | |
| if(!computeOnly) | |
| memcpy(uri->canon_uri+uri->canon_len, data->host, data->host_len*sizeof(WCHAR)); | |
| uri->canon_len += data->host_len; | |
| } | |
| uri->host_len = uri->canon_len - uri->host_start; | |
| if(!computeOnly) | |
| TRACE("(%p %p %x %d): Canonicalized IPv4 address, ip=%s len=%d\n", | |
| data, uri, flags, computeOnly, | |
| debugstr_wn(uri->canon_uri+uri->host_start, uri->host_len), | |
| uri->host_len); | |
| } | |
| return TRUE; | |
| } | |
| /* Attempts to canonicalize the IPv6 address of the URI. | |
| * | |
| * Multiple things happen during the canonicalization of an IPv6 address: | |
| * 1) Any leading zero's in a h16 component are removed. | |
| * Ex: [0001:0022::] -> [1:22::] | |
| * | |
| * 2) The longest sequence of zero h16 components are compressed | |
| * into a "::" (elision). If there's a tie, the first is chosen. | |
| * | |
| * Ex: [0:0:0:0:1:6:7:8] -> [::1:6:7:8] | |
| * [0:0:0:0:1:2::] -> [::1:2:0:0] | |
| * [0:0:1:2:0:0:7:8] -> [::1:2:0:0:7:8] | |
| * | |
| * 3) If an IPv4 address is attached to the IPv6 address, it's | |
| * also normalized. | |
| * Ex: [::001.002.022.000] -> [::1.2.22.0] | |
| * | |
| * 4) If an elision is present, but, only represents one h16 component | |
| * it's expanded. | |
| * | |
| * Ex: [1::2:3:4:5:6:7] -> [1:0:2:3:4:5:6:7] | |
| * | |
| * 5) If the IPv6 address contains an IPv4 address and there exists | |
| * at least 1 non-zero h16 component the IPv4 address is converted | |
| * into two h16 components, otherwise it's normalized and kept as is. | |
| * | |
| * Ex: [::192.200.003.4] -> [::192.200.3.4] | |
| * [ffff::192.200.003.4] -> [ffff::c0c8:3041] | |
| * | |
| * NOTE: | |
| * For unknown scheme types Windows simply copies the address over without any | |
| * changes. | |
| * | |
| * IPv4 address can be included in an elision if all its components are 0's. | |
| */ | |
| static BOOL canonicalize_ipv6address(const parse_data *data, Uri *uri, | |
| DWORD flags, BOOL computeOnly) { | |
| uri->host_start = uri->canon_len; | |
| if(data->scheme_type == URL_SCHEME_UNKNOWN) { | |
| if(!computeOnly) | |
| memcpy(uri->canon_uri+uri->canon_len, data->host, data->host_len*sizeof(WCHAR)); | |
| uri->canon_len += data->host_len; | |
| } else { | |
| USHORT values[8]; | |
| INT elision_start; | |
| DWORD i, elision_len; | |
| if(!ipv6_to_number(&(data->ipv6_address), values)) { | |
| TRACE("(%p %p %x %d): Failed to compute numerical value for IPv6 address.\n", | |
| data, uri, flags, computeOnly); | |
| return FALSE; | |
| } | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = '['; | |
| ++uri->canon_len; | |
| /* Find where the elision should occur (if any). */ | |
| compute_elision_location(&(data->ipv6_address), values, &elision_start, &elision_len); | |
| TRACE("%p %p %x %d): Elision starts at %d, len=%u\n", data, uri, flags, | |
| computeOnly, elision_start, elision_len); | |
| for(i = 0; i < 8; ++i) { | |
| BOOL in_elision = (elision_start > -1 && i >= elision_start && | |
| i < elision_start+elision_len); | |
| BOOL do_ipv4 = (i == 6 && data->ipv6_address.ipv4 && !in_elision && | |
| data->ipv6_address.h16_count == 0); | |
| if(i == elision_start) { | |
| if(!computeOnly) { | |
| uri->canon_uri[uri->canon_len] = ':'; | |
| uri->canon_uri[uri->canon_len+1] = ':'; | |
| } | |
| uri->canon_len += 2; | |
| } | |
| /* We can ignore the current component if we're in the elision. */ | |
| if(in_elision) | |
| continue; | |
| /* We only add a ':' if we're not at i == 0, or when we're at | |
| * the very end of elision range since the ':' colon was handled | |
| * earlier. Otherwise we would end up with ":::" after elision. | |
| */ | |
| if(i != 0 && !(elision_start > -1 && i == elision_start+elision_len)) { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = ':'; | |
| ++uri->canon_len; | |
| } | |
| if(do_ipv4) { | |
| UINT val; | |
| DWORD len; | |
| /* Combine the two parts of the IPv4 address values. */ | |
| val = values[i]; | |
| val <<= 16; | |
| val += values[i+1]; | |
| if(!computeOnly) | |
| len = ui2ipv4(uri->canon_uri+uri->canon_len, val); | |
| else | |
| len = ui2ipv4(NULL, val); | |
| uri->canon_len += len; | |
| ++i; | |
| } else { | |
| /* Write a regular h16 component to the URI. */ | |
| /* Short circuit for the trivial case. */ | |
| if(values[i] == 0) { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = '0'; | |
| ++uri->canon_len; | |
| } else { | |
| static const WCHAR formatW[] = {'%','x',0}; | |
| if(!computeOnly) | |
| uri->canon_len += sprintfW(uri->canon_uri+uri->canon_len, | |
| formatW, values[i]); | |
| else { | |
| WCHAR tmp[5]; | |
| uri->canon_len += sprintfW(tmp, formatW, values[i]); | |
| } | |
| } | |
| } | |
| } | |
| /* Add the closing ']'. */ | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = ']'; | |
| ++uri->canon_len; | |
| } | |
| uri->host_len = uri->canon_len - uri->host_start; | |
| if(!computeOnly) | |
| TRACE("(%p %p %x %d): Canonicalized IPv6 address %s, len=%d\n", data, uri, flags, | |
| computeOnly, debugstr_wn(uri->canon_uri+uri->host_start, uri->host_len), | |
| uri->host_len); | |
| return TRUE; | |
| } | |
| /* Attempts to canonicalize the host of the URI (if any). */ | |
| static BOOL canonicalize_host(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { | |
| uri->host_start = -1; | |
| uri->host_len = 0; | |
| uri->domain_offset = -1; | |
| if(data->host) { | |
| switch(data->host_type) { | |
| case Uri_HOST_DNS: | |
| uri->host_type = Uri_HOST_DNS; | |
| if(!canonicalize_reg_name(data, uri, flags, computeOnly)) | |
| return FALSE; | |
| break; | |
| case Uri_HOST_IPV4: | |
| uri->host_type = Uri_HOST_IPV4; | |
| if(!canonicalize_ipv4address(data, uri, flags, computeOnly)) | |
| return FALSE; | |
| break; | |
| case Uri_HOST_IPV6: | |
| if(!canonicalize_ipv6address(data, uri, flags, computeOnly)) | |
| return FALSE; | |
| uri->host_type = Uri_HOST_IPV6; | |
| break; | |
| case Uri_HOST_UNKNOWN: | |
| if(data->host_len > 0 || data->scheme_type != URL_SCHEME_FILE) { | |
| uri->host_start = uri->canon_len; | |
| /* Nothing happens to unknown host types. */ | |
| if(!computeOnly) | |
| memcpy(uri->canon_uri+uri->canon_len, data->host, data->host_len*sizeof(WCHAR)); | |
| uri->canon_len += data->host_len; | |
| uri->host_len = data->host_len; | |
| } | |
| uri->host_type = Uri_HOST_UNKNOWN; | |
| break; | |
| default: | |
| FIXME("(%p %p %x %d): Canonicalization for host type %d not supported.\n", data, | |
| uri, flags, computeOnly, data->host_type); | |
| return FALSE; | |
| } | |
| } | |
| return TRUE; | |
| } | |
| static BOOL canonicalize_port(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { | |
| BOOL has_default_port = FALSE; | |
| USHORT default_port = 0; | |
| DWORD i; | |
| uri->port_offset = -1; | |
| /* Check if the scheme has a default port. */ | |
| for(i = 0; i < sizeof(default_ports)/sizeof(default_ports[0]); ++i) { | |
| if(default_ports[i].scheme == data->scheme_type) { | |
| has_default_port = TRUE; | |
| default_port = default_ports[i].port; | |
| break; | |
| } | |
| } | |
| uri->has_port = data->has_port || has_default_port; | |
| /* Possible cases: | |
| * 1) Has a port which is the default port. | |
| * 2) Has a port (not the default). | |
| * 3) Doesn't have a port, but, scheme has a default port. | |
| * 4) No port. | |
| */ | |
| if(has_default_port && data->has_port && data->port_value == default_port) { | |
| /* If it's the default port and this flag isn't set, don't do anything. */ | |
| if(flags & Uri_CREATE_NO_CANONICALIZE) { | |
| uri->port_offset = uri->canon_len-uri->authority_start; | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = ':'; | |
| ++uri->canon_len; | |
| if(data->port) { | |
| /* Copy the original port over. */ | |
| if(!computeOnly) | |
| memcpy(uri->canon_uri+uri->canon_len, data->port, data->port_len*sizeof(WCHAR)); | |
| uri->canon_len += data->port_len; | |
| } else { | |
| if(!computeOnly) | |
| uri->canon_len += ui2str(uri->canon_uri+uri->canon_len, data->port_value); | |
| else | |
| uri->canon_len += ui2str(NULL, data->port_value); | |
| } | |
| } | |
| uri->port = default_port; | |
| } else if(data->has_port) { | |
| uri->port_offset = uri->canon_len-uri->authority_start; | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = ':'; | |
| ++uri->canon_len; | |
| if(flags & Uri_CREATE_NO_CANONICALIZE && data->port) { | |
| /* Copy the original over without changes. */ | |
| if(!computeOnly) | |
| memcpy(uri->canon_uri+uri->canon_len, data->port, data->port_len*sizeof(WCHAR)); | |
| uri->canon_len += data->port_len; | |
| } else { | |
| if(!computeOnly) | |
| uri->canon_len += ui2str(uri->canon_uri+uri->canon_len, data->port_value); | |
| else | |
| uri->canon_len += ui2str(NULL, data->port_value); | |
| } | |
| uri->port = data->port_value; | |
| } else if(has_default_port) | |
| uri->port = default_port; | |
| return TRUE; | |
| } | |
| /* Canonicalizes the authority of the URI represented by the parse_data. */ | |
| static BOOL canonicalize_authority(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { | |
| uri->authority_start = uri->canon_len; | |
| uri->authority_len = 0; | |
| if(!canonicalize_userinfo(data, uri, flags, computeOnly)) | |
| return FALSE; | |
| if(!canonicalize_host(data, uri, flags, computeOnly)) | |
| return FALSE; | |
| if(!canonicalize_port(data, uri, flags, computeOnly)) | |
| return FALSE; | |
| if(uri->host_start != -1 || (data->is_relative && (data->password || data->username))) | |
| uri->authority_len = uri->canon_len - uri->authority_start; | |
| else | |
| uri->authority_start = -1; | |
| return TRUE; | |
| } | |
| /* Attempts to canonicalize the path of a hierarchical URI. | |
| * | |
| * Things that happen: | |
| * 1). Forbidden characters are percent encoded, unless the NO_ENCODE_FORBIDDEN | |
| * flag is set or it's a file URI. Forbidden characters are always encoded | |
| * for file schemes regardless and forbidden characters are never encoded | |
| * for unknown scheme types. | |
| * | |
| * 2). For known scheme types '\\' are changed to '/'. | |
| * | |
| * 3). Percent encoded, unreserved characters are decoded to their actual values. | |
| * Unless the scheme type is unknown. For file schemes any percent encoded | |
| * character in the unreserved or reserved set is decoded. | |
| * | |
| * 4). For File schemes if the path is starts with a drive letter and doesn't | |
| * start with a '/' then one is appended. | |
| * Ex: file://c:/test.mp3 -> file:///c:/test.mp3 | |
| * | |
| * 5). Dot segments are removed from the path for all scheme types | |
| * unless NO_CANONICALIZE flag is set. Dot segments aren't removed | |
| * for wildcard scheme types. | |
| * | |
| * NOTES: | |
| * file://c:/test%20test -> file:///c:/test%2520test | |
| * file://c:/test%3Etest -> file:///c:/test%253Etest | |
| * if Uri_CREATE_FILE_USE_DOS_PATH is not set: | |
| * file:///c:/test%20test -> file:///c:/test%20test | |
| * file:///c:/test%test -> file:///c:/test%25test | |
| */ | |
| static DWORD canonicalize_path_hierarchical(const WCHAR *path, DWORD path_len, URL_SCHEME scheme_type, BOOL has_host, DWORD flags, | |
| BOOL is_implicit_scheme, WCHAR *ret_path) { | |
| const BOOL known_scheme = scheme_type != URL_SCHEME_UNKNOWN; | |
| const BOOL is_file = scheme_type == URL_SCHEME_FILE; | |
| const BOOL is_res = scheme_type == URL_SCHEME_RES; | |
| const WCHAR *ptr; | |
| BOOL escape_pct = FALSE; | |
| DWORD len = 0; | |
| if(!path) | |
| return 0; | |
| ptr = path; | |
| if(is_file && !has_host) { | |
| /* Check if a '/' needs to be appended for the file scheme. */ | |
| if(path_len > 1 && is_drive_path(ptr) && !(flags & Uri_CREATE_FILE_USE_DOS_PATH)) { | |
| if(ret_path) | |
| ret_path[len] = '/'; | |
| len++; | |
| escape_pct = TRUE; | |
| } else if(*ptr == '/') { | |
| if(!(flags & Uri_CREATE_FILE_USE_DOS_PATH)) { | |
| /* Copy the extra '/' over. */ | |
| if(ret_path) | |
| ret_path[len] = '/'; | |
| len++; | |
| } | |
| ++ptr; | |
| } | |
| if(is_drive_path(ptr)) { | |
| if(ret_path) { | |
| ret_path[len] = *ptr; | |
| /* If there's a '|' after the drive letter, convert it to a ':'. */ | |
| ret_path[len+1] = ':'; | |
| } | |
| ptr += 2; | |
| len += 2; | |
| } | |
| } | |
| if(!is_file && *path && *path != '/') { | |
| /* Prepend a '/' to the path if it doesn't have one. */ | |
| if(ret_path) | |
| ret_path[len] = '/'; | |
| len++; | |
| } | |
| for(; ptr < path+path_len; ++ptr) { | |
| BOOL do_default_action = TRUE; | |
| if(*ptr == '%' && !is_res) { | |
| const WCHAR *tmp = ptr; | |
| WCHAR val; | |
| /* Check if the % represents a valid encoded char, or if it needs encoding. */ | |
| BOOL force_encode = !check_pct_encoded(&tmp) && is_file && !(flags&Uri_CREATE_FILE_USE_DOS_PATH); | |
| val = decode_pct_val(ptr); | |
| if(force_encode || escape_pct) { | |
| /* Escape the percent sign in the file URI. */ | |
| if(ret_path) | |
| pct_encode_val(*ptr, ret_path+len); | |
| len += 3; | |
| do_default_action = FALSE; | |
| } else if((is_unreserved(val) && known_scheme) || | |
| (is_file && !is_implicit_scheme && (is_unreserved(val) || is_reserved(val) || | |
| (val && flags&Uri_CREATE_FILE_USE_DOS_PATH && !is_forbidden_dos_path_char(val))))) { | |
| if(ret_path) | |
| ret_path[len] = val; | |
| len++; | |
| ptr += 2; | |
| continue; | |
| } | |
| } else if(*ptr == '/' && is_file && (flags & Uri_CREATE_FILE_USE_DOS_PATH)) { | |
| /* Convert the '/' back to a '\\'. */ | |
| if(ret_path) | |
| ret_path[len] = '\\'; | |
| len++; | |
| do_default_action = FALSE; | |
| } else if(*ptr == '\\' && known_scheme) { | |
| if(!(is_file && (flags & Uri_CREATE_FILE_USE_DOS_PATH))) { | |
| /* Convert '\\' into a '/'. */ | |
| if(ret_path) | |
| ret_path[len] = '/'; | |
| len++; | |
| do_default_action = FALSE; | |
| } | |
| } else if(known_scheme && !is_res && !is_unreserved(*ptr) && !is_reserved(*ptr) && | |
| (!(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS) || is_file)) { | |
| if(!is_file || !(flags & Uri_CREATE_FILE_USE_DOS_PATH)) { | |
| /* Escape the forbidden character. */ | |
| if(ret_path) | |
| pct_encode_val(*ptr, ret_path+len); | |
| len += 3; | |
| do_default_action = FALSE; | |
| } | |
| } | |
| if(do_default_action) { | |
| if(ret_path) | |
| ret_path[len] = *ptr; | |
| len++; | |
| } | |
| } | |
| /* Removing the dot segments only happens when it's not in | |
| * computeOnly mode and it's not a wildcard scheme. File schemes | |
| * with USE_DOS_PATH set don't get dot segments removed. | |
| */ | |
| if(!(is_file && (flags & Uri_CREATE_FILE_USE_DOS_PATH)) && | |
| scheme_type != URL_SCHEME_WILDCARD) { | |
| if(!(flags & Uri_CREATE_NO_CANONICALIZE) && ret_path) { | |
| /* Remove the dot segments (if any) and reset everything to the new | |
| * correct length. | |
| */ | |
| len = remove_dot_segments(ret_path, len); | |
| } | |
| } | |
| if(ret_path) | |
| TRACE("Canonicalized path %s len=%d\n", debugstr_wn(ret_path, len), len); | |
| return len; | |
| } | |
| /* Attempts to canonicalize the path for an opaque URI. | |
| * | |
| * For known scheme types: | |
| * 1) forbidden characters are percent encoded if | |
| * NO_ENCODE_FORBIDDEN_CHARACTERS isn't set. | |
| * | |
| * 2) Percent encoded, unreserved characters are decoded | |
| * to their actual values, for known scheme types. | |
| * | |
| * 3) '\\' are changed to '/' for known scheme types | |
| * except for mailto schemes. | |
| * | |
| * 4) For file schemes, if USE_DOS_PATH is set all '/' | |
| * are converted to backslashes. | |
| * | |
| * 5) For file schemes, if USE_DOS_PATH isn't set all '\' | |
| * are converted to forward slashes. | |
| */ | |
| static BOOL canonicalize_path_opaque(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { | |
| const WCHAR *ptr; | |
| const BOOL known_scheme = data->scheme_type != URL_SCHEME_UNKNOWN; | |
| const BOOL is_file = data->scheme_type == URL_SCHEME_FILE; | |
| const BOOL is_mk = data->scheme_type == URL_SCHEME_MK; | |
| if(!data->path) { | |
| uri->path_start = -1; | |
| uri->path_len = 0; | |
| return TRUE; | |
| } | |
| uri->path_start = uri->canon_len; | |
| if(is_mk){ | |
| /* hijack this flag for SCHEME_MK to tell the function when to start | |
| * converting slashes */ | |
| flags |= Uri_CREATE_FILE_USE_DOS_PATH; | |
| } | |
| /* For javascript: URIs, simply copy path part without any canonicalization */ | |
| if(data->scheme_type == URL_SCHEME_JAVASCRIPT) { | |
| if(!computeOnly) | |
| memcpy(uri->canon_uri+uri->canon_len, data->path, data->path_len*sizeof(WCHAR)); | |
| uri->path_len = data->path_len; | |
| uri->canon_len += data->path_len; | |
| return TRUE; | |
| } | |
| /* Windows doesn't allow a "//" to appear after the scheme | |
| * of a URI, if it's an opaque URI. | |
| */ | |
| if(data->scheme && *(data->path) == '/' && *(data->path+1) == '/') { | |
| /* So it inserts a "/." before the "//" if it exists. */ | |
| if(!computeOnly) { | |
| uri->canon_uri[uri->canon_len] = '/'; | |
| uri->canon_uri[uri->canon_len+1] = '.'; | |
| } | |
| uri->canon_len += 2; | |
| } | |
| for(ptr = data->path; ptr < data->path+data->path_len; ++ptr) { | |
| BOOL do_default_action = TRUE; | |
| if(*ptr == '%' && known_scheme) { | |
| WCHAR val = decode_pct_val(ptr); | |
| if(is_unreserved(val)) { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = val; | |
| ++uri->canon_len; | |
| ptr += 2; | |
| continue; | |
| } | |
| } else if(*ptr == '/' && is_file && (flags & Uri_CREATE_FILE_USE_DOS_PATH)) { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = '\\'; | |
| ++uri->canon_len; | |
| do_default_action = FALSE; | |
| } else if(*ptr == '\\') { | |
| if((data->is_relative || is_mk || is_file) && !(flags & Uri_CREATE_FILE_USE_DOS_PATH)) { | |
| /* Convert to a '/'. */ | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = '/'; | |
| ++uri->canon_len; | |
| do_default_action = FALSE; | |
| } | |
| } else if(is_mk && *ptr == ':' && ptr + 1 < data->path + data->path_len && *(ptr + 1) == ':') { | |
| flags &= ~Uri_CREATE_FILE_USE_DOS_PATH; | |
| } else if(known_scheme && !is_unreserved(*ptr) && !is_reserved(*ptr) && | |
| !(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS)) { | |
| if(!(is_file && (flags & Uri_CREATE_FILE_USE_DOS_PATH))) { | |
| if(!computeOnly) | |
| pct_encode_val(*ptr, uri->canon_uri+uri->canon_len); | |
| uri->canon_len += 3; | |
| do_default_action = FALSE; | |
| } | |
| } | |
| if(do_default_action) { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = *ptr; | |
| ++uri->canon_len; | |
| } | |
| } | |
| if(is_mk && !computeOnly && !(flags & Uri_CREATE_NO_CANONICALIZE)) { | |
| DWORD new_len = remove_dot_segments(uri->canon_uri + uri->path_start, | |
| uri->canon_len - uri->path_start); | |
| uri->canon_len = uri->path_start + new_len; | |
| } | |
| uri->path_len = uri->canon_len - uri->path_start; | |
| if(!computeOnly) | |
| TRACE("(%p %p %x %d): Canonicalized opaque URI path %s len=%d\n", data, uri, flags, computeOnly, | |
| debugstr_wn(uri->canon_uri+uri->path_start, uri->path_len), uri->path_len); | |
| return TRUE; | |
| } | |
| /* Determines how the URI represented by the parse_data should be canonicalized. | |
| * | |
| * Essentially, if the parse_data represents an hierarchical URI then it calls | |
| * canonicalize_authority and the canonicalization functions for the path. If the | |
| * URI is opaque it canonicalizes the path of the URI. | |
| */ | |
| static BOOL canonicalize_hierpart(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { | |
| if(!data->is_opaque || (data->is_relative && (data->password || data->username))) { | |
| /* "//" is only added for non-wildcard scheme types. | |
| * | |
| * A "//" is only added to a relative URI if it has a | |
| * host or port component (this only happens if a IUriBuilder | |
| * is generating an IUri). | |
| */ | |
| if((data->is_relative && (data->host || data->has_port)) || | |
| (!data->is_relative && data->scheme_type != URL_SCHEME_WILDCARD)) { | |
| if(data->scheme_type == URL_SCHEME_WILDCARD) | |
| FIXME("Here\n"); | |
| if(!computeOnly) { | |
| INT pos = uri->canon_len; | |
| uri->canon_uri[pos] = '/'; | |
| uri->canon_uri[pos+1] = '/'; | |
| } | |
| uri->canon_len += 2; | |
| } | |
| if(!canonicalize_authority(data, uri, flags, computeOnly)) | |
| return FALSE; | |
| if(data->is_relative && (data->password || data->username)) { | |
| if(!canonicalize_path_opaque(data, uri, flags, computeOnly)) | |
| return FALSE; | |
| } else { | |
| if(!computeOnly) | |
| uri->path_start = uri->canon_len; | |
| uri->path_len = canonicalize_path_hierarchical(data->path, data->path_len, data->scheme_type, data->host_len != 0, | |
| flags, data->has_implicit_scheme, computeOnly ? NULL : uri->canon_uri+uri->canon_len); | |
| uri->canon_len += uri->path_len; | |
| if(!computeOnly && !uri->path_len) | |
| uri->path_start = -1; | |
| } | |
| } else { | |
| /* Opaque URI's don't have an authority. */ | |
| uri->userinfo_start = uri->userinfo_split = -1; | |
| uri->userinfo_len = 0; | |
| uri->host_start = -1; | |
| uri->host_len = 0; | |
| uri->host_type = Uri_HOST_UNKNOWN; | |
| uri->has_port = FALSE; | |
| uri->authority_start = -1; | |
| uri->authority_len = 0; | |
| uri->domain_offset = -1; | |
| uri->port_offset = -1; | |
| if(is_hierarchical_scheme(data->scheme_type)) { | |
| DWORD i; | |
| /* Absolute URIs aren't displayed for known scheme types | |
| * which should be hierarchical URIs. | |
| */ | |
| uri->display_modifiers |= URI_DISPLAY_NO_ABSOLUTE_URI; | |
| /* Windows also sets the port for these (if they have one). */ | |
| for(i = 0; i < sizeof(default_ports)/sizeof(default_ports[0]); ++i) { | |
| if(data->scheme_type == default_ports[i].scheme) { | |
| uri->has_port = TRUE; | |
| uri->port = default_ports[i].port; | |
| break; | |
| } | |
| } | |
| } | |
| if(!canonicalize_path_opaque(data, uri, flags, computeOnly)) | |
| return FALSE; | |
| } | |
| if(uri->path_start > -1 && !computeOnly) | |
| /* Finding file extensions happens for both types of URIs. */ | |
| uri->extension_offset = find_file_extension(uri->canon_uri+uri->path_start, uri->path_len); | |
| else | |
| uri->extension_offset = -1; | |
| return TRUE; | |
| } | |
| /* Attempts to canonicalize the query string of the URI. | |
| * | |
| * Things that happen: | |
| * 1) For known scheme types forbidden characters | |
| * are percent encoded, unless the NO_DECODE_EXTRA_INFO flag is set | |
| * or NO_ENCODE_FORBIDDEN_CHARACTERS is set. | |
| * | |
| * 2) For known scheme types, percent encoded, unreserved characters | |
| * are decoded as long as the NO_DECODE_EXTRA_INFO flag isn't set. | |
| */ | |
| static BOOL canonicalize_query(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { | |
| const WCHAR *ptr, *end; | |
| const BOOL known_scheme = data->scheme_type != URL_SCHEME_UNKNOWN; | |
| if(!data->query) { | |
| uri->query_start = -1; | |
| uri->query_len = 0; | |
| return TRUE; | |
| } | |
| uri->query_start = uri->canon_len; | |
| end = data->query+data->query_len; | |
| for(ptr = data->query; ptr < end; ++ptr) { | |
| if(*ptr == '%') { | |
| if(known_scheme && !(flags & Uri_CREATE_NO_DECODE_EXTRA_INFO)) { | |
| WCHAR val = decode_pct_val(ptr); | |
| if(is_unreserved(val)) { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = val; | |
| ++uri->canon_len; | |
| ptr += 2; | |
| continue; | |
| } | |
| } | |
| } else if(known_scheme && !is_unreserved(*ptr) && !is_reserved(*ptr)) { | |
| if(!(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS) && | |
| !(flags & Uri_CREATE_NO_DECODE_EXTRA_INFO)) { | |
| if(!computeOnly) | |
| pct_encode_val(*ptr, uri->canon_uri+uri->canon_len); | |
| uri->canon_len += 3; | |
| continue; | |
| } | |
| } | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = *ptr; | |
| ++uri->canon_len; | |
| } | |
| uri->query_len = uri->canon_len - uri->query_start; | |
| if(!computeOnly) | |
| TRACE("(%p %p %x %d): Canonicalized query string %s len=%d\n", data, uri, flags, | |
| computeOnly, debugstr_wn(uri->canon_uri+uri->query_start, uri->query_len), | |
| uri->query_len); | |
| return TRUE; | |
| } | |
| static BOOL canonicalize_fragment(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { | |
| const WCHAR *ptr, *end; | |
| const BOOL known_scheme = data->scheme_type != URL_SCHEME_UNKNOWN; | |
| if(!data->fragment) { | |
| uri->fragment_start = -1; | |
| uri->fragment_len = 0; | |
| return TRUE; | |
| } | |
| uri->fragment_start = uri->canon_len; | |
| end = data->fragment + data->fragment_len; | |
| for(ptr = data->fragment; ptr < end; ++ptr) { | |
| if(*ptr == '%') { | |
| if(known_scheme && !(flags & Uri_CREATE_NO_DECODE_EXTRA_INFO)) { | |
| WCHAR val = decode_pct_val(ptr); | |
| if(is_unreserved(val)) { | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = val; | |
| ++uri->canon_len; | |
| ptr += 2; | |
| continue; | |
| } | |
| } | |
| } else if(known_scheme && !is_unreserved(*ptr) && !is_reserved(*ptr)) { | |
| if(!(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS) && | |
| !(flags & Uri_CREATE_NO_DECODE_EXTRA_INFO)) { | |
| if(!computeOnly) | |
| pct_encode_val(*ptr, uri->canon_uri+uri->canon_len); | |
| uri->canon_len += 3; | |
| continue; | |
| } | |
| } | |
| if(!computeOnly) | |
| uri->canon_uri[uri->canon_len] = *ptr; | |
| ++uri->canon_len; | |
| } | |
| uri->fragment_len = uri->canon_len - uri->fragment_start; | |
| if(!computeOnly) | |
| TRACE("(%p %p %x %d): Canonicalized fragment %s len=%d\n", data, uri, flags, | |
| computeOnly, debugstr_wn(uri->canon_uri+uri->fragment_start, uri->fragment_len), | |
| uri->fragment_len); | |
| return TRUE; | |
| } | |
| /* Canonicalizes the scheme information specified in the parse_data using the specified flags. */ | |
| static BOOL canonicalize_scheme(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { | |
| uri->scheme_start = -1; | |
| uri->scheme_len = 0; | |
| if(!data->scheme) { | |
| /* The only type of URI that doesn't have to have a scheme is a relative | |
| * URI. | |
| */ | |
| if(!data->is_relative) { | |
| FIXME("(%p %p %x): Unable to determine the scheme type of %s.\n", data, | |
| uri, flags, debugstr_w(data->uri)); | |
| return FALSE; | |
| } | |
| } else { | |
| if(!computeOnly) { | |
| DWORD i; | |
| INT pos = uri->canon_len; | |
| for(i = 0; i < data->scheme_len; ++i) { | |
| /* Scheme name must be lower case after canonicalization. */ | |
| uri->canon_uri[i + pos] = tolowerW(data->scheme[i]); | |
| } | |
| uri->canon_uri[i + pos] = ':'; | |
| uri->scheme_start = pos; | |
| TRACE("(%p %p %x): Canonicalized scheme=%s, len=%d.\n", data, uri, flags, | |
| debugstr_wn(uri->canon_uri+uri->scheme_start, data->scheme_len), data->scheme_len); | |
| } | |
| /* This happens in both computation modes. */ | |
| uri->canon_len += data->scheme_len + 1; | |
| uri->scheme_len = data->scheme_len; | |
| } | |
| return TRUE; | |
| } | |
| /* Computes what the length of the URI specified by the parse_data will be | |
| * after canonicalization occurs using the specified flags. | |
| * | |
| * This function will return a non-zero value indicating the length of the canonicalized | |
| * URI, or -1 on error. | |
| */ | |
| static int compute_canonicalized_length(const parse_data *data, DWORD flags) { | |
| Uri uri; | |
| memset(&uri, 0, sizeof(Uri)); | |
| TRACE("(%p %x): Beginning to compute canonicalized length for URI %s\n", data, flags, | |
| debugstr_w(data->uri)); | |
| if(!canonicalize_scheme(data, &uri, flags, TRUE)) { | |
| ERR("(%p %x): Failed to compute URI scheme length.\n", data, flags); | |
| return -1; | |
| } | |
| if(!canonicalize_hierpart(data, &uri, flags, TRUE)) { | |
| ERR("(%p %x): Failed to compute URI hierpart length.\n", data, flags); | |
| return -1; | |
| } | |
| if(!canonicalize_query(data, &uri, flags, TRUE)) { | |
| ERR("(%p %x): Failed to compute query string length.\n", data, flags); | |
| return -1; | |
| } | |
| if(!canonicalize_fragment(data, &uri, flags, TRUE)) { | |
| ERR("(%p %x): Failed to compute fragment length.\n", data, flags); | |
| return -1; | |
| } | |
| TRACE("(%p %x): Finished computing canonicalized URI length. length=%d\n", data, flags, uri.canon_len); | |
| return uri.canon_len; | |
| } | |
| /* Canonicalizes the URI data specified in the parse_data, using the given flags. If the | |
| * canonicalization succeeds it will store all the canonicalization information | |
| * in the pointer to the Uri. | |
| * | |
| * To canonicalize a URI this function first computes what the length of the URI | |
| * specified by the parse_data will be. Once this is done it will then perform the actual | |
| * canonicalization of the URI. | |
| */ | |
| static HRESULT canonicalize_uri(const parse_data *data, Uri *uri, DWORD flags) { | |
| INT len; | |
| uri->canon_uri = NULL; | |
| uri->canon_size = uri->canon_len = 0; | |
| TRACE("(%p %p %x): beginning to canonicalize URI %s.\n", data, uri, flags, debugstr_w(data->uri)); | |
| /* First try to compute the length of the URI. */ | |
| len = compute_canonicalized_length(data, flags); | |
| if(len == -1) { | |
| ERR("(%p %p %x): Could not compute the canonicalized length of %s.\n", data, uri, flags, | |
| debugstr_w(data->uri)); | |
| return E_INVALIDARG; | |
| } | |
| uri->canon_uri = heap_alloc((len+1)*sizeof(WCHAR)); | |
| if(!uri->canon_uri) | |
| return E_OUTOFMEMORY; | |
| uri->canon_size = len; | |
| if(!canonicalize_scheme(data, uri, flags, FALSE)) { | |
| ERR("(%p %p %x): Unable to canonicalize the scheme of the URI.\n", data, uri, flags); | |
| return E_INVALIDARG; | |
| } | |
| uri->scheme_type = data->scheme_type; | |
| if(!canonicalize_hierpart(data, uri, flags, FALSE)) { | |
| ERR("(%p %p %x): Unable to canonicalize the heirpart of the URI\n", data, uri, flags); | |
| return E_INVALIDARG; | |
| } | |
| if(!canonicalize_query(data, uri, flags, FALSE)) { | |
| ERR("(%p %p %x): Unable to canonicalize query string of the URI.\n", | |
| data, uri, flags); | |
| return E_INVALIDARG; | |
| } | |
| if(!canonicalize_fragment(data, uri, flags, FALSE)) { | |
| ERR("(%p %p %x): Unable to canonicalize fragment of the URI.\n", | |
| data, uri, flags); | |
| return E_INVALIDARG; | |
| } | |
| /* There's a possibility we didn't use all the space we allocated | |
| * earlier. | |
| */ | |
| if(uri->canon_len < uri->canon_size) { | |
| /* This happens if the URI is hierarchical and dot | |
| * segments were removed from its path. | |
| */ | |
| WCHAR *tmp = heap_realloc(uri->canon_uri, (uri->canon_len+1)*sizeof(WCHAR)); | |
| if(!tmp) | |
| return E_OUTOFMEMORY; | |
| uri->canon_uri = tmp; | |
| uri->canon_size = uri->canon_len; | |
| } | |
| uri->canon_uri[uri->canon_len] = '\0'; | |
| TRACE("(%p %p %x): finished canonicalizing the URI. uri=%s\n", data, uri, flags, debugstr_w(uri->canon_uri)); | |
| return S_OK; | |
| } | |
| static HRESULT get_builder_component(LPWSTR *component, DWORD *component_len, | |
| LPCWSTR source, DWORD source_len, | |
| LPCWSTR *output, DWORD *output_len) | |
| { | |
| if(!output_len) { | |
| if(output) | |
| *output = NULL; | |
| return E_POINTER; | |
| } | |
| if(!output) { | |
| *output_len = 0; | |
| return E_POINTER; | |
| } | |
| if(!(*component) && source) { | |
| /* Allocate 'component', and copy the contents from 'source' | |
| * into the new allocation. | |
| */ | |
| *component = heap_alloc((source_len+1)*sizeof(WCHAR)); | |
| if(!(*component)) | |
| return E_OUTOFMEMORY; | |
| memcpy(*component, source, source_len*sizeof(WCHAR)); | |
| (*component)[source_len] = '\0'; | |
| *component_len = source_len; | |
| } | |
| *output = *component; | |
| *output_len = *component_len; | |
| return *output ? S_OK : S_FALSE; | |
| } | |
| /* Allocates 'component' and copies the string from 'new_value' into 'component'. | |
| * If 'prefix' is set and 'new_value' isn't NULL, then it checks if 'new_value' | |
| * starts with 'prefix'. If it doesn't then 'prefix' is prepended to 'component'. | |
| * | |
| * If everything is successful, then will set 'success_flag' in 'flags'. | |
| */ | |
| static HRESULT set_builder_component(LPWSTR *component, DWORD *component_len, LPCWSTR new_value, | |
| WCHAR prefix, DWORD *flags, DWORD success_flag) | |
| { | |
| heap_free(*component); | |
| if(!new_value) { | |
| *component = NULL; | |
| *component_len = 0; | |
| } else { | |
| BOOL add_prefix = FALSE; | |
| DWORD len = lstrlenW(new_value); | |
| DWORD pos = 0; | |
| if(prefix && *new_value != prefix) { | |
| add_prefix = TRUE; | |
| *component = heap_alloc((len+2)*sizeof(WCHAR)); | |
| } else | |
| *component = heap_alloc((len+1)*sizeof(WCHAR)); | |
| if(!(*component)) | |
| return E_OUTOFMEMORY; | |
| if(add_prefix) | |
| (*component)[pos++] = prefix; | |
| memcpy(*component+pos, new_value, (len+1)*sizeof(WCHAR)); | |
| *component_len = len+pos; | |
| } | |
| *flags |= success_flag; | |
| return S_OK; | |
| } | |
| static void reset_builder(UriBuilder *builder) { | |
| if(builder->uri) | |
| IUri_Release(&builder->uri->IUri_iface); | |
| builder->uri = NULL; | |
| heap_free(builder->fragment); | |
| builder->fragment = NULL; | |
| builder->fragment_len = 0; | |
| heap_free(builder->host); | |
| builder->host = NULL; | |
| builder->host_len = 0; | |
| heap_free(builder->password); | |
| builder->password = NULL; | |
| builder->password_len = 0; | |
| heap_free(builder->path); | |
| builder->path = NULL; | |
| builder->path_len = 0; | |
| heap_free(builder->query); | |
| builder->query = NULL; | |
| builder->query_len = 0; | |
| heap_free(builder->scheme); | |
| builder->scheme = NULL; | |
| builder->scheme_len = 0; | |
| heap_free(builder->username); | |
| builder->username = NULL; | |
| builder->username_len = 0; | |
| builder->has_port = FALSE; | |
| builder->port = 0; | |
| builder->modified_props = 0; | |
| } | |
| static HRESULT validate_scheme_name(const UriBuilder *builder, parse_data *data, DWORD flags) { | |
| const WCHAR *component; | |
| const WCHAR *ptr; | |
| const WCHAR **pptr; | |
| DWORD expected_len; | |
| if(builder->scheme) { | |
| ptr = builder->scheme; | |
| expected_len = builder->scheme_len; | |
| } else if(builder->uri && builder->uri->scheme_start > -1) { | |
| ptr = builder->uri->canon_uri+builder->uri->scheme_start; | |
| expected_len = builder->uri->scheme_len; | |
| } else { | |
| static const WCHAR nullW[] = {0}; | |
| ptr = nullW; | |
| expected_len = 0; | |
| } | |
| component = ptr; | |
| pptr = &ptr; | |
| if(parse_scheme(pptr, data, flags, ALLOW_NULL_TERM_SCHEME) && | |
| data->scheme_len == expected_len) { | |
| if(data->scheme) | |
| TRACE("(%p %p %x): Found valid scheme component %s len=%d.\n", builder, data, flags, | |
| debugstr_wn(data->scheme, data->scheme_len), data->scheme_len); | |
| } else { | |
| TRACE("(%p %p %x): Invalid scheme component found %s.\n", builder, data, flags, | |
| debugstr_wn(component, expected_len)); | |
| return INET_E_INVALID_URL; | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT validate_username(const UriBuilder *builder, parse_data *data, DWORD flags) { | |
| const WCHAR *ptr; | |
| const WCHAR **pptr; | |
| DWORD expected_len; | |
| if(builder->username) { | |
| ptr = builder->username; | |
| expected_len = builder->username_len; | |
| } else if(!(builder->modified_props & Uri_HAS_USER_NAME) && builder->uri && | |
| builder->uri->userinfo_start > -1 && builder->uri->userinfo_split != 0) { | |
| /* Just use the username from the base Uri. */ | |
| data->username = builder->uri->canon_uri+builder->uri->userinfo_start; | |
| data->username_len = (builder->uri->userinfo_split > -1) ? | |
| builder->uri->userinfo_split : builder->uri->userinfo_len; | |
| ptr = NULL; | |
| } else { | |
| ptr = NULL; | |
| expected_len = 0; | |
| } | |
| if(ptr) { | |
| const WCHAR *component = ptr; | |
| pptr = &ptr; | |
| if(parse_username(pptr, data, flags, ALLOW_NULL_TERM_USER_NAME) && | |
| data->username_len == expected_len) | |
| TRACE("(%p %p %x): Found valid username component %s len=%d.\n", builder, data, flags, | |
| debugstr_wn(data->username, data->username_len), data->username_len); | |
| else { | |
| TRACE("(%p %p %x): Invalid username component found %s.\n", builder, data, flags, | |
| debugstr_wn(component, expected_len)); | |
| return INET_E_INVALID_URL; | |
| } | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT validate_password(const UriBuilder *builder, parse_data *data, DWORD flags) { | |
| const WCHAR *ptr; | |
| const WCHAR **pptr; | |
| DWORD expected_len; | |
| if(builder->password) { | |
| ptr = builder->password; | |
| expected_len = builder->password_len; | |
| } else if(!(builder->modified_props & Uri_HAS_PASSWORD) && builder->uri && | |
| builder->uri->userinfo_split > -1) { | |
| data->password = builder->uri->canon_uri+builder->uri->userinfo_start+builder->uri->userinfo_split+1; | |
| data->password_len = builder->uri->userinfo_len-builder->uri->userinfo_split-1; | |
| ptr = NULL; | |
| } else { | |
| ptr = NULL; | |
| expected_len = 0; | |
| } | |
| if(ptr) { | |
| const WCHAR *component = ptr; | |
| pptr = &ptr; | |
| if(parse_password(pptr, data, flags, ALLOW_NULL_TERM_PASSWORD) && | |
| data->password_len == expected_len) | |
| TRACE("(%p %p %x): Found valid password component %s len=%d.\n", builder, data, flags, | |
| debugstr_wn(data->password, data->password_len), data->password_len); | |
| else { | |
| TRACE("(%p %p %x): Invalid password component found %s.\n", builder, data, flags, | |
| debugstr_wn(component, expected_len)); | |
| return INET_E_INVALID_URL; | |
| } | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT validate_userinfo(const UriBuilder *builder, parse_data *data, DWORD flags) { | |
| HRESULT hr; | |
| hr = validate_username(builder, data, flags); | |
| if(FAILED(hr)) | |
| return hr; | |
| hr = validate_password(builder, data, flags); | |
| if(FAILED(hr)) | |
| return hr; | |
| return S_OK; | |
| } | |
| static HRESULT validate_host(const UriBuilder *builder, parse_data *data, DWORD flags) { | |
| const WCHAR *ptr; | |
| const WCHAR **pptr; | |
| DWORD expected_len; | |
| if(builder->host) { | |
| ptr = builder->host; | |
| expected_len = builder->host_len; | |
| } else if(!(builder->modified_props & Uri_HAS_HOST) && builder->uri && builder->uri->host_start > -1) { | |
| ptr = builder->uri->canon_uri + builder->uri->host_start; | |
| expected_len = builder->uri->host_len; | |
| } else | |
| ptr = NULL; | |
| if(ptr) { | |
| const WCHAR *component = ptr; | |
| DWORD extras = ALLOW_BRACKETLESS_IP_LITERAL|IGNORE_PORT_DELIMITER|SKIP_IP_FUTURE_CHECK; | |
| pptr = &ptr; | |
| if(parse_host(pptr, data, flags, extras) && data->host_len == expected_len) | |
| TRACE("(%p %p %x): Found valid host name %s len=%d type=%d.\n", builder, data, flags, | |
| debugstr_wn(data->host, data->host_len), data->host_len, data->host_type); | |
| else { | |
| TRACE("(%p %p %x): Invalid host name found %s.\n", builder, data, flags, | |
| debugstr_wn(component, expected_len)); | |
| return INET_E_INVALID_URL; | |
| } | |
| } | |
| return S_OK; | |
| } | |
| static void setup_port(const UriBuilder *builder, parse_data *data, DWORD flags) { | |
| if(builder->modified_props & Uri_HAS_PORT) { | |
| if(builder->has_port) { | |
| data->has_port = TRUE; | |
| data->port_value = builder->port; | |
| } | |
| } else if(builder->uri && builder->uri->has_port) { | |
| data->has_port = TRUE; | |
| data->port_value = builder->uri->port; | |
| } | |
| if(data->has_port) | |
| TRACE("(%p %p %x): Using %u as port for IUri.\n", builder, data, flags, data->port_value); | |
| } | |
| static HRESULT validate_path(const UriBuilder *builder, parse_data *data, DWORD flags) { | |
| const WCHAR *ptr = NULL; | |
| const WCHAR *component; | |
| const WCHAR **pptr; | |
| DWORD expected_len; | |
| BOOL check_len = TRUE; | |
| BOOL valid = FALSE; | |
| if(builder->path) { | |
| ptr = builder->path; | |
| expected_len = builder->path_len; | |
| } else if(!(builder->modified_props & Uri_HAS_PATH) && | |
| builder->uri && builder->uri->path_start > -1) { | |
| ptr = builder->uri->canon_uri+builder->uri->path_start; | |
| expected_len = builder->uri->path_len; | |
| } else { | |
| static const WCHAR nullW[] = {0}; | |
| ptr = nullW; | |
| check_len = FALSE; | |
| expected_len = -1; | |
| } | |
| component = ptr; | |
| pptr = &ptr; | |
| /* How the path is validated depends on what type of | |
| * URI it is. | |
| */ | |
| valid = data->is_opaque ? | |
| parse_path_opaque(pptr, data, flags) : parse_path_hierarchical(pptr, data, flags); | |
| if(!valid || (check_len && expected_len != data->path_len)) { | |
| TRACE("(%p %p %x): Invalid path component %s.\n", builder, data, flags, | |
| debugstr_wn(component, expected_len) ); | |
| return INET_E_INVALID_URL; | |
| } | |
| TRACE("(%p %p %x): Valid path component %s len=%d.\n", builder, data, flags, | |
| debugstr_wn(data->path, data->path_len), data->path_len); | |
| return S_OK; | |
| } | |
| static HRESULT validate_query(const UriBuilder *builder, parse_data *data, DWORD flags) { | |
| const WCHAR *ptr = NULL; | |
| const WCHAR **pptr; | |
| DWORD expected_len; | |
| if(builder->query) { | |
| ptr = builder->query; | |
| expected_len = builder->query_len; | |
| } else if(!(builder->modified_props & Uri_HAS_QUERY) && builder->uri && | |
| builder->uri->query_start > -1) { | |
| ptr = builder->uri->canon_uri+builder->uri->query_start; | |
| expected_len = builder->uri->query_len; | |
| } | |
| if(ptr) { | |
| const WCHAR *component = ptr; | |
| pptr = &ptr; | |
| if(parse_query(pptr, data, flags) && expected_len == data->query_len) | |
| TRACE("(%p %p %x): Valid query component %s len=%d.\n", builder, data, flags, | |
| debugstr_wn(data->query, data->query_len), data->query_len); | |
| else { | |
| TRACE("(%p %p %x): Invalid query component %s.\n", builder, data, flags, | |
| debugstr_wn(component, expected_len)); | |
| return INET_E_INVALID_URL; | |
| } | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT validate_fragment(const UriBuilder *builder, parse_data *data, DWORD flags) { | |
| const WCHAR *ptr = NULL; | |
| const WCHAR **pptr; | |
| DWORD expected_len; | |
| if(builder->fragment) { | |
| ptr = builder->fragment; | |
| expected_len = builder->fragment_len; | |
| } else if(!(builder->modified_props & Uri_HAS_FRAGMENT) && builder->uri && | |
| builder->uri->fragment_start > -1) { | |
| ptr = builder->uri->canon_uri+builder->uri->fragment_start; | |
| expected_len = builder->uri->fragment_len; | |
| } | |
| if(ptr) { | |
| const WCHAR *component = ptr; | |
| pptr = &ptr; | |
| if(parse_fragment(pptr, data, flags) && expected_len == data->fragment_len) | |
| TRACE("(%p %p %x): Valid fragment component %s len=%d.\n", builder, data, flags, | |
| debugstr_wn(data->fragment, data->fragment_len), data->fragment_len); | |
| else { | |
| TRACE("(%p %p %x): Invalid fragment component %s.\n", builder, data, flags, | |
| debugstr_wn(component, expected_len)); | |
| return INET_E_INVALID_URL; | |
| } | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT validate_components(const UriBuilder *builder, parse_data *data, DWORD flags) { | |
| HRESULT hr; | |
| memset(data, 0, sizeof(parse_data)); | |
| TRACE("(%p %p %x): Beginning to validate builder components.\n", builder, data, flags); | |
| hr = validate_scheme_name(builder, data, flags); | |
| if(FAILED(hr)) | |
| return hr; | |
| /* Extra validation for file schemes. */ | |
| if(data->scheme_type == URL_SCHEME_FILE) { | |
| if((builder->password || (builder->uri && builder->uri->userinfo_split > -1)) || | |
| (builder->username || (builder->uri && builder->uri->userinfo_start > -1))) { | |
| TRACE("(%p %p %x): File schemes can't contain a username or password.\n", | |
| builder, data, flags); | |
| return INET_E_INVALID_URL; | |
| } | |
| } | |
| hr = validate_userinfo(builder, data, flags); | |
| if(FAILED(hr)) | |
| return hr; | |
| hr = validate_host(builder, data, flags); | |
| if(FAILED(hr)) | |
| return hr; | |
| setup_port(builder, data, flags); | |
| /* The URI is opaque if it doesn't have an authority component. */ | |
| if(!data->is_relative) | |
| data->is_opaque = !data->username && !data->password && !data->host && !data->has_port | |
| && data->scheme_type != URL_SCHEME_FILE; | |
| else | |
| data->is_opaque = !data->host && !data->has_port; | |
| hr = validate_path(builder, data, flags); | |
| if(FAILED(hr)) | |
| return hr; | |
| hr = validate_query(builder, data, flags); | |
| if(FAILED(hr)) | |
| return hr; | |
| hr = validate_fragment(builder, data, flags); | |
| if(FAILED(hr)) | |
| return hr; | |
| TRACE("(%p %p %x): Finished validating builder components.\n", builder, data, flags); | |
| return S_OK; | |
| } | |
| static HRESULT compare_file_paths(const Uri *a, const Uri *b, BOOL *ret) | |
| { | |
| WCHAR *canon_path_a, *canon_path_b; | |
| DWORD len_a, len_b; | |
| if(!a->path_len) { | |
| *ret = !b->path_len; | |
| return S_OK; | |
| } | |
| if(!b->path_len) { | |
| *ret = FALSE; | |
| return S_OK; | |
| } | |
| /* Fast path */ | |
| if(a->path_len == b->path_len && !memicmpW(a->canon_uri+a->path_start, b->canon_uri+b->path_start, a->path_len)) { | |
| *ret = TRUE; | |
| return S_OK; | |
| } | |
| len_a = canonicalize_path_hierarchical(a->canon_uri+a->path_start, a->path_len, a->scheme_type, FALSE, 0, FALSE, NULL); | |
| len_b = canonicalize_path_hierarchical(b->canon_uri+b->path_start, b->path_len, b->scheme_type, FALSE, 0, FALSE, NULL); | |
| canon_path_a = heap_alloc(len_a*sizeof(WCHAR)); | |
| if(!canon_path_a) | |
| return E_OUTOFMEMORY; | |
| canon_path_b = heap_alloc(len_b*sizeof(WCHAR)); | |
| if(!canon_path_b) { | |
| heap_free(canon_path_a); | |
| return E_OUTOFMEMORY; | |
| } | |
| len_a = canonicalize_path_hierarchical(a->canon_uri+a->path_start, a->path_len, a->scheme_type, FALSE, 0, FALSE, canon_path_a); | |
| len_b = canonicalize_path_hierarchical(b->canon_uri+b->path_start, b->path_len, b->scheme_type, FALSE, 0, FALSE, canon_path_b); | |
| *ret = len_a == len_b && !memicmpW(canon_path_a, canon_path_b, len_a); | |
| heap_free(canon_path_a); | |
| heap_free(canon_path_b); | |
| return S_OK; | |
| } | |
| /* Checks if the two Uri's are logically equivalent. It's a simple | |
| * comparison, since they are both of type Uri, and it can access | |
| * the properties of each Uri directly without the need to go | |
| * through the "IUri_Get*" interface calls. | |
| */ | |
| static HRESULT compare_uris(const Uri *a, const Uri *b, BOOL *ret) { | |
| const BOOL known_scheme = a->scheme_type != URL_SCHEME_UNKNOWN; | |
| const BOOL are_hierarchical = a->authority_start > -1 && b->authority_start > -1; | |
| HRESULT hres; | |
| *ret = FALSE; | |
| if(a->scheme_type != b->scheme_type) | |
| return S_OK; | |
| /* Only compare the scheme names (if any) if their unknown scheme types. */ | |
| if(!known_scheme) { | |
| if((a->scheme_start > -1 && b->scheme_start > -1) && | |
| (a->scheme_len == b->scheme_len)) { | |
| /* Make sure the schemes are the same. */ | |
| if(StrCmpNW(a->canon_uri+a->scheme_start, b->canon_uri+b->scheme_start, a->scheme_len)) | |
| return S_OK; | |
| } else if(a->scheme_len != b->scheme_len) | |
| /* One of the Uri's has a scheme name, while the other doesn't. */ | |
| return S_OK; | |
| } | |
| /* If they have a userinfo component, perform case sensitive compare. */ | |
| if((a->userinfo_start > -1 && b->userinfo_start > -1) && | |
| (a->userinfo_len == b->userinfo_len)) { | |
| if(StrCmpNW(a->canon_uri+a->userinfo_start, b->canon_uri+b->userinfo_start, a->userinfo_len)) | |
| return S_OK; | |
| } else if(a->userinfo_len != b->userinfo_len) | |
| /* One of the Uri's had a userinfo, while the other one doesn't. */ | |
| return S_OK; | |
| /* Check if they have a host name. */ | |
| if((a->host_start > -1 && b->host_start > -1) && | |
| (a->host_len == b->host_len)) { | |
| /* Perform a case insensitive compare if they are a known scheme type. */ | |
| if(known_scheme) { | |
| if(StrCmpNIW(a->canon_uri+a->host_start, b->canon_uri+b->host_start, a->host_len)) | |
| return S_OK; | |
| } else if(StrCmpNW(a->canon_uri+a->host_start, b->canon_uri+b->host_start, a->host_len)) | |
| return S_OK; | |
| } else if(a->host_len != b->host_len) | |
| /* One of the Uri's had a host, while the other one didn't. */ | |
| return S_OK; | |
| if(a->has_port && b->has_port) { | |
| if(a->port != b->port) | |
| return S_OK; | |
| } else if(a->has_port || b->has_port) | |
| /* One had a port, while the other one didn't. */ | |
| return S_OK; | |
| /* Windows is weird with how it handles paths. For example | |
| * One URI could be "http://google.com" (after canonicalization) | |
| * and one could be "http://google.com/" and the IsEqual function | |
| * would still evaluate to TRUE, but, only if they are both hierarchical | |
| * URIs. | |
| */ | |
| if(a->scheme_type == URL_SCHEME_FILE) { | |
| BOOL cmp; | |
| hres = compare_file_paths(a, b, &cmp); | |
| if(FAILED(hres) || !cmp) | |
| return hres; | |
| } else if((a->path_start > -1 && b->path_start > -1) && | |
| (a->path_len == b->path_len)) { | |
| if(StrCmpNW(a->canon_uri+a->path_start, b->canon_uri+b->path_start, a->path_len)) | |
| return S_OK; | |
| } else if(are_hierarchical && a->path_len == -1 && b->path_len == 0) { | |
| if(*(a->canon_uri+a->path_start) != '/') | |
| return S_OK; | |
| } else if(are_hierarchical && b->path_len == 1 && a->path_len == 0) { | |
| if(*(b->canon_uri+b->path_start) != '/') | |
| return S_OK; | |
| } else if(a->path_len != b->path_len) | |
| return S_OK; | |
| /* Compare the query strings of the two URIs. */ | |
| if((a->query_start > -1 && b->query_start > -1) && | |
| (a->query_len == b->query_len)) { | |
| if(StrCmpNW(a->canon_uri+a->query_start, b->canon_uri+b->query_start, a->query_len)) | |
| return S_OK; | |
| } else if(a->query_len != b->query_len) | |
| return S_OK; | |
| if((a->fragment_start > -1 && b->fragment_start > -1) && | |
| (a->fragment_len == b->fragment_len)) { | |
| if(StrCmpNW(a->canon_uri+a->fragment_start, b->canon_uri+b->fragment_start, a->fragment_len)) | |
| return S_OK; | |
| } else if(a->fragment_len != b->fragment_len) | |
| return S_OK; | |
| /* If we get here, the two URIs are equivalent. */ | |
| *ret = TRUE; | |
| return S_OK; | |
| } | |
| static void convert_to_dos_path(const WCHAR *path, DWORD path_len, | |
| WCHAR *output, DWORD *output_len) | |
| { | |
| const WCHAR *ptr = path; | |
| if(path_len > 3 && *ptr == '/' && is_drive_path(path+1)) | |
| /* Skip over the leading / before the drive path. */ | |
| ++ptr; | |
| for(; ptr < path+path_len; ++ptr) { | |
| if(*ptr == '/') { | |
| if(output) | |
| *output++ = '\\'; | |
| (*output_len)++; | |
| } else { | |
| if(output) | |
| *output++ = *ptr; | |
| (*output_len)++; | |
| } | |
| } | |
| } | |
| /* Generates a raw uri string using the parse_data. */ | |
| static DWORD generate_raw_uri(const parse_data *data, BSTR uri, DWORD flags) { | |
| DWORD length = 0; | |
| if(data->scheme) { | |
| if(uri) { | |
| memcpy(uri, data->scheme, data->scheme_len*sizeof(WCHAR)); | |
| uri[data->scheme_len] = ':'; | |
| } | |
| length += data->scheme_len+1; | |
| } | |
| if(!data->is_opaque) { | |
| /* For the "//" which appears before the authority component. */ | |
| if(uri) { | |
| uri[length] = '/'; | |
| uri[length+1] = '/'; | |
| } | |
| length += 2; | |
| /* Check if we need to add the "\\" before the host name | |
| * of a UNC server name in a DOS path. | |
| */ | |
| if(flags & RAW_URI_CONVERT_TO_DOS_PATH && | |
| data->scheme_type == URL_SCHEME_FILE && data->host) { | |
| if(uri) { | |
| uri[length] = '\\'; | |
| uri[length+1] = '\\'; | |
| } | |
| length += 2; | |
| } | |
| } | |
| if(data->username) { | |
| if(uri) | |
| memcpy(uri+length, data->username, data->username_len*sizeof(WCHAR)); | |
| length += data->username_len; | |
| } | |
| if(data->password) { | |
| if(uri) { | |
| uri[length] = ':'; | |
| memcpy(uri+length+1, data->password, data->password_len*sizeof(WCHAR)); | |
| } | |
| length += data->password_len+1; | |
| } | |
| if(data->password || data->username) { | |
| if(uri) | |
| uri[length] = '@'; | |
| ++length; | |
| } | |
| if(data->host) { | |
| /* IPv6 addresses get the brackets added around them if they don't already | |
| * have them. | |
| */ | |
| const BOOL add_brackets = data->host_type == Uri_HOST_IPV6 && *(data->host) != '['; | |
| if(add_brackets) { | |
| if(uri) | |
| uri[length] = '['; | |
| ++length; | |
| } | |
| if(uri) | |
| memcpy(uri+length, data->host, data->host_len*sizeof(WCHAR)); | |
| length += data->host_len; | |
| if(add_brackets) { | |
| if(uri) | |
| uri[length] = ']'; | |
| length++; | |
| } | |
| } | |
| if(data->has_port) { | |
| /* The port isn't included in the raw uri if it's the default | |
| * port for the scheme type. | |
| */ | |
| DWORD i; | |
| BOOL is_default = FALSE; | |
| for(i = 0; i < sizeof(default_ports)/sizeof(default_ports[0]); ++i) { | |
| if(data->scheme_type == default_ports[i].scheme && | |
| data->port_value == default_ports[i].port) | |
| is_default = TRUE; | |
| } | |
| if(!is_default || flags & RAW_URI_FORCE_PORT_DISP) { | |
| if(uri) | |
| uri[length] = ':'; | |
| ++length; | |
| if(uri) | |
| length += ui2str(uri+length, data->port_value); | |
| else | |
| length += ui2str(NULL, data->port_value); | |
| } | |
| } | |
| /* Check if a '/' should be added before the path for hierarchical URIs. */ | |
| if(!data->is_opaque && data->path && *(data->path) != '/') { | |
| if(uri) | |
| uri[length] = '/'; | |
| ++length; | |
| } | |
| if(data->path) { | |
| if(!data->is_opaque && data->scheme_type == URL_SCHEME_FILE && | |
| flags & RAW_URI_CONVERT_TO_DOS_PATH) { | |
| DWORD len = 0; | |
| if(uri) | |
| convert_to_dos_path(data->path, data->path_len, uri+length, &len); | |
| else | |
| convert_to_dos_path(data->path, data->path_len, NULL, &len); | |
| length += len; | |
| } else { | |
| if(uri) | |
| memcpy(uri+length, data->path, data->path_len*sizeof(WCHAR)); | |
| length += data->path_len; | |
| } | |
| } | |
| if(data->query) { | |
| if(uri) | |
| memcpy(uri+length, data->query, data->query_len*sizeof(WCHAR)); | |
| length += data->query_len; | |
| } | |
| if(data->fragment) { | |
| if(uri) | |
| memcpy(uri+length, data->fragment, data->fragment_len*sizeof(WCHAR)); | |
| length += data->fragment_len; | |
| } | |
| if(uri) | |
| TRACE("(%p %p): Generated raw uri=%s len=%d\n", data, uri, debugstr_wn(uri, length), length); | |
| else | |
| TRACE("(%p %p): Computed raw uri len=%d\n", data, uri, length); | |
| return length; | |
| } | |
| static HRESULT generate_uri(const UriBuilder *builder, const parse_data *data, Uri *uri, DWORD flags) { | |
| HRESULT hr; | |
| DWORD length = generate_raw_uri(data, NULL, 0); | |
| uri->raw_uri = SysAllocStringLen(NULL, length); | |
| if(!uri->raw_uri) | |
| return E_OUTOFMEMORY; | |
| generate_raw_uri(data, uri->raw_uri, 0); | |
| hr = canonicalize_uri(data, uri, flags); | |
| if(FAILED(hr)) { | |
| if(hr == E_INVALIDARG) | |
| return INET_E_INVALID_URL; | |
| return hr; | |
| } | |
| uri->create_flags = flags; | |
| return S_OK; | |
| } | |
| static inline Uri* impl_from_IUri(IUri *iface) | |
| { | |
| return CONTAINING_RECORD(iface, Uri, IUri_iface); | |
| } | |
| static inline void destroy_uri_obj(Uri *This) | |
| { | |
| SysFreeString(This->raw_uri); | |
| heap_free(This->canon_uri); | |
| heap_free(This); | |
| } | |
| static HRESULT WINAPI Uri_QueryInterface(IUri *iface, REFIID riid, void **ppv) | |
| { | |
| Uri *This = impl_from_IUri(iface); | |
| if(IsEqualGUID(&IID_IUnknown, riid)) { | |
| TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv); | |
| *ppv = &This->IUri_iface; | |
| }else if(IsEqualGUID(&IID_IUri, riid)) { | |
| TRACE("(%p)->(IID_IUri %p)\n", This, ppv); | |
| *ppv = &This->IUri_iface; | |
| }else if(IsEqualGUID(&IID_IUriBuilderFactory, riid)) { | |
| TRACE("(%p)->(IID_IUriBuilderFactory %p)\n", This, ppv); | |
| *ppv = &This->IUriBuilderFactory_iface; | |
| }else if(IsEqualGUID(&IID_IPersistStream, riid)) { | |
| TRACE("(%p)->(IID_IPersistStream %p)\n", This, ppv); | |
| *ppv = &This->IPersistStream_iface; | |
| }else if(IsEqualGUID(&IID_IMarshal, riid)) { | |
| TRACE("(%p)->(IID_IMarshal %p)\n", This, ppv); | |
| *ppv = &This->IMarshal_iface; | |
| }else if(IsEqualGUID(&IID_IUriObj, riid)) { | |
| TRACE("(%p)->(IID_IUriObj %p)\n", This, ppv); | |
| *ppv = This; | |
| return S_OK; | |
| }else { | |
| TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv); | |
| *ppv = NULL; | |
| return E_NOINTERFACE; | |
| } | |
| IUnknown_AddRef((IUnknown*)*ppv); | |
| return S_OK; | |
| } | |
| static ULONG WINAPI Uri_AddRef(IUri *iface) | |
| { | |
| Uri *This = impl_from_IUri(iface); | |
| LONG ref = InterlockedIncrement(&This->ref); | |
| TRACE("(%p) ref=%d\n", This, ref); | |
| return ref; | |
| } | |
| static ULONG WINAPI Uri_Release(IUri *iface) | |
| { | |
| Uri *This = impl_from_IUri(iface); | |
| LONG ref = InterlockedDecrement(&This->ref); | |
| TRACE("(%p) ref=%d\n", This, ref); | |
| if(!ref) | |
| destroy_uri_obj(This); | |
| return ref; | |
| } | |
| static HRESULT WINAPI Uri_GetPropertyBSTR(IUri *iface, Uri_PROPERTY uriProp, BSTR *pbstrProperty, DWORD dwFlags) | |
| { | |
| Uri *This = impl_from_IUri(iface); | |
| HRESULT hres; | |
| TRACE("(%p %s)->(%d %p %x)\n", This, debugstr_w(This->canon_uri), uriProp, pbstrProperty, dwFlags); | |
| if(!This->create_flags) | |
| return E_UNEXPECTED; | |
| if(!pbstrProperty) | |
| return E_POINTER; | |
| if(uriProp > Uri_PROPERTY_STRING_LAST) { | |
| /* It only returns S_FALSE for the ZONE property... */ | |
| if(uriProp == Uri_PROPERTY_ZONE) { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| if(!(*pbstrProperty)) | |
| return E_OUTOFMEMORY; | |
| return S_FALSE; | |
| } | |
| *pbstrProperty = NULL; | |
| return E_INVALIDARG; | |
| } | |
| /* Don't have support for flags yet. */ | |
| if(dwFlags) { | |
| FIXME("(%p)->(%d %p %x)\n", This, uriProp, pbstrProperty, dwFlags); | |
| return E_NOTIMPL; | |
| } | |
| switch(uriProp) { | |
| case Uri_PROPERTY_ABSOLUTE_URI: | |
| if(This->display_modifiers & URI_DISPLAY_NO_ABSOLUTE_URI) { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| hres = S_FALSE; | |
| } else { | |
| if(This->scheme_type != URL_SCHEME_UNKNOWN && This->userinfo_start > -1) { | |
| if(This->userinfo_len == 0) { | |
| /* Don't include the '@' after the userinfo component. */ | |
| *pbstrProperty = SysAllocStringLen(NULL, This->canon_len-1); | |
| hres = S_OK; | |
| if(*pbstrProperty) { | |
| /* Copy everything before it. */ | |
| memcpy(*pbstrProperty, This->canon_uri, This->userinfo_start*sizeof(WCHAR)); | |
| /* And everything after it. */ | |
| memcpy(*pbstrProperty+This->userinfo_start, This->canon_uri+This->userinfo_start+1, | |
| (This->canon_len-This->userinfo_start-1)*sizeof(WCHAR)); | |
| } | |
| } else if(This->userinfo_split == 0 && This->userinfo_len == 1) { | |
| /* Don't include the ":@" */ | |
| *pbstrProperty = SysAllocStringLen(NULL, This->canon_len-2); | |
| hres = S_OK; | |
| if(*pbstrProperty) { | |
| memcpy(*pbstrProperty, This->canon_uri, This->userinfo_start*sizeof(WCHAR)); | |
| memcpy(*pbstrProperty+This->userinfo_start, This->canon_uri+This->userinfo_start+2, | |
| (This->canon_len-This->userinfo_start-2)*sizeof(WCHAR)); | |
| } | |
| } else { | |
| *pbstrProperty = SysAllocString(This->canon_uri); | |
| hres = S_OK; | |
| } | |
| } else { | |
| *pbstrProperty = SysAllocString(This->canon_uri); | |
| hres = S_OK; | |
| } | |
| } | |
| if(!(*pbstrProperty)) | |
| hres = E_OUTOFMEMORY; | |
| break; | |
| case Uri_PROPERTY_AUTHORITY: | |
| if(This->authority_start > -1) { | |
| if(This->port_offset > -1 && is_default_port(This->scheme_type, This->port) && | |
| This->display_modifiers & URI_DISPLAY_NO_DEFAULT_PORT_AUTH) | |
| /* Don't include the port in the authority component. */ | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri+This->authority_start, This->port_offset); | |
| else | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri+This->authority_start, This->authority_len); | |
| hres = S_OK; | |
| } else { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| hres = S_FALSE; | |
| } | |
| if(!(*pbstrProperty)) | |
| hres = E_OUTOFMEMORY; | |
| break; | |
| case Uri_PROPERTY_DISPLAY_URI: | |
| /* The Display URI contains everything except for the userinfo for known | |
| * scheme types. | |
| */ | |
| if(This->scheme_type != URL_SCHEME_UNKNOWN && This->userinfo_start > -1) { | |
| *pbstrProperty = SysAllocStringLen(NULL, This->canon_len-This->userinfo_len); | |
| if(*pbstrProperty) { | |
| /* Copy everything before the userinfo over. */ | |
| memcpy(*pbstrProperty, This->canon_uri, This->userinfo_start*sizeof(WCHAR)); | |
| /* Copy everything after the userinfo over. */ | |
| memcpy(*pbstrProperty+This->userinfo_start, | |
| This->canon_uri+This->userinfo_start+This->userinfo_len+1, | |
| (This->canon_len-(This->userinfo_start+This->userinfo_len+1))*sizeof(WCHAR)); | |
| } | |
| } else | |
| *pbstrProperty = SysAllocString(This->canon_uri); | |
| if(!(*pbstrProperty)) | |
| hres = E_OUTOFMEMORY; | |
| else | |
| hres = S_OK; | |
| break; | |
| case Uri_PROPERTY_DOMAIN: | |
| if(This->domain_offset > -1) { | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri+This->host_start+This->domain_offset, | |
| This->host_len-This->domain_offset); | |
| hres = S_OK; | |
| } else { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| hres = S_FALSE; | |
| } | |
| if(!(*pbstrProperty)) | |
| hres = E_OUTOFMEMORY; | |
| break; | |
| case Uri_PROPERTY_EXTENSION: | |
| if(This->extension_offset > -1) { | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri+This->path_start+This->extension_offset, | |
| This->path_len-This->extension_offset); | |
| hres = S_OK; | |
| } else { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| hres = S_FALSE; | |
| } | |
| if(!(*pbstrProperty)) | |
| hres = E_OUTOFMEMORY; | |
| break; | |
| case Uri_PROPERTY_FRAGMENT: | |
| if(This->fragment_start > -1) { | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri+This->fragment_start, This->fragment_len); | |
| hres = S_OK; | |
| } else { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| hres = S_FALSE; | |
| } | |
| if(!(*pbstrProperty)) | |
| hres = E_OUTOFMEMORY; | |
| break; | |
| case Uri_PROPERTY_HOST: | |
| if(This->host_start > -1) { | |
| /* The '[' and ']' aren't included for IPv6 addresses. */ | |
| if(This->host_type == Uri_HOST_IPV6) | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri+This->host_start+1, This->host_len-2); | |
| else | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri+This->host_start, This->host_len); | |
| hres = S_OK; | |
| } else { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| hres = S_FALSE; | |
| } | |
| if(!(*pbstrProperty)) | |
| hres = E_OUTOFMEMORY; | |
| break; | |
| case Uri_PROPERTY_PASSWORD: | |
| if(This->userinfo_split > -1) { | |
| *pbstrProperty = SysAllocStringLen( | |
| This->canon_uri+This->userinfo_start+This->userinfo_split+1, | |
| This->userinfo_len-This->userinfo_split-1); | |
| hres = S_OK; | |
| } else { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| hres = S_FALSE; | |
| } | |
| if(!(*pbstrProperty)) | |
| return E_OUTOFMEMORY; | |
| break; | |
| case Uri_PROPERTY_PATH: | |
| if(This->path_start > -1) { | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri+This->path_start, This->path_len); | |
| hres = S_OK; | |
| } else { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| hres = S_FALSE; | |
| } | |
| if(!(*pbstrProperty)) | |
| hres = E_OUTOFMEMORY; | |
| break; | |
| case Uri_PROPERTY_PATH_AND_QUERY: | |
| if(This->path_start > -1) { | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri+This->path_start, This->path_len+This->query_len); | |
| hres = S_OK; | |
| } else if(This->query_start > -1) { | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri+This->query_start, This->query_len); | |
| hres = S_OK; | |
| } else { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| hres = S_FALSE; | |
| } | |
| if(!(*pbstrProperty)) | |
| hres = E_OUTOFMEMORY; | |
| break; | |
| case Uri_PROPERTY_QUERY: | |
| if(This->query_start > -1) { | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri+This->query_start, This->query_len); | |
| hres = S_OK; | |
| } else { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| hres = S_FALSE; | |
| } | |
| if(!(*pbstrProperty)) | |
| hres = E_OUTOFMEMORY; | |
| break; | |
| case Uri_PROPERTY_RAW_URI: | |
| *pbstrProperty = SysAllocString(This->raw_uri); | |
| if(!(*pbstrProperty)) | |
| hres = E_OUTOFMEMORY; | |
| else | |
| hres = S_OK; | |
| break; | |
| case Uri_PROPERTY_SCHEME_NAME: | |
| if(This->scheme_start > -1) { | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri + This->scheme_start, This->scheme_len); | |
| hres = S_OK; | |
| } else { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| hres = S_FALSE; | |
| } | |
| if(!(*pbstrProperty)) | |
| hres = E_OUTOFMEMORY; | |
| break; | |
| case Uri_PROPERTY_USER_INFO: | |
| if(This->userinfo_start > -1) { | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri+This->userinfo_start, This->userinfo_len); | |
| hres = S_OK; | |
| } else { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| hres = S_FALSE; | |
| } | |
| if(!(*pbstrProperty)) | |
| hres = E_OUTOFMEMORY; | |
| break; | |
| case Uri_PROPERTY_USER_NAME: | |
| if(This->userinfo_start > -1 && This->userinfo_split != 0) { | |
| /* If userinfo_split is set, that means a password exists | |
| * so the username is only from userinfo_start to userinfo_split. | |
| */ | |
| if(This->userinfo_split > -1) { | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri + This->userinfo_start, This->userinfo_split); | |
| hres = S_OK; | |
| } else { | |
| *pbstrProperty = SysAllocStringLen(This->canon_uri + This->userinfo_start, This->userinfo_len); | |
| hres = S_OK; | |
| } | |
| } else { | |
| *pbstrProperty = SysAllocStringLen(NULL, 0); | |
| hres = S_FALSE; | |
| } | |
| if(!(*pbstrProperty)) | |
| return E_OUTOFMEMORY; | |
| break; | |
| default: | |
| FIXME("(%p)->(%d %p %x)\n", This, uriProp, pbstrProperty, dwFlags); | |
| hres = E_NOTIMPL; | |
| } | |
| return hres; | |
| } | |
| static HRESULT WINAPI Uri_GetPropertyLength(IUri *iface, Uri_PROPERTY uriProp, DWORD *pcchProperty, DWORD dwFlags) | |
| { | |
| Uri *This = impl_from_IUri(iface); | |
| HRESULT hres; | |
| TRACE("(%p %s)->(%d %p %x)\n", This, debugstr_w(This->canon_uri), uriProp, pcchProperty, dwFlags); | |
| if(!This->create_flags) | |
| return E_UNEXPECTED; | |
| if(!pcchProperty) | |
| return E_INVALIDARG; | |
| /* Can only return a length for a property if it's a string. */ | |
| if(uriProp > Uri_PROPERTY_STRING_LAST) | |
| return E_INVALIDARG; | |
| /* Don't have support for flags yet. */ | |
| if(dwFlags) { | |
| FIXME("(%p)->(%d %p %x)\n", This, uriProp, pcchProperty, dwFlags); | |
| return E_NOTIMPL; | |
| } | |
| switch(uriProp) { | |
| case Uri_PROPERTY_ABSOLUTE_URI: | |
| if(This->display_modifiers & URI_DISPLAY_NO_ABSOLUTE_URI) { | |
| *pcchProperty = 0; | |
| hres = S_FALSE; | |
| } else { | |
| if(This->scheme_type != URL_SCHEME_UNKNOWN) { | |
| if(This->userinfo_start > -1 && This->userinfo_len == 0) | |
| /* Don't include the '@' in the length. */ | |
| *pcchProperty = This->canon_len-1; | |
| else if(This->userinfo_start > -1 && This->userinfo_len == 1 && | |
| This->userinfo_split == 0) | |
| /* Don't include the ":@" in the length. */ | |
| *pcchProperty = This->canon_len-2; | |
| else | |
| *pcchProperty = This->canon_len; | |
| } else | |
| *pcchProperty = This->canon_len; | |
| hres = S_OK; | |
| } | |
| break; | |
| case Uri_PROPERTY_AUTHORITY: | |
| if(This->port_offset > -1 && | |
| This->display_modifiers & URI_DISPLAY_NO_DEFAULT_PORT_AUTH && | |
| is_default_port(This->scheme_type, This->port)) | |
| /* Only count up until the port in the authority. */ | |
| *pcchProperty = This->port_offset; | |
| else | |
| *pcchProperty = This->authority_len; | |
| hres = (This->authority_start > -1) ? S_OK : S_FALSE; | |
| break; | |
| case Uri_PROPERTY_DISPLAY_URI: | |
| if(This->scheme_type != URL_SCHEME_UNKNOWN && This->userinfo_start > -1) | |
| *pcchProperty = This->canon_len-This->userinfo_len-1; | |
| else | |
| *pcchProperty = This->canon_len; | |
| hres = S_OK; | |
| break; | |
| case Uri_PROPERTY_DOMAIN: | |
| if(This->domain_offset > -1) | |
| *pcchProperty = This->host_len - This->domain_offset; | |
| else | |
| *pcchProperty = 0; | |
| hres = (This->domain_offset > -1) ? S_OK : S_FALSE; | |
| break; | |
| case Uri_PROPERTY_EXTENSION: | |
| if(This->extension_offset > -1) { | |
| *pcchProperty = This->path_len - This->extension_offset; | |
| hres = S_OK; | |
| } else { | |
| *pcchProperty = 0; | |
| hres = S_FALSE; | |
| } | |
| break; | |
| case Uri_PROPERTY_FRAGMENT: | |
| *pcchProperty = This->fragment_len; | |
| hres = (This->fragment_start > -1) ? S_OK : S_FALSE; | |
| break; | |
| case Uri_PROPERTY_HOST: | |
| *pcchProperty = This->host_len; | |
| /* '[' and ']' aren't included in the length. */ | |
| if(This->host_type == Uri_HOST_IPV6) | |
| *pcchProperty -= 2; | |
| hres = (This->host_start > -1) ? S_OK : S_FALSE; | |
| break; | |
| case Uri_PROPERTY_PASSWORD: | |
| *pcchProperty = (This->userinfo_split > -1) ? This->userinfo_len-This->userinfo_split-1 : 0; | |
| hres = (This->userinfo_split > -1) ? S_OK : S_FALSE; | |
| break; | |
| case Uri_PROPERTY_PATH: | |
| *pcchProperty = This->path_len; | |
| hres = (This->path_start > -1) ? S_OK : S_FALSE; | |
| break; | |
| case Uri_PROPERTY_PATH_AND_QUERY: | |
| *pcchProperty = This->path_len+This->query_len; | |
| hres = (This->path_start > -1 || This->query_start > -1) ? S_OK : S_FALSE; | |
| break; | |
| case Uri_PROPERTY_QUERY: | |
| *pcchProperty = This->query_len; | |
| hres = (This->query_start > -1) ? S_OK : S_FALSE; | |
| break; | |
| case Uri_PROPERTY_RAW_URI: | |
| *pcchProperty = SysStringLen(This->raw_uri); | |
| hres = S_OK; | |
| break; | |
| case Uri_PROPERTY_SCHEME_NAME: | |
| *pcchProperty = This->scheme_len; | |
| hres = (This->scheme_start > -1) ? S_OK : S_FALSE; | |
| break; | |
| case Uri_PROPERTY_USER_INFO: | |
| *pcchProperty = This->userinfo_len; | |
| hres = (This->userinfo_start > -1) ? S_OK : S_FALSE; | |
| break; | |
| case Uri_PROPERTY_USER_NAME: | |
| *pcchProperty = (This->userinfo_split > -1) ? This->userinfo_split : This->userinfo_len; | |
| if(This->userinfo_split == 0) | |
| hres = S_FALSE; | |
| else | |
| hres = (This->userinfo_start > -1) ? S_OK : S_FALSE; | |
| break; | |
| default: | |
| FIXME("(%p)->(%d %p %x)\n", This, uriProp, pcchProperty, dwFlags); | |
| hres = E_NOTIMPL; | |
| } | |
| return hres; | |
| } | |
| static HRESULT WINAPI Uri_GetPropertyDWORD(IUri *iface, Uri_PROPERTY uriProp, DWORD *pcchProperty, DWORD dwFlags) | |
| { | |
| Uri *This = impl_from_IUri(iface); | |
| HRESULT hres; | |
| TRACE("(%p %s)->(%d %p %x)\n", This, debugstr_w(This->canon_uri), uriProp, pcchProperty, dwFlags); | |
| if(!This->create_flags) | |
| return E_UNEXPECTED; | |
| if(!pcchProperty) | |
| return E_INVALIDARG; | |
| /* Microsoft's implementation for the ZONE property of a URI seems to be lacking... | |
| * From what I can tell, instead of checking which URLZONE the URI belongs to it | |
| * simply assigns URLZONE_INVALID and returns E_NOTIMPL. This also applies to the GetZone | |
| * function. | |
| */ | |
| if(uriProp == Uri_PROPERTY_ZONE) { | |
| *pcchProperty = URLZONE_INVALID; | |
| return E_NOTIMPL; | |
| } | |
| if(uriProp < Uri_PROPERTY_DWORD_START) { | |
| *pcchProperty = 0; | |
| return E_INVALIDARG; | |
| } | |
| switch(uriProp) { | |
| case Uri_PROPERTY_HOST_TYPE: | |
| *pcchProperty = This->host_type; | |
| hres = S_OK; | |
| break; | |
| case Uri_PROPERTY_PORT: | |
| if(!This->has_port) { | |
| *pcchProperty = 0; | |
| hres = S_FALSE; | |
| } else { | |
| *pcchProperty = This->port; | |
| hres = S_OK; | |
| } | |
| break; | |
| case Uri_PROPERTY_SCHEME: | |
| *pcchProperty = This->scheme_type; | |
| hres = S_OK; | |
| break; | |
| default: | |
| FIXME("(%p)->(%d %p %x)\n", This, uriProp, pcchProperty, dwFlags); | |
| hres = E_NOTIMPL; | |
| } | |
| return hres; | |
| } | |
| static HRESULT WINAPI Uri_HasProperty(IUri *iface, Uri_PROPERTY uriProp, BOOL *pfHasProperty) | |
| { | |
| Uri *This = impl_from_IUri(iface); | |
| TRACE("(%p %s)->(%d %p)\n", This, debugstr_w(This->canon_uri), uriProp, pfHasProperty); | |
| if(!pfHasProperty) | |
| return E_INVALIDARG; | |
| switch(uriProp) { | |
| case Uri_PROPERTY_ABSOLUTE_URI: | |
| *pfHasProperty = !(This->display_modifiers & URI_DISPLAY_NO_ABSOLUTE_URI); | |
| break; | |
| case Uri_PROPERTY_AUTHORITY: | |
| *pfHasProperty = This->authority_start > -1; | |
| break; | |
| case Uri_PROPERTY_DISPLAY_URI: | |
| *pfHasProperty = TRUE; | |
| break; | |
| case Uri_PROPERTY_DOMAIN: | |
| *pfHasProperty = This->domain_offset > -1; | |
| break; | |
| case Uri_PROPERTY_EXTENSION: | |
| *pfHasProperty = This->extension_offset > -1; | |
| break; | |
| case Uri_PROPERTY_FRAGMENT: | |
| *pfHasProperty = This->fragment_start > -1; | |
| break; | |
| case Uri_PROPERTY_HOST: | |
| *pfHasProperty = This->host_start > -1; | |
| break; | |
| case Uri_PROPERTY_PASSWORD: | |
| *pfHasProperty = This->userinfo_split > -1; | |
| break; | |
| case Uri_PROPERTY_PATH: | |
| *pfHasProperty = This->path_start > -1; | |
| break; | |
| case Uri_PROPERTY_PATH_AND_QUERY: | |
| *pfHasProperty = (This->path_start > -1 || This->query_start > -1); | |
| break; | |
| case Uri_PROPERTY_QUERY: | |
| *pfHasProperty = This->query_start > -1; | |
| break; | |
| case Uri_PROPERTY_RAW_URI: | |
| *pfHasProperty = TRUE; | |
| break; | |
| case Uri_PROPERTY_SCHEME_NAME: | |
| *pfHasProperty = This->scheme_start > -1; | |
| break; | |
| case Uri_PROPERTY_USER_INFO: | |
| *pfHasProperty = This->userinfo_start > -1; | |
| break; | |
| case Uri_PROPERTY_USER_NAME: | |
| if(This->userinfo_split == 0) | |
| *pfHasProperty = FALSE; | |
| else | |
| *pfHasProperty = This->userinfo_start > -1; | |
| break; | |
| case Uri_PROPERTY_HOST_TYPE: | |
| *pfHasProperty = TRUE; | |
| break; | |
| case Uri_PROPERTY_PORT: | |
| *pfHasProperty = This->has_port; | |
| break; | |
| case Uri_PROPERTY_SCHEME: | |
| *pfHasProperty = TRUE; | |
| break; | |
| case Uri_PROPERTY_ZONE: | |
| *pfHasProperty = FALSE; | |
| break; | |
| default: | |
| FIXME("(%p)->(%d %p): Unsupported property type.\n", This, uriProp, pfHasProperty); | |
| return E_NOTIMPL; | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI Uri_GetAbsoluteUri(IUri *iface, BSTR *pstrAbsoluteUri) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrAbsoluteUri); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_ABSOLUTE_URI, pstrAbsoluteUri, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetAuthority(IUri *iface, BSTR *pstrAuthority) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrAuthority); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_AUTHORITY, pstrAuthority, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetDisplayUri(IUri *iface, BSTR *pstrDisplayUri) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrDisplayUri); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_DISPLAY_URI, pstrDisplayUri, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetDomain(IUri *iface, BSTR *pstrDomain) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrDomain); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_DOMAIN, pstrDomain, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetExtension(IUri *iface, BSTR *pstrExtension) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrExtension); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_EXTENSION, pstrExtension, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetFragment(IUri *iface, BSTR *pstrFragment) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrFragment); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_FRAGMENT, pstrFragment, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetHost(IUri *iface, BSTR *pstrHost) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrHost); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_HOST, pstrHost, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetPassword(IUri *iface, BSTR *pstrPassword) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrPassword); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_PASSWORD, pstrPassword, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetPath(IUri *iface, BSTR *pstrPath) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrPath); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_PATH, pstrPath, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetPathAndQuery(IUri *iface, BSTR *pstrPathAndQuery) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrPathAndQuery); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_PATH_AND_QUERY, pstrPathAndQuery, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetQuery(IUri *iface, BSTR *pstrQuery) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrQuery); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_QUERY, pstrQuery, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetRawUri(IUri *iface, BSTR *pstrRawUri) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrRawUri); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_RAW_URI, pstrRawUri, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetSchemeName(IUri *iface, BSTR *pstrSchemeName) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrSchemeName); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_SCHEME_NAME, pstrSchemeName, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetUserInfo(IUri *iface, BSTR *pstrUserInfo) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrUserInfo); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_USER_INFO, pstrUserInfo, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetUserName(IUri *iface, BSTR *pstrUserName) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pstrUserName); | |
| return IUri_GetPropertyBSTR(iface, Uri_PROPERTY_USER_NAME, pstrUserName, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetHostType(IUri *iface, DWORD *pdwHostType) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pdwHostType); | |
| return IUri_GetPropertyDWORD(iface, Uri_PROPERTY_HOST_TYPE, pdwHostType, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetPort(IUri *iface, DWORD *pdwPort) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pdwPort); | |
| return IUri_GetPropertyDWORD(iface, Uri_PROPERTY_PORT, pdwPort, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetScheme(IUri *iface, DWORD *pdwScheme) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pdwScheme); | |
| return IUri_GetPropertyDWORD(iface, Uri_PROPERTY_SCHEME, pdwScheme, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetZone(IUri *iface, DWORD *pdwZone) | |
| { | |
| TRACE("(%p)->(%p)\n", iface, pdwZone); | |
| return IUri_GetPropertyDWORD(iface, Uri_PROPERTY_ZONE,pdwZone, 0); | |
| } | |
| static HRESULT WINAPI Uri_GetProperties(IUri *iface, DWORD *pdwProperties) | |
| { | |
| Uri *This = impl_from_IUri(iface); | |
| TRACE("(%p %s)->(%p)\n", This, debugstr_w(This->canon_uri), pdwProperties); | |
| if(!This->create_flags) | |
| return E_UNEXPECTED; | |
| if(!pdwProperties) | |
| return E_INVALIDARG; | |
| /* All URIs have these. */ | |
| *pdwProperties = Uri_HAS_DISPLAY_URI|Uri_HAS_RAW_URI|Uri_HAS_SCHEME|Uri_HAS_HOST_TYPE; | |
| if(!(This->display_modifiers & URI_DISPLAY_NO_ABSOLUTE_URI)) | |
| *pdwProperties |= Uri_HAS_ABSOLUTE_URI; | |
| if(This->scheme_start > -1) | |
| *pdwProperties |= Uri_HAS_SCHEME_NAME; | |
| if(This->authority_start > -1) { | |
| *pdwProperties |= Uri_HAS_AUTHORITY; | |
| if(This->userinfo_start > -1) { | |
| *pdwProperties |= Uri_HAS_USER_INFO; | |
| if(This->userinfo_split != 0) | |
| *pdwProperties |= Uri_HAS_USER_NAME; | |
| } | |
| if(This->userinfo_split > -1) | |
| *pdwProperties |= Uri_HAS_PASSWORD; | |
| if(This->host_start > -1) | |
| *pdwProperties |= Uri_HAS_HOST; | |
| if(This->domain_offset > -1) | |
| *pdwProperties |= Uri_HAS_DOMAIN; | |
| } | |
| if(This->has_port) | |
| *pdwProperties |= Uri_HAS_PORT; | |
| if(This->path_start > -1) | |
| *pdwProperties |= Uri_HAS_PATH|Uri_HAS_PATH_AND_QUERY; | |
| if(This->query_start > -1) | |
| *pdwProperties |= Uri_HAS_QUERY|Uri_HAS_PATH_AND_QUERY; | |
| if(This->extension_offset > -1) | |
| *pdwProperties |= Uri_HAS_EXTENSION; | |
| if(This->fragment_start > -1) | |
| *pdwProperties |= Uri_HAS_FRAGMENT; | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI Uri_IsEqual(IUri *iface, IUri *pUri, BOOL *pfEqual) | |
| { | |
| Uri *This = impl_from_IUri(iface); | |
| Uri *other; | |
| TRACE("(%p %s)->(%p %p)\n", This, debugstr_w(This->canon_uri), pUri, pfEqual); | |
| if(!This->create_flags) | |
| return E_UNEXPECTED; | |
| if(!pfEqual) | |
| return E_POINTER; | |
| if(!pUri) { | |
| *pfEqual = FALSE; | |
| /* For some reason Windows returns S_OK here... */ | |
| return S_OK; | |
| } | |
| /* Try to convert it to a Uri (allows for a more simple comparison). */ | |
| if(!(other = get_uri_obj(pUri))) { | |
| FIXME("(%p)->(%p %p) No support for unknown IUri's yet.\n", iface, pUri, pfEqual); | |
| return E_NOTIMPL; | |
| } | |
| TRACE("comparing to %s\n", debugstr_w(other->canon_uri)); | |
| return compare_uris(This, other, pfEqual); | |
| } | |
| static const IUriVtbl UriVtbl = { | |
| Uri_QueryInterface, | |
| Uri_AddRef, | |
| Uri_Release, | |
| Uri_GetPropertyBSTR, | |
| Uri_GetPropertyLength, | |
| Uri_GetPropertyDWORD, | |
| Uri_HasProperty, | |
| Uri_GetAbsoluteUri, | |
| Uri_GetAuthority, | |
| Uri_GetDisplayUri, | |
| Uri_GetDomain, | |
| Uri_GetExtension, | |
| Uri_GetFragment, | |
| Uri_GetHost, | |
| Uri_GetPassword, | |
| Uri_GetPath, | |
| Uri_GetPathAndQuery, | |
| Uri_GetQuery, | |
| Uri_GetRawUri, | |
| Uri_GetSchemeName, | |
| Uri_GetUserInfo, | |
| Uri_GetUserName, | |
| Uri_GetHostType, | |
| Uri_GetPort, | |
| Uri_GetScheme, | |
| Uri_GetZone, | |
| Uri_GetProperties, | |
| Uri_IsEqual | |
| }; | |
| static inline Uri* impl_from_IUriBuilderFactory(IUriBuilderFactory *iface) | |
| { | |
| return CONTAINING_RECORD(iface, Uri, IUriBuilderFactory_iface); | |
| } | |
| static HRESULT WINAPI UriBuilderFactory_QueryInterface(IUriBuilderFactory *iface, REFIID riid, void **ppv) | |
| { | |
| Uri *This = impl_from_IUriBuilderFactory(iface); | |
| return IUri_QueryInterface(&This->IUri_iface, riid, ppv); | |
| } | |
| static ULONG WINAPI UriBuilderFactory_AddRef(IUriBuilderFactory *iface) | |
| { | |
| Uri *This = impl_from_IUriBuilderFactory(iface); | |
| return IUri_AddRef(&This->IUri_iface); | |
| } | |
| static ULONG WINAPI UriBuilderFactory_Release(IUriBuilderFactory *iface) | |
| { | |
| Uri *This = impl_from_IUriBuilderFactory(iface); | |
| return IUri_Release(&This->IUri_iface); | |
| } | |
| static HRESULT WINAPI UriBuilderFactory_CreateIUriBuilder(IUriBuilderFactory *iface, | |
| DWORD dwFlags, | |
| DWORD_PTR dwReserved, | |
| IUriBuilder **ppIUriBuilder) | |
| { | |
| Uri *This = impl_from_IUriBuilderFactory(iface); | |
| TRACE("(%p)->(%08x %08x %p)\n", This, dwFlags, (DWORD)dwReserved, ppIUriBuilder); | |
| if(!ppIUriBuilder) | |
| return E_POINTER; | |
| if(dwFlags || dwReserved) { | |
| *ppIUriBuilder = NULL; | |
| return E_INVALIDARG; | |
| } | |
| return CreateIUriBuilder(NULL, 0, 0, ppIUriBuilder); | |
| } | |
| static HRESULT WINAPI UriBuilderFactory_CreateInitializedIUriBuilder(IUriBuilderFactory *iface, | |
| DWORD dwFlags, | |
| DWORD_PTR dwReserved, | |
| IUriBuilder **ppIUriBuilder) | |
| { | |
| Uri *This = impl_from_IUriBuilderFactory(iface); | |
| TRACE("(%p)->(%08x %08x %p)\n", This, dwFlags, (DWORD)dwReserved, ppIUriBuilder); | |
| if(!ppIUriBuilder) | |
| return E_POINTER; | |
| if(dwFlags || dwReserved) { | |
| *ppIUriBuilder = NULL; | |
| return E_INVALIDARG; | |
| } | |
| return CreateIUriBuilder(&This->IUri_iface, 0, 0, ppIUriBuilder); | |
| } | |
| static const IUriBuilderFactoryVtbl UriBuilderFactoryVtbl = { | |
| UriBuilderFactory_QueryInterface, | |
| UriBuilderFactory_AddRef, | |
| UriBuilderFactory_Release, | |
| UriBuilderFactory_CreateIUriBuilder, | |
| UriBuilderFactory_CreateInitializedIUriBuilder | |
| }; | |
| static inline Uri* impl_from_IPersistStream(IPersistStream *iface) | |
| { | |
| return CONTAINING_RECORD(iface, Uri, IPersistStream_iface); | |
| } | |
| static HRESULT WINAPI PersistStream_QueryInterface(IPersistStream *iface, REFIID riid, void **ppvObject) | |
| { | |
| Uri *This = impl_from_IPersistStream(iface); | |
| return IUri_QueryInterface(&This->IUri_iface, riid, ppvObject); | |
| } | |
| static ULONG WINAPI PersistStream_AddRef(IPersistStream *iface) | |
| { | |
| Uri *This = impl_from_IPersistStream(iface); | |
| return IUri_AddRef(&This->IUri_iface); | |
| } | |
| static ULONG WINAPI PersistStream_Release(IPersistStream *iface) | |
| { | |
| Uri *This = impl_from_IPersistStream(iface); | |
| return IUri_Release(&This->IUri_iface); | |
| } | |
| static HRESULT WINAPI PersistStream_GetClassID(IPersistStream *iface, CLSID *pClassID) | |
| { | |
| Uri *This = impl_from_IPersistStream(iface); | |
| TRACE("(%p)->(%p)\n", This, pClassID); | |
| if(!pClassID) | |
| return E_INVALIDARG; | |
| *pClassID = CLSID_CUri; | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI PersistStream_IsDirty(IPersistStream *iface) | |
| { | |
| Uri *This = impl_from_IPersistStream(iface); | |
| TRACE("(%p)\n", This); | |
| return S_FALSE; | |
| } | |
| struct persist_uri { | |
| DWORD size; | |
| DWORD unk1[2]; | |
| DWORD create_flags; | |
| DWORD unk2[3]; | |
| DWORD fields_no; | |
| BYTE data[1]; | |
| }; | |
| static HRESULT WINAPI PersistStream_Load(IPersistStream *iface, IStream *pStm) | |
| { | |
| Uri *This = impl_from_IPersistStream(iface); | |
| struct persist_uri *data; | |
| parse_data parse; | |
| DWORD size; | |
| HRESULT hr; | |
| TRACE("(%p)->(%p)\n", This, pStm); | |
| if(This->create_flags) | |
| return E_UNEXPECTED; | |
| if(!pStm) | |
| return E_INVALIDARG; | |
| hr = IStream_Read(pStm, &size, sizeof(DWORD), NULL); | |
| if(FAILED(hr)) | |
| return hr; | |
| data = heap_alloc(size); | |
| if(!data) | |
| return E_OUTOFMEMORY; | |
| hr = IStream_Read(pStm, data->unk1, size-sizeof(DWORD)-2, NULL); | |
| if(FAILED(hr)) { | |
| heap_free(data); | |
| return hr; | |
| } | |
| if(size < sizeof(struct persist_uri)) { | |
| heap_free(data); | |
| return S_OK; | |
| } | |
| if(*(DWORD*)data->data != Uri_PROPERTY_RAW_URI) { | |
| heap_free(data); | |
| ERR("Can't find raw_uri\n"); | |
| return E_UNEXPECTED; | |
| } | |
| This->raw_uri = SysAllocString((WCHAR*)(data->data+sizeof(DWORD)*2)); | |
| if(!This->raw_uri) { | |
| heap_free(data); | |
| return E_OUTOFMEMORY; | |
| } | |
| This->create_flags = data->create_flags; | |
| heap_free(data); | |
| TRACE("%x %s\n", This->create_flags, debugstr_w(This->raw_uri)); | |
| memset(&parse, 0, sizeof(parse_data)); | |
| parse.uri = This->raw_uri; | |
| if(!parse_uri(&parse, This->create_flags)) { | |
| SysFreeString(This->raw_uri); | |
| This->create_flags = 0; | |
| return E_UNEXPECTED; | |
| } | |
| hr = canonicalize_uri(&parse, This, This->create_flags); | |
| if(FAILED(hr)) { | |
| SysFreeString(This->raw_uri); | |
| This->create_flags = 0; | |
| return hr; | |
| } | |
| return S_OK; | |
| } | |
| static inline BYTE* persist_stream_add_strprop(Uri *This, BYTE *p, DWORD type, DWORD len, WCHAR *data) | |
| { | |
| len *= sizeof(WCHAR); | |
| *(DWORD*)p = type; | |
| p += sizeof(DWORD); | |
| *(DWORD*)p = len+sizeof(WCHAR); | |
| p += sizeof(DWORD); | |
| memcpy(p, data, len); | |
| p += len; | |
| *(WCHAR*)p = 0; | |
| return p+sizeof(WCHAR); | |
| } | |
| static inline void persist_stream_save(Uri *This, IStream *pStm, BOOL marshal, struct persist_uri *data) | |
| { | |
| BYTE *p = NULL; | |
| data->create_flags = This->create_flags; | |
| if(This->create_flags) { | |
| data->fields_no = 1; | |
| p = persist_stream_add_strprop(This, data->data, Uri_PROPERTY_RAW_URI, | |
| SysStringLen(This->raw_uri), This->raw_uri); | |
| } | |
| if(This->scheme_type!=URL_SCHEME_HTTP && This->scheme_type!=URL_SCHEME_HTTPS | |
| && This->scheme_type!=URL_SCHEME_FTP) | |
| return; | |
| if(This->fragment_len) { | |
| data->fields_no++; | |
| p = persist_stream_add_strprop(This, p, Uri_PROPERTY_FRAGMENT, | |
| This->fragment_len, This->canon_uri+This->fragment_start); | |
| } | |
| if(This->host_len) { | |
| data->fields_no++; | |
| if(This->host_type == Uri_HOST_IPV6) | |
| p = persist_stream_add_strprop(This, p, Uri_PROPERTY_HOST, | |
| This->host_len-2, This->canon_uri+This->host_start+1); | |
| else | |
| p = persist_stream_add_strprop(This, p, Uri_PROPERTY_HOST, | |
| This->host_len, This->canon_uri+This->host_start); | |
| } | |
| if(This->userinfo_split > -1) { | |
| data->fields_no++; | |
| p = persist_stream_add_strprop(This, p, Uri_PROPERTY_PASSWORD, | |
| This->userinfo_len-This->userinfo_split-1, | |
| This->canon_uri+This->userinfo_start+This->userinfo_split+1); | |
| } | |
| if(This->path_len) { | |
| data->fields_no++; | |
| p = persist_stream_add_strprop(This, p, Uri_PROPERTY_PATH, | |
| This->path_len, This->canon_uri+This->path_start); | |
| } else if(marshal) { | |
| WCHAR no_path = '/'; | |
| data->fields_no++; | |
| p = persist_stream_add_strprop(This, p, Uri_PROPERTY_PATH, 1, &no_path); | |
| } | |
| if(This->has_port) { | |
| data->fields_no++; | |
| *(DWORD*)p = Uri_PROPERTY_PORT; | |
| p += sizeof(DWORD); | |
| *(DWORD*)p = sizeof(DWORD); | |
| p += sizeof(DWORD); | |
| *(DWORD*)p = This->port; | |
| p += sizeof(DWORD); | |
| } | |
| if(This->query_len) { | |
| data->fields_no++; | |
| p = persist_stream_add_strprop(This, p, Uri_PROPERTY_QUERY, | |
| This->query_len, This->canon_uri+This->query_start); | |
| } | |
| if(This->scheme_len) { | |
| data->fields_no++; | |
| p = persist_stream_add_strprop(This, p, Uri_PROPERTY_SCHEME_NAME, | |
| This->scheme_len, This->canon_uri+This->scheme_start); | |
| } | |
| if(This->userinfo_start>-1 && This->userinfo_split!=0) { | |
| data->fields_no++; | |
| if(This->userinfo_split > -1) | |
| p = persist_stream_add_strprop(This, p, Uri_PROPERTY_USER_NAME, | |
| This->userinfo_split, This->canon_uri+This->userinfo_start); | |
| else | |
| p = persist_stream_add_strprop(This, p, Uri_PROPERTY_USER_NAME, | |
| This->userinfo_len, This->canon_uri+This->userinfo_start); | |
| } | |
| } | |
| static HRESULT WINAPI PersistStream_Save(IPersistStream *iface, IStream *pStm, BOOL fClearDirty) | |
| { | |
| Uri *This = impl_from_IPersistStream(iface); | |
| struct persist_uri *data; | |
| ULARGE_INTEGER size; | |
| HRESULT hres; | |
| TRACE("(%p)->(%p %x)\n", This, pStm, fClearDirty); | |
| if(!pStm) | |
| return E_INVALIDARG; | |
| hres = IPersistStream_GetSizeMax(&This->IPersistStream_iface, &size); | |
| if(FAILED(hres)) | |
| return hres; | |
| data = heap_alloc_zero(size.u.LowPart); | |
| if(!data) | |
| return E_OUTOFMEMORY; | |
| data->size = size.u.LowPart; | |
| persist_stream_save(This, pStm, FALSE, data); | |
| hres = IStream_Write(pStm, data, data->size-2, NULL); | |
| heap_free(data); | |
| return hres; | |
| } | |
| static HRESULT WINAPI PersistStream_GetSizeMax(IPersistStream *iface, ULARGE_INTEGER *pcbSize) | |
| { | |
| Uri *This = impl_from_IPersistStream(iface); | |
| TRACE("(%p)->(%p)\n", This, pcbSize); | |
| if(!pcbSize) | |
| return E_INVALIDARG; | |
| pcbSize->u.LowPart = 2+sizeof(struct persist_uri); | |
| pcbSize->u.HighPart = 0; | |
| if(This->create_flags) | |
| pcbSize->u.LowPart += (SysStringLen(This->raw_uri)+1)*sizeof(WCHAR) + 2*sizeof(DWORD); | |
| else /* there's no place for fields no */ | |
| pcbSize->u.LowPart -= sizeof(DWORD); | |
| if(This->scheme_type!=URL_SCHEME_HTTP && This->scheme_type!=URL_SCHEME_HTTPS | |
| && This->scheme_type!=URL_SCHEME_FTP) | |
| return S_OK; | |
| if(This->fragment_len) | |
| pcbSize->u.LowPart += (This->fragment_len+1)*sizeof(WCHAR) + 2*sizeof(DWORD); | |
| if(This->host_len) { | |
| if(This->host_type == Uri_HOST_IPV6) | |
| pcbSize->u.LowPart += (This->host_len-1)*sizeof(WCHAR) + 2*sizeof(DWORD); | |
| else | |
| pcbSize->u.LowPart += (This->host_len+1)*sizeof(WCHAR) + 2*sizeof(DWORD); | |
| } | |
| if(This->userinfo_split > -1) | |
| pcbSize->u.LowPart += (This->userinfo_len-This->userinfo_split)*sizeof(WCHAR) + 2*sizeof(DWORD); | |
| if(This->path_len) | |
| pcbSize->u.LowPart += (This->path_len+1)*sizeof(WCHAR) + 2*sizeof(DWORD); | |
| if(This->has_port) | |
| pcbSize->u.LowPart += 3*sizeof(DWORD); | |
| if(This->query_len) | |
| pcbSize->u.LowPart += (This->query_len+1)*sizeof(WCHAR) + 2*sizeof(DWORD); | |
| if(This->scheme_len) | |
| pcbSize->u.LowPart += (This->scheme_len+1)*sizeof(WCHAR) + 2*sizeof(DWORD); | |
| if(This->userinfo_start>-1 && This->userinfo_split!=0) { | |
| if(This->userinfo_split > -1) | |
| pcbSize->u.LowPart += (This->userinfo_split+1)*sizeof(WCHAR) + 2*sizeof(DWORD); | |
| else | |
| pcbSize->u.LowPart += (This->userinfo_len+1)*sizeof(WCHAR) + 2*sizeof(DWORD); | |
| } | |
| return S_OK; | |
| } | |
| static const IPersistStreamVtbl PersistStreamVtbl = { | |
| PersistStream_QueryInterface, | |
| PersistStream_AddRef, | |
| PersistStream_Release, | |
| PersistStream_GetClassID, | |
| PersistStream_IsDirty, | |
| PersistStream_Load, | |
| PersistStream_Save, | |
| PersistStream_GetSizeMax | |
| }; | |
| static inline Uri* impl_from_IMarshal(IMarshal *iface) | |
| { | |
| return CONTAINING_RECORD(iface, Uri, IMarshal_iface); | |
| } | |
| static HRESULT WINAPI Marshal_QueryInterface(IMarshal *iface, REFIID riid, void **ppvObject) | |
| { | |
| Uri *This = impl_from_IMarshal(iface); | |
| return IUri_QueryInterface(&This->IUri_iface, riid, ppvObject); | |
| } | |
| static ULONG WINAPI Marshal_AddRef(IMarshal *iface) | |
| { | |
| Uri *This = impl_from_IMarshal(iface); | |
| return IUri_AddRef(&This->IUri_iface); | |
| } | |
| static ULONG WINAPI Marshal_Release(IMarshal *iface) | |
| { | |
| Uri *This = impl_from_IMarshal(iface); | |
| return IUri_Release(&This->IUri_iface); | |
| } | |
| static HRESULT WINAPI Marshal_GetUnmarshalClass(IMarshal *iface, REFIID riid, void *pv, | |
| DWORD dwDestContext, void *pvDestContext, DWORD mshlflags, CLSID *pCid) | |
| { | |
| Uri *This = impl_from_IMarshal(iface); | |
| TRACE("(%p)->(%s %p %x %p %x %p)\n", This, debugstr_guid(riid), pv, | |
| dwDestContext, pvDestContext, mshlflags, pCid); | |
| if(!pCid || (dwDestContext!=MSHCTX_LOCAL && dwDestContext!=MSHCTX_NOSHAREDMEM | |
| && dwDestContext!=MSHCTX_INPROC)) | |
| return E_INVALIDARG; | |
| *pCid = CLSID_CUri; | |
| return S_OK; | |
| } | |
| struct inproc_marshal_uri { | |
| DWORD size; | |
| DWORD mshlflags; | |
| DWORD unk[4]; /* process identifier? */ | |
| Uri *uri; | |
| }; | |
| static HRESULT WINAPI Marshal_GetMarshalSizeMax(IMarshal *iface, REFIID riid, void *pv, | |
| DWORD dwDestContext, void *pvDestContext, DWORD mshlflags, DWORD *pSize) | |
| { | |
| Uri *This = impl_from_IMarshal(iface); | |
| ULARGE_INTEGER size; | |
| HRESULT hres; | |
| TRACE("(%p)->(%s %p %x %p %x %p)\n", This, debugstr_guid(riid), pv, | |
| dwDestContext, pvDestContext, mshlflags, pSize); | |
| if(!pSize || (dwDestContext!=MSHCTX_LOCAL && dwDestContext!=MSHCTX_NOSHAREDMEM | |
| && dwDestContext!=MSHCTX_INPROC)) | |
| return E_INVALIDARG; | |
| if(dwDestContext == MSHCTX_INPROC) { | |
| *pSize = sizeof(struct inproc_marshal_uri); | |
| return S_OK; | |
| } | |
| hres = IPersistStream_GetSizeMax(&This->IPersistStream_iface, &size); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(!This->path_len && (This->scheme_type==URL_SCHEME_HTTP | |
| || This->scheme_type==URL_SCHEME_HTTPS | |
| || This->scheme_type==URL_SCHEME_FTP)) | |
| size.u.LowPart += 3*sizeof(DWORD); | |
| *pSize = size.u.LowPart+2*sizeof(DWORD); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI Marshal_MarshalInterface(IMarshal *iface, IStream *pStm, REFIID riid, | |
| void *pv, DWORD dwDestContext, void *pvDestContext, DWORD mshlflags) | |
| { | |
| Uri *This = impl_from_IMarshal(iface); | |
| DWORD *data; | |
| DWORD size; | |
| HRESULT hres; | |
| TRACE("(%p)->(%p %s %p %x %p %x)\n", This, pStm, debugstr_guid(riid), pv, | |
| dwDestContext, pvDestContext, mshlflags); | |
| if(!pStm || mshlflags!=MSHLFLAGS_NORMAL || (dwDestContext!=MSHCTX_LOCAL | |
| && dwDestContext!=MSHCTX_NOSHAREDMEM && dwDestContext!=MSHCTX_INPROC)) | |
| return E_INVALIDARG; | |
| if(dwDestContext == MSHCTX_INPROC) { | |
| struct inproc_marshal_uri data; | |
| data.size = sizeof(data); | |
| data.mshlflags = MSHCTX_INPROC; | |
| data.unk[0] = 0; | |
| data.unk[1] = 0; | |
| data.unk[2] = 0; | |
| data.unk[3] = 0; | |
| data.uri = This; | |
| hres = IStream_Write(pStm, &data, data.size, NULL); | |
| if(FAILED(hres)) | |
| return hres; | |
| IUri_AddRef(&This->IUri_iface); | |
| return S_OK; | |
| } | |
| hres = IMarshal_GetMarshalSizeMax(iface, riid, pv, dwDestContext, | |
| pvDestContext, mshlflags, &size); | |
| if(FAILED(hres)) | |
| return hres; | |
| data = heap_alloc_zero(size); | |
| if(!data) | |
| return E_OUTOFMEMORY; | |
| data[0] = size; | |
| data[1] = dwDestContext; | |
| data[2] = size-2*sizeof(DWORD); | |
| persist_stream_save(This, pStm, TRUE, (struct persist_uri*)(data+2)); | |
| hres = IStream_Write(pStm, data, data[0]-2, NULL); | |
| heap_free(data); | |
| return hres; | |
| } | |
| static HRESULT WINAPI Marshal_UnmarshalInterface(IMarshal *iface, | |
| IStream *pStm, REFIID riid, void **ppv) | |
| { | |
| Uri *This = impl_from_IMarshal(iface); | |
| DWORD header[2]; | |
| HRESULT hres; | |
| TRACE("(%p)->(%p %s %p)\n", This, pStm, debugstr_guid(riid), ppv); | |
| if(This->create_flags) | |
| return E_UNEXPECTED; | |
| if(!pStm || !riid || !ppv) | |
| return E_INVALIDARG; | |
| hres = IStream_Read(pStm, header, sizeof(header), NULL); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(header[1]!=MSHCTX_LOCAL && header[1]!=MSHCTX_NOSHAREDMEM | |
| && header[1]!=MSHCTX_INPROC) | |
| return E_UNEXPECTED; | |
| if(header[1] == MSHCTX_INPROC) { | |
| struct inproc_marshal_uri data; | |
| parse_data parse; | |
| hres = IStream_Read(pStm, data.unk, sizeof(data)-2*sizeof(DWORD), NULL); | |
| if(FAILED(hres)) | |
| return hres; | |
| This->raw_uri = SysAllocString(data.uri->raw_uri); | |
| if(!This->raw_uri) { | |
| return E_OUTOFMEMORY; | |
| } | |
| memset(&parse, 0, sizeof(parse_data)); | |
| parse.uri = This->raw_uri; | |
| if(!parse_uri(&parse, data.uri->create_flags)) | |
| return E_INVALIDARG; | |
| hres = canonicalize_uri(&parse, This, data.uri->create_flags); | |
| if(FAILED(hres)) | |
| return hres; | |
| This->create_flags = data.uri->create_flags; | |
| IUri_Release(&data.uri->IUri_iface); | |
| return IUri_QueryInterface(&This->IUri_iface, riid, ppv); | |
| } | |
| hres = IPersistStream_Load(&This->IPersistStream_iface, pStm); | |
| if(FAILED(hres)) | |
| return hres; | |
| return IUri_QueryInterface(&This->IUri_iface, riid, ppv); | |
| } | |
| static HRESULT WINAPI Marshal_ReleaseMarshalData(IMarshal *iface, IStream *pStm) | |
| { | |
| Uri *This = impl_from_IMarshal(iface); | |
| LARGE_INTEGER off; | |
| DWORD header[2]; | |
| HRESULT hres; | |
| TRACE("(%p)->(%p)\n", This, pStm); | |
| if(!pStm) | |
| return E_INVALIDARG; | |
| hres = IStream_Read(pStm, header, 2*sizeof(DWORD), NULL); | |
| if(FAILED(hres)) | |
| return hres; | |
| if(header[1] == MSHCTX_INPROC) { | |
| struct inproc_marshal_uri data; | |
| hres = IStream_Read(pStm, data.unk, sizeof(data)-2*sizeof(DWORD), NULL); | |
| if(FAILED(hres)) | |
| return hres; | |
| IUri_Release(&data.uri->IUri_iface); | |
| return S_OK; | |
| } | |
| off.u.LowPart = header[0]-sizeof(header)-2; | |
| off.u.HighPart = 0; | |
| return IStream_Seek(pStm, off, STREAM_SEEK_CUR, NULL); | |
| } | |
| static HRESULT WINAPI Marshal_DisconnectObject(IMarshal *iface, DWORD dwReserved) | |
| { | |
| Uri *This = impl_from_IMarshal(iface); | |
| TRACE("(%p)->(%x)\n", This, dwReserved); | |
| return S_OK; | |
| } | |
| static const IMarshalVtbl MarshalVtbl = { | |
| Marshal_QueryInterface, | |
| Marshal_AddRef, | |
| Marshal_Release, | |
| Marshal_GetUnmarshalClass, | |
| Marshal_GetMarshalSizeMax, | |
| Marshal_MarshalInterface, | |
| Marshal_UnmarshalInterface, | |
| Marshal_ReleaseMarshalData, | |
| Marshal_DisconnectObject | |
| }; | |
| HRESULT Uri_Construct(IUnknown *pUnkOuter, LPVOID *ppobj) | |
| { | |
| Uri *ret = heap_alloc_zero(sizeof(Uri)); | |
| TRACE("(%p %p)\n", pUnkOuter, ppobj); | |
| *ppobj = ret; | |
| if(!ret) | |
| return E_OUTOFMEMORY; | |
| ret->IUri_iface.lpVtbl = &UriVtbl; | |
| ret->IUriBuilderFactory_iface.lpVtbl = &UriBuilderFactoryVtbl; | |
| ret->IPersistStream_iface.lpVtbl = &PersistStreamVtbl; | |
| ret->IMarshal_iface.lpVtbl = &MarshalVtbl; | |
| ret->ref = 1; | |
| *ppobj = &ret->IUri_iface; | |
| return S_OK; | |
| } | |
| /*********************************************************************** | |
| * CreateUri (urlmon.@) | |
| * | |
| * Creates a new IUri object using the URI represented by pwzURI. This function | |
| * parses and validates the components of pwzURI and then canonicalizes the | |
| * parsed components. | |
| * | |
| * PARAMS | |
| * pwzURI [I] The URI to parse, validate, and canonicalize. | |
| * dwFlags [I] Flags which can affect how the parsing/canonicalization is performed. | |
| * dwReserved [I] Reserved (not used). | |
| * ppURI [O] The resulting IUri after parsing/canonicalization occurs. | |
| * | |
| * RETURNS | |
| * Success: Returns S_OK. ppURI contains the pointer to the newly allocated IUri. | |
| * Failure: E_INVALIDARG if there are invalid flag combinations in dwFlags, or an | |
| * invalid parameter, or pwzURI doesn't represent a valid URI. | |
| * E_OUTOFMEMORY if any memory allocation fails. | |
| * | |
| * NOTES | |
| * Default flags: | |
| * Uri_CREATE_CANONICALIZE, Uri_CREATE_DECODE_EXTRA_INFO, Uri_CREATE_CRACK_UNKNOWN_SCHEMES, | |
| * Uri_CREATE_PRE_PROCESS_HTML_URI, Uri_CREATE_NO_IE_SETTINGS. | |
| */ | |
| HRESULT WINAPI CreateUri(LPCWSTR pwzURI, DWORD dwFlags, DWORD_PTR dwReserved, IUri **ppURI) | |
| { | |
| const DWORD supported_flags = Uri_CREATE_ALLOW_RELATIVE|Uri_CREATE_ALLOW_IMPLICIT_WILDCARD_SCHEME| | |
| Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME|Uri_CREATE_NO_CANONICALIZE|Uri_CREATE_CANONICALIZE| | |
| Uri_CREATE_DECODE_EXTRA_INFO|Uri_CREATE_NO_DECODE_EXTRA_INFO|Uri_CREATE_CRACK_UNKNOWN_SCHEMES| | |
| Uri_CREATE_NO_CRACK_UNKNOWN_SCHEMES|Uri_CREATE_PRE_PROCESS_HTML_URI|Uri_CREATE_NO_PRE_PROCESS_HTML_URI| | |
| Uri_CREATE_NO_IE_SETTINGS|Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS|Uri_CREATE_FILE_USE_DOS_PATH; | |
| Uri *ret; | |
| HRESULT hr; | |
| parse_data data; | |
| TRACE("(%s %x %x %p)\n", debugstr_w(pwzURI), dwFlags, (DWORD)dwReserved, ppURI); | |
| if(!ppURI) | |
| return E_INVALIDARG; | |
| if(!pwzURI) { | |
| *ppURI = NULL; | |
| return E_INVALIDARG; | |
| } | |
| /* Check for invalid flags. */ | |
| if(has_invalid_flag_combination(dwFlags)) { | |
| *ppURI = NULL; | |
| return E_INVALIDARG; | |
| } | |
| /* Currently unsupported. */ | |
| if(dwFlags & ~supported_flags) | |
| FIXME("Ignoring unsupported flag(s) %x\n", dwFlags & ~supported_flags); | |
| hr = Uri_Construct(NULL, (void**)&ret); | |
| if(FAILED(hr)) { | |
| *ppURI = NULL; | |
| return hr; | |
| } | |
| /* Explicitly set the default flags if it doesn't cause a flag conflict. */ | |
| apply_default_flags(&dwFlags); | |
| /* Pre process the URI, unless told otherwise. */ | |
| if(!(dwFlags & Uri_CREATE_NO_PRE_PROCESS_HTML_URI)) | |
| ret->raw_uri = pre_process_uri(pwzURI); | |
| else | |
| ret->raw_uri = SysAllocString(pwzURI); | |
| if(!ret->raw_uri) { | |
| heap_free(ret); | |
| return E_OUTOFMEMORY; | |
| } | |
| memset(&data, 0, sizeof(parse_data)); | |
| data.uri = ret->raw_uri; | |
| /* Validate and parse the URI into its components. */ | |
| if(!parse_uri(&data, dwFlags)) { | |
| /* Encountered an unsupported or invalid URI */ | |
| IUri_Release(&ret->IUri_iface); | |
| *ppURI = NULL; | |
| return E_INVALIDARG; | |
| } | |
| /* Canonicalize the URI. */ | |
| hr = canonicalize_uri(&data, ret, dwFlags); | |
| if(FAILED(hr)) { | |
| IUri_Release(&ret->IUri_iface); | |
| *ppURI = NULL; | |
| return hr; | |
| } | |
| ret->create_flags = dwFlags; | |
| *ppURI = &ret->IUri_iface; | |
| return S_OK; | |
| } | |
| /*********************************************************************** | |
| * CreateUriWithFragment (urlmon.@) | |
| * | |
| * Creates a new IUri object. This is almost the same as CreateUri, expect that | |
| * it allows you to explicitly specify a fragment (pwzFragment) for pwzURI. | |
| * | |
| * PARAMS | |
| * pwzURI [I] The URI to parse and perform canonicalization on. | |
| * pwzFragment [I] The explicit fragment string which should be added to pwzURI. | |
| * dwFlags [I] The flags which will be passed to CreateUri. | |
| * dwReserved [I] Reserved (not used). | |
| * ppURI [O] The resulting IUri after parsing/canonicalization. | |
| * | |
| * RETURNS | |
| * Success: S_OK. ppURI contains the pointer to the newly allocated IUri. | |
| * Failure: E_INVALIDARG if pwzURI already contains a fragment and pwzFragment | |
| * isn't NULL. Will also return E_INVALIDARG for the same reasons as | |
| * CreateUri will. E_OUTOFMEMORY if any allocation fails. | |
| */ | |
| HRESULT WINAPI CreateUriWithFragment(LPCWSTR pwzURI, LPCWSTR pwzFragment, DWORD dwFlags, | |
| DWORD_PTR dwReserved, IUri **ppURI) | |
| { | |
| HRESULT hres; | |
| TRACE("(%s %s %x %x %p)\n", debugstr_w(pwzURI), debugstr_w(pwzFragment), dwFlags, (DWORD)dwReserved, ppURI); | |
| if(!ppURI) | |
| return E_INVALIDARG; | |
| if(!pwzURI) { | |
| *ppURI = NULL; | |
| return E_INVALIDARG; | |
| } | |
| /* Check if a fragment should be appended to the URI string. */ | |
| if(pwzFragment) { | |
| WCHAR *uriW; | |
| DWORD uri_len, frag_len; | |
| BOOL add_pound; | |
| /* Check if the original URI already has a fragment component. */ | |
| if(StrChrW(pwzURI, '#')) { | |
| *ppURI = NULL; | |
| return E_INVALIDARG; | |
| } | |
| uri_len = lstrlenW(pwzURI); | |
| frag_len = lstrlenW(pwzFragment); | |
| /* If the fragment doesn't start with a '#', one will be added. */ | |
| add_pound = *pwzFragment != '#'; | |
| if(add_pound) | |
| uriW = heap_alloc((uri_len+frag_len+2)*sizeof(WCHAR)); | |
| else | |
| uriW = heap_alloc((uri_len+frag_len+1)*sizeof(WCHAR)); | |
| if(!uriW) | |
| return E_OUTOFMEMORY; | |
| memcpy(uriW, pwzURI, uri_len*sizeof(WCHAR)); | |
| if(add_pound) | |
| uriW[uri_len++] = '#'; | |
| memcpy(uriW+uri_len, pwzFragment, (frag_len+1)*sizeof(WCHAR)); | |
| hres = CreateUri(uriW, dwFlags, 0, ppURI); | |
| heap_free(uriW); | |
| } else | |
| /* A fragment string wasn't specified, so just forward the call. */ | |
| hres = CreateUri(pwzURI, dwFlags, 0, ppURI); | |
| return hres; | |
| } | |
| static HRESULT build_uri(const UriBuilder *builder, IUri **uri, DWORD create_flags, | |
| DWORD use_orig_flags, DWORD encoding_mask) | |
| { | |
| HRESULT hr; | |
| parse_data data; | |
| Uri *ret; | |
| if(!uri) | |
| return E_POINTER; | |
| if(encoding_mask && (!builder->uri || builder->modified_props)) { | |
| *uri = NULL; | |
| return E_NOTIMPL; | |
| } | |
| /* Decide what flags should be used when creating the Uri. */ | |
| if((use_orig_flags & UriBuilder_USE_ORIGINAL_FLAGS) && builder->uri) | |
| create_flags = builder->uri->create_flags; | |
| else { | |
| if(has_invalid_flag_combination(create_flags)) { | |
| *uri = NULL; | |
| return E_INVALIDARG; | |
| } | |
| /* Set the default flags if they don't cause a conflict. */ | |
| apply_default_flags(&create_flags); | |
| } | |
| /* Return the base IUri if no changes have been made and the create_flags match. */ | |
| if(builder->uri && !builder->modified_props && builder->uri->create_flags == create_flags) { | |
| *uri = &builder->uri->IUri_iface; | |
| IUri_AddRef(*uri); | |
| return S_OK; | |
| } | |
| hr = validate_components(builder, &data, create_flags); | |
| if(FAILED(hr)) { | |
| *uri = NULL; | |
| return hr; | |
| } | |
| hr = Uri_Construct(NULL, (void**)&ret); | |
| if(FAILED(hr)) { | |
| *uri = NULL; | |
| return hr; | |
| } | |
| hr = generate_uri(builder, &data, ret, create_flags); | |
| if(FAILED(hr)) { | |
| IUri_Release(&ret->IUri_iface); | |
| *uri = NULL; | |
| return hr; | |
| } | |
| *uri = &ret->IUri_iface; | |
| return S_OK; | |
| } | |
| static inline UriBuilder* impl_from_IUriBuilder(IUriBuilder *iface) | |
| { | |
| return CONTAINING_RECORD(iface, UriBuilder, IUriBuilder_iface); | |
| } | |
| static HRESULT WINAPI UriBuilder_QueryInterface(IUriBuilder *iface, REFIID riid, void **ppv) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| if(IsEqualGUID(&IID_IUnknown, riid)) { | |
| TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv); | |
| *ppv = &This->IUriBuilder_iface; | |
| }else if(IsEqualGUID(&IID_IUriBuilder, riid)) { | |
| TRACE("(%p)->(IID_IUriBuilder %p)\n", This, ppv); | |
| *ppv = &This->IUriBuilder_iface; | |
| }else { | |
| TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv); | |
| *ppv = NULL; | |
| return E_NOINTERFACE; | |
| } | |
| IUnknown_AddRef((IUnknown*)*ppv); | |
| return S_OK; | |
| } | |
| static ULONG WINAPI UriBuilder_AddRef(IUriBuilder *iface) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| LONG ref = InterlockedIncrement(&This->ref); | |
| TRACE("(%p) ref=%d\n", This, ref); | |
| return ref; | |
| } | |
| static ULONG WINAPI UriBuilder_Release(IUriBuilder *iface) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| LONG ref = InterlockedDecrement(&This->ref); | |
| TRACE("(%p) ref=%d\n", This, ref); | |
| if(!ref) { | |
| if(This->uri) IUri_Release(&This->uri->IUri_iface); | |
| heap_free(This->fragment); | |
| heap_free(This->host); | |
| heap_free(This->password); | |
| heap_free(This->path); | |
| heap_free(This->query); | |
| heap_free(This->scheme); | |
| heap_free(This->username); | |
| heap_free(This); | |
| } | |
| return ref; | |
| } | |
| static HRESULT WINAPI UriBuilder_CreateUriSimple(IUriBuilder *iface, | |
| DWORD dwAllowEncodingPropertyMask, | |
| DWORD_PTR dwReserved, | |
| IUri **ppIUri) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| HRESULT hr; | |
| TRACE("(%p)->(%d %d %p)\n", This, dwAllowEncodingPropertyMask, (DWORD)dwReserved, ppIUri); | |
| hr = build_uri(This, ppIUri, 0, UriBuilder_USE_ORIGINAL_FLAGS, dwAllowEncodingPropertyMask); | |
| if(hr == E_NOTIMPL) | |
| FIXME("(%p)->(%d %d %p)\n", This, dwAllowEncodingPropertyMask, (DWORD)dwReserved, ppIUri); | |
| return hr; | |
| } | |
| static HRESULT WINAPI UriBuilder_CreateUri(IUriBuilder *iface, | |
| DWORD dwCreateFlags, | |
| DWORD dwAllowEncodingPropertyMask, | |
| DWORD_PTR dwReserved, | |
| IUri **ppIUri) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| HRESULT hr; | |
| TRACE("(%p)->(0x%08x %d %d %p)\n", This, dwCreateFlags, dwAllowEncodingPropertyMask, (DWORD)dwReserved, ppIUri); | |
| if(dwCreateFlags == -1) | |
| hr = build_uri(This, ppIUri, 0, UriBuilder_USE_ORIGINAL_FLAGS, dwAllowEncodingPropertyMask); | |
| else | |
| hr = build_uri(This, ppIUri, dwCreateFlags, 0, dwAllowEncodingPropertyMask); | |
| if(hr == E_NOTIMPL) | |
| FIXME("(%p)->(0x%08x %d %d %p)\n", This, dwCreateFlags, dwAllowEncodingPropertyMask, (DWORD)dwReserved, ppIUri); | |
| return hr; | |
| } | |
| static HRESULT WINAPI UriBuilder_CreateUriWithFlags(IUriBuilder *iface, | |
| DWORD dwCreateFlags, | |
| DWORD dwUriBuilderFlags, | |
| DWORD dwAllowEncodingPropertyMask, | |
| DWORD_PTR dwReserved, | |
| IUri **ppIUri) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| HRESULT hr; | |
| TRACE("(%p)->(0x%08x 0x%08x %d %d %p)\n", This, dwCreateFlags, dwUriBuilderFlags, | |
| dwAllowEncodingPropertyMask, (DWORD)dwReserved, ppIUri); | |
| hr = build_uri(This, ppIUri, dwCreateFlags, dwUriBuilderFlags, dwAllowEncodingPropertyMask); | |
| if(hr == E_NOTIMPL) | |
| FIXME("(%p)->(0x%08x 0x%08x %d %d %p)\n", This, dwCreateFlags, dwUriBuilderFlags, | |
| dwAllowEncodingPropertyMask, (DWORD)dwReserved, ppIUri); | |
| return hr; | |
| } | |
| static HRESULT WINAPI UriBuilder_GetIUri(IUriBuilder *iface, IUri **ppIUri) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%p)\n", This, ppIUri); | |
| if(!ppIUri) | |
| return E_POINTER; | |
| if(This->uri) { | |
| IUri *uri = &This->uri->IUri_iface; | |
| IUri_AddRef(uri); | |
| *ppIUri = uri; | |
| } else | |
| *ppIUri = NULL; | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI UriBuilder_SetIUri(IUriBuilder *iface, IUri *pIUri) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%p)\n", This, pIUri); | |
| if(pIUri) { | |
| Uri *uri; | |
| if((uri = get_uri_obj(pIUri))) { | |
| /* Only reset the builder if its Uri isn't the same as | |
| * the Uri passed to the function. | |
| */ | |
| if(This->uri != uri) { | |
| reset_builder(This); | |
| This->uri = uri; | |
| if(uri->has_port) | |
| This->port = uri->port; | |
| IUri_AddRef(pIUri); | |
| } | |
| } else { | |
| FIXME("(%p)->(%p) Unknown IUri types not supported yet.\n", This, pIUri); | |
| return E_NOTIMPL; | |
| } | |
| } else if(This->uri) | |
| /* Only reset the builder if its Uri isn't NULL. */ | |
| reset_builder(This); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI UriBuilder_GetFragment(IUriBuilder *iface, DWORD *pcchFragment, LPCWSTR *ppwzFragment) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%p %p)\n", This, pcchFragment, ppwzFragment); | |
| if(!This->uri || This->uri->fragment_start == -1 || This->modified_props & Uri_HAS_FRAGMENT) | |
| return get_builder_component(&This->fragment, &This->fragment_len, NULL, 0, ppwzFragment, pcchFragment); | |
| else | |
| return get_builder_component(&This->fragment, &This->fragment_len, This->uri->canon_uri+This->uri->fragment_start, | |
| This->uri->fragment_len, ppwzFragment, pcchFragment); | |
| } | |
| static HRESULT WINAPI UriBuilder_GetHost(IUriBuilder *iface, DWORD *pcchHost, LPCWSTR *ppwzHost) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%p %p)\n", This, pcchHost, ppwzHost); | |
| if(!This->uri || This->uri->host_start == -1 || This->modified_props & Uri_HAS_HOST) | |
| return get_builder_component(&This->host, &This->host_len, NULL, 0, ppwzHost, pcchHost); | |
| else { | |
| if(This->uri->host_type == Uri_HOST_IPV6) | |
| /* Don't include the '[' and ']' around the address. */ | |
| return get_builder_component(&This->host, &This->host_len, This->uri->canon_uri+This->uri->host_start+1, | |
| This->uri->host_len-2, ppwzHost, pcchHost); | |
| else | |
| return get_builder_component(&This->host, &This->host_len, This->uri->canon_uri+This->uri->host_start, | |
| This->uri->host_len, ppwzHost, pcchHost); | |
| } | |
| } | |
| static HRESULT WINAPI UriBuilder_GetPassword(IUriBuilder *iface, DWORD *pcchPassword, LPCWSTR *ppwzPassword) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%p %p)\n", This, pcchPassword, ppwzPassword); | |
| if(!This->uri || This->uri->userinfo_split == -1 || This->modified_props & Uri_HAS_PASSWORD) | |
| return get_builder_component(&This->password, &This->password_len, NULL, 0, ppwzPassword, pcchPassword); | |
| else { | |
| const WCHAR *start = This->uri->canon_uri+This->uri->userinfo_start+This->uri->userinfo_split+1; | |
| DWORD len = This->uri->userinfo_len-This->uri->userinfo_split-1; | |
| return get_builder_component(&This->password, &This->password_len, start, len, ppwzPassword, pcchPassword); | |
| } | |
| } | |
| static HRESULT WINAPI UriBuilder_GetPath(IUriBuilder *iface, DWORD *pcchPath, LPCWSTR *ppwzPath) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%p %p)\n", This, pcchPath, ppwzPath); | |
| if(!This->uri || This->uri->path_start == -1 || This->modified_props & Uri_HAS_PATH) | |
| return get_builder_component(&This->path, &This->path_len, NULL, 0, ppwzPath, pcchPath); | |
| else | |
| return get_builder_component(&This->path, &This->path_len, This->uri->canon_uri+This->uri->path_start, | |
| This->uri->path_len, ppwzPath, pcchPath); | |
| } | |
| static HRESULT WINAPI UriBuilder_GetPort(IUriBuilder *iface, BOOL *pfHasPort, DWORD *pdwPort) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%p %p)\n", This, pfHasPort, pdwPort); | |
| if(!pfHasPort) { | |
| if(pdwPort) | |
| *pdwPort = 0; | |
| return E_POINTER; | |
| } | |
| if(!pdwPort) { | |
| *pfHasPort = FALSE; | |
| return E_POINTER; | |
| } | |
| *pfHasPort = This->has_port; | |
| *pdwPort = This->port; | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI UriBuilder_GetQuery(IUriBuilder *iface, DWORD *pcchQuery, LPCWSTR *ppwzQuery) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%p %p)\n", This, pcchQuery, ppwzQuery); | |
| if(!This->uri || This->uri->query_start == -1 || This->modified_props & Uri_HAS_QUERY) | |
| return get_builder_component(&This->query, &This->query_len, NULL, 0, ppwzQuery, pcchQuery); | |
| else | |
| return get_builder_component(&This->query, &This->query_len, This->uri->canon_uri+This->uri->query_start, | |
| This->uri->query_len, ppwzQuery, pcchQuery); | |
| } | |
| static HRESULT WINAPI UriBuilder_GetSchemeName(IUriBuilder *iface, DWORD *pcchSchemeName, LPCWSTR *ppwzSchemeName) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%p %p)\n", This, pcchSchemeName, ppwzSchemeName); | |
| if(!This->uri || This->uri->scheme_start == -1 || This->modified_props & Uri_HAS_SCHEME_NAME) | |
| return get_builder_component(&This->scheme, &This->scheme_len, NULL, 0, ppwzSchemeName, pcchSchemeName); | |
| else | |
| return get_builder_component(&This->scheme, &This->scheme_len, This->uri->canon_uri+This->uri->scheme_start, | |
| This->uri->scheme_len, ppwzSchemeName, pcchSchemeName); | |
| } | |
| static HRESULT WINAPI UriBuilder_GetUserName(IUriBuilder *iface, DWORD *pcchUserName, LPCWSTR *ppwzUserName) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%p %p)\n", This, pcchUserName, ppwzUserName); | |
| if(!This->uri || This->uri->userinfo_start == -1 || This->uri->userinfo_split == 0 || | |
| This->modified_props & Uri_HAS_USER_NAME) | |
| return get_builder_component(&This->username, &This->username_len, NULL, 0, ppwzUserName, pcchUserName); | |
| else { | |
| const WCHAR *start = This->uri->canon_uri+This->uri->userinfo_start; | |
| /* Check if there's a password in the userinfo section. */ | |
| if(This->uri->userinfo_split > -1) | |
| /* Don't include the password. */ | |
| return get_builder_component(&This->username, &This->username_len, start, | |
| This->uri->userinfo_split, ppwzUserName, pcchUserName); | |
| else | |
| return get_builder_component(&This->username, &This->username_len, start, | |
| This->uri->userinfo_len, ppwzUserName, pcchUserName); | |
| } | |
| } | |
| static HRESULT WINAPI UriBuilder_SetFragment(IUriBuilder *iface, LPCWSTR pwzNewValue) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%s)\n", This, debugstr_w(pwzNewValue)); | |
| return set_builder_component(&This->fragment, &This->fragment_len, pwzNewValue, '#', | |
| &This->modified_props, Uri_HAS_FRAGMENT); | |
| } | |
| static HRESULT WINAPI UriBuilder_SetHost(IUriBuilder *iface, LPCWSTR pwzNewValue) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%s)\n", This, debugstr_w(pwzNewValue)); | |
| /* Host name can't be set to NULL. */ | |
| if(!pwzNewValue) | |
| return E_INVALIDARG; | |
| return set_builder_component(&This->host, &This->host_len, pwzNewValue, 0, | |
| &This->modified_props, Uri_HAS_HOST); | |
| } | |
| static HRESULT WINAPI UriBuilder_SetPassword(IUriBuilder *iface, LPCWSTR pwzNewValue) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%s)\n", This, debugstr_w(pwzNewValue)); | |
| return set_builder_component(&This->password, &This->password_len, pwzNewValue, 0, | |
| &This->modified_props, Uri_HAS_PASSWORD); | |
| } | |
| static HRESULT WINAPI UriBuilder_SetPath(IUriBuilder *iface, LPCWSTR pwzNewValue) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%s)\n", This, debugstr_w(pwzNewValue)); | |
| return set_builder_component(&This->path, &This->path_len, pwzNewValue, 0, | |
| &This->modified_props, Uri_HAS_PATH); | |
| } | |
| static HRESULT WINAPI UriBuilder_SetPort(IUriBuilder *iface, BOOL fHasPort, DWORD dwNewValue) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%d %d)\n", This, fHasPort, dwNewValue); | |
| This->has_port = fHasPort; | |
| This->port = dwNewValue; | |
| This->modified_props |= Uri_HAS_PORT; | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI UriBuilder_SetQuery(IUriBuilder *iface, LPCWSTR pwzNewValue) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%s)\n", This, debugstr_w(pwzNewValue)); | |
| return set_builder_component(&This->query, &This->query_len, pwzNewValue, '?', | |
| &This->modified_props, Uri_HAS_QUERY); | |
| } | |
| static HRESULT WINAPI UriBuilder_SetSchemeName(IUriBuilder *iface, LPCWSTR pwzNewValue) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%s)\n", This, debugstr_w(pwzNewValue)); | |
| /* Only set the scheme name if it's not NULL or empty. */ | |
| if(!pwzNewValue || !*pwzNewValue) | |
| return E_INVALIDARG; | |
| return set_builder_component(&This->scheme, &This->scheme_len, pwzNewValue, 0, | |
| &This->modified_props, Uri_HAS_SCHEME_NAME); | |
| } | |
| static HRESULT WINAPI UriBuilder_SetUserName(IUriBuilder *iface, LPCWSTR pwzNewValue) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%s)\n", This, debugstr_w(pwzNewValue)); | |
| return set_builder_component(&This->username, &This->username_len, pwzNewValue, 0, | |
| &This->modified_props, Uri_HAS_USER_NAME); | |
| } | |
| static HRESULT WINAPI UriBuilder_RemoveProperties(IUriBuilder *iface, DWORD dwPropertyMask) | |
| { | |
| const DWORD accepted_flags = Uri_HAS_AUTHORITY|Uri_HAS_DOMAIN|Uri_HAS_EXTENSION|Uri_HAS_FRAGMENT|Uri_HAS_HOST| | |
| Uri_HAS_PASSWORD|Uri_HAS_PATH|Uri_HAS_PATH_AND_QUERY|Uri_HAS_QUERY| | |
| Uri_HAS_USER_INFO|Uri_HAS_USER_NAME; | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(0x%08x)\n", This, dwPropertyMask); | |
| if(dwPropertyMask & ~accepted_flags) | |
| return E_INVALIDARG; | |
| if(dwPropertyMask & Uri_HAS_FRAGMENT) | |
| UriBuilder_SetFragment(iface, NULL); | |
| /* Even though you can't set the host name to NULL or an | |
| * empty string, you can still remove it... for some reason. | |
| */ | |
| if(dwPropertyMask & Uri_HAS_HOST) | |
| set_builder_component(&This->host, &This->host_len, NULL, 0, | |
| &This->modified_props, Uri_HAS_HOST); | |
| if(dwPropertyMask & Uri_HAS_PASSWORD) | |
| UriBuilder_SetPassword(iface, NULL); | |
| if(dwPropertyMask & Uri_HAS_PATH) | |
| UriBuilder_SetPath(iface, NULL); | |
| if(dwPropertyMask & Uri_HAS_PORT) | |
| UriBuilder_SetPort(iface, FALSE, 0); | |
| if(dwPropertyMask & Uri_HAS_QUERY) | |
| UriBuilder_SetQuery(iface, NULL); | |
| if(dwPropertyMask & Uri_HAS_USER_NAME) | |
| UriBuilder_SetUserName(iface, NULL); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI UriBuilder_HasBeenModified(IUriBuilder *iface, BOOL *pfModified) | |
| { | |
| UriBuilder *This = impl_from_IUriBuilder(iface); | |
| TRACE("(%p)->(%p)\n", This, pfModified); | |
| if(!pfModified) | |
| return E_POINTER; | |
| *pfModified = This->modified_props > 0; | |
| return S_OK; | |
| } | |
| static const IUriBuilderVtbl UriBuilderVtbl = { | |
| UriBuilder_QueryInterface, | |
| UriBuilder_AddRef, | |
| UriBuilder_Release, | |
| UriBuilder_CreateUriSimple, | |
| UriBuilder_CreateUri, | |
| UriBuilder_CreateUriWithFlags, | |
| UriBuilder_GetIUri, | |
| UriBuilder_SetIUri, | |
| UriBuilder_GetFragment, | |
| UriBuilder_GetHost, | |
| UriBuilder_GetPassword, | |
| UriBuilder_GetPath, | |
| UriBuilder_GetPort, | |
| UriBuilder_GetQuery, | |
| UriBuilder_GetSchemeName, | |
| UriBuilder_GetUserName, | |
| UriBuilder_SetFragment, | |
| UriBuilder_SetHost, | |
| UriBuilder_SetPassword, | |
| UriBuilder_SetPath, | |
| UriBuilder_SetPort, | |
| UriBuilder_SetQuery, | |
| UriBuilder_SetSchemeName, | |
| UriBuilder_SetUserName, | |
| UriBuilder_RemoveProperties, | |
| UriBuilder_HasBeenModified, | |
| }; | |
| /*********************************************************************** | |
| * CreateIUriBuilder (urlmon.@) | |
| */ | |
| HRESULT WINAPI CreateIUriBuilder(IUri *pIUri, DWORD dwFlags, DWORD_PTR dwReserved, IUriBuilder **ppIUriBuilder) | |
| { | |
| UriBuilder *ret; | |
| TRACE("(%p %x %x %p)\n", pIUri, dwFlags, (DWORD)dwReserved, ppIUriBuilder); | |
| if(!ppIUriBuilder) | |
| return E_POINTER; | |
| ret = heap_alloc_zero(sizeof(UriBuilder)); | |
| if(!ret) | |
| return E_OUTOFMEMORY; | |
| ret->IUriBuilder_iface.lpVtbl = &UriBuilderVtbl; | |
| ret->ref = 1; | |
| if(pIUri) { | |
| Uri *uri; | |
| if((uri = get_uri_obj(pIUri))) { | |
| if(!uri->create_flags) { | |
| heap_free(ret); | |
| return E_UNEXPECTED; | |
| } | |
| IUri_AddRef(pIUri); | |
| ret->uri = uri; | |
| if(uri->has_port) | |
| /* Windows doesn't set 'has_port' to TRUE in this case. */ | |
| ret->port = uri->port; | |
| } else { | |
| heap_free(ret); | |
| *ppIUriBuilder = NULL; | |
| FIXME("(%p %x %x %p): Unknown IUri types not supported yet.\n", pIUri, dwFlags, | |
| (DWORD)dwReserved, ppIUriBuilder); | |
| return E_NOTIMPL; | |
| } | |
| } | |
| *ppIUriBuilder = &ret->IUriBuilder_iface; | |
| return S_OK; | |
| } | |
| /* Merges the base path with the relative path and stores the resulting path | |
| * and path len in 'result' and 'result_len'. | |
| */ | |
| static HRESULT merge_paths(parse_data *data, const WCHAR *base, DWORD base_len, const WCHAR *relative, | |
| DWORD relative_len, WCHAR **result, DWORD *result_len, DWORD flags) | |
| { | |
| const WCHAR *end = NULL; | |
| DWORD base_copy_len = 0; | |
| WCHAR *ptr; | |
| if(base_len) { | |
| if(data->scheme_type == URL_SCHEME_MK && *relative == '/') { | |
| /* Find '::' segment */ | |
| for(end = base; end < base+base_len-1; end++) { | |
| if(end[0] == ':' && end[1] == ':') { | |
| end++; | |
| break; | |
| } | |
| } | |
| /* If not found, try finding the end of @xxx: */ | |
| if(end == base+base_len-1) | |
| end = *base == '@' ? memchr(base, ':', base_len) : NULL; | |
| }else { | |
| /* Find the characters that will be copied over from the base path. */ | |
| end = memrchrW(base, '/', base_len); | |
| if(!end && data->scheme_type == URL_SCHEME_FILE) | |
| /* Try looking for a '\\'. */ | |
| end = memrchrW(base, '\\', base_len); | |
| } | |
| } | |
| if(end) { | |
| base_copy_len = (end+1)-base; | |
| *result = heap_alloc((base_copy_len+relative_len+1)*sizeof(WCHAR)); | |
| } else | |
| *result = heap_alloc((relative_len+1)*sizeof(WCHAR)); | |
| if(!(*result)) { | |
| *result_len = 0; | |
| return E_OUTOFMEMORY; | |
| } | |
| ptr = *result; | |
| if(end) { | |
| memcpy(ptr, base, base_copy_len*sizeof(WCHAR)); | |
| ptr += base_copy_len; | |
| } | |
| memcpy(ptr, relative, relative_len*sizeof(WCHAR)); | |
| ptr += relative_len; | |
| *ptr = '\0'; | |
| *result_len = (ptr-*result); | |
| TRACE("ret %s\n", debugstr_wn(*result, *result_len)); | |
| return S_OK; | |
| } | |
| static HRESULT combine_uri(Uri *base, Uri *relative, DWORD flags, IUri **result, DWORD extras) { | |
| Uri *ret; | |
| HRESULT hr; | |
| parse_data data; | |
| Uri *proc_uri = base; | |
| DWORD create_flags = 0, len = 0; | |
| memset(&data, 0, sizeof(parse_data)); | |
| /* Base case is when the relative Uri has a scheme name, | |
| * if it does, then 'result' will contain the same data | |
| * as the relative Uri. | |
| */ | |
| if(relative->scheme_start > -1) { | |
| data.uri = SysAllocString(relative->raw_uri); | |
| if(!data.uri) { | |
| *result = NULL; | |
| return E_OUTOFMEMORY; | |
| } | |
| parse_uri(&data, Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME); | |
| hr = Uri_Construct(NULL, (void**)&ret); | |
| if(FAILED(hr)) { | |
| *result = NULL; | |
| return hr; | |
| } | |
| if(extras & COMBINE_URI_FORCE_FLAG_USE) { | |
| if(flags & URL_DONT_SIMPLIFY) | |
| create_flags |= Uri_CREATE_NO_CANONICALIZE; | |
| if(flags & URL_DONT_UNESCAPE_EXTRA_INFO) | |
| create_flags |= Uri_CREATE_NO_DECODE_EXTRA_INFO; | |
| } | |
| ret->raw_uri = data.uri; | |
| hr = canonicalize_uri(&data, ret, create_flags); | |
| if(FAILED(hr)) { | |
| IUri_Release(&ret->IUri_iface); | |
| *result = NULL; | |
| return hr; | |
| } | |
| apply_default_flags(&create_flags); | |
| ret->create_flags = create_flags; | |
| *result = &ret->IUri_iface; | |
| } else { | |
| WCHAR *path = NULL; | |
| DWORD raw_flags = 0; | |
| if(base->scheme_start > -1) { | |
| data.scheme = base->canon_uri+base->scheme_start; | |
| data.scheme_len = base->scheme_len; | |
| data.scheme_type = base->scheme_type; | |
| } else { | |
| data.is_relative = TRUE; | |
| data.scheme_type = URL_SCHEME_UNKNOWN; | |
| create_flags |= Uri_CREATE_ALLOW_RELATIVE; | |
| } | |
| if(relative->authority_start > -1) | |
| proc_uri = relative; | |
| if(proc_uri->authority_start > -1) { | |
| if(proc_uri->userinfo_start > -1 && proc_uri->userinfo_split != 0) { | |
| data.username = proc_uri->canon_uri+proc_uri->userinfo_start; | |
| data.username_len = (proc_uri->userinfo_split > -1) ? proc_uri->userinfo_split : proc_uri->userinfo_len; | |
| } | |
| if(proc_uri->userinfo_split > -1) { | |
| data.password = proc_uri->canon_uri+proc_uri->userinfo_start+proc_uri->userinfo_split+1; | |
| data.password_len = proc_uri->userinfo_len-proc_uri->userinfo_split-1; | |
| } | |
| if(proc_uri->host_start > -1) { | |
| data.host = proc_uri->canon_uri+proc_uri->host_start; | |
| data.host_len = proc_uri->host_len; | |
| data.host_type = proc_uri->host_type; | |
| } | |
| if(proc_uri->has_port) { | |
| data.has_port = TRUE; | |
| data.port_value = proc_uri->port; | |
| } | |
| } else if(base->scheme_type != URL_SCHEME_FILE) | |
| data.is_opaque = TRUE; | |
| if(proc_uri == relative || relative->path_start == -1 || !relative->path_len) { | |
| if(proc_uri->path_start > -1) { | |
| data.path = proc_uri->canon_uri+proc_uri->path_start; | |
| data.path_len = proc_uri->path_len; | |
| } else if(!data.is_opaque) { | |
| /* Just set the path as a '/' if the base didn't have | |
| * one and if it's a hierarchical URI. | |
| */ | |
| static const WCHAR slashW[] = {'/',0}; | |
| data.path = slashW; | |
| data.path_len = 1; | |
| } | |
| if(relative->query_start > -1) | |
| proc_uri = relative; | |
| if(proc_uri->query_start > -1) { | |
| data.query = proc_uri->canon_uri+proc_uri->query_start; | |
| data.query_len = proc_uri->query_len; | |
| } | |
| } else { | |
| const WCHAR *ptr, **pptr; | |
| DWORD path_offset = 0, path_len = 0; | |
| /* There's two possibilities on what will happen to the path component | |
| * of the result IUri. First, if the relative path begins with a '/' | |
| * then the resulting path will just be the relative path. Second, if | |
| * relative path doesn't begin with a '/' then the base path and relative | |
| * path are merged together. | |
| */ | |
| if(relative->path_len && *(relative->canon_uri+relative->path_start) == '/' && data.scheme_type != URL_SCHEME_MK) { | |
| WCHAR *tmp = NULL; | |
| BOOL copy_drive_path = FALSE; | |
| /* If the relative IUri's path starts with a '/', then we | |
| * don't use the base IUri's path. Unless the base IUri | |
| * is a file URI, in which case it uses the drive path of | |
| * the base IUri (if it has any) in the new path. | |
| */ | |
| if(base->scheme_type == URL_SCHEME_FILE) { | |
| if(base->path_len > 3 && *(base->canon_uri+base->path_start) == '/' && | |
| is_drive_path(base->canon_uri+base->path_start+1)) { | |
| path_len += 3; | |
| copy_drive_path = TRUE; | |
| } | |
| } | |
| path_len += relative->path_len; | |
| path = heap_alloc((path_len+1)*sizeof(WCHAR)); | |
| if(!path) { | |
| *result = NULL; | |
| return E_OUTOFMEMORY; | |
| } | |
| tmp = path; | |
| /* Copy the base paths, drive path over. */ | |
| if(copy_drive_path) { | |
| memcpy(tmp, base->canon_uri+base->path_start, 3*sizeof(WCHAR)); | |
| tmp += 3; | |
| } | |
| memcpy(tmp, relative->canon_uri+relative->path_start, relative->path_len*sizeof(WCHAR)); | |
| path[path_len] = '\0'; | |
| } else { | |
| /* Merge the base path with the relative path. */ | |
| hr = merge_paths(&data, base->canon_uri+base->path_start, base->path_len, | |
| relative->canon_uri+relative->path_start, relative->path_len, | |
| &path, &path_len, flags); | |
| if(FAILED(hr)) { | |
| *result = NULL; | |
| return hr; | |
| } | |
| /* If the resulting IUri is a file URI, the drive path isn't | |
| * reduced out when the dot segments are removed. | |
| */ | |
| if(path_len >= 3 && data.scheme_type == URL_SCHEME_FILE && !data.host) { | |
| if(*path == '/' && is_drive_path(path+1)) | |
| path_offset = 2; | |
| else if(is_drive_path(path)) | |
| path_offset = 1; | |
| } | |
| } | |
| /* Check if the dot segments need to be removed from the path. */ | |
| if(!(flags & URL_DONT_SIMPLIFY) && !data.is_opaque) { | |
| DWORD offset = (path_offset > 0) ? path_offset+1 : 0; | |
| DWORD new_len = remove_dot_segments(path+offset,path_len-offset); | |
| if(new_len != path_len) { | |
| WCHAR *tmp = heap_realloc(path, (offset+new_len+1)*sizeof(WCHAR)); | |
| if(!tmp) { | |
| heap_free(path); | |
| *result = NULL; | |
| return E_OUTOFMEMORY; | |
| } | |
| tmp[new_len+offset] = '\0'; | |
| path = tmp; | |
| path_len = new_len+offset; | |
| } | |
| } | |
| if(relative->query_start > -1) { | |
| data.query = relative->canon_uri+relative->query_start; | |
| data.query_len = relative->query_len; | |
| } | |
| /* Make sure the path component is valid. */ | |
| ptr = path; | |
| pptr = &ptr; | |
| if((data.is_opaque && !parse_path_opaque(pptr, &data, 0)) || | |
| (!data.is_opaque && !parse_path_hierarchical(pptr, &data, 0))) { | |
| heap_free(path); | |
| *result = NULL; | |
| return E_INVALIDARG; | |
| } | |
| } | |
| if(relative->fragment_start > -1) { | |
| data.fragment = relative->canon_uri+relative->fragment_start; | |
| data.fragment_len = relative->fragment_len; | |
| } | |
| if(flags & URL_DONT_SIMPLIFY) | |
| raw_flags |= RAW_URI_FORCE_PORT_DISP; | |
| if(flags & URL_FILE_USE_PATHURL) | |
| raw_flags |= RAW_URI_CONVERT_TO_DOS_PATH; | |
| len = generate_raw_uri(&data, data.uri, raw_flags); | |
| data.uri = SysAllocStringLen(NULL, len); | |
| if(!data.uri) { | |
| heap_free(path); | |
| *result = NULL; | |
| return E_OUTOFMEMORY; | |
| } | |
| generate_raw_uri(&data, data.uri, raw_flags); | |
| hr = Uri_Construct(NULL, (void**)&ret); | |
| if(FAILED(hr)) { | |
| SysFreeString(data.uri); | |
| heap_free(path); | |
| *result = NULL; | |
| return hr; | |
| } | |
| if(flags & URL_DONT_SIMPLIFY) | |
| create_flags |= Uri_CREATE_NO_CANONICALIZE; | |
| if(flags & URL_FILE_USE_PATHURL) | |
| create_flags |= Uri_CREATE_FILE_USE_DOS_PATH; | |
| ret->raw_uri = data.uri; | |
| hr = canonicalize_uri(&data, ret, create_flags); | |
| if(FAILED(hr)) { | |
| IUri_Release(&ret->IUri_iface); | |
| *result = NULL; | |
| return hr; | |
| } | |
| if(flags & URL_DONT_SIMPLIFY) | |
| ret->display_modifiers |= URI_DISPLAY_NO_DEFAULT_PORT_AUTH; | |
| apply_default_flags(&create_flags); | |
| ret->create_flags = create_flags; | |
| *result = &ret->IUri_iface; | |
| heap_free(path); | |
| } | |
| return S_OK; | |
| } | |
| /*********************************************************************** | |
| * CoInternetCombineIUri (urlmon.@) | |
| */ | |
| HRESULT WINAPI CoInternetCombineIUri(IUri *pBaseUri, IUri *pRelativeUri, DWORD dwCombineFlags, | |
| IUri **ppCombinedUri, DWORD_PTR dwReserved) | |
| { | |
| HRESULT hr; | |
| IInternetProtocolInfo *info; | |
| Uri *relative, *base; | |
| TRACE("(%p %p %x %p %x)\n", pBaseUri, pRelativeUri, dwCombineFlags, ppCombinedUri, (DWORD)dwReserved); | |
| if(!ppCombinedUri) | |
| return E_INVALIDARG; | |
| if(!pBaseUri || !pRelativeUri) { | |
| *ppCombinedUri = NULL; | |
| return E_INVALIDARG; | |
| } | |
| relative = get_uri_obj(pRelativeUri); | |
| base = get_uri_obj(pBaseUri); | |
| if(!relative || !base) { | |
| *ppCombinedUri = NULL; | |
| FIXME("(%p %p %x %p %x) Unknown IUri types not supported yet.\n", | |
| pBaseUri, pRelativeUri, dwCombineFlags, ppCombinedUri, (DWORD)dwReserved); | |
| return E_NOTIMPL; | |
| } | |
| info = get_protocol_info(base->canon_uri); | |
| if(info) { | |
| WCHAR result[INTERNET_MAX_URL_LENGTH+1]; | |
| DWORD result_len = 0; | |
| hr = IInternetProtocolInfo_CombineUrl(info, base->canon_uri, relative->canon_uri, dwCombineFlags, | |
| result, INTERNET_MAX_URL_LENGTH+1, &result_len, 0); | |
| IInternetProtocolInfo_Release(info); | |
| if(SUCCEEDED(hr)) { | |
| hr = CreateUri(result, Uri_CREATE_ALLOW_RELATIVE, 0, ppCombinedUri); | |
| if(SUCCEEDED(hr)) | |
| return hr; | |
| } | |
| } | |
| return combine_uri(base, relative, dwCombineFlags, ppCombinedUri, 0); | |
| } | |
| /*********************************************************************** | |
| * CoInternetCombineUrlEx (urlmon.@) | |
| */ | |
| HRESULT WINAPI CoInternetCombineUrlEx(IUri *pBaseUri, LPCWSTR pwzRelativeUrl, DWORD dwCombineFlags, | |
| IUri **ppCombinedUri, DWORD_PTR dwReserved) | |
| { | |
| IUri *relative; | |
| Uri *base; | |
| HRESULT hr; | |
| IInternetProtocolInfo *info; | |
| TRACE("(%p %s %x %p %x)\n", pBaseUri, debugstr_w(pwzRelativeUrl), dwCombineFlags, | |
| ppCombinedUri, (DWORD)dwReserved); | |
| if(!ppCombinedUri) | |
| return E_POINTER; | |
| if(!pwzRelativeUrl) { | |
| *ppCombinedUri = NULL; | |
| return E_UNEXPECTED; | |
| } | |
| if(!pBaseUri) { | |
| *ppCombinedUri = NULL; | |
| return E_INVALIDARG; | |
| } | |
| base = get_uri_obj(pBaseUri); | |
| if(!base) { | |
| *ppCombinedUri = NULL; | |
| FIXME("(%p %s %x %p %x) Unknown IUri's not supported yet.\n", pBaseUri, debugstr_w(pwzRelativeUrl), | |
| dwCombineFlags, ppCombinedUri, (DWORD)dwReserved); | |
| return E_NOTIMPL; | |
| } | |
| info = get_protocol_info(base->canon_uri); | |
| if(info) { | |
| WCHAR result[INTERNET_MAX_URL_LENGTH+1]; | |
| DWORD result_len = 0; | |
| hr = IInternetProtocolInfo_CombineUrl(info, base->canon_uri, pwzRelativeUrl, dwCombineFlags, | |
| result, INTERNET_MAX_URL_LENGTH+1, &result_len, 0); | |
| IInternetProtocolInfo_Release(info); | |
| if(SUCCEEDED(hr)) { | |
| hr = CreateUri(result, Uri_CREATE_ALLOW_RELATIVE, 0, ppCombinedUri); | |
| if(SUCCEEDED(hr)) | |
| return hr; | |
| } | |
| } | |
| hr = CreateUri(pwzRelativeUrl, Uri_CREATE_ALLOW_RELATIVE|Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME, 0, &relative); | |
| if(FAILED(hr)) { | |
| *ppCombinedUri = NULL; | |
| return hr; | |
| } | |
| hr = combine_uri(base, get_uri_obj(relative), dwCombineFlags, ppCombinedUri, COMBINE_URI_FORCE_FLAG_USE); | |
| IUri_Release(relative); | |
| return hr; | |
| } | |
| static HRESULT parse_canonicalize(const Uri *uri, DWORD flags, LPWSTR output, | |
| DWORD output_len, DWORD *result_len) | |
| { | |
| const WCHAR *ptr = NULL; | |
| WCHAR *path = NULL; | |
| const WCHAR **pptr; | |
| DWORD len = 0; | |
| BOOL reduce_path; | |
| /* URL_UNESCAPE only has effect if none of the URL_ESCAPE flags are set. */ | |
| const BOOL allow_unescape = !(flags & URL_ESCAPE_UNSAFE) && | |
| !(flags & URL_ESCAPE_SPACES_ONLY) && | |
| !(flags & URL_ESCAPE_PERCENT); | |
| /* Check if the dot segments need to be removed from the | |
| * path component. | |
| */ | |
| if(uri->scheme_start > -1 && uri->path_start > -1) { | |
| ptr = uri->canon_uri+uri->scheme_start+uri->scheme_len+1; | |
| pptr = &ptr; | |
| } | |
| reduce_path = !(flags & URL_DONT_SIMPLIFY) && | |
| ptr && check_hierarchical(pptr); | |
| for(ptr = uri->canon_uri; ptr < uri->canon_uri+uri->canon_len; ++ptr) { | |
| BOOL do_default_action = TRUE; | |
| /* Keep track of the path if we need to remove dot segments from | |
| * it later. | |
| */ | |
| if(reduce_path && !path && ptr == uri->canon_uri+uri->path_start) | |
| path = output+len; | |
| /* Check if it's time to reduce the path. */ | |
| if(reduce_path && ptr == uri->canon_uri+uri->path_start+uri->path_len) { | |
| DWORD current_path_len = (output+len) - path; | |
| DWORD new_path_len = remove_dot_segments(path, current_path_len); | |
| /* Update the current length. */ | |
| len -= (current_path_len-new_path_len); | |
| reduce_path = FALSE; | |
| } | |
| if(*ptr == '%') { | |
| const WCHAR decoded = decode_pct_val(ptr); | |
| if(decoded) { | |
| if(allow_unescape && (flags & URL_UNESCAPE)) { | |
| if(len < output_len) | |
| output[len] = decoded; | |
| len++; | |
| ptr += 2; | |
| do_default_action = FALSE; | |
| } | |
| } | |
| /* See if %'s needed to encoded. */ | |
| if(do_default_action && (flags & URL_ESCAPE_PERCENT)) { | |
| if(len + 3 < output_len) | |
| pct_encode_val(*ptr, output+len); | |
| len += 3; | |
| do_default_action = FALSE; | |
| } | |
| } else if(*ptr == ' ') { | |
| if((flags & URL_ESCAPE_SPACES_ONLY) && | |
| !(flags & URL_ESCAPE_UNSAFE)) { | |
| if(len + 3 < output_len) | |
| pct_encode_val(*ptr, output+len); | |
| len += 3; | |
| do_default_action = FALSE; | |
| } | |
| } else if(!is_reserved(*ptr) && !is_unreserved(*ptr)) { | |
| if(flags & URL_ESCAPE_UNSAFE) { | |
| if(len + 3 < output_len) | |
| pct_encode_val(*ptr, output+len); | |
| len += 3; | |
| do_default_action = FALSE; | |
| } | |
| } | |
| if(do_default_action) { | |
| if(len < output_len) | |
| output[len] = *ptr; | |
| len++; | |
| } | |
| } | |
| /* Sometimes the path is the very last component of the IUri, so | |
| * see if the dot segments need to be reduced now. | |
| */ | |
| if(reduce_path && path) { | |
| DWORD current_path_len = (output+len) - path; | |
| DWORD new_path_len = remove_dot_segments(path, current_path_len); | |
| /* Update the current length. */ | |
| len -= (current_path_len-new_path_len); | |
| } | |
| if(len < output_len) | |
| output[len] = 0; | |
| else | |
| output[output_len-1] = 0; | |
| /* The null terminator isn't included in the length. */ | |
| *result_len = len; | |
| if(len >= output_len) | |
| return STRSAFE_E_INSUFFICIENT_BUFFER; | |
| return S_OK; | |
| } | |
| static HRESULT parse_friendly(IUri *uri, LPWSTR output, DWORD output_len, | |
| DWORD *result_len) | |
| { | |
| HRESULT hr; | |
| DWORD display_len; | |
| BSTR display; | |
| hr = IUri_GetPropertyLength(uri, Uri_PROPERTY_DISPLAY_URI, &display_len, 0); | |
| if(FAILED(hr)) { | |
| *result_len = 0; | |
| return hr; | |
| } | |
| *result_len = display_len; | |
| if(display_len+1 > output_len) | |
| return STRSAFE_E_INSUFFICIENT_BUFFER; | |
| hr = IUri_GetDisplayUri(uri, &display); | |
| if(FAILED(hr)) { | |
| *result_len = 0; | |
| return hr; | |
| } | |
| memcpy(output, display, (display_len+1)*sizeof(WCHAR)); | |
| SysFreeString(display); | |
| return S_OK; | |
| } | |
| static HRESULT parse_rootdocument(const Uri *uri, LPWSTR output, DWORD output_len, | |
| DWORD *result_len) | |
| { | |
| static const WCHAR colon_slashesW[] = {':','/','/'}; | |
| WCHAR *ptr; | |
| DWORD len = 0; | |
| /* Windows only returns the root document if the URI has an authority | |
| * and it's not an unknown scheme type or a file scheme type. | |
| */ | |
| if(uri->authority_start == -1 || | |
| uri->scheme_type == URL_SCHEME_UNKNOWN || | |
| uri->scheme_type == URL_SCHEME_FILE) { | |
| *result_len = 0; | |
| if(!output_len) | |
| return STRSAFE_E_INSUFFICIENT_BUFFER; | |
| output[0] = 0; | |
| return S_OK; | |
| } | |
| len = uri->scheme_len+uri->authority_len; | |
| /* For the "://" and '/' which will be added. */ | |
| len += 4; | |
| if(len+1 > output_len) { | |
| *result_len = len; | |
| return STRSAFE_E_INSUFFICIENT_BUFFER; | |
| } | |
| ptr = output; | |
| memcpy(ptr, uri->canon_uri+uri->scheme_start, uri->scheme_len*sizeof(WCHAR)); | |
| /* Add the "://". */ | |
| ptr += uri->scheme_len; | |
| memcpy(ptr, colon_slashesW, sizeof(colon_slashesW)); | |
| /* Add the authority. */ | |
| ptr += sizeof(colon_slashesW)/sizeof(WCHAR); | |
| memcpy(ptr, uri->canon_uri+uri->authority_start, uri->authority_len*sizeof(WCHAR)); | |
| /* Add the '/' after the authority. */ | |
| ptr += uri->authority_len; | |
| *ptr = '/'; | |
| ptr[1] = 0; | |
| *result_len = len; | |
| return S_OK; | |
| } | |
| static HRESULT parse_document(const Uri *uri, LPWSTR output, DWORD output_len, | |
| DWORD *result_len) | |
| { | |
| DWORD len = 0; | |
| /* It has to be a known scheme type, but, it can't be a file | |
| * scheme. It also has to hierarchical. | |
| */ | |
| if(uri->scheme_type == URL_SCHEME_UNKNOWN || | |
| uri->scheme_type == URL_SCHEME_FILE || | |
| uri->authority_start == -1) { | |
| *result_len = 0; | |
| if(output_len < 1) | |
| return STRSAFE_E_INSUFFICIENT_BUFFER; | |
| output[0] = 0; | |
| return S_OK; | |
| } | |
| if(uri->fragment_start > -1) | |
| len = uri->fragment_start; | |
| else | |
| len = uri->canon_len; | |
| *result_len = len; | |
| if(len+1 > output_len) | |
| return STRSAFE_E_INSUFFICIENT_BUFFER; | |
| memcpy(output, uri->canon_uri, len*sizeof(WCHAR)); | |
| output[len] = 0; | |
| return S_OK; | |
| } | |
| static HRESULT parse_path_from_url(const Uri *uri, LPWSTR output, DWORD output_len, | |
| DWORD *result_len) | |
| { | |
| const WCHAR *path_ptr; | |
| WCHAR buffer[INTERNET_MAX_URL_LENGTH+1]; | |
| WCHAR *ptr; | |
| if(uri->scheme_type != URL_SCHEME_FILE) { | |
| *result_len = 0; | |
| if(output_len > 0) | |
| output[0] = 0; | |
| return E_INVALIDARG; | |
| } | |
| ptr = buffer; | |
| if(uri->host_start > -1) { | |
| static const WCHAR slash_slashW[] = {'\\','\\'}; | |
| memcpy(ptr, slash_slashW, sizeof(slash_slashW)); | |
| ptr += sizeof(slash_slashW)/sizeof(WCHAR); | |
| memcpy(ptr, uri->canon_uri+uri->host_start, uri->host_len*sizeof(WCHAR)); | |
| ptr += uri->host_len; | |
| } | |
| path_ptr = uri->canon_uri+uri->path_start; | |
| if(uri->path_len > 3 && *path_ptr == '/' && is_drive_path(path_ptr+1)) | |
| /* Skip past the '/' in front of the drive path. */ | |
| ++path_ptr; | |
| for(; path_ptr < uri->canon_uri+uri->path_start+uri->path_len; ++path_ptr, ++ptr) { | |
| BOOL do_default_action = TRUE; | |
| if(*path_ptr == '%') { | |
| const WCHAR decoded = decode_pct_val(path_ptr); | |
| if(decoded) { | |
| *ptr = decoded; | |
| path_ptr += 2; | |
| do_default_action = FALSE; | |
| } | |
| } else if(*path_ptr == '/') { | |
| *ptr = '\\'; | |
| do_default_action = FALSE; | |
| } | |
| if(do_default_action) | |
| *ptr = *path_ptr; | |
| } | |
| *ptr = 0; | |
| *result_len = ptr-buffer; | |
| if(*result_len+1 > output_len) | |
| return STRSAFE_E_INSUFFICIENT_BUFFER; | |
| memcpy(output, buffer, (*result_len+1)*sizeof(WCHAR)); | |
| return S_OK; | |
| } | |
| static HRESULT parse_url_from_path(IUri *uri, LPWSTR output, DWORD output_len, | |
| DWORD *result_len) | |
| { | |
| HRESULT hr; | |
| BSTR received; | |
| DWORD len = 0; | |
| hr = IUri_GetPropertyLength(uri, Uri_PROPERTY_ABSOLUTE_URI, &len, 0); | |
| if(FAILED(hr)) { | |
| *result_len = 0; | |
| return hr; | |
| } | |
| *result_len = len; | |
| if(len+1 > output_len) | |
| return STRSAFE_E_INSUFFICIENT_BUFFER; | |
| hr = IUri_GetAbsoluteUri(uri, &received); | |
| if(FAILED(hr)) { | |
| *result_len = 0; | |
| return hr; | |
| } | |
| memcpy(output, received, (len+1)*sizeof(WCHAR)); | |
| SysFreeString(received); | |
| return S_OK; | |
| } | |
| static HRESULT parse_schema(IUri *uri, LPWSTR output, DWORD output_len, | |
| DWORD *result_len) | |
| { | |
| HRESULT hr; | |
| DWORD len; | |
| BSTR received; | |
| hr = IUri_GetPropertyLength(uri, Uri_PROPERTY_SCHEME_NAME, &len, 0); | |
| if(FAILED(hr)) { | |
| *result_len = 0; | |
| return hr; | |
| } | |
| *result_len = len; | |
| if(len+1 > output_len) | |
| return STRSAFE_E_INSUFFICIENT_BUFFER; | |
| hr = IUri_GetSchemeName(uri, &received); | |
| if(FAILED(hr)) { | |
| *result_len = 0; | |
| return hr; | |
| } | |
| memcpy(output, received, (len+1)*sizeof(WCHAR)); | |
| SysFreeString(received); | |
| return S_OK; | |
| } | |
| static HRESULT parse_site(IUri *uri, LPWSTR output, DWORD output_len, DWORD *result_len) | |
| { | |
| HRESULT hr; | |
| DWORD len; | |
| BSTR received; | |
| hr = IUri_GetPropertyLength(uri, Uri_PROPERTY_HOST, &len, 0); | |
| if(FAILED(hr)) { | |
| *result_len = 0; | |
| return hr; | |
| } | |
| *result_len = len; | |
| if(len+1 > output_len) | |
| return STRSAFE_E_INSUFFICIENT_BUFFER; | |
| hr = IUri_GetHost(uri, &received); | |
| if(FAILED(hr)) { | |
| *result_len = 0; | |
| return hr; | |
| } | |
| memcpy(output, received, (len+1)*sizeof(WCHAR)); | |
| SysFreeString(received); | |
| return S_OK; | |
| } | |
| static HRESULT parse_domain(IUri *uri, LPWSTR output, DWORD output_len, DWORD *result_len) | |
| { | |
| HRESULT hr; | |
| DWORD len; | |
| BSTR received; | |
| hr = IUri_GetPropertyLength(uri, Uri_PROPERTY_DOMAIN, &len, 0); | |
| if(FAILED(hr)) { | |
| *result_len = 0; | |
| return hr; | |
| } | |
| *result_len = len; | |
| if(len+1 > output_len) | |
| return STRSAFE_E_INSUFFICIENT_BUFFER; | |
| hr = IUri_GetDomain(uri, &received); | |
| if(FAILED(hr)) { | |
| *result_len = 0; | |
| return hr; | |
| } | |
| memcpy(output, received, (len+1)*sizeof(WCHAR)); | |
| SysFreeString(received); | |
| return S_OK; | |
| } | |
| static HRESULT parse_anchor(IUri *uri, LPWSTR output, DWORD output_len, DWORD *result_len) | |
| { | |
| HRESULT hr; | |
| DWORD len; | |
| BSTR received; | |
| hr = IUri_GetPropertyLength(uri, Uri_PROPERTY_FRAGMENT, &len, 0); | |
| if(FAILED(hr)) { | |
| *result_len = 0; | |
| return hr; | |
| } | |
| *result_len = len; | |
| if(len+1 > output_len) | |
| return STRSAFE_E_INSUFFICIENT_BUFFER; | |
| hr = IUri_GetFragment(uri, &received); | |
| if(FAILED(hr)) { | |
| *result_len = 0; | |
| return hr; | |
| } | |
| memcpy(output, received, (len+1)*sizeof(WCHAR)); | |
| SysFreeString(received); | |
| return S_OK; | |
| } | |
| /*********************************************************************** | |
| * CoInternetParseIUri (urlmon.@) | |
| */ | |
| HRESULT WINAPI CoInternetParseIUri(IUri *pIUri, PARSEACTION ParseAction, DWORD dwFlags, | |
| LPWSTR pwzResult, DWORD cchResult, DWORD *pcchResult, | |
| DWORD_PTR dwReserved) | |
| { | |
| HRESULT hr; | |
| Uri *uri; | |
| IInternetProtocolInfo *info; | |
| TRACE("(%p %d %x %p %d %p %x)\n", pIUri, ParseAction, dwFlags, pwzResult, | |
| cchResult, pcchResult, (DWORD)dwReserved); | |
| if(!pcchResult) | |
| return E_POINTER; | |
| if(!pwzResult || !pIUri) { | |
| *pcchResult = 0; | |
| return E_INVALIDARG; | |
| } | |
| if(!(uri = get_uri_obj(pIUri))) { | |
| *pcchResult = 0; | |
| FIXME("(%p %d %x %p %d %p %x) Unknown IUri's not supported for this action.\n", | |
| pIUri, ParseAction, dwFlags, pwzResult, cchResult, pcchResult, (DWORD)dwReserved); | |
| return E_NOTIMPL; | |
| } | |
| info = get_protocol_info(uri->canon_uri); | |
| if(info) { | |
| hr = IInternetProtocolInfo_ParseUrl(info, uri->canon_uri, ParseAction, dwFlags, | |
| pwzResult, cchResult, pcchResult, 0); | |
| IInternetProtocolInfo_Release(info); | |
| if(SUCCEEDED(hr)) return hr; | |
| } | |
| switch(ParseAction) { | |
| case PARSE_CANONICALIZE: | |
| hr = parse_canonicalize(uri, dwFlags, pwzResult, cchResult, pcchResult); | |
| break; | |
| case PARSE_FRIENDLY: | |
| hr = parse_friendly(pIUri, pwzResult, cchResult, pcchResult); | |
| break; | |
| case PARSE_ROOTDOCUMENT: | |
| hr = parse_rootdocument(uri, pwzResult, cchResult, pcchResult); | |
| break; | |
| case PARSE_DOCUMENT: | |
| hr = parse_document(uri, pwzResult, cchResult, pcchResult); | |
| break; | |
| case PARSE_PATH_FROM_URL: | |
| hr = parse_path_from_url(uri, pwzResult, cchResult, pcchResult); | |
| break; | |
| case PARSE_URL_FROM_PATH: | |
| hr = parse_url_from_path(pIUri, pwzResult, cchResult, pcchResult); | |
| break; | |
| case PARSE_SCHEMA: | |
| hr = parse_schema(pIUri, pwzResult, cchResult, pcchResult); | |
| break; | |
| case PARSE_SITE: | |
| hr = parse_site(pIUri, pwzResult, cchResult, pcchResult); | |
| break; | |
| case PARSE_DOMAIN: | |
| hr = parse_domain(pIUri, pwzResult, cchResult, pcchResult); | |
| break; | |
| case PARSE_LOCATION: | |
| case PARSE_ANCHOR: | |
| hr = parse_anchor(pIUri, pwzResult, cchResult, pcchResult); | |
| break; | |
| case PARSE_SECURITY_URL: | |
| case PARSE_MIME: | |
| case PARSE_SERVER: | |
| case PARSE_SECURITY_DOMAIN: | |
| *pcchResult = 0; | |
| hr = E_FAIL; | |
| break; | |
| default: | |
| *pcchResult = 0; | |
| hr = E_NOTIMPL; | |
| FIXME("(%p %d %x %p %d %p %x) Partial stub.\n", pIUri, ParseAction, dwFlags, | |
| pwzResult, cchResult, pcchResult, (DWORD)dwReserved); | |
| } | |
| return hr; | |
| } |