Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e43393a

Browse files
[3.8] gh-100001: Omit control characters in http.server stderr logs. (GH-100002) (#100033)
* gh-100001: Omit control characters in http.server stderr logs. (GH-100002) Replace control characters in http.server.BaseHTTPRequestHandler.log_message with an escaped \xHH sequence to avoid causing problems for the terminal the output is printed to. (cherry picked from commit d8ab0a4) Co-authored-by: Gregory P. Smith <[email protected]> * also escape \s (backport of PR #100038). * add versionadded and remove extraneous 'to' Co-authored-by: Gregory P. Smith <[email protected]>
1 parent b50b6f9 commit e43393a

File tree

4 files changed

+46
-2
lines changed

4 files changed

+46
-2
lines changed

Doc/library/http.server.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,3 +488,12 @@ Security Considerations
488488
:class:`SimpleHTTPRequestHandler` will follow symbolic links when handling
489489
requests, this makes it possible for files outside of the specified directory
490490
to be served.
491+
492+
Earlier versions of Python did not scrub control characters from the
493+
log messages emitted to stderr from ``python -m http.server`` or the
494+
default :class:`BaseHTTPRequestHandler` ``.log_message``
495+
implementation. This could allow remote clients connecting to your
496+
server to send nefarious control codes to your terminal.
497+
498+
.. versionadded:: 3.8.16
499+
scrubbing control characters from log messages

Lib/http/server.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
import html
9494
import http.client
9595
import io
96+
import itertools
9697
import mimetypes
9798
import os
9899
import posixpath
@@ -565,6 +566,11 @@ def log_error(self, format, *args):
565566

566567
self.log_message(format, *args)
567568

569+
# https://en.wikipedia.org/wiki/List_of_Unicode_characters#Control_codes
570+
_control_char_table = str.maketrans(
571+
{c: fr'\x{c:02x}' for c in itertools.chain(range(0x20), range(0x7f,0xa0))})
572+
_control_char_table[ord('\\')] = r'\\'
573+
568574
def log_message(self, format, *args):
569575
"""Log an arbitrary message.
570576
@@ -580,12 +586,16 @@ def log_message(self, format, *args):
580586
The client ip and current date/time are prefixed to
581587
every message.
582588
589+
Unicode control characters are replaced with escaped hex
590+
before writing the output to stderr.
591+
583592
"""
584593

594+
message = format % args
585595
sys.stderr.write("%s - - [%s] %s\n" %
586596
(self.address_string(),
587597
self.log_date_time_string(),
588-
format%args))
598+
message.translate(self._control_char_table)))
589599

590600
def version_string(self):
591601
"""Return the server software version string."""

Lib/test/test_httpservers.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import datetime
2626
import threading
2727
from unittest import mock
28-
from io import BytesIO
28+
from io import BytesIO, StringIO
2929

3030
import unittest
3131
from test import support
@@ -910,6 +910,25 @@ def verify_http_server_response(self, response):
910910
match = self.HTTPResponseMatch.search(response)
911911
self.assertIsNotNone(match)
912912

913+
def test_unprintable_not_logged(self):
914+
# We call the method from the class directly as our Socketless
915+
# Handler subclass overrode it... nice for everything BUT this test.
916+
self.handler.client_address = ('127.0.0.1', 1337)
917+
log_message = BaseHTTPRequestHandler.log_message
918+
with mock.patch.object(sys, 'stderr', StringIO()) as fake_stderr:
919+
log_message(self.handler, '/foo')
920+
log_message(self.handler, '/\033bar\000\033')
921+
log_message(self.handler, '/spam %s.', 'a')
922+
log_message(self.handler, '/spam %s.', '\033\x7f\x9f\xa0beans')
923+
stderr = fake_stderr.getvalue()
924+
self.assertNotIn('\033', stderr) # non-printable chars are caught.
925+
self.assertNotIn('\000', stderr) # non-printable chars are caught.
926+
lines = stderr.splitlines()
927+
self.assertIn('/foo', lines[0])
928+
self.assertIn(r'/\x1bbar\x00\x1b', lines[1])
929+
self.assertIn('/spam a.', lines[2])
930+
self.assertIn('/spam \\x1b\\x7f\\x9f\xa0beans.', lines[3])
931+
913932
def test_http_1_1(self):
914933
result = self.send_typical_request(b'GET / HTTP/1.1\r\n\r\n')
915934
self.verify_http_server_response(result[0])
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
``python -m http.server`` no longer allows terminal control characters sent
2+
within a garbage request to be printed to the stderr server log.
3+
4+
This is done by changing the :mod:`http.server` :class:`BaseHTTPRequestHandler`
5+
``.log_message`` method to replace control characters with a ``\xHH`` hex escape
6+
before printing.

0 commit comments

Comments
 (0)