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