1""" 2URL utils 3""" 4 5 6import re 7import sys 8from urllib.parse import urlparse, urlunparse 9 10import salt.utils.data 11import salt.utils.path 12import salt.utils.platform 13import salt.utils.versions 14 15 16def parse(url): 17 """ 18 Parse a salt:// URL; return the path and a possible saltenv query. 19 """ 20 if not url.startswith("salt://"): 21 return url, None 22 23 # urlparse will split on valid filename chars such as '?' and '&' 24 resource = url.split("salt://", 1)[-1] 25 26 if "?env=" in resource: 27 # "env" is not supported; Use "saltenv". 28 path, saltenv = resource.split("?env=", 1)[0], None 29 elif "?saltenv=" in resource: 30 path, saltenv = resource.split("?saltenv=", 1) 31 else: 32 path, saltenv = resource, None 33 34 if salt.utils.platform.is_windows(): 35 path = salt.utils.path.sanitize_win_path(path) 36 37 return path, saltenv 38 39 40def create(path, saltenv=None): 41 """ 42 join `path` and `saltenv` into a 'salt://' URL. 43 """ 44 if salt.utils.platform.is_windows(): 45 path = salt.utils.path.sanitize_win_path(path) 46 path = salt.utils.data.decode(path) 47 48 query = "saltenv={}".format(saltenv) if saltenv else "" 49 url = salt.utils.data.decode(urlunparse(("file", "", path, "", query, ""))) 50 return "salt://{}".format(url[len("file:///") :]) 51 52 53def is_escaped(url): 54 """ 55 test whether `url` is escaped with `|` 56 """ 57 scheme = urlparse(url).scheme 58 if not scheme: 59 return url.startswith("|") 60 elif scheme == "salt": 61 path, saltenv = parse(url) 62 if salt.utils.platform.is_windows() and "|" in url: 63 return path.startswith("_") 64 else: 65 return path.startswith("|") 66 else: 67 return False 68 69 70def escape(url): 71 """ 72 add escape character `|` to `url` 73 """ 74 if salt.utils.platform.is_windows(): 75 return url 76 77 scheme = urlparse(url).scheme 78 if not scheme: 79 if url.startswith("|"): 80 return url 81 else: 82 return "|{}".format(url) 83 elif scheme == "salt": 84 path, saltenv = parse(url) 85 if path.startswith("|"): 86 return create(path, saltenv) 87 else: 88 return create("|{}".format(path), saltenv) 89 else: 90 return url 91 92 93def unescape(url): 94 """ 95 remove escape character `|` from `url` 96 """ 97 scheme = urlparse(url).scheme 98 if not scheme: 99 return url.lstrip("|") 100 elif scheme == "salt": 101 path, saltenv = parse(url) 102 if salt.utils.platform.is_windows() and "|" in url: 103 return create(path.lstrip("_"), saltenv) 104 else: 105 return create(path.lstrip("|"), saltenv) 106 else: 107 return url 108 109 110def add_env(url, saltenv): 111 """ 112 append `saltenv` to `url` as a query parameter to a 'salt://' url 113 """ 114 if not url.startswith("salt://"): 115 return url 116 117 path, senv = parse(url) 118 return create(path, saltenv) 119 120 121def split_env(url): 122 """ 123 remove the saltenv query parameter from a 'salt://' url 124 """ 125 if not url.startswith("salt://"): 126 return url, None 127 128 path, senv = parse(url) 129 return create(path), senv 130 131 132def validate(url, protos): 133 """ 134 Return true if the passed URL scheme is in the list of accepted protos 135 """ 136 if urlparse(url).scheme in protos: 137 return True 138 return False 139 140 141def strip_proto(url): 142 """ 143 Return a copy of the string with the protocol designation stripped, if one 144 was present. 145 """ 146 return re.sub("^[^:/]+://", "", url) 147 148 149def add_http_basic_auth(url, user=None, password=None, https_only=False): 150 """ 151 Return a string with http basic auth incorporated into it 152 """ 153 if user is None and password is None: 154 return url 155 else: 156 urltuple = urlparse(url) 157 if https_only and urltuple.scheme != "https": 158 raise ValueError("Basic Auth only supported for HTTPS") 159 if password is None: 160 netloc = "{}@{}".format(user, urltuple.netloc) 161 urltuple = urltuple._replace(netloc=netloc) 162 return urlunparse(urltuple) 163 else: 164 netloc = "{}:{}@{}".format(user, password, urltuple.netloc) 165 urltuple = urltuple._replace(netloc=netloc) 166 return urlunparse(urltuple) 167 168 169def redact_http_basic_auth(output): 170 """ 171 Remove HTTP user and password 172 """ 173 # We can't use re.compile because re.compile(someregex).sub() doesn't 174 # support flags even in Python 2.7. 175 url_re = "(https?)://.*@" 176 redacted = r"\1://<redacted>@" 177 if sys.version_info >= (2, 7): 178 # re.sub() supports flags as of 2.7, use this to do a case-insensitive 179 # match. 180 return re.sub(url_re, redacted, output, flags=re.IGNORECASE) 181 else: 182 # We're on python 2.6, test if a lowercased version of the output 183 # string matches the regex... 184 if re.search(url_re, output.lower()): 185 # ... and if it does, perform the regex substitution. 186 return re.sub(url_re, redacted, output.lower()) 187 # No match, just return the original string 188 return output 189