@@ -17,6 +17,10 @@ def __init__(self):
17
17
parser .add_argument ("-f" , "--file" , help = "Specify the file to extract, - for stdin" , type = str , required = True )
18
18
parser .add_argument ("-C" , "--directory" , help = "Target directory for extraction" , type = str )
19
19
parser .add_argument ("-P" , "--absolute-names" , help = "Don't strip leading / from file names" , action = "store_true" )
20
+ parser .add_argument (
21
+ "--keep-directory-symlink" ,
22
+ help = "Follow symlinks to directories when extracting from the archive" ,
23
+ action = "store_true" )
20
24
parser .add_argument ("--exclude" , help = "Exclude file matching given patter" , type = str , action = "append" )
21
25
parser .add_argument ("--transform" , help = "Transform file name" , type = str , action = "append" )
22
26
self .args = parser .parse_args ()
@@ -40,6 +44,25 @@ def _extract(self, file):
40
44
if not target_name :
41
45
continue
42
46
47
+ if not self .args .keep_directory_symlink :
48
+ # _build_target_name prefixed path with directory name but if absolute
49
+ # paths are allowed the path might be outside of target directory
50
+ if self .args .directory and target_name .startswith (self .args .directory ):
51
+ path = self .args .directory
52
+ relative_path = target_name [len (self .args .directory ):].lstrip (os .sep )
53
+ elif target_name .startswith (os .sep ):
54
+ path = os .sep
55
+ relative_path = target_name .lstrip (os .sep )
56
+ else :
57
+ path = "."
58
+ relative_path = target_name
59
+ parts = relative_path .split (os .sep )
60
+ for part in parts :
61
+ path = os .path .join (path , part )
62
+ if os .path .islink (path ):
63
+ os .unlink (path )
64
+ break # only remove first symlink
65
+
43
66
if tarinfo .isdir ():
44
67
paths .append ([target_name , tarinfo ])
45
68
self .makedirs (target_name )
@@ -67,9 +90,11 @@ def _build_target_name(self, source_name):
67
90
name = self ._transform_name (name )
68
91
if not name :
69
92
return None
70
- if not self .args .absolute_names and name .startswith ("/" ):
71
- name = name [1 :]
72
- if not name .startswith ("/" ) and self .args .directory :
93
+ if not self .args .absolute_names :
94
+ name = name .lstrip (os .sep )
95
+ if f"..{ os .sep } " in name or name .endswith (".." ):
96
+ return None
97
+ if not name .startswith (os .sep ) and self .args .directory :
73
98
name = os .path .join (self .args .directory , name )
74
99
return name
75
100
0 commit comments