]>
Commit | Line | Data |
---|---|---|
e0df8241 JR |
1 | from __future__ import annotations |
2 | ||
3 | ||
4 | class IfRange: | |
5 | """Very simple object that represents the `If-Range` header in parsed | |
6 | form. It will either have neither a etag or date or one of either but | |
7 | never both. | |
8 | ||
9 | .. versionadded:: 0.7 | |
10 | """ | |
11 | ||
12 | def __init__(self, etag=None, date=None): | |
13 | #: The etag parsed and unquoted. Ranges always operate on strong | |
14 | #: etags so the weakness information is not necessary. | |
15 | self.etag = etag | |
16 | #: The date in parsed format or `None`. | |
17 | self.date = date | |
18 | ||
19 | def to_header(self): | |
20 | """Converts the object back into an HTTP header.""" | |
21 | if self.date is not None: | |
22 | return http.http_date(self.date) | |
23 | if self.etag is not None: | |
24 | return http.quote_etag(self.etag) | |
25 | return "" | |
26 | ||
27 | def __str__(self): | |
28 | return self.to_header() | |
29 | ||
30 | def __repr__(self): | |
31 | return f"<{type(self).__name__} {str(self)!r}>" | |
32 | ||
33 | ||
34 | class Range: | |
35 | """Represents a ``Range`` header. All methods only support only | |
36 | bytes as the unit. Stores a list of ranges if given, but the methods | |
37 | only work if only one range is provided. | |
38 | ||
39 | :raise ValueError: If the ranges provided are invalid. | |
40 | ||
41 | .. versionchanged:: 0.15 | |
42 | The ranges passed in are validated. | |
43 | ||
44 | .. versionadded:: 0.7 | |
45 | """ | |
46 | ||
47 | def __init__(self, units, ranges): | |
48 | #: The units of this range. Usually "bytes". | |
49 | self.units = units | |
50 | #: A list of ``(begin, end)`` tuples for the range header provided. | |
51 | #: The ranges are non-inclusive. | |
52 | self.ranges = ranges | |
53 | ||
54 | for start, end in ranges: | |
55 | if start is None or (end is not None and (start < 0 or start >= end)): | |
56 | raise ValueError(f"{(start, end)} is not a valid range.") | |
57 | ||
58 | def range_for_length(self, length): | |
59 | """If the range is for bytes, the length is not None and there is | |
60 | exactly one range and it is satisfiable it returns a ``(start, stop)`` | |
61 | tuple, otherwise `None`. | |
62 | """ | |
63 | if self.units != "bytes" or length is None or len(self.ranges) != 1: | |
64 | return None | |
65 | start, end = self.ranges[0] | |
66 | if end is None: | |
67 | end = length | |
68 | if start < 0: | |
69 | start += length | |
70 | if http.is_byte_range_valid(start, end, length): | |
71 | return start, min(end, length) | |
72 | return None | |
73 | ||
74 | def make_content_range(self, length): | |
75 | """Creates a :class:`~werkzeug.datastructures.ContentRange` object | |
76 | from the current range and given content length. | |
77 | """ | |
78 | rng = self.range_for_length(length) | |
79 | if rng is not None: | |
80 | return ContentRange(self.units, rng[0], rng[1], length) | |
81 | return None | |
82 | ||
83 | def to_header(self): | |
84 | """Converts the object back into an HTTP header.""" | |
85 | ranges = [] | |
86 | for begin, end in self.ranges: | |
87 | if end is None: | |
88 | ranges.append(f"{begin}-" if begin >= 0 else str(begin)) | |
89 | else: | |
90 | ranges.append(f"{begin}-{end - 1}") | |
91 | return f"{self.units}={','.join(ranges)}" | |
92 | ||
93 | def to_content_range_header(self, length): | |
94 | """Converts the object into `Content-Range` HTTP header, | |
95 | based on given length | |
96 | """ | |
97 | range = self.range_for_length(length) | |
98 | if range is not None: | |
99 | return f"{self.units} {range[0]}-{range[1] - 1}/{length}" | |
100 | return None | |
101 | ||
102 | def __str__(self): | |
103 | return self.to_header() | |
104 | ||
105 | def __repr__(self): | |
106 | return f"<{type(self).__name__} {str(self)!r}>" | |
107 | ||
108 | ||
109 | def _callback_property(name): | |
110 | def fget(self): | |
111 | return getattr(self, name) | |
112 | ||
113 | def fset(self, value): | |
114 | setattr(self, name, value) | |
115 | if self.on_update is not None: | |
116 | self.on_update(self) | |
117 | ||
118 | return property(fget, fset) | |
119 | ||
120 | ||
121 | class ContentRange: | |
122 | """Represents the content range header. | |
123 | ||
124 | .. versionadded:: 0.7 | |
125 | """ | |
126 | ||
127 | def __init__(self, units, start, stop, length=None, on_update=None): | |
128 | assert http.is_byte_range_valid(start, stop, length), "Bad range provided" | |
129 | self.on_update = on_update | |
130 | self.set(start, stop, length, units) | |
131 | ||
132 | #: The units to use, usually "bytes" | |
133 | units = _callback_property("_units") | |
134 | #: The start point of the range or `None`. | |
135 | start = _callback_property("_start") | |
136 | #: The stop point of the range (non-inclusive) or `None`. Can only be | |
137 | #: `None` if also start is `None`. | |
138 | stop = _callback_property("_stop") | |
139 | #: The length of the range or `None`. | |
140 | length = _callback_property("_length") | |
141 | ||
142 | def set(self, start, stop, length=None, units="bytes"): | |
143 | """Simple method to update the ranges.""" | |
144 | assert http.is_byte_range_valid(start, stop, length), "Bad range provided" | |
145 | self._units = units | |
146 | self._start = start | |
147 | self._stop = stop | |
148 | self._length = length | |
149 | if self.on_update is not None: | |
150 | self.on_update(self) | |
151 | ||
152 | def unset(self): | |
153 | """Sets the units to `None` which indicates that the header should | |
154 | no longer be used. | |
155 | """ | |
156 | self.set(None, None, units=None) | |
157 | ||
158 | def to_header(self): | |
159 | if self.units is None: | |
160 | return "" | |
161 | if self.length is None: | |
162 | length = "*" | |
163 | else: | |
164 | length = self.length | |
165 | if self.start is None: | |
166 | return f"{self.units} */{length}" | |
167 | return f"{self.units} {self.start}-{self.stop - 1}/{length}" | |
168 | ||
169 | def __bool__(self): | |
170 | return self.units is not None | |
171 | ||
172 | def __str__(self): | |
173 | return self.to_header() | |
174 | ||
175 | def __repr__(self): | |
176 | return f"<{type(self).__name__} {str(self)!r}>" | |
177 | ||
178 | ||
179 | # circular dependencies | |
180 | from .. import http |