-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Respect zipped symlinks #1140
Respect zipped symlinks #1140
Changes from all commits
b428871
b493394
7e08351
afce048
b23d425
7ddb5e4
d88100f
c861d49
f01b6d5
21be617
0c421c1
f308c7e
68449db
ee20909
9707304
6744e20
3bc3d9c
2d93efc
e8ac37b
3ebdc6b
2dbb985
611fd0e
3c47b60
8a36c42
b21ec11
b9a4d08
96177ad
fdd34a1
dff30a5
26136f7
027aaa9
9de2d8f
8117e2f
897dc77
c4abf92
37a6d4d
09d3f42
c8963e0
98664d7
a913ea2
e2c1bde
26dcfba
7ed0edd
f834f5c
a681eb4
dc81449
bac006e
c96bb8f
8b67c8d
edff889
23150f3
03e0d45
7b6eb18
16ba85b
69144ea
b0eaf7f
3134c08
eb7a647
420862e
ce2fdca
d6c59e1
a89bf29
7b947cc
7d15dbc
ac13511
58ff5b8
6ec323d
d8d378a
9cf3020
ea9ba58
ea4afe4
eaa1ba1
aa7a1fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,70 @@ | |
|
||
LOG = logging.getLogger(__name__) | ||
|
||
S_IFLNK = 0xA | ||
|
||
|
||
def _is_symlink(file_info): | ||
""" | ||
Check the upper 4 bits of the external attribute for a symlink. | ||
See: https://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute | ||
|
||
Parameters | ||
---------- | ||
file_info : zipfile.ZipInfo | ||
The ZipInfo for a ZipFile | ||
|
||
Returns | ||
------- | ||
bool | ||
A response regarding whether the ZipInfo defines a symlink or not. | ||
""" | ||
|
||
return (file_info.external_attr >> 28) == 0xA | ||
|
||
|
||
def _extract(file_info, output_dir, zip_ref): | ||
""" | ||
Unzip the given file into the given directory while preserving file permissions in the process. | ||
|
||
Parameters | ||
---------- | ||
file_info : zipfile.ZipInfo | ||
The ZipInfo for a ZipFile | ||
|
||
output_dir : str | ||
Path to the directory where the it should be unzipped to | ||
|
||
zip_ref : zipfile.ZipFile | ||
The ZipFile we are working with. | ||
|
||
Returns | ||
------- | ||
string | ||
Returns the target path the Zip Entry was extracted to. | ||
""" | ||
|
||
# Handle any regular file/directory entries | ||
if not _is_symlink(file_info): | ||
return zip_ref.extract(file_info, output_dir) | ||
|
||
source = zip_ref.read(file_info.filename).decode('utf8') | ||
link_name = os.path.normpath(os.path.join(output_dir, file_info.filename)) | ||
|
||
# make leading dirs if needed | ||
leading_dirs = os.path.dirname(link_name) | ||
if not os.path.exists(leading_dirs): | ||
os.makedirs(leading_dirs) | ||
|
||
# If the link already exists, delete it or symlink() fails | ||
if os.path.lexists(link_name): | ||
os.remove(link_name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this have any (negative) side effects? I can't think of any at the moment but asking the question to spark a discussion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tested it fairly extensively and can not think of any negative side effects that might pop up. The unzip intends to overwrite anything that is there regardless ... we will simply delete it prior to writing. |
||
|
||
# Create a symbolic link pointing to source named link_name. | ||
os.symlink(source, link_name) | ||
|
||
return link_name | ||
|
||
|
||
def unzip(zip_file_path, output_dir, permission=None): | ||
""" | ||
|
@@ -40,10 +104,7 @@ def unzip(zip_file_path, output_dir, permission=None): | |
|
||
# For each item in the zip file, extract the file and set permissions if available | ||
for file_info in zip_ref.infolist(): | ||
name = file_info.filename | ||
extracted_path = os.path.join(output_dir, name) | ||
|
||
zip_ref.extract(name, output_dir) | ||
extracted_path = _extract(file_info, output_dir, zip_ref) | ||
_set_permissions(file_info, extracted_path) | ||
|
||
_override_permissions(extracted_path, permission) | ||
|
@@ -81,7 +142,7 @@ def _set_permissions(zip_file_info, extracted_path): | |
""" | ||
|
||
# Permission information is stored in first two bytes. | ||
permission = zip_file_info.external_attr >> 16 | ||
permission = (zip_file_info.external_attr >> 16) & 511 | ||
if not permission: | ||
# Zips created on certain Windows machines, however, might not have any permission information on them. | ||
# Skip setting a permission on these files. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be using the
S_IFLNK
constant from above?Why not use
stat. S_IFLNK
here instead?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not looking at a file on the filesystem. It is looking at a number of bytes in the
file_info
entry, the External Attributes for the file. See The zip format's external file attribute If you want to dig into it. But the short answer is because it isn't a file.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. You made a constant
S_IFLNK = 0xA
above. Can you either remove it the constant or replace the0xA
here withS_IFLNK
. Otherwise, it looks like an unused constant.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ that was with regard to pythons
stat.S_ISLNK()
... I have no good excuse for not comparing to the S_IFLNK = 0xA constant defined directly above the function. :-)