1313import pytest
1414
1515from git import Git , GitConfigParser
16- from git .config import _OMD , cp
16+ from git .config import _OMD , _escape_config_value , _escape_section_subsection , cp
1717from git .util import rmfile
1818
1919from test .lib import SkipTest , TestCase , fixture_path , with_rw_directory
@@ -169,6 +169,7 @@ def test_git_parser_rejects_syntax_rejected_by_git(self):
169169 b"[core]\n key: value\n " ,
170170 b'[core]\n key = "unterminated\n ' ,
171171 b'[core]\n key = "bad\\ q"\n ' ,
172+ b'[core]\n key = "bad\x00 value"\n ' ,
172173 )
173174 for config_content in invalid_configs :
174175 with self .subTest (config_content = config_content ):
@@ -236,16 +237,15 @@ def test_writer_escapes_quoted_subsection_names(self, rw_dir):
236237 self .assertEqual (Git ().config ("--file" , config_path , "--get" , 'submodule.docs"archive\\ part].path' ), "docs" )
237238
238239 @with_rw_directory
239- def test_set_value_rejects_config_injection (self , rw_dir ):
240+ def test_set_value_escapes_config_injection (self , rw_dir ):
240241 config_path = osp .join (rw_dir , "config" )
241242 payload = "foo\n [core]\n hooksPath=/tmp/hooks"
242243
243244 with GitConfigParser (config_path , read_only = False ) as git_config :
244- with pytest .raises (ValueError , match = "CR, LF, or NUL" ):
245- git_config .set_value ("user" , "name" , payload )
245+ git_config .set_value ("user" , "name" , payload )
246246
247247 with GitConfigParser (config_path , read_only = True ) as git_config :
248- self .assertFalse (git_config .has_section ("user" ) )
248+ self .assertEqual (git_config .get_value ("user" , "name" ), payload )
249249 self .assertFalse (git_config .has_section ("core" ))
250250
251251 @with_rw_directory
@@ -321,24 +321,26 @@ def test_writer_rejects_unquoted_section_terminators(self, rw_dir):
321321 self .assertFalse (git_config .has_section ("other" ), "an unsafe section name injected an [other] section" )
322322
323323 @with_rw_directory
324- def test_set_and_add_value_reject_unsafe_value_characters (self , rw_dir ):
324+ def test_set_and_add_value_match_git_control_character_handling (self , rw_dir ):
325325 config_path = osp .join (rw_dir , "config" )
326- bad_values = ("foo\r bar" , "foo\n bar" , "foo\x00 bar" , b"foo\n bar" )
327326
328327 with GitConfigParser (config_path , read_only = False ) as git_config :
329328 git_config .add_section ("user" )
330- for bad_value in bad_values :
331- with pytest .raises (ValueError , match = "CR, LF, or NUL" ):
332- git_config .set ("user" , "name" , bad_value )
333- with pytest .raises (ValueError , match = "CR, LF, or NUL" ):
334- git_config .set_value ("user" , "name" , bad_value )
335- with pytest .raises (ValueError , match = "CR, LF, or NUL" ):
336- git_config .add_value ("user" , "name" , bad_value )
329+ git_config .set ("user" , "carriage-return" , "foo\r bar" )
330+ git_config .set_value ("user" , "line-feed" , "foo\n bar" )
331+ git_config .add_value ("user" , "bytes-line-feed" , b"foo\n bar" )
337332
338- git_config .set_value ("user" , "name" , "safe" )
333+ for setter in (git_config .set , git_config .set_value , git_config .add_value ):
334+ with pytest .raises (ValueError , match = "must not contain NUL" ):
335+ setter ("user" , "name" , "foo\x00 bar" )
339336
340337 with GitConfigParser (config_path , read_only = True ) as git_config :
341- self .assertEqual (git_config .get_value ("user" , "name" ), "safe" )
338+ self .assertEqual (git_config .get_value ("user" , "carriage-return" ), "foo\r bar" )
339+ self .assertEqual (git_config .get_value ("user" , "line-feed" ), "foo\n bar" )
340+ self .assertEqual (git_config .get_value ("user" , "bytes-line-feed" ), "foo\n bar" )
341+
342+ self .assertEqual (Git ().config ("--file" , config_path , "--get" , "user.carriage-return" ), "foo\r bar" )
343+ self .assertEqual (Git ().config ("--file" , config_path , "--get" , "user.line-feed" ), "foo\n bar" )
342344
343345 def test_base (self ):
344346 path_repo = fixture_path ("git_config" )
@@ -482,8 +484,8 @@ def test_multiple_include_paths_with_same_key(self, rw_dir):
482484 # We write it manually because set_value would overwrite the key.
483485 with open (fp_main , "w" ) as f :
484486 f .write ("[include]\n " )
485- f .write (f"\t path = { fp_inc1 } \n " )
486- f .write (f"\t path = { fp_inc2 } \n " )
487+ f .write (f"\t path = { _escape_config_value ( fp_inc1 ) } \n " )
488+ f .write (f"\t path = { _escape_config_value ( fp_inc2 ) } \n " )
487489
488490 with GitConfigParser (fp_main , read_only = True ) as cr :
489491 # Both included files should be loaded.
@@ -509,8 +511,15 @@ def test_conditional_includes_from_git_dir(self, rw_dir):
509511 path2 = osp .join (rw_dir , "config2" )
510512 template = '[includeIf "{}:{}"]\n path={}\n '
511513
514+ def include_config (condition , pattern ):
515+ return template .format (
516+ condition ,
517+ _escape_section_subsection (pattern ),
518+ _escape_config_value (path2 ),
519+ )
520+
512521 with open (path1 , "w" ) as stream :
513- stream .write (template . format ("gitdir" , git_dir , path2 ))
522+ stream .write (include_config ("gitdir" , git_dir ))
514523
515524 # Ensure that config is ignored if no repo is set.
516525 with GitConfigParser (path1 ) as config :
@@ -524,39 +533,39 @@ def test_conditional_includes_from_git_dir(self, rw_dir):
524533
525534 # Ensure that config is ignored if case is incorrect.
526535 with open (path1 , "w" ) as stream :
527- stream .write (template . format ("gitdir" , git_dir .upper (), path2 ))
536+ stream .write (include_config ("gitdir" , git_dir .upper ()))
528537
529538 with GitConfigParser (path1 , repo = repo ) as config :
530539 assert not config ._has_includes ()
531540 assert config ._included_paths () == []
532541
533542 # Ensure that config is included if case is ignored.
534543 with open (path1 , "w" ) as stream :
535- stream .write (template . format ("gitdir/i" , git_dir .upper (), path2 ))
544+ stream .write (include_config ("gitdir/i" , git_dir .upper ()))
536545
537546 with GitConfigParser (path1 , repo = repo ) as config :
538547 assert config ._has_includes ()
539548 assert config ._included_paths () == [("path" , path2 )]
540549
541550 # Ensure that config is included with path using glob pattern.
542551 with open (path1 , "w" ) as stream :
543- stream .write (template . format ("gitdir" , "**/repo1" , path2 ))
552+ stream .write (include_config ("gitdir" , "**/repo1" ))
544553
545554 with GitConfigParser (path1 , repo = repo ) as config :
546555 assert config ._has_includes ()
547556 assert config ._included_paths () == [("path" , path2 )]
548557
549558 # Ensure that config is ignored if path is not matching git_dir.
550559 with open (path1 , "w" ) as stream :
551- stream .write (template . format ("gitdir" , "incorrect" , path2 ))
560+ stream .write (include_config ("gitdir" , "incorrect" ))
552561
553562 with GitConfigParser (path1 , repo = repo ) as config :
554563 assert not config ._has_includes ()
555564 assert config ._included_paths () == []
556565
557566 # Ensure that config is included if path in hierarchy.
558567 with open (path1 , "w" ) as stream :
559- stream .write (template . format ("gitdir" , "target1/" , path2 ))
568+ stream .write (include_config ("gitdir" , "target1/" ))
560569
561570 with GitConfigParser (path1 , repo = repo ) as config :
562571 assert config ._has_includes ()
@@ -578,23 +587,23 @@ def test_conditional_includes_from_branch_name(self, rw_dir):
578587
579588 # Ensure that config is included is branch is correct.
580589 with open (path1 , "w" ) as stream :
581- stream .write (template .format ("/foo/branch" , path2 ))
590+ stream .write (template .format ("/foo/branch" , _escape_config_value ( path2 ) ))
582591
583592 with GitConfigParser (path1 , repo = repo ) as config :
584593 assert config ._has_includes ()
585594 assert config ._included_paths () == [("path" , path2 )]
586595
587596 # Ensure that config is included is branch is incorrect.
588597 with open (path1 , "w" ) as stream :
589- stream .write (template .format ("incorrect" , path2 ))
598+ stream .write (template .format ("incorrect" , _escape_config_value ( path2 ) ))
590599
591600 with GitConfigParser (path1 , repo = repo ) as config :
592601 assert not config ._has_includes ()
593602 assert config ._included_paths () == []
594603
595604 # Ensure that config is included with branch using glob pattern.
596605 with open (path1 , "w" ) as stream :
597- stream .write (template .format ("/foo/**" , path2 ))
606+ stream .write (template .format ("/foo/**" , _escape_config_value ( path2 ) ))
598607
599608 with GitConfigParser (path1 , repo = repo ) as config :
600609 assert config ._has_includes ()
@@ -630,23 +639,23 @@ def test_conditional_includes_remote_url(self, rw_dir):
630639
631640 # Ensure that config with hasconfig and full url is correct.
632641 with open (path1 , "w" ) as stream :
633- stream .write (template .format ("https://github.com/foo/repo" , path2 ))
642+ stream .write (template .format ("https://github.com/foo/repo" , _escape_config_value ( path2 ) ))
634643
635644 with GitConfigParser (path1 , repo = repo ) as config :
636645 assert config ._has_includes ()
637646 assert config ._included_paths () == [("path" , path2 )]
638647
639648 # Ensure that config with hasconfig and incorrect url is incorrect.
640649 with open (path1 , "w" ) as stream :
641- stream .write (template .format ("incorrect" , path2 ))
650+ stream .write (template .format ("incorrect" , _escape_config_value ( path2 ) ))
642651
643652 with GitConfigParser (path1 , repo = repo ) as config :
644653 assert not config ._has_includes ()
645654 assert config ._included_paths () == []
646655
647656 # Ensure that config with hasconfig and url using glob pattern is correct.
648657 with open (path1 , "w" ) as stream :
649- stream .write (template .format ("**/**github.com*/**" , path2 ))
658+ stream .write (template .format ("**/**github.com*/**" , _escape_config_value ( path2 ) ))
650659
651660 with GitConfigParser (path1 , repo = repo ) as config :
652661 assert config ._has_includes ()
0 commit comments