Merge lp:~lool/snapcraft/no-separate-toplevel-dir into lp:~snappy-dev/snapcraft/core

Proposed by Loïc Minier
Status: Merged
Approved by: Michael Terry
Approved revision: 107
Merged at revision: 109
Proposed branch: lp:~lool/snapcraft/no-separate-toplevel-dir
Merge into: lp:~snappy-dev/snapcraft/core
Diff against target: 116 lines (+53/-12)
3 files modified
integration-tests/data/simple-tar/snapcraft.yaml (+12/-0)
integration-tests/units/jobs.pxu (+6/-0)
snapcraft/__init__.py (+35/-12)
To merge this branch: bzr merge lp:~lool/snapcraft/no-separate-toplevel-dir
Reviewer Review Type Date Requested Status
Michael Terry (community) Approve
Review via email: mp+265754@code.launchpad.net

Commit message

Rework tarball unpack to use tarfile. New implementation deals properly with tarballs which ship all files under a common directory, but without an entry for this common directory. Also handles dangerous tarballs with pathes starting with / or ../. Adjust tests accordingly.

NB: this was found while trying to use the upstream tomcat tarball; toplevel parent dir didn't get stripped on unpack.

Description of the change

Rework tarball unpack to use tarfile. New implementation deals properly with tarballs which ship all files under a common directory, but without an entry for this common directory. Also handles dangerous tarballs with pathes starting with / or ../. Adjust tests accordingly.

NB: this was found while trying to use the upstream tomcat tarball; toplevel parent dir didn't get stripped on unpack.

To post a comment you must log in.
Revision history for this message
Michael Terry (mterry) wrote :

