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

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

73 statements  

1from __future__ import annotations 

2 

3import re 

4import typing as t 

5from datetime import datetime 

6 

7from .._internal import _dt_as_utc 

8from ..datastructures.etag import ETag 

9from ..http import _generate_etag 

10from ..http import parse_date 

11 

12 

13def _is_resource_modified( 

14 http_range: str | None = None, 

15 http_if_range: str | None = None, 

16 http_if_modified_since: str | None = None, 

17 http_if_none_match: str | None = None, 

18 http_if_match: str | None = None, 

19 etag: str | None = None, 

20 data: bytes | None = None, 

21 last_modified: datetime | str | None = None, 

22 ignore_if_range: bool = True, 

23) -> bool: 

24 """Convenience method for conditional requests. 

25 :param http_range: Range HTTP header 

26 :param http_if_range: If-Range HTTP header 

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

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

29 :param http_if_match: If-Match HTTP header 

30 :param etag: the ETag for the response for comparison. 

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

32 generate an ETag using :func:`generate_etag`. 

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

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

35 account. 

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

37 

38 .. deprecated:: 3.2 

39 Will be removed in Werkzeug 4.0. ``Response.apply_conditions`` and 

40 ``apply_range`` perform their own checks. 

41 

42 .. versionadded:: 2.2 

43 """ 

44 if etag is None and data is not None: 

45 etag = f'"{_generate_etag(data)}"' 

46 elif data is not None: 

47 raise TypeError("Only one of 'data' or 'etag` may be given.") 

48 

49 unmodified = False 

50 if isinstance(last_modified, str): 

51 last_modified = parse_date(last_modified) 

52 

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

54 # comparisons. Mark naive datetimes as UTC. 

55 if last_modified is not None: 

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

57 

58 if_range = None 

59 if not ignore_if_range and http_range is not None: 

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

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

62 # that does not contain a Range header field. 

63 if_range = ds.IfRange.from_header(http_if_range) 

64 

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

66 modified_since: datetime | None = if_range.date 

67 else: 

68 modified_since = parse_date(http_if_modified_since) 

69 

70 if modified_since and last_modified and last_modified <= modified_since: 

71 unmodified = True 

72 

73 if etag: 

74 if (parsed_etag := ETag.from_header(etag)) is None: 

75 unmodified = False 

76 elif if_range is not None and if_range.etag is not None: 

77 unmodified = if_range.etag == parsed_etag.value 

78 else: 

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

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

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

82 if if_none_match := ds.ETagSet.from_header(http_if_none_match): 

83 unmodified = if_none_match.contains_weak(parsed_etag.value) 

84 

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

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

87 # comparing entity-tags for If-Match" 

88 if if_match := ds.ETagSet.from_header(http_if_match): 

89 unmodified = not if_match.contains_strong(parsed_etag.value) 

90 

91 return not unmodified 

92 

93 

94_cookie_re = re.compile( 

95 r""" 

96 [ \t]* # ignore leading space 

97 ([^ \t=";]+) # key 

98 (?:[ \t]*=[ \t]* # optional =value, ignoring invalid space 

99 ( 

100 "(?:[^\\"]|\\.)*" # quoted value with backslash escapes 

101 | 

102 [^ \t";]* # token value 

103 ) 

104 )? 

105 [ \t]* # ignore trailing space 

106 (?:;|\Z) # only if followed by semicolon or end 

107 """, 

108 flags=re.ASCII | re.VERBOSE, 

109) 

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

111 

112 

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

114 v = m.group(1) 

115 

116 if len(v) == 1: 

117 return v 

118 

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

120 

121 

122def parse_cookie( 

123 cookie: str | None = None, **kwargs: t.Any 

124) -> ds.ImmutableMultiDict[str, str]: 

125 """Parse cookies from a ``Cookie`` header as an :class:`.ImmutableMultiDict`. 

126 

127 :param cookie: The ``Cookie`` header. 

128 

129 .. versionchanged:: 3.2 

130 The ``cls`` parameter is deprecated and will be removed in Werkzeug 4.0. 

131 It will always be ``ImmutableMultiDict``. 

132 

133 .. versionchanged:: 3.0 

134 Passing bytes, and the ``charset`` and ``errors`` parameters, were removed. 

135 

136 .. versionadded:: 2.2 

137 """ 

138 if "cls" in kwargs: 

139 import warnings 

140 

141 warnings.warn( 

142 "The 'cls' parameter is deprecated and will be removed in Werkzeug 4.0." 

143 " It will always be 'ImmutableMultiDict'.", 

144 DeprecationWarning, 

145 stacklevel=2, 

146 ) 

147 

148 cls: type[ds.ImmutableMultiDict[str, str]] = kwargs.get( 

149 "cls", ds.ImmutableMultiDict 

150 ) 

151 

152 if not cookie: 

153 return cls() 

154 

155 out = [] 

156 pos = 0 

157 

158 while True: 

159 if (m := _cookie_re.match(cookie, pos)) is None: 

160 # Skip invalid chars until the next semicolon. 

161 if (pos := cookie.find(";", pos) + 1) == 0: 

162 break 

163 

164 continue 

165 

166 ck, cv = m.groups() 

167 pos = m.end() 

168 

169 if cv is None: 

170 cv = "" 

171 

172 if cv.startswith('"') and cv.endswith('"'): 

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

174 cv = _cookie_unslash_re.sub( 

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

176 ).decode(errors="replace") 

177 

178 out.append((ck, cv)) 

179 

180 return cls(out) 

181 

182 

183# circular dependencies 

184from .. import datastructures as ds # noqa: E402 

185 

186if not t.TYPE_CHECKING: 

187 

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

189 import warnings 

190 

191 if name == "is_resource_modified": 

192 warnings.warn( 

193 "'is_resource_modified' is deprecated and will be removed in" 

194 " Werkzeug 4.0. 'Response.apply_conditions' and 'apply_range'" 

195 " perform their own checks.", 

196 DeprecationWarning, 

197 stacklevel=2, 

198 ) 

199 return _is_resource_modified 

200 

201 raise AttributeError(name)