Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/sansio/utils.py: 23%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

79 statements  

1from __future__ import annotations 

2 

3import re 

4import typing as t 

5from urllib.parse import quote 

6 

7from .._internal import _plain_int 

8from ..datastructures.set import HeaderSet 

9from ..exceptions import SecurityError 

10from ..urls import uri_to_iri 

11 

12_host_re = re.compile( 

13 r""" 

14 (?P<hostname> 

15 [a-z0-9.-]+ # domain or ipv4 

16 | 

17 \[[a-f0-9]*:[a-f0-9.:]+] # ipv6 

18 ) 

19 (?::(?P<port>[1-9][0-9]{,4}))? # optional port 

20 """, 

21 flags=re.ASCII | re.IGNORECASE | re.VERBOSE, 

22) 

23 

24 

25def host_is_trusted( 

26 hostname: str | None, trusted_list: t.Collection[str] | None = None 

27) -> bool: 

28 """Perform some checks on a ``Host`` header ``host:port``. The host must be 

29 made up of valid characters, but this does not check validity beyond that. 

30 If a list of trusted domains is given, the domain must match one. 

31 

32 :param hostname: The ``Host`` header ``host:port`` to check. 

33 :param trusted_list: A list of trusted domains to match. These should 

34 already be IDNA encoded, but will be encoded if needed. If a name starts 

35 with a dot it will match as a suffix, accepting all subdomains. If empty 

36 or ``None``, all domains are allowed. 

37 

38 .. deprecated:: 3.2 

39 Will be private in Werkzeug 4.0. Use ``Request.trusted_hosts`` and 

40 ``Request.host`` instead. 

41 

42 .. versionchanged:: 3.2 

43 The value's characters are validated. 

44 

45 .. versionchanged:: 3.2 

46 ``trusted_list`` defaults to ``None``. 

47 

48 .. versionadded:: 0.9 

49 """ 

50 if not hostname: 

51 return False 

52 

53 if (m := _host_re.fullmatch(hostname)) is None: 

54 return False 

55 

56 hostname, port_str = m.groups() 

57 

58 if port_str and not (1 <= int(port_str) <= 65535): 

59 return False 

60 

61 if not trusted_list: 

62 return True 

63 

64 if isinstance(trusted_list, str): 

65 trusted_list = [trusted_list] 

66 

67 for ref in trusted_list: 

68 if ref.startswith("."): 

69 ref = ref[1:] 

70 suffix_match = True 

71 else: 

72 suffix_match = False 

73 

74 try: 

75 ref = ref.encode("idna").decode("ascii") 

76 except UnicodeEncodeError: 

77 return False 

78 

79 if ref == hostname or (suffix_match and hostname.endswith(f".{ref}")): 

80 return True 

81 

82 return False 

83 

84 

85def get_host( 

86 scheme: str, 

87 host_header: str | None, 

88 server: tuple[str, int | None] | None = None, 

89 trusted_hosts: t.Collection[str] | None = None, 

90) -> str: 

91 """Get and validate a request's ``host:port`` based on the given values. 

92 

93 The ``Host`` header sent by the client is preferred. Otherwise, the server's 

94 configured address is used. The port is omitted if it matches the standard 

95 HTTP or HTTPS ports. 

96 

97 The value is passed through :func:`host_is_trusted`. The host must be made 

98 up of valid characters, but this does not check validity beyond that. If a 

99 list of trusted domains is given, the domain must match one. 

100 

101 If the host header is not available, such as for HTTP/0.9 and 1.0, or it has 

102 invalid characters, the empty string is returned. Subdomain and host 

103 routing, and external URL building, will not work in these cases. 

104 

105 :param scheme: The protocol of the request. Used to omit the standard ports 

106 80 and 443. 

107 :param host_header: The ``Host`` header value. 

108 :param server: The server's configured address ``(host, port)``. The server 

109 may be using a Unix socket and give ``(path, None)``; this is ignored as 

110 it would not produce a useful host value. 

111 :param trusted_hosts: A list of trusted domains to match. These should 

112 already be IDNA encoded, but will be encoded if needed. The port is 

113 ignored for this check. If a name starts with a dot it will match as a 

114 suffix, accepting all subdomains. If empty or ``None``, all domains are 

115 allowed. 

116 

117 :return: Host, with port if necessary. 

118 :raise .SecurityError: If the host is not trusted. 

119 

120 .. versionchanged:: 3.2 

121 When using the server address, Unix sockets are ignored. 

122 

123 .. versionchanged:: 3.1.8 

124 The empty string is again returned if no host header value is available, 

125 or if the characters are invalid. 

126 

127 .. versionchanged:: 3.1.7 

128 The characters of the host value are validated. The empty string is no 

129 longer allowed if no header value is available. 

130 

131 .. versionchanged:: 3.1.3 

132 If ``SERVER_NAME`` is IPv6, it is wrapped in ``[]``. 

133 """ 

