1
0

0.4.27-19-g2a01b18.add-magic-symlink-support-and-tests-for-same.patch 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. Subject: Add MAGIC_SYMLINK support, and tests for same
  2. Origin: upstream, commit 0.4.27-19-g2a01b18 <https://github.com/ahupp/python-magic/commit/0.4.27-19-g2a01b18>
  3. Author: Adam Hupp <adam@hupp.org>
  4. Date: Fri Aug 25 11:02:53 2023 -0700
  5. Forwarded: not-needed
  6. --- a/CHANGELOG
  7. +++ b/CHANGELOG
  8. @@ -1,41 +1,51 @@
  9. +Changes to 0.4.29:
  10. +
  11. +- support MAGIC_SYMLINK (via follow_symlink flag on Magic constructor)
  12. +- correctly throw FileNotFoundException depending on flag
  13. +
  14. Changes to 0.4.28:
  15. - - support "magic-1.dll" on Windows, which is produced by vcpkg
  16. - - add python 3.10 to tox config
  17. - - update test for upstream gzip extensions
  18. +
  19. +- support "magic-1.dll" on Windows, which is produced by vcpkg
  20. +- add python 3.10 to tox config
  21. +- update test for upstream gzip extensions
  22. Changes to 0.4.27:
  23. - - remove spurious pyproject.toml that breaks source builds
  24. +
  25. +- remove spurious pyproject.toml that breaks source builds
  26. Changes to 0.4.26:
  27. - - Use tox for all multi-version testing
  28. - - Fix use of pytest, use it via tox
  29. +
  30. +- Use tox for all multi-version testing
  31. +- Fix use of pytest, use it via tox
  32. Changes to 0.4.25:
  33. - - Support os.PathLike values in Magic.from_file and magic.from_file
  34. - - Handle some versions of libmagic that return mime string without charset
  35. - - Fix tests for file 5.41
  36. - - Include typing stub in package
  37. +
  38. +- Support os.PathLike values in Magic.from_file and magic.from_file
  39. +- Handle some versions of libmagic that return mime string without charset
  40. +- Fix tests for file 5.41
  41. +- Include typing stub in package
  42. Changes to 0.4.24:
  43. - - Fix regression in library loading on some Alpine docker images.
  44. +
  45. +- Fix regression in library loading on some Alpine docker images.
  46. Changes to 0.4.23
  47. - - Include a `py.typed` sentinal to enable type checking
  48. - - Improve fix for attribute error during destruction
  49. - - Cleanup library loading logic
  50. - - Add new homebrew library dir for OSX
  51. +- Include a `py.typed` sentinal to enable type checking
  52. +- Improve fix for attribute error during destruction
  53. +- Cleanup library loading logic
  54. +- Add new homebrew library dir for OSX
  55. Changes to 0.4.21, 0.4.22
  56. - - Unify dll loader between the standard and compat library, fixing load
  57. - failures on some previously supported platforms.
  58. +- Unify dll loader between the standard and compat library, fixing load
  59. + failures on some previously supported platforms.
  60. Changes to 0.4.20
  61. - merge in a compatibility layer for the upstream libmagic python binding.
  62. Since both this package and that one are called 'magic', this compat layer
  63. - removes a very common source of runtime errors. Use of that libmagic API will
  64. + removes a very common source of runtime errors. Use of that libmagic API will
  65. produce a deprecation warning.
  66. - support python 3.9 in tests and pypi metadata
  67. @@ -44,9 +54,9 @@
  68. rather than a filename.
  69. - sometimes the returned description includes snippets of the file, e.g a title
  70. - for MS Word docs. Since this is in an unknown encoding, we would throw a
  71. - unicode decode error trying to decode. Now, it decodes with
  72. - 'backslashreplace' to handle this more gracefully. The undecodable characters
  73. + for MS Word docs. Since this is in an unknown encoding, we would throw a
  74. + unicode decode error trying to decode. Now, it decodes with
  75. + 'backslashreplace' to handle this more gracefully. The undecodable characters
  76. are replaced with hex escapes.
  77. - add support for MAGIC_EXTENSION, to return possible file extensions.
  78. @@ -55,18 +65,18 @@
  79. Changes in 0.4.18
  80. -- Make bindings for magic_[set|get]param optional, and throw NotImplementedError
  81. -if they are used but not supported. Only call setparam() in the constructor if
  82. -it's supported. This prevents breakage on CentOS7 which uses an old version of
  83. -libmagic.
  84. +- Make bindings for magic\_[set|get]param optional, and throw NotImplementedError
  85. + if they are used but not supported. Only call setparam() in the constructor if
  86. + it's supported. This prevents breakage on CentOS7 which uses an old version of
  87. + libmagic.
  88. - Add tests for CentOS 7 & 8
  89. Changes in 0.4.16 and 0.4.17
  90. - add MAGIC_MIME_TYPE constant, use that in preference to MAGIC_MIME internally.
  91. -This sets up for a breaking change in a future major version bump where
  92. -MAGIC_MIME will change to mathch magic.h.
  93. + This sets up for a breaking change in a future major version bump where
  94. + MAGIC_MIME will change to mathch magic.h.
  95. - add magic.version() function to return library version
  96. - add setparam/getparam to control internal behavior
  97. - increase internal limits with setparam to prevent spurious error on some jpeg files
  98. @@ -76,12 +86,12 @@
  99. - include tests in source distribution
  100. - many test improvements:
  101. --- tox runner support
  102. --- remove deprecated test_suite field from setup.py
  103. --- docker tests that cover all LTS ubuntu versions
  104. --- add test for snapp file identification
  105. + -- tox runner support
  106. + -- remove deprecated test_suite field from setup.py
  107. + -- docker tests that cover all LTS ubuntu versions
  108. + -- add test for snapp file identification
  109. - doc improvements
  110. --- document dependency install process for debian
  111. --- various typos
  112. --- document test running process
  113. + -- document dependency install process for debian
  114. + -- various typos
  115. + -- document test running process
  116. --- a/magic/__init__.py
  117. +++ b/magic/__init__.py
  118. @@ -39,7 +39,8 @@
  119. """
  120. def __init__(self, mime=False, magic_file=None, mime_encoding=False,
  121. - keep_going=False, uncompress=False, raw=False, extension=False):
  122. + keep_going=False, uncompress=False, raw=False, extension=False,
  123. + follow_symlinks=False):
  124. """
  125. Create a new libmagic wrapper.
  126. @@ -65,6 +66,9 @@
  127. if extension:
  128. self.flags |= MAGIC_EXTENSION
  129. + if follow_symlinks:
  130. + self.flags |= MAGIC_SYMLINK
  131. +
  132. self.cookie = magic_open(self.flags)
  133. self.lock = threading.Lock()
  134. --- a/test/README
  135. +++ b/test/README
  136. @@ -1,6 +1,4 @@
  137. There are a few ways to run the python-magic tests
  138. - 1. `pytest` will run the test suite against your default version of python
  139. - 2. `./test/run_all_versions.py` will run the tests against all installed versions of python.
  140. - 3. `./test/run_all_docker_test.sh` will run against a variety of different Linux distributions, using docker.
  141. -
  142. +1. `tox` will run the tests against all installed versions of python
  143. +2. `./test/run_all_docker_test.sh` will run against a variety of different Linux distributions, using docker.
  144. --- a/test/python_magic_test.py
  145. +++ b/test/python_magic_test.py
  146. @@ -1,9 +1,10 @@
  147. +import tempfile
  148. import os
  149. # for output which reports a local time
  150. -os.environ['TZ'] = 'GMT'
  151. +os.environ["TZ"] = "GMT"
  152. -if os.environ.get('LC_ALL', '') != 'en_US.UTF-8':
  153. +if os.environ.get("LC_ALL", "") != "en_US.UTF-8":
  154. # this ensure we're in a utf-8 default filesystem encoding which is
  155. # necessary for some tests
  156. raise Exception("must run `export LC_ALL=en_US.UTF-8` before running test suite")
  157. @@ -16,10 +17,11 @@
  158. import sys
  159. # magic_descriptor is broken (?) in centos 7, so don't run those tests
  160. -SKIP_FROM_DESCRIPTOR = bool(os.environ.get('SKIP_FROM_DESCRIPTOR'))
  161. +SKIP_FROM_DESCRIPTOR = bool(os.environ.get("SKIP_FROM_DESCRIPTOR"))
  162. +
  163. class MagicTest(unittest.TestCase):
  164. - TESTDATA_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'testdata'))
  165. + TESTDATA_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "testdata"))
  166. def test_version(self):
  167. try:
  168. @@ -28,20 +30,19 @@
  169. pass
  170. def test_fs_encoding(self):
  171. - self.assertEqual('utf-8', sys.getfilesystemencoding().lower())
  172. + self.assertEqual("utf-8", sys.getfilesystemencoding().lower())
  173. def assert_values(self, m, expected_values, buf_equals_file=True):
  174. for filename, expected_value in expected_values.items():
  175. try:
  176. filename = os.path.join(self.TESTDATA_DIR, filename)
  177. except TypeError:
  178. - filename = os.path.join(
  179. - self.TESTDATA_DIR.encode('utf-8'), filename)
  180. + filename = os.path.join(self.TESTDATA_DIR.encode("utf-8"), filename)
  181. if type(expected_value) is not tuple:
  182. expected_value = (expected_value,)
  183. - with open(filename, 'rb') as f:
  184. + with open(filename, "rb") as f:
  185. buf_value = m.from_buffer(f.read())
  186. file_value = m.from_file(filename)
  187. @@ -55,10 +56,10 @@
  188. def test_from_file_str_and_bytes(self):
  189. filename = os.path.join(self.TESTDATA_DIR, "test.pdf")
  190. - self.assertEqual('application/pdf',
  191. - magic.from_file(filename, mime=True))
  192. - self.assertEqual('application/pdf',
  193. - magic.from_file(filename.encode('utf-8'), mime=True))
  194. + self.assertEqual("application/pdf", magic.from_file(filename, mime=True))
  195. + self.assertEqual(
  196. + "application/pdf", magic.from_file(filename.encode("utf-8"), mime=True)
  197. + )
  198. def test_from_descriptor_str_and_bytes(self):
  199. if SKIP_FROM_DESCRIPTOR:
  200. @@ -66,10 +67,12 @@
  201. filename = os.path.join(self.TESTDATA_DIR, "test.pdf")
  202. with open(filename) as f:
  203. - self.assertEqual('application/pdf',
  204. - magic.from_descriptor(f.fileno(), mime=True))
  205. - self.assertEqual('application/pdf',
  206. - magic.from_descriptor(f.fileno(), mime=True))
  207. + self.assertEqual(
  208. + "application/pdf", magic.from_descriptor(f.fileno(), mime=True)
  209. + )
  210. + self.assertEqual(
  211. + "application/pdf", magic.from_descriptor(f.fileno(), mime=True)
  212. + )
  213. def test_from_buffer_str_and_bytes(self):
  214. if SKIP_FROM_DESCRIPTOR:
  215. @@ -78,125 +81,151 @@
  216. self.assertTrue(
  217. m.from_buffer('#!/usr/bin/env python\nprint("foo")')
  218. - in ("text/x-python", "text/x-script.python"))
  219. + in ("text/x-python", "text/x-script.python")
  220. + )
  221. self.assertTrue(
  222. m.from_buffer(b'#!/usr/bin/env python\nprint("foo")')
  223. - in ("text/x-python", "text/x-script.python"))
  224. + in ("text/x-python", "text/x-script.python")
  225. + )
  226. def test_mime_types(self):
  227. - dest = os.path.join(MagicTest.TESTDATA_DIR,
  228. - b'\xce\xbb'.decode('utf-8'))
  229. - shutil.copyfile(os.path.join(MagicTest.TESTDATA_DIR, 'lambda'), dest)
  230. + dest = os.path.join(MagicTest.TESTDATA_DIR, b"\xce\xbb".decode("utf-8"))
  231. + shutil.copyfile(os.path.join(MagicTest.TESTDATA_DIR, "lambda"), dest)
  232. try:
  233. m = magic.Magic(mime=True)
  234. - self.assert_values(m, {
  235. - 'magic._pyc_': ('application/octet-stream', 'text/x-bytecode.python', 'application/x-bytecode.python'),
  236. - 'test.pdf': 'application/pdf',
  237. - 'test.gz': ('application/gzip', 'application/x-gzip'),
  238. - 'test.snappy.parquet': 'application/octet-stream',
  239. - 'text.txt': 'text/plain',
  240. - b'\xce\xbb'.decode('utf-8'): 'text/plain',
  241. - b'\xce\xbb': 'text/plain',
  242. - })
  243. + self.assert_values(
  244. + m,
  245. + {
  246. + "magic._pyc_": (
  247. + "application/octet-stream",
  248. + "text/x-bytecode.python",
  249. + "application/x-bytecode.python",
  250. + ),
  251. + "test.pdf": "application/pdf",
  252. + "test.gz": ("application/gzip", "application/x-gzip"),
  253. + "test.snappy.parquet": "application/octet-stream",
  254. + "text.txt": "text/plain",
  255. + b"\xce\xbb".decode("utf-8"): "text/plain",
  256. + b"\xce\xbb": "text/plain",
  257. + },
  258. + )
  259. finally:
  260. os.unlink(dest)
  261. def test_descriptions(self):
  262. m = magic.Magic()
  263. - os.environ['TZ'] = 'UTC' # To get last modified date of test.gz in UTC
  264. + os.environ["TZ"] = "UTC" # To get last modified date of test.gz in UTC
  265. try:
  266. - self.assert_values(m, {
  267. - 'magic._pyc_': 'python 2.4 byte-compiled',
  268. - 'test.pdf': ('PDF document, version 1.2',
  269. - 'PDF document, version 1.2, 2 pages',
  270. - 'PDF document, version 1.2, 2 page(s)'),
  271. - 'test.gz':
  272. - ('gzip compressed data, was "test", from Unix, last '
  273. - 'modified: Sun Jun 29 01:32:52 2008',
  274. - 'gzip compressed data, was "test", last modified'
  275. - ': Sun Jun 29 01:32:52 2008, from Unix',
  276. - 'gzip compressed data, was "test", last modified'
  277. - ': Sun Jun 29 01:32:52 2008, from Unix, original size 15',
  278. - 'gzip compressed data, was "test", '
  279. - 'last modified: Sun Jun 29 01:32:52 2008, '
  280. - 'from Unix, original size modulo 2^32 15',
  281. - 'gzip compressed data, was "test", last modified'
  282. - ': Sun Jun 29 01:32:52 2008, from Unix, truncated'
  283. - ),
  284. - 'text.txt': 'ASCII text',
  285. - 'test.snappy.parquet': ('Apache Parquet', 'Par archive data'),
  286. - }, buf_equals_file=False)
  287. + self.assert_values(
  288. + m,
  289. + {
  290. + "magic._pyc_": "python 2.4 byte-compiled",
  291. + "test.pdf": (
  292. + "PDF document, version 1.2",
  293. + "PDF document, version 1.2, 2 pages",
  294. + "PDF document, version 1.2, 2 page(s)",
  295. + ),
  296. + "test.gz": (
  297. + 'gzip compressed data, was "test", from Unix, last '
  298. + "modified: Sun Jun 29 01:32:52 2008",
  299. + 'gzip compressed data, was "test", last modified'
  300. + ": Sun Jun 29 01:32:52 2008, from Unix",
  301. + 'gzip compressed data, was "test", last modified'
  302. + ": Sun Jun 29 01:32:52 2008, from Unix, original size 15",
  303. + 'gzip compressed data, was "test", '
  304. + "last modified: Sun Jun 29 01:32:52 2008, "
  305. + "from Unix, original size modulo 2^32 15",
  306. + 'gzip compressed data, was "test", last modified'
  307. + ": Sun Jun 29 01:32:52 2008, from Unix, truncated",
  308. + ),
  309. + "text.txt": "ASCII text",
  310. + "test.snappy.parquet": ("Apache Parquet", "Par archive data"),
  311. + },
  312. + buf_equals_file=False,
  313. + )
  314. finally:
  315. - del os.environ['TZ']
  316. + del os.environ["TZ"]
  317. def test_extension(self):
  318. try:
  319. m = magic.Magic(extension=True)
  320. - self.assert_values(m, {
  321. - # some versions return '' for the extensions of a gz file,
  322. - # including w/ the command line. Who knows...
  323. - 'test.gz': ('gz/tgz/tpz/zabw/svgz/adz/kmy/xcfgz', 'gz/tgz/tpz/zabw/svgz', '', '???'),
  324. - 'name_use.jpg': 'jpeg/jpg/jpe/jfif',
  325. - })
  326. + self.assert_values(
  327. + m,
  328. + {
  329. + # some versions return '' for the extensions of a gz file,
  330. + # including w/ the command line. Who knows...
  331. + "test.gz": (
  332. + "gz/tgz/tpz/zabw/svgz/adz/kmy/xcfgz",
  333. + "gz/tgz/tpz/zabw/svgz",
  334. + "",
  335. + "???",
  336. + ),
  337. + "name_use.jpg": "jpeg/jpg/jpe/jfif",
  338. + },
  339. + )
  340. except NotImplementedError:
  341. - self.skipTest('MAGIC_EXTENSION not supported in this version')
  342. + self.skipTest("MAGIC_EXTENSION not supported in this version")
  343. def test_unicode_result_nonraw(self):
  344. m = magic.Magic(raw=False)
  345. - src = os.path.join(MagicTest.TESTDATA_DIR, 'pgpunicode')
  346. + src = os.path.join(MagicTest.TESTDATA_DIR, "pgpunicode")
  347. result = m.from_file(src)
  348. # NOTE: This check is added as otherwise some magic files don't identify the test case as a PGP key.
  349. - if 'PGP' in result:
  350. + if "PGP" in result:
  351. assert r"PGP\011Secret Sub-key -" == result
  352. else:
  353. raise unittest.SkipTest("Magic file doesn't return expected type.")
  354. def test_unicode_result_raw(self):
  355. m = magic.Magic(raw=True)
  356. - src = os.path.join(MagicTest.TESTDATA_DIR, 'pgpunicode')
  357. + src = os.path.join(MagicTest.TESTDATA_DIR, "pgpunicode")
  358. result = m.from_file(src)
  359. - if 'PGP' in result:
  360. - assert b'PGP\tSecret Sub-key -' == result.encode('utf-8')
  361. + if "PGP" in result:
  362. + assert b"PGP\tSecret Sub-key -" == result.encode("utf-8")
  363. else:
  364. raise unittest.SkipTest("Magic file doesn't return expected type.")
  365. def test_mime_encodings(self):
  366. m = magic.Magic(mime_encoding=True)
  367. - self.assert_values(m, {
  368. - 'text-iso8859-1.txt': 'iso-8859-1',
  369. - 'text.txt': 'us-ascii',
  370. - })
  371. + self.assert_values(
  372. + m,
  373. + {
  374. + "text-iso8859-1.txt": "iso-8859-1",
  375. + "text.txt": "us-ascii",
  376. + },
  377. + )
  378. def test_errors(self):
  379. m = magic.Magic()
  380. - self.assertRaises(IOError, m.from_file, 'nonexistent')
  381. - self.assertRaises(magic.MagicException, magic.Magic,
  382. - magic_file='nonexistent')
  383. - os.environ['MAGIC'] = 'nonexistent'
  384. + self.assertRaises(IOError, m.from_file, "nonexistent")
  385. + self.assertRaises(magic.MagicException, magic.Magic, magic_file="nonexistent")
  386. + os.environ["MAGIC"] = "nonexistent"
  387. try:
  388. self.assertRaises(magic.MagicException, magic.Magic)
  389. finally:
  390. - del os.environ['MAGIC']
  391. + del os.environ["MAGIC"]
  392. def test_keep_going(self):
  393. - filename = os.path.join(self.TESTDATA_DIR, 'keep-going.jpg')
  394. + filename = os.path.join(self.TESTDATA_DIR, "keep-going.jpg")
  395. m = magic.Magic(mime=True)
  396. - self.assertEqual(m.from_file(filename), 'image/jpeg')
  397. + self.assertEqual(m.from_file(filename), "image/jpeg")
  398. try:
  399. # this will throw if you have an "old" version of the library
  400. # I'm otherwise not sure how to query if keep_going is supported
  401. magic.version()
  402. m = magic.Magic(mime=True, keep_going=True)
  403. - self.assertEqual(m.from_file(filename),
  404. - 'image/jpeg\\012- application/octet-stream')
  405. + self.assertEqual(
  406. + m.from_file(filename), "image/jpeg\\012- application/octet-stream"
  407. + )
  408. except NotImplementedError:
  409. pass
  410. def test_rethrow(self):
  411. old = magic.magic_buffer
  412. try:
  413. +
  414. def t(x, y):
  415. raise magic.MagicException("passthrough")
  416. @@ -217,16 +246,47 @@
  417. def test_name_count(self):
  418. m = magic.Magic()
  419. - with open(os.path.join(self.TESTDATA_DIR, 'name_use.jpg'), 'rb') as f:
  420. + with open(os.path.join(self.TESTDATA_DIR, "name_use.jpg"), "rb") as f:
  421. m.from_buffer(f.read())
  422. def test_pathlike(self):
  423. if sys.version_info < (3, 6):
  424. return
  425. from pathlib import Path
  426. - path = Path(self.TESTDATA_DIR, "test.pdf")
  427. +
  428. + path = Path(self.TESTDATA_DIR, "test.pdf")
  429. m = magic.Magic(mime=True)
  430. - self.assertEqual('application/pdf', m.from_file(path))
  431. + self.assertEqual("application/pdf", m.from_file(path))
  432. +
  433. + def test_symlink(self):
  434. + # TODO: 3.0
  435. + if not hasattr(tempfile, "TemporaryDirectory"):
  436. + return
  437. +
  438. + with tempfile.TemporaryDirectory() as tmp:
  439. + tmp_link = os.path.join(tmp, "test_link")
  440. + tmp_broken = os.path.join(tmp, "nonexistent")
  441. +
  442. + os.symlink(
  443. + os.path.join(self.TESTDATA_DIR, "test.pdf"),
  444. + tmp_link,
  445. + )
  446. +
  447. + os.symlink("/nonexistent", tmp_broken)
  448. +
  449. + m = magic.Magic()
  450. + m_follow = magic.Magic(follow_symlinks=True)
  451. + self.assertTrue(m.from_file(tmp_link).startswith("symbolic link to "))
  452. + self.assertTrue(m_follow.from_file(tmp_link).startswith("PDF document"))
  453. +
  454. + self.assertTrue(
  455. + m.from_file(tmp_broken).startswith(
  456. + "broken symbolic link to /nonexistent"
  457. + )
  458. + )
  459. +
  460. + self.assertRaises(IOError, m_follow.from_file, tmp_broken)
  461. +
  462. -if __name__ == '__main__':
  463. +if __name__ == "__main__":
  464. unittest.main()