From 5cf4894836bd537161df467890d9b986678a7937 Mon Sep 17 00:00:00 2001 From: Anton Samarchyan Date: Wed, 28 Dec 2016 15:20:24 -0500 Subject: [PATCH] Fixed #27628 -- Fixed unarchiving a file without permission data. --- AUTHORS | 1 + django/utils/archive.py | 17 ++++++++++++++--- tests/utils_tests/archives/foobar.tar | Bin 5632 -> 10240 bytes tests/utils_tests/archives/foobar.tar.bz2 | Bin 265 -> 294 bytes tests/utils_tests/archives/foobar.tar.gz | Bin 253 -> 291 bytes tests/utils_tests/archives/foobar.zip | Bin 810 -> 1434 bytes tests/utils_tests/test_archive.py | 4 ++++ 7 files changed, 19 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 82938eea889..4a7396a7a9e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -63,6 +63,7 @@ answer newbie questions, and generally made Django that much better: Anssi Kääriäinen ant9000@netwise.it Anthony Briggs + Anton Samarchyan Antoni Aloy Antonio Cavedoni Antonis Christofides diff --git a/django/utils/archive.py b/django/utils/archive.py index f8c1a0f83b9..57e87658a65 100644 --- a/django/utils/archive.py +++ b/django/utils/archive.py @@ -23,6 +23,7 @@ THE SOFTWARE. """ import os import shutil +import stat import tarfile import zipfile @@ -98,6 +99,16 @@ class BaseArchive(object): """ Base Archive class. Implementations should inherit this class. """ + @staticmethod + def _copy_permissions(mode, filename): + """ + If the file in the archive has some permissions (this assumes a file + won't be writable/executable without being readable), apply those + permissions to the unarchived file. + """ + if mode & stat.S_IROTH: + os.chmod(filename, mode) + def split_leading_dir(self, path): path = str(path) path = path.lstrip('/').lstrip('\\') @@ -164,7 +175,7 @@ class TarArchive(BaseArchive): os.makedirs(dirname) with open(filename, 'wb') as outfile: shutil.copyfileobj(extracted, outfile) - os.chmod(filename, member.mode) + self._copy_permissions(member.mode, filename) finally: if extracted: extracted.close() @@ -200,9 +211,9 @@ class ZipArchive(BaseArchive): else: with open(filename, 'wb') as outfile: outfile.write(data) - # convert ZipInfo.external_attr to mode + # Convert ZipInfo.external_attr to mode mode = info.external_attr >> 16 - os.chmod(filename, mode) + self._copy_permissions(mode, filename) def close(self): self._archive.close() diff --git a/tests/utils_tests/archives/foobar.tar b/tests/utils_tests/archives/foobar.tar index 44886fb33becb174c984ed45a783ded067394931..7e619b41d6bfcb73fe9cc9dfdbfe978f9a79aab4 100644 GIT binary patch literal 10240 zcmeI0VQRxL3`TvF++db%*~b}fNm*e{M$*D|`$>VcD|9p&s0{k~kCKMU<~_@fL(aZ= zX*ne)feO>lsE@Qcq7*{$luCA`)I{cdqzpj}b9ya(Ii2gQsnt{cuJi8eR)1cx_2gar zc@ABvjj(?8KhNWU0aw5k0JsB+RO)#E diff --git a/tests/utils_tests/archives/foobar.tar.bz2 b/tests/utils_tests/archives/foobar.tar.bz2 index 5a3d8a402eda8ef240e1e6724e9200df416e0412..e4e6c6ab688dcf03f420af8092b041f2e252e7bb 100644 GIT binary patch literal 294 zcmV+>0ondST4*^jL0KkKS;0ExQ2+rMdxpq>AOJuB|9}z9`)A%@06+i)00=MvCV(Q7 zLMBnQJxu^;0Mlw10h3JzLj*L$XuyUo&+SCX5Jpl<^**;HR z%g`Xf3sq>N0vbdv5VU6GSx#i2ifTf_m_$Sc%QZ%jqa-#V2yi!*D_Ynkb1W%_dAfxW zL4gg63Su}etqgFnyiE%XK_u_bJGY}zVNjzdnr0joBFe>uLUOMF#@T|YRhWY06yao` zaHvi*6y;Wt1W9R)LJqw|9fS~rn*9)aBB)tCbVJ3ra4(^18Zr>_pOTQ5qhv0HS}#L< PPvY)KrwS4puLalOW3pEDfMC@F#94j-!~tc6c^(%h$9FgGo7^({ea| zr0LuH9)16$T!%GU`b+s-`v-^qJ&f<;;dV{?w^4768aS2>{bPS`owdwCeCf|DjQ%~m z&ht)tjkUl0vcGkuzo%ICzi2;Lh5oyJT2y}nf9M~5K>r@L{-n4@)BG1|e=axR{oljZ zUvmy@oBw(3?>zeVQ2T4{{i>yr=@AOKMY3=kP(9s+9~>cMe;C{sO3*_Tb3 z7OQjFzJ2b~{rh`7>yLm2J?i(!L?8S|LX>L0UPDYmI0P^_+rRsd20eQGljN&^X7_Kw z@}B>iInd{yTK*?U?%#rkf6W}|@}F<>U&Q@e(EMxn=->Z{7=8bh);(FW=@rr#Am3k^6TX$I;{h?;E-<02TlM DKc$5% diff --git a/tests/utils_tests/archives/foobar.zip b/tests/utils_tests/archives/foobar.zip index b02bb3e95a8ca95a5f3f08f53c180426dc48cf08..9956385ee29b15d116b75831333abd994ab2515e 100644 GIT binary patch literal 1434 zcmb7@ze~eF6vtnhMA``ApyD9@0V0SUwUjzIly>N#5L~3y2nB7VX{DfoQKffIMlg|Ki=KDv}dlp(1aYfd_V8qy-zA7({g}4k1xX<--yM4HP}8p$bgxDD3#xq zPTh8A9t`W#1mH_$6n&Zy&GKj=DSG6FMY*bjRlSaP(r#62=N@Og77MPn{C+*V zw|M$~)^u8=Yt`nM;4Ev7AI6TKkqC5yS-ghl6%C@RLC{U+k`UK1ge|(D4mOhFH&fzN zeKRROX!ZWhaRIA0{Nqcnc~K97y59)+G`>(H_9-eEsdaOjhpQt&`XSrmiEOe`gHfH7 zF}sMtlOcu~s==s!jxpci6=Gytk;iP+@KjeL-XaY4-jPS5T8HNFH=?*N?MhNN1h(=GCP7uun#Hkgj$)zQUNja$? zbs}I}T+!6A0M(`C=j$U(sz$Sj6{-TsEj2ilfZc=L9uBA}Nr^>Bb~U0I#EGN|$-Xu` z%7AtH?k-RqK>)~I=YUL* xyC5-!a2vKbgXjXe?F*7F6!&3^LxeVIU}}e126Q7U8%T;12rmI8oIqKD0RWKTZH)i` diff --git a/tests/utils_tests/test_archive.py b/tests/utils_tests/test_archive.py index 42f445ce9c6..b207a1290ac 100644 --- a/tests/utils_tests/test_archive.py +++ b/tests/utils_tests/test_archive.py @@ -51,6 +51,10 @@ class ArchiveTester(object): filepath = os.path.join(self.tmpdir, 'executable') # The file has executable permission. self.assertTrue(os.stat(filepath).st_mode & stat.S_IXOTH) + filepath = os.path.join(self.tmpdir, 'no_permissions') + # The file is readable even though it doesn't have permission data in + # the archive. + self.assertTrue(os.stat(filepath).st_mode & stat.S_IROTH) def test_extract_function_with_leadpath(self): extract(self.archive_lead_path, self.tmpdir)