134 if host_header is not None: 

135 host = host_header 

136 # The port server[1] will be None for a Unix socket. Ignore in that case. 

137 elif server is not None and server[1] is not None: 

138 host = server[0] 

139 

140 # If SERVER_NAME is IPv6, wrap it in [] to match Host header. 

141 # Check for : because domain or IPv4 can't have that. 

142 if ":" in host and host[0] != "[": 

143 host = f"[{host}]" 

144 

145 host = f"{host}:{server[1]}" 

146 else: 

147 # Pass through empty host from HTTP/0.9 and 1.0. 

148 return "" 

149 

150 if scheme in {"http", "ws"}: 

151 host = host.removesuffix(":80") 

152 elif scheme in {"https", "wss"}: 

153 host = host.removesuffix(":443") 

154 

155 if not host_is_trusted(host, trusted_hosts): 

156 if trusted_hosts: 

157 raise SecurityError(f"Host {host!r} is not trusted.") 

158 

159 # Invalid characters, treat as empty. 

160 return "" 

161 

162 return host 

163 

164 

165def get_current_url( 

166 scheme: str, 

167 host: str, 

168 root_path: str | None = None, 

169 path: str | None = None, 

170 query_string: bytes | None = None, 

171) -> str: 

172 """Recreate the URL for a request. If an optional part isn't 

173 provided, it and subsequent parts are not included in the URL. 

174 

175 The URL is an IRI, not a URI, so it may contain Unicode characters. 

176 Use :func:`~werkzeug.urls.iri_to_uri` to convert it to ASCII. 

177 

178 :param scheme: The protocol the request used, like ``"https"``. 

179 :param host: The host the request was made to. See :func:`get_host`. 

180 :param root_path: Prefix that the application is mounted under. This 

181 is prepended to ``path``. 

182 :param path: The path part of the URL after ``root_path``. 

183 :param query_string: The portion of the URL after the "?". 

184 """ 

185 url = [scheme, "://", host] 

186 

187 if root_path is None: 

188 url.append("/") 

189 return uri_to_iri("".join(url)) 

190 

191 # safe = https://url.spec.whatwg.org/#url-path-segment-string 

192 # as well as percent for things that are already quoted 

193 url.append(quote(root_path.rstrip("/"), safe="!$&'()*+,/:;=@%")) 

194 url.append("/") 

195 

196 if path is None: 

197 return uri_to_iri("".join(url)) 

198 

199 url.append(quote(path.lstrip("/"), safe="!$&'()*+,/:;=@%")) 

200 

201 if query_string: 

202 url.append("?") 

203 url.append(quote(query_string, safe="!$&'()*+,/:;=?@%")) 

204 

205 return uri_to_iri("".join(url)) 

206 

207 

208def _get_content_length( 

209 http_content_length: str | None = None, 

210 http_transfer_encoding: str | None = None, 

211) -> int | None: 

212 """Return the ``Content-Length`` header value as an int. If the header is not given 

213 or the ``Transfer-Encoding`` header is ``chunked``, ``None`` is returned to indicate 

214 a streaming request. If the value is not an integer, or negative, 0 is returned. 

215 

216 :param http_content_length: The Content-Length HTTP header. 

217 :param http_transfer_encoding: The Transfer-Encoding HTTP header. 

218 

219 .. deprecated:: 3.2 

220 Will be removed in Werkzeug 4.0. Use ``Request.content_length`` instead. 

221 

222 .. versionadded:: 2.2 

223 """ 

224 if ( 

225 http_transfer_encoding is not None 

226 and "chunked" in HeaderSet.from_header(http_transfer_encoding) 

227 ) or http_content_length is None: 

228 return None 

229 

230 try: 

231 return max(0, _plain_int(http_content_length)) 

232 except ValueError: 

233 return 0 

234 

235 

236if not t.TYPE_CHECKING: 

237 

238 def __getattr__(name: str) -> t.Any: 

239 import warnings 

240 

241 if name == "get_content_length": 

242 warnings.warn( 

243 "The 'get_content_length' function is deprecated and will be removed in" 

244 " Werkzeug 4.0. Use 'Request.content_length' instead.", 

245 DeprecationWarning, 

246 stacklevel=2, 

247 ) 

248 return _get_content_length 

249 

250 raise AttributeError(name)