Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/sansio/http.py: 35%

75 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-09 06:08 +0000

1from __future__ import annotations 

2 

3import re 

4import typing as t 

5import warnings 

6from datetime import datetime 

7 

8from .._internal import _dt_as_utc 

9from ..http import generate_etag 

10from ..http import parse_date 

11from ..http import parse_etags 

12from ..http import parse_if_range_header 

13from ..http import unquote_etag 

14 

15_etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)') 

16 

17 

18def is_resource_modified( 

19 http_range: str | None = None, 

20 http_if_range: str | None = None, 

21 http_if_modified_since: str | None = None, 

22 http_if_none_match: str | None = None, 

23 http_if_match: str | None = None, 

24 etag: str | None = None, 

25 data: bytes | None = None, 

26 last_modified: datetime | str | None = None, 

27 ignore_if_range: bool = True, 

28) -> bool: 

29 """Convenience method for conditional requests. 

30 :param http_range: Range HTTP header 

31 :param http_if_range: If-Range HTTP header 

32 :param http_if_modified_since: If-Modified-Since HTTP header 

33 :param http_if_none_match: If-None-Match HTTP header 

34 :param http_if_match: If-Match HTTP header 

35 :param etag: the etag for the response for comparison. 

36 :param data: or alternatively the data of the response to automatically 

37 generate an etag using :func:`generate_etag`. 

38 :param last_modified: an optional date of the last modification. 

39 :param ignore_if_range: If `False`, `If-Range` header will be taken into 

40 account. 

41 :return: `True` if the resource was modified, otherwise `False`. 

42 

43 .. versionadded:: 2.2 

44 """ 

45 if etag is None and data is not None: 

46 etag = generate_etag(data) 

47 elif data is not None: 

48 raise TypeError("both data and etag given") 

49 

50 unmodified = False 

51 if isinstance(last_modified, str): 

52 last_modified = parse_date(last_modified) 

53 

54 # HTTP doesn't use microsecond, remove it to avoid false positive 

55 # comparisons. Mark naive datetimes as UTC. 

56 if last_modified is not None: 

57 last_modified = _dt_as_utc(last_modified.replace(microsecond=0)) 

58 

59 if_range = None 

60 if not ignore_if_range and http_range is not None: 

61 # https://tools.ietf.org/html/rfc7233#section-3.2 

62 # A server MUST ignore an If-Range header field received in a request 

63 # that does not contain a Range header field. 

64 if_range = parse_if_range_header(http_if_range) 

65 

66 if if_range is not None and if_range.date is not None: 

67 modified_since: datetime | None = if_range.date 

68 else: 

69 modified_since = parse_date(http_if_modified_since) 

70 

71 if modified_since and last_modified and last_modified <= modified_since: 

72 unmodified = True 

73 

74 if etag: 

75 etag, _ = unquote_etag(etag) 

76 etag = t.cast(str, etag) 

77 

78 if if_range is not None and if_range.etag is not None: 

79 unmodified = parse_etags(if_range.etag).contains(etag) 

80 else: 

81 if_none_match = parse_etags(http_if_none_match) 

82 if if_none_match: 

83 # https://tools.ietf.org/html/rfc7232#section-3.2 

84 # "A recipient MUST use the weak comparison function when comparing 

85 # entity-tags for If-None-Match" 

86 unmodified = if_none_match.contains_weak(etag) 

87 

88 # https://tools.ietf.org/html/rfc7232#section-3.1 

89 # "Origin server MUST use the strong comparison function when 

90 # comparing entity-tags for If-Match" 

91 if_match = parse_etags(http_if_match) 

92 if if_match: 

93 unmodified = not if_match.is_strong(etag) 

94 

95 return not unmodified 

96 

97 

98_cookie_re = re.compile( 

99 r""" 

100 ([^=;]*) 

101 (?:\s*=\s* 

102 ( 

103 "(?:[^\\"]|\\.)*" 

104 | 

105 .*? 

106 ) 

107 )? 

108 \s*;\s* 

109 """, 

110 flags=re.ASCII | re.VERBOSE, 

111) 

112_cookie_unslash_re = re.compile(rb"\\([0-3][0-7]{2}|.)") 

113 

114 

115def _cookie_unslash_replace(m: t.Match[bytes]) -> bytes: 

116 v = m.group(1) 

117 

118 if len(v) == 1: 

119 return v 

120 

121 return int(v, 8).to_bytes(1, "big") 

122 

123 

124def parse_cookie( 

125 cookie: str | None = None, 

126 charset: str | None = None, 

127 errors: str | None = None, 

128 cls: type[ds.MultiDict] | None = None, 

129) -> ds.MultiDict[str, str]: 

130 """Parse a cookie from a string. 

131 

132 The same key can be provided multiple times, the values are stored 

133 in-order. The default :class:`MultiDict` will have the first value 

134 first, and all values can be retrieved with 

135 :meth:`MultiDict.getlist`. 

136 

137 :param cookie: The cookie header as a string. 

138 :param cls: A dict-like class to store the parsed cookies in. 

139 Defaults to :class:`MultiDict`. 

140 

141 .. versionchanged:: 2.3 

142 Passing bytes, and the ``charset`` and ``errors`` parameters, are deprecated and 

143 will be removed in Werkzeug 3.0. 

144 

145 .. versionadded:: 2.2 

146 """ 

147 if cls is None: 

148 cls = ds.MultiDict 

149 

150 if isinstance(cookie, bytes): 

151 warnings.warn( 

152 "The 'cookie' parameter must be a string. Passing bytes is deprecated and" 

153 " will not be supported in Werkzeug 3.0.", 

154 DeprecationWarning, 

155 stacklevel=2, 

156 ) 

157 cookie = cookie.decode() 

158 

159 if charset is not None: 

160 warnings.warn( 

161 "The 'charset' parameter is deprecated and will be removed in Werkzeug 3.0", 

162 DeprecationWarning, 

163 stacklevel=2, 

164 ) 

165 else: 

166 charset = "utf-8" 

167 

168 if errors is not None: 

169 warnings.warn( 

170 "The 'errors' parameter is deprecated and will be removed in Werkzeug 3.0", 

171 DeprecationWarning, 

172 stacklevel=2, 

173 ) 

174 else: 

175 errors = "replace" 

176 

177 if not cookie: 

178 return cls() 

179 

180 cookie = f"{cookie};" 

181 out = [] 

182 

183 for ck, cv in _cookie_re.findall(cookie): 

184 ck = ck.strip() 

185 cv = cv.strip() 

186 

187 if not ck: 

188 continue 

189 

190 if len(cv) >= 2 and cv[0] == cv[-1] == '"': 

191 # Work with bytes here, since a UTF-8 character could be multiple bytes. 

192 cv = _cookie_unslash_re.sub( 

193 _cookie_unslash_replace, cv[1:-1].encode() 

194 ).decode(charset, errors) 

195 

196 out.append((ck, cv)) 

197 

198 return cls(out) 

199 

200 

201# circular dependencies 

202from .. import datastructures as ds