Thanks! I didn't realize tar didn't handle evil paths for us. :(

And I totally forgot about the tarfile module.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'integration-tests/data/simple-tar/evil-parent.tar.gz'
0Binary files integration-tests/data/simple-tar/evil-parent.tar.gz 1970-01-01 00:00:00 +0000 and integration-tests/data/simple-tar/evil-parent.tar.gz 2015-07-24 01:25:43 +0000 differ0Binary files integration-tests/data/simple-tar/evil-parent.tar.gz 1970-01-01 00:00:00 +0000 and integration-tests/data/simple-tar/evil-parent.tar.gz 2015-07-24 01:25:43 +0000 differ
=== added file 'integration-tests/data/simple-tar/evil-slash.tar.gz'
1Binary files integration-tests/data/simple-tar/evil-slash.tar.gz 1970-01-01 00:00:00 +0000 and integration-tests/data/simple-tar/evil-slash.tar.gz 2015-07-24 01:25:43 +0000 differ1Binary files integration-tests/data/simple-tar/evil-slash.tar.gz 1970-01-01 00:00:00 +0000 and integration-tests/data/simple-tar/evil-slash.tar.gz 2015-07-24 01:25:43 +0000 differ
=== added file 'integration-tests/data/simple-tar/nodir.tar.xz'
2Binary files integration-tests/data/simple-tar/nodir.tar.xz 1970-01-01 00:00:00 +0000 and integration-tests/data/simple-tar/nodir.tar.xz 2015-07-24 01:25:43 +0000 differ2Binary files integration-tests/data/simple-tar/nodir.tar.xz 1970-01-01 00:00:00 +0000 and integration-tests/data/simple-tar/nodir.tar.xz 2015-07-24 01:25:43 +0000 differ
=== added file 'integration-tests/data/simple-tar/simple.tar.bz2'
3Binary files integration-tests/data/simple-tar/simple.tar.bz2 1970-01-01 00:00:00 +0000 and integration-tests/data/simple-tar/simple.tar.bz2 2015-07-24 01:25:43 +0000 differ3Binary files integration-tests/data/simple-tar/simple.tar.bz2 1970-01-01 00:00:00 +0000 and integration-tests/data/simple-tar/simple.tar.bz2 2015-07-24 01:25:43 +0000 differ
=== modified file 'integration-tests/data/simple-tar/snapcraft.yaml'
--- integration-tests/data/simple-tar/snapcraft.yaml 2015-07-17 19:39:00 +0000
+++ integration-tests/data/simple-tar/snapcraft.yaml 2015-07-24 01:25:43 +0000
@@ -5,9 +5,21 @@
5 onedeep:5 onedeep:
6 plugin: tar-content6 plugin: tar-content
7 source: onedeep.tar.gz7 source: onedeep.tar.gz
8 nodir:
9 plugin: tar-content
10 source: nodir.tar.xz
8 flat:11 flat:
9 plugin: tar-content12 plugin: tar-content
10 source: flat.tar.xz13 source: flat.tar.xz
14 simple:
15 plugin: tar-content
16 source: simple.tar.bz2
17 evil-parent:
18 plugin: tar-content
19 source: evil-parent.tar.gz
20 evil-slash:
21 plugin: tar-content
22 source: evil-slash.tar.gz
11 project:23 project:
12 plugin: make-project24 plugin: make-project
13 source: project.tar.xz25 source: project.tar.xz
1426
=== modified file 'integration-tests/units/jobs.pxu'
--- integration-tests/units/jobs.pxu 2015-07-22 21:05:20 +0000
+++ integration-tests/units/jobs.pxu 2015-07-24 01:25:43 +0000
@@ -82,6 +82,12 @@
82 test -e stage/onedeep82 test -e stage/onedeep
83 test -e stage/onedeepdir/onedeep283 test -e stage/onedeepdir/onedeep2
84 test -e stage/oneflat84 test -e stage/oneflat
85 test -e stage/dir-simple
86 test -e stage/top-simple
87 test -e stage/notop
88 test -e stage/notopdir
89 test -e stage/parent
90 test -e stage/slash
85 test "$(./stage/bin/test)" = "tarproject"91 test "$(./stage/bin/test)" = "tarproject"
8692
87id: snapcraft/normal/simple-make93id: snapcraft/normal/simple-make
8894
=== modified file 'snapcraft/__init__.py'
--- snapcraft/__init__.py 2015-07-22 18:59:28 +0000
+++ snapcraft/__init__.py 2015-07-24 01:25:43 +0000
@@ -15,9 +15,11 @@
15# along with this program. If not, see <http://www.gnu.org/licenses/>.15# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
17import os17import os
18import re
19import tarfile
20import urllib.parse
21
18import snapcraft.common22import snapcraft.common
19import subprocess
20import urllib.parse
2123
2224
23class BasePlugin:25class BasePlugin:
@@ -102,16 +104,37 @@
102 else:104 else:
103 tarball = os.path.abspath(source)105 tarball = os.path.abspath(source)
104106
105 # If there's a single toplevel directory, ignore it107 with tarfile.open(tarball) as tar:
106 try:108 def filter_members(tar):
107 topfiles = subprocess.check_output(['tar', '--list', '-f', tarball, '--exclude', '*/*']).strip()109 """Filters members and member names:
108 except Exception:110 - strips common prefix
109 return False111 - bans dangerous names"""
110 strip_cmd = []112 members = tar.getmembers()
111 if topfiles and len(topfiles.split(b'\n')) == 1 and chr(topfiles[-1]) == '/':113 common = os.path.commonprefix([m.name for m in members])
112 strip_cmd = ['--strip-components=1']114
113115 # commonprefix() works a character at a time and will
114 return self.run(['tar'] + strip_cmd + ['-xf', tarball], cwd=destdir)116 # consider "d/ab" and "d/abc" to have common prefix "d/ab";
117 # check all members either start with common dir
118 for m in members:
119 if not (m.name.startswith(common + "/") or
120 m.isdir() and m.name == common):
121 # commonprefix() didn't return a dir name; go up one
122 # level
123 common = os.path.dirname(common)
124 break
125
126 for m in members:
127 if m.name == common:
128 continue
129 if m.name.startswith(common + "/"):
130 m.name = m.name[len(common + "/"):]
131 # strip leading "/", "./" or "../" as many times as needed
132 m.name = re.sub(r'^(\.{0,2}/)*', r'', m.name)
133 yield m
134
135 tar.extractall(members=filter_members(tar), path=destdir)
136
137 return True
115138
116 def get_source(self, source, source_type=None, source_tag=None, source_branch=None):139 def get_source(self, source, source_type=None, source_tag=None, source_branch=None):
117 if source_type is None:140 if source_type is None:

Subscribers

People subscribed via source and target branches