3939bmsgd2luayAoaW4gIm15IG90aGVyIGNvbnRleHQiKQB3aW5rIHdpbmsA
4040'''
4141
42+ # .mo file with an invalid magic number
43+ GNU_MO_DATA_BAD_MAGIC_NUMBER = base64 .b64encode (b'ABCD' )
44+
4245# This data contains an invalid major version number (5)
4346# An unexpected major version number should be treated as an error when
4447# parsing a .mo file
8790ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
8891'''
8992
93+ # Corrupt .mo file
94+ # Generated from
95+ #
96+ # msgid "foo"
97+ # msgstr "bar"
98+ #
99+ # with msgfmt --no-hash
100+ #
101+ # The translation offset is changed to 0xFFFFFFFF,
102+ # making it larger than the file size, which should
103+ # raise an error when parsing.
104+ GNU_MO_DATA_CORRUPT = base64 .b64encode (bytes ([
105+ 0xDE , 0x12 , 0x04 , 0x95 , # Magic
106+ 0x00 , 0x00 , 0x00 , 0x00 , # Version
107+ 0x01 , 0x00 , 0x00 , 0x00 , # Message count
108+ 0x1C , 0x00 , 0x00 , 0x00 , # Message offset
109+ 0x24 , 0x00 , 0x00 , 0x00 , # Translation offset
110+ 0x00 , 0x00 , 0x00 , 0x00 , # Hash table size
111+ 0x2C , 0x00 , 0x00 , 0x00 , # Hash table offset
112+ 0x03 , 0x00 , 0x00 , 0x00 , # 1st message length
113+ 0x2C , 0x00 , 0x00 , 0x00 , # 1st message offset
114+ 0x03 , 0x00 , 0x00 , 0x00 , # 1st trans length
115+ 0xFF , 0xFF , 0xFF , 0xFF , # 1st trans offset (Modified to make it invalid)
116+ 0x66 , 0x6F , 0x6F , 0x00 , # Message data
117+ 0x62 , 0x61 , 0x72 , 0x00 , # Message data
118+ ]))
90119
91120UMO_DATA = b'''\
92121 3hIElQAAAAADAAAAHAAAADQAAAAAAAAAAAAAAAAAAABMAAAABAAAAE0AAAAQAAAAUgAAAA8BAABj
111140
112141LOCALEDIR = os .path .join ('xx' , 'LC_MESSAGES' )
113142MOFILE = os .path .join (LOCALEDIR , 'gettext.mo' )
143+ MOFILE_BAD_MAGIC_NUMBER = os .path .join (LOCALEDIR , 'gettext_bad_magic_number.mo' )
114144MOFILE_BAD_MAJOR_VERSION = os .path .join (LOCALEDIR , 'gettext_bad_major_version.mo' )
115145MOFILE_BAD_MINOR_VERSION = os .path .join (LOCALEDIR , 'gettext_bad_minor_version.mo' )
146+ MOFILE_CORRUPT = os .path .join (LOCALEDIR , 'gettext_corrupt.mo' )
116147UMOFILE = os .path .join (LOCALEDIR , 'ugettext.mo' )
117148MMOFILE = os .path .join (LOCALEDIR , 'metadata.mo' )
118149
@@ -131,10 +162,14 @@ def setUpClass(cls):
131162 os .makedirs (LOCALEDIR )
132163 with open (MOFILE , 'wb' ) as fp :
133164 fp .write (base64 .decodebytes (GNU_MO_DATA ))
165+ with open (MOFILE_BAD_MAGIC_NUMBER , 'wb' ) as fp :
166+ fp .write (base64 .decodebytes (GNU_MO_DATA_BAD_MAGIC_NUMBER ))
134167 with open (MOFILE_BAD_MAJOR_VERSION , 'wb' ) as fp :
135168 fp .write (base64 .decodebytes (GNU_MO_DATA_BAD_MAJOR_VERSION ))
136169 with open (MOFILE_BAD_MINOR_VERSION , 'wb' ) as fp :
137170 fp .write (base64 .decodebytes (GNU_MO_DATA_BAD_MINOR_VERSION ))
171+ with open (MOFILE_CORRUPT , 'wb' ) as fp :
172+ fp .write (base64 .decodebytes (GNU_MO_DATA_CORRUPT ))
138173 with open (UMOFILE , 'wb' ) as fp :
139174 fp .write (base64 .decodebytes (UMO_DATA ))
140175 with open (MMOFILE , 'wb' ) as fp :
@@ -249,6 +284,16 @@ def test_bindtextdomain(self):
249284 def test_textdomain (self ):
250285 self .assertEqual (gettext .textdomain (), 'gettext' )
251286
287+ def test_bad_magic_number (self ):
288+ with open (MOFILE_BAD_MAGIC_NUMBER , 'rb' ) as fp :
289+ with self .assertRaises (OSError ) as cm :
290+ gettext .GNUTranslations (fp )
291+
292+ exception = cm .exception
293+ self .assertEqual (exception .errno , 0 )
294+ self .assertEqual (exception .strerror , "Bad magic number" )
295+ self .assertEqual (exception .filename , MOFILE_BAD_MAGIC_NUMBER )
296+
252297 def test_bad_major_version (self ):
253298 with open (MOFILE_BAD_MAJOR_VERSION , 'rb' ) as fp :
254299 with self .assertRaises (OSError ) as cm :
@@ -264,6 +309,16 @@ def test_bad_minor_version(self):
264309 # Check that no error is thrown with a bad minor version number
265310 gettext .GNUTranslations (fp )
266311
312+ def test_corrupt_file (self ):
313+ with open (MOFILE_CORRUPT , 'rb' ) as fp :
314+ with self .assertRaises (OSError ) as cm :
315+ gettext .GNUTranslations (fp )
316+
317+ exception = cm .exception
318+ self .assertEqual (exception .errno , 0 )
319+ self .assertEqual (exception .strerror , "File is corrupt" )
320+ self .assertEqual (exception .filename , MOFILE_CORRUPT )
321+
267322 def test_some_translations (self ):
268323 eq = self .assertEqual
269324 # test some translations
@@ -768,6 +823,76 @@ def test_expand_lang(self):
768823 self .assertEqual (gettext ._expand_lang (locale ), expanded )
769824
770825
826+ class FindTestCase (unittest .TestCase ):
827+
828+ def setUp (self ):
829+ self .env = self .enterContext (os_helper .EnvironmentVarGuard ())
830+ self .tempdir = self .enterContext (os_helper .temp_cwd ())
831+
832+ for key in ('LANGUAGE' , 'LC_ALL' , 'LC_MESSAGES' , 'LANG' ):
833+ self .env .unset (key )
834+
835+ def create_mo_file (self , lang ):
836+ locale_dir = os .path .join (self .tempdir , "locale" )
837+ mofile_dir = os .path .join (locale_dir , lang , "LC_MESSAGES" )
838+ os .makedirs (mofile_dir )
839+ mo_file = os .path .join (mofile_dir , "mofile.mo" )
840+ with open (mo_file , "wb" ) as f :
841+ f .write (GNU_MO_DATA )
842+ return mo_file
843+
844+ def test_find_with_env_vars (self ):
845+ # test that find correctly finds the environment variables
846+ # when languages are not supplied
847+ mo_file = self .create_mo_file ("ga_IE" )
848+ for var in ('LANGUAGE' , 'LC_ALL' , 'LC_MESSAGES' , 'LANG' ):
849+ self .env .set (var , 'ga_IE' )
850+ result = gettext .find ("mofile" ,
851+ localedir = os .path .join (self .tempdir , "locale" ))
852+ self .assertEqual (result , mo_file )
853+ self .env .unset (var )
854+
855+ def test_find_with_languages (self ):
856+ # test that passed languages are used
857+ self .env .set ('LANGUAGE' , 'pt_BR' )
858+ mo_file = self .create_mo_file ("ga_IE" )
859+
860+ result = gettext .find ("mofile" ,
861+ localedir = os .path .join (self .tempdir , "locale" ),
862+ languages = ['ga_IE' ])
863+ self .assertEqual (result , mo_file )
864+
865+ @unittest .mock .patch ('gettext._expand_lang' )
866+ def test_find_with_no_lang (self , patch_expand_lang ):
867+ # no language can be found
868+ gettext .find ('foo' )
869+ patch_expand_lang .assert_called_with ('C' )
870+
871+ @unittest .mock .patch ('gettext._expand_lang' )
872+ def test_find_with_c (self , patch_expand_lang ):
873+ # 'C' is already in languages
874+ self .env .set ('LANGUAGE' , 'C' )
875+ gettext .find ('foo' )
876+ patch_expand_lang .assert_called_with ('C' )
877+
878+ def test_find_all (self ):
879+ # test that all are returned when all is set
880+ paths = []
881+ for lang in ["ga_IE" , "es_ES" ]:
882+ paths .append (self .create_mo_file (lang ))
883+ result = gettext .find ('mofile' ,
884+ localedir = os .path .join (self .tempdir , "locale" ),
885+ languages = ["ga_IE" , "es_ES" ], all = True )
886+ self .assertEqual (sorted (result ), sorted (paths ))
887+
888+ def test_find_deduplication (self ):
889+ # test that find removes duplicate languages
890+ mo_file = [self .create_mo_file ('ga_IE' )]
891+ result = gettext .find ("mofile" , localedir = os .path .join (self .tempdir , "locale" ),
892+ languages = ['ga_IE' , 'ga_IE' ], all = True )
893+ self .assertEqual (result , mo_file )
894+
895+
771896class MiscTestCase (unittest .TestCase ):
772897 def test__all__ (self ):
773898 support .check__all__ (self , gettext ,
0 commit comments