-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
gh-143916: Allow HTAB in wsgiref header values #144118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,7 +9,11 @@ | |||||||||
| # existence of which force quoting of the parameter value. | ||||||||||
| import re | ||||||||||
| tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') | ||||||||||
| _control_chars_re = re.compile(r'[\x00-\x1F\x7F]') | ||||||||||
| # Disallowed characters for headers and values. | ||||||||||
| # HTAB (\x09) is allowed in header values, but | ||||||||||
| # not in header names. (RFC 9110 Section 5.5) | ||||||||||
| _name_disallowed_re = re.compile(r'[\x00-\x1F\x7F]') | ||||||||||
| _value_disallowed_re = re.compile(r'[\x00-\x08\x0A-\x1F\x7F]') | ||||||||||
|
|
||||||||||
| def _formatparam(param, value=None, quote=1): | ||||||||||
| """Convenience function to format and return a key=value pair. | ||||||||||
|
|
@@ -36,13 +40,13 @@ def __init__(self, headers=None): | |||||||||
| self._headers = headers | ||||||||||
| if __debug__: | ||||||||||
| for k, v in headers: | ||||||||||
| self._convert_string_type(k) | ||||||||||
| self._convert_string_type(k, name=True) | ||||||||||
| self._convert_string_type(v) | ||||||||||
|
|
||||||||||
| def _convert_string_type(self, value): | ||||||||||
| def _convert_string_type(self, value, *, name=False): | ||||||||||
|
sethmlarson marked this conversation as resolved.
Outdated
|
||||||||||
| """Convert/check value type.""" | ||||||||||
| if type(value) is str: | ||||||||||
| if _control_chars_re.search(value): | ||||||||||
| if (_name_disallowed_re if name else _value_disallowed_re).search(value): | ||||||||||
|
sethmlarson marked this conversation as resolved.
Outdated
|
||||||||||
| raise ValueError("Control characters not allowed in headers") | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't get what are values and statuses. I prefer to just say "headers".
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi, sorry for the confusion, I was missing one word: I wanted to say "in header names, values and statuses" because there seems to be a differenciation between values and statuses and names (see in my PR) and that's why I added this to the error message and now I thought that this is now more consistent as it's kind of the same error message. And there's a difference in the handling between header names and values (see in the issue linked here), but I think that for the short error message it's not required to go into detail with the exact allowed and disallowed characters. But thanks for the information that I missed one word, hope that this is clear now! Thanks!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| return value | ||||||||||
| raise AssertionError("Header names/values must be" | ||||||||||
|
|
@@ -56,14 +60,14 @@ def __setitem__(self, name, val): | |||||||||
| """Set the value of a header.""" | ||||||||||
| del self[name] | ||||||||||
| self._headers.append( | ||||||||||
| (self._convert_string_type(name), self._convert_string_type(val))) | ||||||||||
| (self._convert_string_type(name, name=True), self._convert_string_type(val))) | ||||||||||
|
|
||||||||||
| def __delitem__(self,name): | ||||||||||
| """Delete all occurrences of a header, if present. | ||||||||||
|
|
||||||||||
| Does *not* raise an exception if the header is missing. | ||||||||||
| """ | ||||||||||
| name = self._convert_string_type(name.lower()) | ||||||||||
| name = self._convert_string_type(name.lower(), name=True) | ||||||||||
| self._headers[:] = [kv for kv in self._headers if kv[0].lower() != name] | ||||||||||
|
|
||||||||||
| def __getitem__(self,name): | ||||||||||
|
|
@@ -90,13 +94,13 @@ def get_all(self, name): | |||||||||
| fields deleted and re-inserted are always appended to the header list. | ||||||||||
| If no fields exist with the given name, returns an empty list. | ||||||||||
| """ | ||||||||||
| name = self._convert_string_type(name.lower()) | ||||||||||
| name = self._convert_string_type(name.lower(), name=True) | ||||||||||
| return [kv[1] for kv in self._headers if kv[0].lower()==name] | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def get(self,name,default=None): | ||||||||||
| """Get the first header value for 'name', or return 'default'""" | ||||||||||
| name = self._convert_string_type(name.lower()) | ||||||||||
| name = self._convert_string_type(name.lower(), name=True) | ||||||||||
| for k,v in self._headers: | ||||||||||
| if k.lower()==name: | ||||||||||
| return v | ||||||||||
|
|
@@ -151,7 +155,7 @@ def setdefault(self,name,value): | |||||||||
| and value 'value'.""" | ||||||||||
| result = self.get(name) | ||||||||||
| if result is None: | ||||||||||
| self._headers.append((self._convert_string_type(name), | ||||||||||
| self._headers.append((self._convert_string_type(name, name=True), | ||||||||||
| self._convert_string_type(value))) | ||||||||||
| return value | ||||||||||
| else: | ||||||||||
|
|
@@ -178,10 +182,10 @@ def add_header(self, _name, _value, **_params): | |||||||||
| _value = self._convert_string_type(_value) | ||||||||||
| parts.append(_value) | ||||||||||
| for k, v in _params.items(): | ||||||||||
| k = self._convert_string_type(k) | ||||||||||
| k = self._convert_string_type(k, name=True) | ||||||||||
| if v is None: | ||||||||||
| parts.append(k.replace('_', '-')) | ||||||||||
| else: | ||||||||||
| v = self._convert_string_type(v) | ||||||||||
| parts.append(_formatparam(k.replace('_', '-'), v)) | ||||||||||
| self._headers.append((self._convert_string_type(_name), "; ".join(parts))) | ||||||||||
| self._headers.append((self._convert_string_type(_name, name=True), "; ".join(parts))) | ||||||||||
Uh oh!
There was an error while loading. Please reload this page.