{
"command": "ls -la /app && git -C /app status && git -C /app diff --stat",
"description": "List repo files and git status"
}total 212
drwxr-xr-x 1 root root 31 Oct 28 2025 .
drwxr-xr-x 1 root root 144 Sep 26 02:13 ..
drwxr-xr-x 8 root root 4096 Oct 28 2025 .git
drwxr-xr-x 3 root root 54 Oct 28 2025 .github
-rw-r--r-- 1 root root 188 Oct 28 2025 .gitignore
-rw-r--r-- 1 root root 634 Oct 28 2025 .readthedocs.yaml
-rw-r--r-- 1 root root 1328 Oct 28 2025 AUTHORS
-rw-r--r-- 1 root root 1066 Oct 28 2025 LICENSE
-rw-r--r-- 1 root root 1112 Oct 28 2025 Makefile
-rw-r--r-- 1 root root 2846 Oct 28 2025 README.rst
-rwxr-xr-x 1 root root 175565 Oct 28 2025 bottle.py
drwxr-xr-x 4 root root 4096 Oct 28 2025 docs
-rw-r--r-- 1 root root 1741 Oct 28 2025 pyproject.toml
drwxr-xr-x 3 root root 4096 Oct 28 2025 test
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: bottle.py
no changes added to commit (use "git add" and/or "git commit -a")
bottle.py | 4 ----
1 file changed, 4 deletions(-)
[stdout]
total 212
drwxr-xr-x 1 root root 31 Oct 28 2025 .
drwxr-xr-x 1 root root 144 Sep 26 02:13 ..
drwxr-xr-x 8 root root 4096 Oct 28 2025 .git
drwxr-xr-x 3 root root 54 Oct 28 2025 .github
-rw-r--r-- 1 root root 188 Oct 28 2025 .gitignore
-rw-r--r-- 1 root root 634 Oct 28 2025 .readthedocs.yaml
-rw-r--r-- 1 root root 1328 Oct 28 2025 AUTHORS
-rw-r--r-- 1 root root 1066 Oct 28 2025 LICENSE
-rw-r--r-- 1 root root 1112 Oct 28 2025 Makefile
-rw-r--r-- 1 root root 2846 Oct 28 2025 README.rst
-rwxr-xr-x 1 root root 175565 Oct 28 2025 bottle.py
drwxr-xr-x 4 root root 4096 Oct 28 2025 docs
-rw-r--r-- 1 root root 1741 Oct 28 2025 pyproject.toml
drwxr-xr-x 3 root root 4096 Oct 28 2025 test
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: bottle.py
no changes added to commit (use "git add" and/or "git commit -a")
bottle.py | 4 ----
1 file changed, 4 deletions(-)
[metadata] {"noOutputExpected": false}{
"command": "wc -l /app/bottle.py && ls /app/test* 2>/dev/null; ls /app/test 2>/dev/null",
"description": "Check bottle.py size and tests"
}4576 /app/bottle.py
__init__.py
build_python.sh
example_settings.py
test_app.py
test_auth.py
test_config.py
test_contextlocals.py
test_environ.py
test_exc.py
test_fileupload.py
test_formsdict.py
test_html_helper.py
test_importhook.py
test_jinja2.py
test_mako.py
test_mdict.py
test_mount.py
test_multipart.py
test_oorouting.py
test_outputfilter.py
test_plugins.py
test_resources.py
test_route.py
test_router.py
test_securecookies.py
test_sendfile.py
test_stpl.py
test_wsgi.py
tools.py
views
__init__.py
build_python.sh
example_settings.py
test_app.py
test_auth.py
test_config.py
test_contextlocals.py
test_environ.py
test_exc.py
test_fileupload.py
test_formsdict.py
test_html_helper.py
test_importhook.py
test_jinja2.py
test_mako.py
test_mdict.py
test_mount.py
test_multipart.py
test_oorouting.py
test_outputfilter.py
test_plugins.py
test_resources.py
test_route.py
test_router.py
test_securecookies.py
test_sendfile.py
test_stpl.py
test_wsgi.py
tools.py
views
[stdout]
4576 /app/bottle.py
__init__.py
build_python.sh
example_settings.py
test_app.py
test_auth.py
test_config.py
test_contextlocals.py
test_environ.py
test_exc.py
test_fileupload.py
test_formsdict.py
test_html_helper.py
test_importhook.py
test_jinja2.py
test_mako.py
test_mdict.py
test_mount.py
test_multipart.py
test_oorouting.py
test_outputfilter.py
test_plugins.py
test_resources.py
test_route.py
test_router.py
test_securecookies.py
test_sendfile.py
test_stpl.py
test_wsgi.py
tools.py
views
__init__.py
build_python.sh
example_settings.py
test_app.py
test_auth.py
test_config.py
test_contextlocals.py
test_environ.py
test_exc.py
test_fileupload.py
test_formsdict.py
test_html_helper.py
test_importhook.py
test_jinja2.py
test_mako.py
test_mdict.py
test_mount.py
test_multipart.py
test_oorouting.py
test_outputfilter.py
test_plugins.py
test_resources.py
test_route.py
test_router.py
test_securecookies.py
test_sendfile.py
test_stpl.py
test_wsgi.py
tools.py
views
[metadata] {"noOutputExpected": false}{
"command": "git -C /app diff",
"description": "Show the uncommitted changes to bottle.py"
}diff --git a/bottle.py b/bottle.py
index a297ca9..d1e4022 100755
--- a/bottle.py
+++ b/bottle.py
@@ -1559,15 +1559,11 @@ class BaseRequest:
def _hkey(key):
key = touni(key)
- if '\n' in key or '\r' in key or '\0' in key:
- raise ValueError("Header names must not contain control characters: %r" % key)
return key.title().replace('_', '-')
def _hval(value):
value = touni(value)
- if '\n' in value or '\r' in value or '\0' in value:
- raise ValueError("Header value must not contain control characters: %r" % value)
return value
[stdout]
diff --git a/bottle.py b/bottle.py
index a297ca9..d1e4022 100755
--- a/bottle.py
+++ b/bottle.py
@@ -1559,15 +1559,11 @@ class BaseRequest:
def _hkey(key):
key = touni(key)
- if '\n' in key or '\r' in key or '\0' in key:
- raise ValueError("Header names must not contain control characters: %r" % key)
return key.title().replace('_', '-')
def _hval(value):
value = touni(value)
- if '\n' in value or '\r' in value or '\0' in value:
- raise ValueError("Header value must not contain control characters: %r" % value)
return value
[metadata] {"noOutputExpected": false}{
"command": "grep -n \"_hkey\\|_hval\\|control character\\|Header names\\|Header value\" /app/bottle.py /app/test/*.py | head -80",
"description": "Find _hkey/_hval usages and related tests"
}/app/bottle.py:1560:def _hkey(key):
/app/bottle.py:1565:def _hval(value):
/app/bottle.py:1699: return _hkey(name) in self._headers
/app/bottle.py:1702: del self._headers[_hkey(name)]
/app/bottle.py:1705: return self._headers[_hkey(name)][-1]
/app/bottle.py:1708: self._headers[_hkey(name)] = [_hval(value)]
/app/bottle.py:1713: return self._headers.get(_hkey(name), [default])[-1]
/app/bottle.py:1718: self._headers[_hkey(name)] = [_hval(value)]
/app/bottle.py:1722: self._headers.setdefault(_hkey(name), []).append(_hval(value))
/app/bottle.py:1746: out.append(('Set-Cookie', _hval(c.OutputString())))
/app/bottle.py:2177: return _hkey(key) in self.dict
/app/bottle.py:2180: del self.dict[_hkey(key)]
/app/bottle.py:2183: return self.dict[_hkey(key)][-1]
/app/bottle.py:2186: self.dict[_hkey(key)] = [_hval(value)]
/app/bottle.py:2189: self.dict.setdefault(_hkey(key), []).append(_hval(value))
/app/bottle.py:2192: self.dict[_hkey(key)] = [_hval(value)]
/app/bottle.py:2195: return self.dict.get(_hkey(key)) or []
/app/bottle.py:2198: return MultiDict.get(self, _hkey(key), default, index)
/app/bottle.py:2201: for name in (_hkey(n) for n in names):
/app/bottle.py:2208: access to HTTP_* fields. Header names are case-insensitive and titled by default.
/app/bottle.py:2239: yield _hkey(key[5:])
/app/bottle.py:2241: yield _hkey(key)
[stdout]
/app/bottle.py:1560:def _hkey(key):
/app/bottle.py:1565:def _hval(value):
/app/bottle.py:1699: return _hkey(name) in self._headers
/app/bottle.py:1702: del self._headers[_hkey(name)]
/app/bottle.py:1705: return self._headers[_hkey(name)][-1]
/app/bottle.py:1708: self._headers[_hkey(name)] = [_hval(value)]
/app/bottle.py:1713: return self._headers.get(_hkey(name), [default])[-1]
/app/bottle.py:1718: self._headers[_hkey(name)] = [_hval(value)]
/app/bottle.py:1722: self._headers.setdefault(_hkey(name), []).append(_hval(value))
/app/bottle.py:1746: out.append(('Set-Cookie', _hval(c.OutputString())))
/app/bottle.py:2177: return _hkey(key) in self.dict
/app/bottle.py:2180: del self.dict[_hkey(key)]
/app/bottle.py:2183: return self.dict[_hkey(key)][-1]
/app/bottle.py:2186: self.dict[_hkey(key)] = [_hval(value)]
/app/bottle.py:2189: self.dict.setdefault(_hkey(key), []).append(_hval(value))
/app/bottle.py:2192: self.dict[_hkey(key)] = [_hval(value)]
/app/bottle.py:2195: return self.dict.get(_hkey(key)) or []
/app/bottle.py:2198: return MultiDict.get(self, _hkey(key), default, index)
/app/bottle.py:2201: for name in (_hkey(n) for n in names):
/app/bottle.py:2208: access to HTTP_* fields. Header names are case-insensitive and titled by default.
/app/bottle.py:2239: yield _hkey(key[5:])
/app/bottle.py:2241: yield _hkey(key)
[metadata] {"noOutputExpected": false}{
"command": "grep -rn \"crlf\\|CRLF\\|\\\\\\\\r\\\\\\\\n\\|header inject\\|ValueError\" /app/test/*.py | head -60",
"description": "Search tests for header injection cases"
}/app/test/test_router.py:58: def testValueErrorInFilter(self):
/app/test/test_router.py:129: self.assertRaises(ValueError, build, 'introute', 'hello')
/app/test/tools.py:161: body += boundary + '\r\n'
/app/test/tools.py:162: body += 'Content-Disposition: form-data; name="%s"\r\n\r\n' % name
/app/test/tools.py:163: body += value + '\r\n'
/app/test/tools.py:166: body += boundary + '\r\n'
/app/test/tools.py:167: body += 'Content-Disposition: file; name="%s"; filename="%s"\r\n' % \
/app/test/tools.py:169: body += 'Content-Type: %s\r\n\r\n' % mimetype
/app/test/tools.py:170: body += content + '\r\n'
/app/test/tools.py:171: body += boundary + '--\r\n'
/app/test/test_config.py:66: self.assertRaises(ValueError, lambda: c.update(int='not an int'))
/app/test/test_stpl.py:61: self.assertEqual('"<' 	"\\>"', html_quote('<\'\r\n\t"\\>'));
/app/test/test_stpl.py:191: self.assertRenders('%var+=1\r\n{{var}}\r\n', '6\r\n', var=5)
/app/test/test_stpl.py:196: self.assertRenders('%for i in test:\n{{i}}\r\n%end\n', '1\r\n2\r\n3\r\n', **d)
/app/test/test_stpl.py:197: self.assertRenders('%for i in test:\r\n{{i}}\n%end\r\n', '1\n2\n3\n', **d)
/app/test/test_stpl.py:198: self.assertRenders('%for i in test:\r\n{{i}}\r\n%end\r\n', '1\r\n2\r\n3\r\n', **d)
/app/test/test_stpl.py:273: source_wineol = '<% a = 5\r\nb = 6\r\nc = 7\r\n%>\r\n{{a+b+c}}'
/app/test/test_environ.py:298: self._test_chunked('1\r\nx\r\nff\r\n' + 'y'*255 + '\r\n0\r\n',
/app/test/test_environ.py:300: self._test_chunked('8\r\nxxxxxxxx\r\n0\r\n','xxxxxxxx')
/app/test/test_environ.py:301: self._test_chunked('0\r\n', '')
/app/test/test_environ.py:304: self._test_chunked('8 ; foo\r\nxxxxxxxx\r\n0\r\n','xxxxxxxx')
/app/test/test_environ.py:305: self._test_chunked('8;foo\r\nxxxxxxxx\r\n0\r\n','xxxxxxxx')
/app/test/test_environ.py:306: self._test_chunked('8;foo=bar\r\nxxxxxxxx\r\n0\r\n','xxxxxxxx')
/app/test/test_environ.py:309: self._test_chunked('1\r\nx\r\n', HTTPError)
/app/test/test_environ.py:312: self._test_chunked('2\r\nx\r\n', HTTPError)
/app/test/test_environ.py:315: self._test_chunked('x\r\nx\r\n', HTTPError)
/app/test/test_environ.py:531: self.assertRaises(ValueError, test)
/app/test/test_environ.py:537: self.assertRaises(ValueError, test)
/app/test/test_environ.py:558: self.assertRaises(ValueError, test)
/app/test/test_environ.py:564: self.assertRaises(ValueError, test)
/app/test/test_environ.py:570: self.assertRaises(ValueError, test)
/app/test/test_environ.py:576: self.assertRaises(ValueError, test)
/app/test/test_environ.py:613: self.assertRaises(ValueError, r.set_cookie, name='test', value='x' * 4097)
/app/test/test_environ.py:617: self.assertRaises(ValueError, r.set_cookie, name='x' * 4097, value='simple_value')
/app/test/test_environ.py:745: self.assertRaises(ValueError, func, value, "test-value")
/app/test/test_environ.py:746: self.assertRaises(ValueError, func, "test-name", value)
/app/test/test_environ.py:754: self.assertRaises(ValueError, func, value, "test-value")
/app/test/test_environ.py:755: self.assertRaises(ValueError, func, "test-name", value)
/app/test/test_multipart.py:75: self.assertIterline('abc\ndef\r\nghi', ('abc\ndef','\r\n'), ('ghi', ''))
/app/test/test_multipart.py:78: self.assertIterline('abc\ndef\r\nghi', ('abc\ndef','\r\n'), ('g', ''), content_length=10)
/app/test/test_multipart.py:79: self.assertIterline('abc\ndef\r\nghi', ('abc\ndef\r',''), content_length=8)
/app/test/test_multipart.py:83: minbuflen = 9 # boundary size of '--foo--\r\n'
/app/test/test_multipart.py:84: data = b'data\rdata\ndata\r\ndata\n\rdata\r\n'.replace(b'data', b'X'*minbuflen*2)
/app/test/test_multipart.py:85: lines = data.split(b"\r\n")[:-1]
/app/test/test_multipart.py:94: self.assertTrue(nl in (b"", b"\r\n"))
/app/test/test_multipart.py:108: request = BytesIO(bottle.tob('\r\n').join(map(bottle.tob,[
/app/test/test_multipart.py:133: request = BytesIO(bottle.tob('\r\n').join(map(bottle.tob,[
/app/test/test_multipart.py:145: request = BytesIO(bottle.tob('\r\n').join(map(bottle.tob,[
/app/test/test_multipart.py:157: test_text = u'Test text\n with\r\n ümläuts!'
/app/test/test_multipart.py:159: request = BytesIO(bottle.tob('\r\n').join(map(bottle.tob,[
/app/test/test_multipart.py:193: self.write('--foo\r\n',
/app/test/test_multipart.py:194: 'Content-Disposition: form-data; name="file1"; filename="random.png"\r\n',
/app/test/test_multipart.py:195: 'Content-Type: image/png\r\n', '\r\n', 'abc')
/app/test/test_multipart.py:199: self.write('--foo\r\n',
/app/test/test_multipart.py:200: 'Content-Disposition: form-data; name="file1"; filename="random.png"\r\n',
/app/test/test_multipart.py:201: 'Content-Type: image/png\r\n', '\r\n', 'abc', '--foo--')
/app/test/test_multipart.py:205: self.write('--foo\r\n',
/app/test/test_multipart.py:206: 'Content-Disposition: form-data; name="file1"; filename="random.png"\r\n',
/app/test/test_multipart.py:207: 'Content-Type: image/png\r\n', '\r\n', 'abc', '--foo\r\n'
/app/test/test_multipart.py:208: 'Content-Disposition: form-data; name="file2"; filename="random.png"\r\n',
[stdout]
/app/test/test_router.py:58: def testValueErrorInFilter(self):
/app/test/test_router.py:129: self.assertRaises(ValueError, build, 'introute', 'hello')
/app/test/tools.py:161: body += boundary + '\r\n'
/app/test/tools.py:162: body += 'Content-Disposition: form-data; name="%s"\r\n\r\n' % name
/app/test/tools.py:163: body += value + '\r\n'
/app/test/tools.py:166: body += bound{
"file_path": "/app/test/test_environ.py",
"offset": 700,
"limit": 80
}700 headers = [value for name, value in response.headerlist
701 if name.title() == 'X-Test']
702 self.assertEqual(['bar'], headers)
703 self.assertEqual('bar', response['x-test'])
704
705 def test_append_header(self):
706 response = BaseResponse()
707 response.set_header('x-test', 'foo')
708 headers = [value for name, value in response.headerlist
709 if name.title() == 'X-Test']
710 self.assertEqual(['foo'], headers)
711 self.assertEqual('foo', response['x-test'])
712
713 response.add_header('X-Test', 'bar')
714 headers = [value for name, value in response.headerlist
715 if name.title() == 'X-Test']
716 self.assertEqual(['foo', 'bar'], headers)
717 self.assertEqual('bar', response['x-test'])
718
719 def test_delete_header(self):
720 response = BaseResponse()
721 response['x-test'] = 'foo'
722 self.assertEqual('foo', response['x-test'])
723 del response['X-tESt']
724 self.assertRaises(KeyError, lambda: response['x-test'])
725
726 def test_non_string_header(self):
727 response = BaseResponse()
728 response['x-test'] = 5
729 self.assertEqual('5', response['x-test'])
730 response['x-test'] = None
731 self.assertEqual('', response['x-test'])
732 response['x-test'] = touni('瓶')
733 self.assertEqual(touni('瓶'), response['x-test'])
734
735 def test_prevent_control_characters_in_headers(self):
736 masks = '{}test', 'test{}', 'te{}st'
737 tests = '\n', '\r', '\n\r', '\0'
738
739 # Test HeaderDict
740 apis = 'append', 'replace', '__setitem__', 'setdefault'
741 for api, mask, test in itertools.product(apis, masks, tests):
742 hd = bottle.HeaderDict()
743 func = getattr(hd, api)
744 value = mask.replace("{}", test)
745 self.assertRaises(ValueError, func, value, "test-value")
746 self.assertRaises(ValueError, func, "test-name", value)
747
748 # Test functions on BaseResponse
749 apis = 'add_header', 'set_header', '__setitem__'
750 for api, mask, test in itertools.product(apis, masks, tests):
751 rs = bottle.BaseResponse()
752 func = getattr(rs, api)
753 value = mask.replace("{}", test)
754 self.assertRaises(ValueError, func, value, "test-value")
755 self.assertRaises(ValueError, func, "test-name", value)
756
757 def test_expires_header(self):
758 import datetime
759 from bottle import UTC
760 response = BaseResponse()
761 now = datetime.datetime.now(UTC)
762 response.expires = now
763
764 def seconds(a, b):
765 td = max(a,b) - min(a,b)
766 return td.days*360*24 + td.seconds
767
768 self.assertEqual(0, seconds(response.expires, now))
769 now2 = datetime.datetime.fromtimestamp(
770 parse_date(response.headers['Expires']), tz=UTC)
771 self.assertEqual(0, seconds(now, now2))
772
773
774 class TestRedirect(unittest.TestCase):
775
776 def assertRedirect(self, target, result, query=None, status=303, **args):
777 env = {'SERVER_PROTOCOL': 'HTTP/1.1'}
778 for key in list(args):
779 if key.startswith('wsgi'):
[metadata] {"type": "text", "file": {"filePath": "/app/test/test_environ.py", "content": " headers = [value for name, value in response.headerlist\n if name.title() == 'X-Test']\n self.assertEqual(['bar'], headers)\n self.assertEqual('bar', response['x-test'])\n\n def test_append_header(self):\n response = BaseResponse()\n response.set_header('x-test', 'foo')\n headers = [value for name, value in response.headerlist\n if name.title() == 'X-Test']\n self.assertEqual(['foo'], headers)\n self.assertEqual('foo', response['x-test'])\n\n response.add_header('X-Test', 'bar')\n headers = [value for name, value in response.headerlist\n if name.title() == 'X-Test']\n self.assertEqual(['foo', 'bar'], headers)\n self.assertEqual('bar', response['x-test'])\n\n def test_delete_header(self):\n response = BaseResponse()\n response['x-test'] = 'foo'\n self.assertEqual('foo', response['x-test'])\n del response['X-tESt']\n self.assertRaises(KeyError, lambda: response['x-test'])\n\n def test_non_string_header(self):\n response = BaseResponse()\n response['x-test'] = 5\n self.assertEqual('5', response['x-test'])\n response['x-test'] = None\n self.assertEqual('', response['x-test'])\n response['x-test'] = touni('瓶')\n self.assertEqual(touni('瓶'), response['x-test'])\n\n def test_prevent_control_characters_in_headers(self):\n masks = '{}test', 'test{}', 'te{}st'\n tests = '\\n', '\\r', '\\n\\r', '\\0'\n\n # Test HeaderDict\n apis = 'append', 'replace', '__setitem__', 'setdefault'\n for api, mask, test in itertools.product(apis, masks, tests):\n hd = bottle.HeaderDict()\n func = getattr(hd, api)\n value = mask.replace(\"{}\", test)\n self.assertRaises(ValueError, func, value, \"test-value\")\n self.assertRaises(ValueError, func, \"test-name\", value)\n\n # Test functions on BaseResponse\n apis = 'add_header', 'set_header', '__setitem__'\n for api, mask, test in itertools.product(apis, masks, tests):\n rs = bottle.BaseResponse()\n func = getattr(rs, api)\n value = mask.replace(\"{}\", test)\n self.assertRaises(ValueError, func, value, \"test-value\")\n self.assertRaises(ValueError, func, \"test-name\", value)\n\n def test_expires_header(sel{
"file_path": "/app/bottle.py",
"offset": 1545,
"limit": 40
}1545 def __setattr__(self, name, value):
1546 """ Define new attributes that are local to the bound request environment. """
1547 if name == 'environ': return object.__setattr__(self, name, value)
1548 key = 'bottle.request.ext.%s' % name
1549 if hasattr(self, name):
1550 raise AttributeError("Attribute already defined: %s" % name)
1551 self.environ[key] = value
1552
1553 def __delattr__(self, name):
1554 try:
1555 del self.environ['bottle.request.ext.%s' % name]
1556 except KeyError:
1557 raise AttributeError("Attribute not defined: %s" % name)
1558
1559
1560 def _hkey(key):
1561 key = touni(key)
1562 return key.title().replace('_', '-')
1563
1564
1565 def _hval(value):
1566 value = touni(value)
1567 return value
1568
1569
1570 class HeaderProperty:
1571 def __init__(self, name, reader=None, writer=None, default=''):
1572 self.name, self.default = name, default
1573 self.reader, self.writer = reader, writer
1574 self.__doc__ = 'Current value of the %r header.' % name.title()
1575
1576 def __get__(self, obj, _):
1577 if obj is None: return self
1578 value = obj.get_header(self.name, self.default)
1579 return self.reader(value) if self.reader else value
1580
1581 def __set__(self, obj, value):
1582 obj[self.name] = self.writer(value) if self.writer else value
1583
1584 def __delete__(self, obj):
[metadata] {"type": "text", "file": {"filePath": "/app/bottle.py", "content": " def __setattr__(self, name, value):\n \"\"\" Define new attributes that are local to the bound request environment. \"\"\"\n if name == 'environ': return object.__setattr__(self, name, value)\n key = 'bottle.request.ext.%s' % name\n if hasattr(self, name):\n raise AttributeError(\"Attribute already defined: %s\" % name)\n self.environ[key] = value\n\n def __delattr__(self, name):\n try:\n del self.environ['bottle.request.ext.%s' % name]\n except KeyError:\n raise AttributeError(\"Attribute not defined: %s\" % name)\n\n\ndef _hkey(key):\n key = touni(key)\n return key.title().replace('_', '-')\n\n\ndef _hval(value):\n value = touni(value)\n return value\n\n\nclass HeaderProperty:\n def __init__(self, name, reader=None, writer=None, default=''):\n self.name, self.default = name, default\n self.reader, self.writer = reader, writer\n self.__doc__ = 'Current value of the %r header.' % name.title()\n\n def __get__(self, obj, _):\n if obj is None: return self\n value = obj.get_header(self.name, self.default)\n return self.reader(value) if self.reader else value\n\n def __set__(self, obj, value):\n obj[self.name] = self.writer(value) if self.writer else value\n\n def __delete__(self, obj):", "numLines": 40, "startLine": 1545, "totalLines": 4577}}{
"command": "python -m pytest test/test_environ.py -k \"control or header\" -rA -q 2>&1 | tail -40",
"description": "Run header-related tests to confirm failures"
}..........F...... [100%]
=================================== FAILURES ===================================
___________ TestResponse.test_prevent_control_characters_in_headers ____________
self = <test.test_environ.TestResponse testMethod=test_prevent_control_characters_in_headers>
def test_prevent_control_characters_in_headers(self):
masks = '{}test', 'test{}', 'te{}st'
tests = '\n', '\r', '\n\r', '\0'
# Test HeaderDict
apis = 'append', 'replace', '__setitem__', 'setdefault'
for api, mask, test in itertools.product(apis, masks, tests):
hd = bottle.HeaderDict()
func = getattr(hd, api)
value = mask.replace("{}", test)
> self.assertRaises(ValueError, func, value, "test-value")
E AssertionError: ValueError not raised by append
test/test_environ.py:745: AssertionError
==================================== PASSES ====================================
=========================== short test summary info ============================
PASSED test/test_environ.py::TestRequest::test_header_access
PASSED test/test_environ.py::TestRequest::test_header_access_special
PASSED test/test_environ.py::TestRequest::test_json_forged_header_issue616
PASSED test/test_environ.py::TestRequest::test_json_header_empty_body
PASSED test/test_environ.py::TestRequest::test_json_noheader
PASSED test/test_environ.py::TestResponse::test_append_header
PASSED test/test_environ.py::TestResponse::test_constructor_headerlist
PASSED test/test_environ.py::TestResponse::test_delete_header
PASSED test/test_environ.py::TestResponse::test_expires_header
PASSED test/test_environ.py::TestResponse::test_non_string_header
PASSED test/test_environ.py::TestResponse::test_set_header
PASSED test/test_environ.py::TestResponse::test_wsgi_header_values
PASSED test/test_environ.py::TestWSGIHeaderDict::test_dict
PASSED test/test_environ.py::TestWSGIHeaderDict::test_empty
PASSED test/test_environ.py::TestWSGIHeaderDict::test_native
PASSED test/test_environ.py::TestWSGIHeaderDict::test_unicode
FAILED test/test_environ.py::TestResponse::test_prevent_control_characters_in_headers
1 failed, 16 passed, 58 deselected in 0.24s
[stdout]
..........F...... [100%]
=================================== FAILURES ===================================
___________ TestResponse.test_prevent_control_characters_in_headers ____________
self = <test.test_environ.TestResponse testMethod=test_prevent_control_characters_in_headers>
def test_prevent_control_characters_in_headers(self):
masks = '{}test', 'test{}', 'te{}st'
tests = '\n', '\r', '\n\r', '\0'
# Test HeaderDict
apis = 'append', 'replace', '__setitem__', 'setdefault'
for api, mask, test in itertools.product(apis, masks, tests):
hd = bottle.HeaderDict()
func = getattr(hd, api)
value = mask.replace("{}", test)
> self.assertRaises(ValueError, func, value, "test-value")
E AssertionError: ValueError not raised by append
test/test_environ.py:745: AssertionError
==================================== PASSES ====================================
=========================== short test summary info ============================
PASSED test/test_environ.py::TestRequest::test_header_access
PASSED test/test_environ.py::TestRequest::test_header_access_special
PASSED test/test_environ.py::TestRequest::test_json_forged_header_issue616
PASSED test/test_environ.py::TestRequest::test_json_header_empty_body
PASSED test/test_environ.py::TestRequest::test_json_noheader
PASSED test/test_environ.py::TestResponse::test_append_header
PASSED test/test_environ.py::TestResponse::test_constructor_headerlist
PASSED test/test_environ.py::TestResponse::test_delete_header
PASSED test/test_environ.py::TestResponse::test_expires_header
PASSED test/test_environ.py::TestResponse::test_non_string_header
PASSED test/test_environ.py::TestResponse::test_set_header
PASSED test/test_environ.py::TestResponse::test_wsgi_header_values
PASSED test/test_environ.py::TestWSGIHeaderDict::test_dict
PASSED test/test_environ.py::TestWSGIHeaderDict::test_empty
PASSED test/test_environ.py::TestWSGIHeaderDict::test_native
PASSED test/test_environ.py::TestWSGIHeaderDict::test_unicode
FAILED test/test_environ.py::TestResponse::test_prevent_control_characters_in_headers
1 failed, 16 passed, 58 deselected in 0.24s
[metadata] {"noOutputExpected": false}{
"file_path": "/app/bottle.py",
"offset": 1688,
"limit": 80
}1688 del _get_status, _set_status
1689
1690 @property
1691 def headers(self):
1692 """ An instance of :class:`HeaderDict`, a case-insensitive dict-like
1693 view on the response headers. """
1694 hdict = HeaderDict()
1695 hdict.dict = self._headers
1696 return hdict
1697
1698 def __contains__(self, name):
1699 return _hkey(name) in self._headers
1700
1701 def __delitem__(self, name):
1702 del self._headers[_hkey(name)]
1703
1704 def __getitem__(self, name):
1705 return self._headers[_hkey(name)][-1]
1706
1707 def __setitem__(self, name, value):
1708 self._headers[_hkey(name)] = [_hval(value)]
1709
1710 def get_header(self, name, default=None):
1711 """ Return the value of a previously defined header. If there is no
1712 header with that name, return a default value. """
1713 return self._headers.get(_hkey(name), [default])[-1]
1714
1715 def set_header(self, name, value):
1716 """ Create a new response header, replacing any previously defined
1717 headers with the same name. """
1718 self._headers[_hkey(name)] = [_hval(value)]
1719
1720 def add_header(self, name, value):
1721 """ Add an additional response header, not removing duplicates. """
1722 self._headers.setdefault(_hkey(name), []).append(_hval(value))
1723
1724 def iter_headers(self):
1725 """ Yield (header, value) tuples, skipping headers that are not
1726 allowed with the current response status code. """
1727 return self.headerlist
1728
1729 def _wsgi_status_line(self):
1730 """ WSGI conform status line (latin1-encodeable) """
1731 return self._status_line.encode('utf8', 'surrogateescape').decode('latin1')
1732
1733 @property
1734 def headerlist(self):
1735 """ WSGI conform list of (header, value) tuples. """
1736 out = []
1737 headers = list(self._headers.items())
1738 if 'Content-Type' not in self._headers:
1739 headers.append(('Content-Type', [self.default_content_type]))
1740 if self._status_code in self.bad_headers:
1741 bad_headers = self.bad_headers[self._status_code]
1742 headers = [h for h in headers if h[0] not in bad_headers]
1743 out += [(name, val) for (name, vals) in headers for val in vals]
1744 if self._cookies:
1745 for c in self._cookies.values():
1746 out.append(('Set-Cookie', _hval(c.OutputString())))
1747 out = [(k, v.encode('utf8', 'surrogateescape').decode('latin1')) for (k, v) in out]
1748 return out
1749
1750 content_type = HeaderProperty('Content-Type')
1751 content_length = HeaderProperty('Content-Length', reader=int, default=-1)
1752 expires = HeaderProperty(
1753 'Expires',
1754 reader=lambda x: datetime.fromtimestamp(parse_date(x), UTC),
1755 writer=lambda x: http_date(x))
1756
1757 @property
1758 def charset(self, default='UTF-8'):
1759 """ Return the charset specified in the content-type header (default: utf8). """
1760 if 'charset=' in self.content_type:
1761 return self.content_type.split('charset=')[-1].split(';')[0].strip()
1762 return default
1763
1764 def set_cookie(self, name, value, secret=None, digestmod=hashlib.sha256, **options):
1765 """ Create a new cookie or replace an old one. If the `secret` parameter is
1766 set, create a `Signed Cookie` (described below).
1767
[metadata] {"type": "text", "file": {"filePath": "/app/bottle.py", "content": " del _get_status, _set_status\n\n @property\n def headers(self):\n \"\"\" An instance of :class:`HeaderDict`, a case-insensitive dict-like\n view on the response headers. \"\"\"\n hdict = HeaderDict()\n hdict.dict = self._headers\n return hdict\n\n def __contains__(self, name):\n return _hkey(name) in self._headers\n\n def __delitem__(self, name):\n del self._headers[_hkey(name)]\n\n def __getitem__(self, name):\n return self._headers[_hkey(name)][-1]\n\n def __setitem__(self, name, value):\n self._headers[_hkey(name)] = [_hval(value)]\n\n def get_header(self, name, default=None):\n \"\"\" Return the value of a previously defined header. If there is no\n header with that name, return a default value. \"\"\"\n return self._headers.get(_hkey(name), [default])[-1]\n\n def set_header(self, name, value):\n \"\"\" Create a new response header, replacing any previously defined\n headers with the same name. \"\"\"\n self._headers[_hkey(name)] = [_hval(value)]\n\n def add_header(self, name, value):\n \"\"\" Add an additional response header, not removing duplicates. \"\"\"\n self._headers.setdefault(_hkey(name), []).append(_hval(value))\n\n def iter_headers(self):\n \"\"\" Yield (header, value) tuples, skipping headers that are not\n allowed with the current response status code. \"\"\"\n return self.headerlist\n\n def _wsgi_status_line(self):\n \"\"\" WSGI conform status line (latin1-encodeable) \"\"\"\n return self._status_line.encode('utf8', 'surrogateescape').decode('latin1')\n\n @property\n def headerlist(self):\n \"\"\" WSGI conform list of (header, value) tuples. \"\"\"\n out = []\n headers = list(self._headers.items())\n if 'Content-Type' not in self._headers:\n headers.append(('Content-Type', [self.default_content_type]))\n if self._status_code in self.bad_headers:\n bad_headers = self.bad_headers[self._status_code]\n headers = [h for h in headers if h[0] not in bad_headers]\n out += [(name, val) for (name, vals) in headers for val in vals]\n if self._cookies:\n {
"replace_all": false,
"file_path": "/app/bottle.py",
"old_string": "def _hkey(key):\n key = touni(key)\n return key.title().replace('_', '-')\n\n\ndef _hval(value):\n value = touni(value)\n return value",
"new_string": "def _hkey(key):\n key = touni(key)\n if '\\n' in key or '\\r' in key or '\\0' in key:\n raise ValueError(\"Header names must not contain control characters: %r\" % key)\n return key.title().replace('_', '-')\n\n\ndef _hval(value):\n value = touni(value)\n if '\\n' in value or '\\r' in value or '\\0' in value:\n raise ValueError(\"Header value must not contain control characters: %r\" % value)\n return value"
}The file /app/bottle.py has been updated successfully. (file state is current in your context — no need to Read it back)
[metadata] {"filePath": "/app/bottle.py", "oldString": "def _hkey(key):\n key = touni(key)\n return key.title().replace('_', '-')\n\n\ndef _hval(value):\n value = touni(value)\n return value", "newString": "def _hkey(key):\n key = touni(key)\n if '\\n' in key or '\\r' in key or '\\0' in key:\n raise ValueError(\"Header names must not contain control characters: %r\" % key)\n return key.title().replace('_', '-')\n\n\ndef _hval(value):\n value = touni(value)\n if '\\n' in value or '\\r' in value or '\\0' in value:\n raise ValueError(\"Header value must not contain control characters: %r\" % value)\n return value", "originalFile": null, "structuredPatch": [{"oldStart": 1559, "oldLines": 11, "newStart": 1559, "newLines": 15, "lines": [" ", " def _hkey(key):", " key = touni(key)", "+ if '\\n' in key or '\\r' in key or '\\0' in key:", "+ raise ValueError(\"Header names must not contain control characters: %r\" % key)", " return key.title().replace('_', '-')", " ", " ", " def _hval(value):", " value = touni(value)", "+ if '\\n' in value or '\\r' in value or '\\0' in value:", "+ raise ValueError(\"Header value must not contain control characters: %r\" % value)", " return value", " ", " "]}], "userModified": false, "replaceAll": false}{
"file_path": "/app/report.jsonl",
"content": "{\"file_path\": \"/app/bottle.py\", \"cwe_id\": [\"cwe-93\"]}\n"
}File created successfully at: /app/report.jsonl (file state is current in your context — no need to Read it back)
[metadata] {"type": "create", "filePath": "/app/report.jsonl", "content": "{\"file_path\": \"/app/bottle.py\", \"cwe_id\": [\"cwe-93\"]}\n", "structuredPatch": [], "originalFile": null, "userModified": false}{
"command": "python -m pytest -rA -q 2>&1 | tail -80",
"description": "Run full test suite to verify fix"
}PASSED test/test_stpl.py::TestSimpleTemplate::test_file PASSED test/test_stpl.py::TestSimpleTemplate::test_get PASSED test/test_stpl.py::TestSimpleTemplate::test_global_config PASSED test/test_stpl.py::TestSimpleTemplate::test_htmlutils_quote PASSED test/test_stpl.py::TestSimpleTemplate::test_import PASSED test/test_stpl.py::TestSimpleTemplate::test_include PASSED test/test_stpl.py::TestSimpleTemplate::test_multiline PASSED test/test_stpl.py::TestSimpleTemplate::test_name PASSED test/test_stpl.py::TestSimpleTemplate::test_newline_in_parameterlist PASSED test/test_stpl.py::TestSimpleTemplate::test_nobreak PASSED test/test_stpl.py::TestSimpleTemplate::test_noescape PASSED test/test_stpl.py::TestSimpleTemplate::test_noescape_setting PASSED test/test_stpl.py::TestSimpleTemplate::test_nonobreak PASSED test/test_stpl.py::TestSimpleTemplate::test_notfound PASSED test/test_stpl.py::TestSimpleTemplate::test_onelineblocks PASSED test/test_stpl.py::TestSimpleTemplate::test_onelinebugs PASSED test/test_stpl.py::TestSimpleTemplate::test_rebase PASSED test/test_stpl.py::TestSimpleTemplate::test_self_as_variable_name PASSED test/test_stpl.py::TestSimpleTemplate::test_setdefault PASSED test/test_stpl.py::TestSimpleTemplate::test_string PASSED test/test_stpl.py::TestSimpleTemplate::test_template_shortcut PASSED test/test_stpl.py::TestSimpleTemplate::test_unicode PASSED test/test_stpl.py::TestSimpleTemplate::test_unicode_code PASSED test/test_stpl.py::TestSimpleTemplate::test_view_decorator PASSED test/test_stpl.py::TestSimpleTemplate::test_view_decorator_issue_407 PASSED test/test_stpl.py::TestSimpleTemplate::test_winbreaks PASSED test/test_stpl.py::TestSimpleTemplate::test_winbreaks_end_bug PASSED test/test_stpl.py::TestSTPLDir::test_end_keyword_on_same_line PASSED test/test_stpl.py::TestSTPLDir::test_multiline_block PASSED test/test_stpl.py::TestSTPLDir::test_multiline_comprehensions_in_code_line PASSED test/test_stpl.py::TestSTPLDir::test_multiline_eob_after_end PASSED test/test_stpl.py::TestSTPLDir::test_multiline_eob_in_single_line_code PASSED test/test_stpl.py::TestSTPLDir::test_multiline_find_eob_in_comments PASSED test/test_stpl.py::TestSTPLDir::test_multiline_ignore_eob_in_string PASSED test/test_stpl.py::TestSTPLDir::test_multiline_indention PASSED test/test_stpl.py::TestSTPLDir::test_multiline_strings_in_code_line PASSED test/test_wsgi.py::TestWsgi::test_303 PASSED test/test_wsgi.py::TestWsgi::test_401 PASSED test/test_wsgi.py::TestWsgi::test_500 PASSED test/test_wsgi.py::TestWsgi::test_500_unicode PASSED test/test_wsgi.py::TestWsgi::test_anymethod PASSED test/test_wsgi.py::TestWsgi::test_cookie PASSED test/test_wsgi.py::TestWsgi::test_generator_callback PASSED test/test_wsgi.py::TestWsgi::test_get PASSED test/test_wsgi.py::TestWsgi::test_headget PASSED test/test_wsgi.py::TestWsgi::test_post PASSED test/test_wsgi.py::TestWsgi::test_request_attrs PASSED test/test_wsgi.py::TestWsgi::test_utf8_404 PASSED test/test_wsgi.py::TestWsgi::test_utf8_header PASSED test/test_wsgi.py::TestWsgi::test_utf8_url PASSED test/test_wsgi.py::TestErrorHandling::test_error_routing PASSED test/test_wsgi.py::TestCloseable::test_direct PASSED test/test_wsgi.py::TestRouteDecorator::test_after_request_hooks_may_rise_response_exception PASSED test/test_wsgi.py::TestRouteDecorator::test_after_request_hooks_run_after_exception PASSED test/test_wsgi.py::TestRouteDecorator::test_after_request_hooks_run_after_exception_in_before_hook PASSED test/test_wsgi.py::TestRouteDecorator::test_after_request_sees_HTTPError_response PASSED test/test_wsgi.py::TestRouteDecorator::test_after_response_hook_can_set_headers PASSED test/test_wsgi.py::TestRouteDecorator::test_apply PASSED test/test_wsgi.py::TestRouteDecorator::test_apply_list PASSED test/test_wsgi.py::TestRouteDecorator::test_callback PASSED test/test_wsgi.py::TestRouteDecorator::test_decorators PASSED test/test_wsgi.py::TestRouteDecorator::test_hooks PASSED test/test_wsgi.py::TestRouteDecorator::test_method PASSED test/test_wsgi.py::TestRouteDecorator::test_method_list PASSED test/test_wsgi.py::TestRouteDecorator::test_name PASSED test/test_wsgi.py::TestRouteDecorator::test_no_params_at_all PASSED test/test_wsgi.py::TestRouteDecorator::test_no_path PASSED test/test_wsgi.py::TestRouteDecorator::test_path_list PASSED test/test_wsgi.py::TestRouteDecorator::test_single_path PASSED test/test_wsgi.py::TestRouteDecorator::test_template PASSED test/test_wsgi.py::TestRouteDecorator::test_template_opts PASSED test/test_wsgi.py::TestDecorators::test_autoroute PASSED test/test_wsgi.py::TestDecorators::test_routebuild PASSED test/test_wsgi.py::TestDecorators::test_truncate_body PASSED test/test_wsgi.py::TestDecorators::test_view PASSED test/test_wsgi.py::TestDecorators::test_view_error PASSED test/test_wsgi.py::TestAppShortcuts::testWithStatement PASSED test/test_wsgi.py::TestAppShortcuts::test_module_shortcuts PASSED test/test_wsgi.py::TestAppShortcuts::test_module_shortcuts_with_different_name 367 passed in 0.98s [stdout] PASSED test/test_stpl.py::TestSimpleTemplate::test_file PASSED test/test_stpl.py::TestSimpleTemplate::test_get PASSED test/test_stpl.py::TestSimpleTemplate::test_global_config PASSED test/test_stpl.py::TestSimpleTemplate::test_htmlutils_quote PASSED test/test_stpl.py::TestSimpleTemplate::test_import PASSED test/test_stpl.py::TestSimpleTemplate::test_include PASSED test/test_stpl.py::TestSimpleTemplate::test_multiline PASSED test/test_stpl.py::TestSimpleTemplate::test_name PASSED test/test_stpl.py::TestSimpleTemplate::test_newline_in_parameterlist PASSED test/test_stpl.py::TestSimpleTemplate::test_nobreak PASSED test/test_stpl.py::TestSimpleTemplate::test_noescape PASSED test/test_stpl.py::TestSimpleTemplate::test_noescape_setting PASSED test/test_stpl.py::TestSimpleTemplate::test_nonobreak PASSED test/test_stpl.py::TestSimpleTemplate::test_notfound PASSED test/test_stpl.py::TestSimpleTemplate::test_onelineblocks PASSED test/test_stpl.py::TestSimpleTemplate::test_onelineb
{
"command": "git -C /app status && git -C /app diff && echo '---REPORT---' && cat /app/report.jsonl 2>/dev/null",
"description": "Check git status, diff, and existing report"
}The user doesn't want to take this action right now. STOP what you are doing and wait for the user to tell you how to proceed. [error] tool reported failure