Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/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

65 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 parse_etags 

11from ..http import parse_if_range_header 

12from ..http import unquote_etag 

13 

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

15 

16 

17def is_resource_modified( 

18 http_range: str | None = None, 

19 http_if_range: str | None = None, 

20 http_if_modified_since: str | None = None, 

21 http_if_none_match: str | None = None, 

22 http_if_match: str | None = None, 

23 etag: str | None = None, 

24 data: bytes | None = None, 

25 last_modified: datetime | str | None = None, 

26 ignore_if_range: bool = True, 

27) -> bool: 

28 """Convenience method for conditional requests. 

29 :param http_range: Range HTTP header 

30 :param http_if_range: If-Range HTTP header 

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

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

33 :param http_if_match: If-Match HTTP header 

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

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

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

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

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

39 account. 

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

41 

42 .. versionadded:: 2.2 

43 """ 

44 if etag is None and data is not None: 

45 etag = generate_etag(data) 

46 elif data is not None: 

47 raise TypeError("both data and etag 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 = parse_if_range_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 etag, _ = unquote_etag(etag) 

75 etag = t.cast(str, etag) 

76 

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

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

79 else: 

80 if_none_match = parse_etags(http_if_none_match) 

81 if if_none_match: 

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

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

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

85 unmodified = if_none_match.contains_weak(etag) 

86 

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

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

89 # comparing entity-tags for If-Match" 

90 if_match = parse_etags(http_if_match) 

91 if if_match: 

92 unmodified = not if_match.is_strong(etag) 

93 

94 return not unmodified 

95 

96 

97_cookie_re = re.compile( 

98 r""" 

99 ([^=;]*) 

100 (?:\s*=\s* 

101 ( 

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

103 | 

104 .*? 

105 ) 

106 )? 

107 \s*;\s* 

108 """, 

109 flags=re.ASCII | re.VERBOSE, 

110) 

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

112 

113 

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

115 v = m.group(1) 

116 

117 if len(v) == 1: 

118 return v 

119 

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

121 

122 

123def parse_cookie( 

124 cookie: str | None = None, 

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

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

127 """Parse a cookie from a string. 

128 

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

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

131 first, and all values can be retrieved with 

132 :meth:`MultiDict.getlist`. 

133 

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

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

136 Defaults to :class:`MultiDict`. 

137 

138 .. versionchanged:: 3.0 

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

140 

141 .. versionadded:: 2.2 

142 """ 

143 if cls is None: 

144 cls = t.cast("type[ds.MultiDict[str, str]]", ds.MultiDict) 

145 

146 if not cookie: 

147 return cls() 

148 

149 cookie = f"{cookie};" 

150 out = [] 

151 

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

153 ck = ck.strip() 

154 cv = cv.strip() 

155 

156 if not ck: 

157 continue 

158 

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

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

161 cv = _cookie_unslash_re.sub( 

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

163 ).decode(errors="replace") 

164 

165 out.append((ck, cv)) 

166 

167 return cls(out) 

168 

169 

170# circular dependencies 

171from .. import datastructures as ds