@@ -94,7 +94,6 @@ def test_django_logger_debug(self):
94
94
95
95
96
96
class LoggingAssertionMixin :
97
-
98
97
def assertLogRecord (
99
98
self ,
100
99
logger_cm ,
@@ -147,6 +146,14 @@ def test_page_not_found_warning(self):
147
146
msg = "Not Found: /does_not_exist/" ,
148
147
)
149
148
149
+ def test_control_chars_escaped (self ):
150
+ self .assertLogsRequest (
151
+ url = "/%1B[1;31mNOW IN RED!!!1B[0m/" ,
152
+ level = "WARNING" ,
153
+ status_code = 404 ,
154
+ msg = r"Not Found: /\x1b[1;31mNOW IN RED!!!1B[0m/" ,
155
+ )
156
+
150
157
async def test_async_page_not_found_warning (self ):
151
158
logger = "django.request"
152
159
level = "WARNING"
@@ -155,6 +162,16 @@ async def test_async_page_not_found_warning(self):
155
162
156
163
self .assertLogRecord (cm , level , "Not Found: /does_not_exist/" , 404 )
157
164
165
+ async def test_async_control_chars_escaped (self ):
166
+ logger = "django.request"
167
+ level = "WARNING"
168
+ with self .assertLogs (logger , level ) as cm :
169
+ await self .async_client .get (r"/%1B[1;31mNOW IN RED!!!1B[0m/" )
170
+
171
+ self .assertLogRecord (
172
+ cm , level , r"Not Found: /\x1b[1;31mNOW IN RED!!!1B[0m/" , 404
173
+ )
174
+
158
175
def test_page_not_found_raised (self ):
159
176
self .assertLogsRequest (
160
177
url = "/does_not_exist_raised/" ,
@@ -686,6 +703,7 @@ def assertResponseLogged(self, logger_cm, msg, levelno, status_code, request):
686
703
self .assertEqual (record .levelno , levelno )
687
704
self .assertEqual (record .status_code , status_code )
688
705
self .assertEqual (record .request , request )
706
+ return record
689
707
690
708
def test_missing_response_raises_attribute_error (self ):
691
709
with self .assertRaises (AttributeError ):
@@ -787,3 +805,62 @@ def test_logs_with_custom_logger(self):
787
805
self .assertEqual (
788
806
f"WARNING:my.custom.logger:{ msg } " , log_stream .getvalue ().strip ()
789
807
)
808
+
809
+ def test_unicode_escape_escaping (self ):
810
+ test_cases = [
811
+ # Control characters.
812
+ ("line\n break" , "line\\ nbreak" ),
813
+ ("carriage\r return" , "carriage\\ rreturn" ),
814
+ ("tab\t separated" , "tab\\ tseparated" ),
815
+ ("formfeed\f " , "formfeed\\ x0c" ),
816
+ ("bell\a " , "bell\\ x07" ),
817
+ ("multi\n line\n text" , "multi\\ nline\\ ntext" ),
818
+ # Slashes.
819
+ ("slash\\ test" , "slash\\ \\ test" ),
820
+ ("back\\ slash" , "back\\ \\ slash" ),
821
+ # Quotes.
822
+ ('quote"test"' , 'quote"test"' ),
823
+ ("quote'test'" , "quote'test'" ),
824
+ # Accented, composed characters, emojis and symbols.
825
+ ("café" , "caf\\ xe9" ),
826
+ ("e\u0301 " , "e\\ u0301" ), # e + combining acute
827
+ ("smile🙂" , "smile\\ U0001f642" ),
828
+ ("weird ☃️" , "weird \\ u2603\\ ufe0f" ),
829
+ # Non-Latin alphabets.
830
+ ("Привет" , "\\ u041f\\ u0440\\ u0438\\ u0432\\ u0435\\ u0442" ),
831
+ ("你好" , "\\ u4f60\\ u597d" ),
832
+ # ANSI escape sequences.
833
+ ("escape\x1b [31mred\x1b [0m" , "escape\\ x1b[31mred\\ x1b[0m" ),
834
+ (
835
+ "/\x1b [1;31mCAUTION!!YOU ARE PWNED\x1b [0m/" ,
836
+ "/\\ x1b[1;31mCAUTION!!YOU ARE PWNED\\ x1b[0m/" ,
837
+ ),
838
+ (
839
+ "/\r \n \r \n 1984-04-22 INFO Listening on 0.0.0.0:8080\r \n \r \n " ,
840
+ "/\\ r\\ n\\ r\\ n1984-04-22 INFO Listening on 0.0.0.0:8080\\ r\\ n\\ r\\ n" ,
841
+ ),
842
+ # Plain safe input.
843
+ ("normal-path" , "normal-path" ),
844
+ ("slash/colon:" , "slash/colon:" ),
845
+ # Non strings.
846
+ (0 , "0" ),
847
+ ([1 , 2 , 3 ], "[1, 2, 3]" ),
848
+ ({"test" : "🙂" }, "{'test': '🙂'}" ),
849
+ ]
850
+
851
+ msg = "Test message: %s"
852
+ for case , expected in test_cases :
853
+ with self .assertLogs ("django.request" , level = "ERROR" ) as cm :
854
+ with self .subTest (case = case ):
855
+ response = HttpResponse (status = 318 )
856
+ log_response (msg , case , response = response , level = "error" )
857
+
858
+ record = self .assertResponseLogged (
859
+ cm ,
860
+ msg % expected ,
861
+ levelno = logging .ERROR ,
862
+ status_code = 318 ,
863
+ request = None ,
864
+ )
865
+ # Log record is always a single line.
866
+ self .assertEqual (len (record .getMessage ().splitlines ()), 1 )
0 commit comments