(root)/
Python-3.12.0/
Lib/
test/
test_mailcap.py
       1  import copy
       2  import os
       3  import sys
       4  import test.support
       5  import unittest
       6  from test.support import os_helper
       7  from test.support import warnings_helper
       8  
       9  
      10  mailcap = warnings_helper.import_deprecated('mailcap')
      11  
      12  
      13  # Location of mailcap file
      14  MAILCAPFILE = test.support.findfile("mailcap.txt")
      15  
      16  # Dict to act as mock mailcap entry for this test
      17  # The keys and values should match the contents of MAILCAPFILE
      18  MAILCAPDICT = {
      19      'application/x-movie':
      20          [{'compose': 'moviemaker %s',
      21            'x11-bitmap': '"/usr/lib/Zmail/bitmaps/movie.xbm"',
      22            'description': '"Movie"',
      23            'view': 'movieplayer %s',
      24            'lineno': 4}],
      25      'application/*':
      26          [{'copiousoutput': '',
      27            'view': 'echo "This is \\"%t\\" but        is 50 \\% Greek to me" \\; cat %s',
      28            'lineno': 5}],
      29      'audio/basic':
      30          [{'edit': 'audiocompose %s',
      31            'compose': 'audiocompose %s',
      32            'description': '"An audio fragment"',
      33            'view': 'showaudio %s',
      34            'lineno': 6}],
      35      'video/mpeg':
      36          [{'view': 'mpeg_play %s', 'lineno': 13}],
      37      'application/postscript':
      38          [{'needsterminal': '', 'view': 'ps-to-terminal %s', 'lineno': 1},
      39           {'compose': 'idraw %s', 'view': 'ps-to-terminal %s', 'lineno': 2}],
      40      'application/x-dvi':
      41          [{'view': 'xdvi %s', 'lineno': 3}],
      42      'message/external-body':
      43          [{'composetyped': 'extcompose %s',
      44            'description': '"A reference to data stored in an external location"',
      45            'needsterminal': '',
      46            'view': 'showexternal %s %{access-type} %{name} %{site}     %{directory} %{mode} %{server}',
      47            'lineno': 10}],
      48      'text/richtext':
      49          [{'test': 'test "`echo     %{charset} | tr \'[A-Z]\' \'[a-z]\'`"  = iso-8859-8',
      50            'copiousoutput': '',
      51            'view': 'shownonascii iso-8859-8 -e richtext -p %s',
      52            'lineno': 11}],
      53      'image/x-xwindowdump':
      54          [{'view': 'display %s', 'lineno': 9}],
      55      'audio/*':
      56          [{'view': '/usr/local/bin/showaudio %t', 'lineno': 7}],
      57      'video/*':
      58          [{'view': 'animate %s', 'lineno': 12}],
      59      'application/frame':
      60          [{'print': '"cat %s | lp"', 'view': 'showframe %s', 'lineno': 0}],
      61      'image/rgb':
      62          [{'view': 'display %s', 'lineno': 8}]
      63  }
      64  
      65  # For backwards compatibility, readmailcapfile() and lookup() still support
      66  # the old version of mailcapdict without line numbers.
      67  MAILCAPDICT_DEPRECATED = copy.deepcopy(MAILCAPDICT)
      68  for entry_list in MAILCAPDICT_DEPRECATED.values():
      69      for entry in entry_list:
      70          entry.pop('lineno')
      71  
      72  
      73  class ESC[4;38;5;81mHelperFunctionTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      74  
      75      def test_listmailcapfiles(self):
      76          # The return value for listmailcapfiles() will vary by system.
      77          # So verify that listmailcapfiles() returns a list of strings that is of
      78          # non-zero length.
      79          mcfiles = mailcap.listmailcapfiles()
      80          self.assertIsInstance(mcfiles, list)
      81          for m in mcfiles:
      82              self.assertIsInstance(m, str)
      83          with os_helper.EnvironmentVarGuard() as env:
      84              # According to RFC 1524, if MAILCAPS env variable exists, use that
      85              # and only that.
      86              if "MAILCAPS" in env:
      87                  env_mailcaps = env["MAILCAPS"].split(os.pathsep)
      88              else:
      89                  env_mailcaps = ["/testdir1/.mailcap", "/testdir2/mailcap"]
      90                  env["MAILCAPS"] = os.pathsep.join(env_mailcaps)
      91                  mcfiles = mailcap.listmailcapfiles()
      92          self.assertEqual(env_mailcaps, mcfiles)
      93  
      94      def test_readmailcapfile(self):
      95          # Test readmailcapfile() using test file. It should match MAILCAPDICT.
      96          with open(MAILCAPFILE, 'r') as mcf:
      97              with self.assertWarns(DeprecationWarning):
      98                  d = mailcap.readmailcapfile(mcf)
      99          self.assertDictEqual(d, MAILCAPDICT_DEPRECATED)
     100  
     101      def test_lookup(self):
     102          # Test without key
     103          expected = [{'view': 'animate %s', 'lineno': 12},
     104                      {'view': 'mpeg_play %s', 'lineno': 13}]
     105          actual = mailcap.lookup(MAILCAPDICT, 'video/mpeg')
     106          self.assertListEqual(expected, actual)
     107  
     108          # Test with key
     109          key = 'compose'
     110          expected = [{'edit': 'audiocompose %s',
     111                       'compose': 'audiocompose %s',
     112                       'description': '"An audio fragment"',
     113                       'view': 'showaudio %s',
     114                       'lineno': 6}]
     115          actual = mailcap.lookup(MAILCAPDICT, 'audio/basic', key)
     116          self.assertListEqual(expected, actual)
     117  
     118          # Test on user-defined dicts without line numbers
     119          expected = [{'view': 'mpeg_play %s'}, {'view': 'animate %s'}]
     120          actual = mailcap.lookup(MAILCAPDICT_DEPRECATED, 'video/mpeg')
     121          self.assertListEqual(expected, actual)
     122  
     123      def test_subst(self):
     124          plist = ['id=1', 'number=2', 'total=3']
     125          # test case: ([field, MIMEtype, filename, plist=[]], <expected string>)
     126          test_cases = [
     127              (["", "audio/*", "foo.txt"], ""),
     128              (["echo foo", "audio/*", "foo.txt"], "echo foo"),
     129              (["echo %s", "audio/*", "foo.txt"], "echo foo.txt"),
     130              (["echo %t", "audio/wav", "foo.txt"], "echo audio/wav"),
     131              (["echo \\%t", "audio/*", "foo.txt"], "echo %t"),
     132              (["echo foo", "audio/*", "foo.txt", plist], "echo foo"),
     133              (["echo %{total}", "audio/*", "foo.txt", plist], "echo 3")
     134          ]
     135          for tc in test_cases:
     136              self.assertEqual(mailcap.subst(*tc[0]), tc[1])
     137  
     138  
     139  class ESC[4;38;5;81mGetcapsTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     140  
     141      def test_mock_getcaps(self):
     142          # Test mailcap.getcaps() using mock mailcap file in this dir.
     143          # Temporarily override any existing system mailcap file by pointing the
     144          # MAILCAPS environment variable to our mock file.
     145          with os_helper.EnvironmentVarGuard() as env:
     146              env["MAILCAPS"] = MAILCAPFILE
     147              caps = mailcap.getcaps()
     148              self.assertDictEqual(caps, MAILCAPDICT)
     149  
     150      def test_system_mailcap(self):
     151          # Test mailcap.getcaps() with mailcap file(s) on system, if any.
     152          caps = mailcap.getcaps()
     153          self.assertIsInstance(caps, dict)
     154          mailcapfiles = mailcap.listmailcapfiles()
     155          existingmcfiles = [mcf for mcf in mailcapfiles if os.path.exists(mcf)]
     156          if existingmcfiles:
     157              # At least 1 mailcap file exists, so test that.
     158              for (k, v) in caps.items():
     159                  self.assertIsInstance(k, str)
     160                  self.assertIsInstance(v, list)
     161                  for e in v:
     162                      self.assertIsInstance(e, dict)
     163          else:
     164              # No mailcap files on system. getcaps() should return empty dict.
     165              self.assertEqual({}, caps)
     166  
     167  
     168  class ESC[4;38;5;81mFindmatchTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     169  
     170      def test_findmatch(self):
     171  
     172          # default findmatch arguments
     173          c = MAILCAPDICT
     174          fname = "foo.txt"
     175          plist = ["access-type=default", "name=john", "site=python.org",
     176                   "directory=/tmp", "mode=foo", "server=bar"]
     177          audio_basic_entry = {
     178              'edit': 'audiocompose %s',
     179              'compose': 'audiocompose %s',
     180              'description': '"An audio fragment"',
     181              'view': 'showaudio %s',
     182              'lineno': 6
     183          }
     184          audio_entry = {"view": "/usr/local/bin/showaudio %t", 'lineno': 7}
     185          video_entry = {'view': 'animate %s', 'lineno': 12}
     186          message_entry = {
     187              'composetyped': 'extcompose %s',
     188              'description': '"A reference to data stored in an external location"', 'needsterminal': '',
     189              'view': 'showexternal %s %{access-type} %{name} %{site}     %{directory} %{mode} %{server}',
     190              'lineno': 10,
     191          }
     192  
     193          # test case: (findmatch args, findmatch keyword args, expected output)
     194          #   positional args: caps, MIMEtype
     195          #   keyword args: key="view", filename="/dev/null", plist=[]
     196          #   output: (command line, mailcap entry)
     197          cases = [
     198              ([{}, "video/mpeg"], {}, (None, None)),
     199              ([c, "foo/bar"], {}, (None, None)),
     200              ([c, "video/mpeg"], {}, ('animate /dev/null', video_entry)),
     201              ([c, "audio/basic", "edit"], {}, ("audiocompose /dev/null", audio_basic_entry)),
     202              ([c, "audio/basic", "compose"], {}, ("audiocompose /dev/null", audio_basic_entry)),
     203              ([c, "audio/basic", "description"], {}, ('"An audio fragment"', audio_basic_entry)),
     204              ([c, "audio/basic", "foobar"], {}, (None, None)),
     205              ([c, "video/*"], {"filename": fname}, ("animate %s" % fname, video_entry)),
     206              ([c, "audio/basic", "compose"],
     207               {"filename": fname},
     208               ("audiocompose %s" % fname, audio_basic_entry)),
     209              ([c, "audio/basic"],
     210               {"key": "description", "filename": fname},
     211               ('"An audio fragment"', audio_basic_entry)),
     212              ([c, "audio/wav"],
     213               {"filename": fname},
     214               ("/usr/local/bin/showaudio audio/wav", audio_entry)),
     215              ([c, "message/external-body"],
     216               {"plist": plist},
     217               ("showexternal /dev/null default john python.org     /tmp foo bar", message_entry))
     218          ]
     219          self._run_cases(cases)
     220  
     221      @unittest.skipUnless(os.name == "posix", "Requires 'test' command on system")
     222      @unittest.skipIf(sys.platform == "vxworks", "'test' command is not supported on VxWorks")
     223      @unittest.skipUnless(
     224          test.support.has_subprocess_support,
     225          "'test' command needs process support."
     226      )
     227      def test_test(self):
     228          # findmatch() will automatically check any "test" conditions and skip
     229          # the entry if the check fails.
     230          caps = {"test/pass": [{"test": "test 1 -eq 1"}],
     231                  "test/fail": [{"test": "test 1 -eq 0"}]}
     232          # test case: (findmatch args, findmatch keyword args, expected output)
     233          #   positional args: caps, MIMEtype, key ("test")
     234          #   keyword args: N/A
     235          #   output: (command line, mailcap entry)
     236          cases = [
     237              # findmatch will return the mailcap entry for test/pass because it evaluates to true
     238              ([caps, "test/pass", "test"], {}, ("test 1 -eq 1", {"test": "test 1 -eq 1"})),
     239              # findmatch will return None because test/fail evaluates to false
     240              ([caps, "test/fail", "test"], {}, (None, None))
     241          ]
     242          self._run_cases(cases)
     243  
     244      def test_unsafe_mailcap_input(self):
     245          with self.assertWarnsRegex(mailcap.UnsafeMailcapInput,
     246                                     'Refusing to substitute parameter.*'
     247                                     'into a shell command'):
     248              unsafe_param = mailcap.subst("echo %{total}",
     249                                           "audio/wav",
     250                                           "foo.txt",
     251                                           ["total=*"])
     252              self.assertEqual(unsafe_param, None)
     253  
     254          with self.assertWarnsRegex(mailcap.UnsafeMailcapInput,
     255                                     'Refusing to substitute MIME type'
     256                                     '.*into a shell'):
     257              unsafe_mimetype = mailcap.subst("echo %t", "audio/*", "foo.txt")
     258              self.assertEqual(unsafe_mimetype, None)
     259  
     260          with self.assertWarnsRegex(mailcap.UnsafeMailcapInput,
     261                                     'Refusing to use mailcap with filename.*'
     262                                     'Use a safe temporary filename.'):
     263              unsafe_filename = mailcap.findmatch(MAILCAPDICT,
     264                                                  "audio/wav",
     265                                                  filename="foo*.txt")
     266              self.assertEqual(unsafe_filename, (None, None))
     267  
     268      def _run_cases(self, cases):
     269          for c in cases:
     270              self.assertEqual(mailcap.findmatch(*c[0], **c[1]), c[2])
     271  
     272  
     273  if __name__ == '__main__':
     274      unittest.main()