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

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

62 statements  

1from __future__ import annotations 

2 

3import re 

4import typing as t 

5from datetime import datetime 

6 

7from .._internal import _dt_as_utc 

8from ..http import generate_etag 

9from ..http import parse_date 

10from ..http import unquote_etag 

11 

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

13 

14 

15def is_resource_modified( 

16 http_range: str | None = None, 

17 http_if_range: str | None = None, 

18 http_if_modified_since: str | None = None, 

19 http_if_none_match: str | None = None, 

20 http_if_match: str | None = None, 

21 etag: str | None = None, 

22 data: bytes | None = None, 

23 last_modified: datetime | str | None = None, 

24 ignore_if_range: bool = True, 

25) -> bool: 

26 """Convenience method for conditional requests. 

27 :param http_range: Range HTTP header 

28 :param http_if_range: If-Range HTTP header 

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

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

31 :param http_if_match: If-Match HTTP header 

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

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

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

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

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

37 account. 

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

39 

40 .. versionadded:: 2.2 

41 """ 

42 if etag is None and data is not None: 

43 etag = generate_etag(data) 

44 elif data is not None: 

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

46 

47 unmodified = False 

48 if isinstance(last_modified, str): 

49 last_modified = parse_date(last_modified) 

50 

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

52 # comparisons. Mark naive datetimes as UTC. 

53 if last_modified is not None: 

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

55 

56 if_range = None 

57 if not ignore_if_range and http_range is not None: 

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

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

60 # that does not contain a Range header field. 

61 if_range = ds.IfRange.from_header(http_if_range) 

62 

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

64 modified_since: datetime | None = if_range.date 

65 else: 

66 modified_since = parse_date(http_if_modified_since) 

67 

68 if modified_since and last_modified and last_modified <= modified_since: 

69 unmodified = True 

70 

71 if etag: 

72 etag, _ = unquote_etag(etag) 

73 

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

75 unmodified = ds.ETags.from_header(if_range.etag).contains(etag) 

76 else: 

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

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

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

80 if if_none_match := ds.ETags.from_header(http_if_none_match): 

81 unmodified = if_none_match.contains_weak(etag) 

82 

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

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

85 # comparing entity-tags for If-Match" 

86 if if_match := ds.ETags.from_header(http_if_match): 

87 unmodified = not if_match.is_strong(etag) 

88 

89 return not unmodified 

90 

91 

92_cookie_re = re.compile( 

93 r""" 

94 ([^=;]*) 

95 (?:\s*=\s* 

96 ( 

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

98 | 

99 .*? 

100 ) 

101 )? 

102 \s*;\s* 

103 """, 

104 flags=re.ASCII | re.VERBOSE, 

105) 

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

107 

108 

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

110 v = m.group(1) 

111 

112 if len(v) == 1: 

113 return v 

114 

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

116 

117 

118def parse_cookie( 

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

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

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

122 

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

124 

125 .. versionchanged:: 3.2 

126 The ``cls`` parameter is deprecated and will be removed in Werkzeug 3.3. 

127 It will always be ``ImmutableMultiDict``. 

128 

129 .. versionchanged:: 3.0 

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

131 

132 .. versionadded:: 2.2 

133 """ 

134 if "cls" in kwargs: 

135 import warnings 

136 

137 warnings.warn( 

138 "The 'cls' parameter is deprecated and will be removed in Werkzeug 3.3." 

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

140 DeprecationWarning, 

141 stacklevel=2, 

142 ) 

143 

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

145 "cls", ds.ImmutableMultiDict 

146 ) 

147 

148 if not cookie: 

149 return cls() 

150 

151 cookie = f"{cookie};" 

152 out = [] 

153 

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

155 ck = ck.strip() 

156 cv = cv.strip() 

157 

158 if not ck: 

159 continue 

160 

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

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

163 cv = _cookie_unslash_re.sub( 

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

165 ).decode(errors="replace") 

166 

167 out.append((ck, cv)) 

168 

169 return cls(out) 

170 

171 

172# circular dependencies 

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