util::unzip (public)

 util::unzip -source source -destination destination [ -overwrite ]

Defined in packages/acs-tcl/tcl/utilities-procs.tcl

Unzip helper, performing generic path checks (../, absolute paths, Windows absolute paths). The function prefer bsdtar (Debian libarchive) when present and no CP850 recoding is needed. For legacy CP850 filenames, use unzip -O CP850, but only if the archive does not contain symlink entries.

Switches:
-source (required)
must be the name of a valid zip file to be decompressed
-destination (required)
must be the name of a valid directory to contain decompressed files
-overwrite (optional, boolean)
Boolean flag

Testcases:
zip_and_unzip
Source code:
    #
    # Follow the following preferences_
    #  - Prefer bsdtar (libarchive) when present and no CP850 recoding is needed.
    #  - For legacy CP850 filenames, use unzip -O CP850, but only if the archive
    #    does not contain symlink entries.
    #  - Reject path traversal (../) and absolute paths (Unix + Windows style)
    #    in archive entry names before extraction.
    #

    # Make sure the destination directory exists.
    if {![file isdirectory $destination]} {
        file mkdir $destination
    }

    # Generic path checks. Errors out on failures.
    util::archive_check_paths -archive $source

    # Tools for extraction
    set bsdtarCmd [util::which bsdtar]
    set unzipCmd [util::which unzip]
    set zipinfoCmd [util::which zipinfo]

    #
    # Check whether we have to use CP850 handling (unzip -O CP850).
    # This is Linux-only and depends on util::zip_file_contains_valid_filenames.
    #
    set need_cp850 0
    if {[info commands ::util::zip_file_contains_valid_filenames] ne ""
        && $::tcl_platform(os) eq "Linux"
        && ![::util::zip_file_contains_valid_filenames $source]} {

        #
        # The option "-O" works apparently only under Linux and might
        # depend on the version of "unzip". We assume here that the
        # broken characters are from Windows (code page 850).
        #
        set need_cp850 1
    }

    #
    # If CP850 recoding is needed, be conservative: reject archives
    # that contain symlinks, since recoding can do harm with symlink
    # paths and targets.
    #
    if {$need_cp850} {
        if {[catch {util::archive_has_symlinks -archive $source} result]} {
            #
            # We cannot reliably check for symlinks in this archive, but we
            # know we would have to recode filenames. Be conservative.
            #
            error "Zip archive '$source' appears to need CP850 recoding, but symlink inspection failed: $result. Refusing to extract for safety."
        }
        if {$result} {
            error "Zip archive '$source' requires CP850 recoding and contains symbolic links. This combination is rejected for security reasons."
        }
    }

    #
    # Prefer bsdtar when:
    #   - it is available
    #   - we do NOT need special CP850 handling
    #
    if {$bsdtarCmd ne "" && !$need_cp850} {
        #
        # bsdtar can read ZIP files as well.
        #
        #   -x        extract
        #   -f file   archive file
        #   -C dir    chdir before extracting (like unzip -d)
        #   -k        do not overwrite existing files (like unzip -n)
        #   --no-same-owner       do not restore uid/gid from archive
        #   --no-same-permissions do not restore exact permissions
        #
        set cmd [list exec -- $bsdtarCmd -x]
        if {!$overwrite_p} {
            lappend cmd -k
        }
        lappend cmd --no-same-owner --no-same-permissions
        lappend cmd -f $source -C $destination

        {*}$cmd
        return
    }

    #
    # Fallback: classic unzip, with optional CP850 handling.
    #
    if {$unzipCmd eq ""} {
        if {$need_cp850} {
            error "Zip file contains non-UTF-8 filenames and 'unzip' (needed for -O CP850) is not available on the system."
        } else {
            error "Neither bsdtar nor unzip command found on the system."
        }
    }

    set extra_options {}
    if {$need_cp850} {
        lappend extra_options -O CP850
    }

    # -n means we don't overwrite existing files, -o forces overwrite
    set overwrite_opt [expr {$overwrite_p ? "-o" : "-n"}]

    exec $unzipCmd {*}$extra_options $overwrite_opt $source -d $destination
XQL Not present:
PostgreSQL, Oracle
Generic XQL file:
packages/acs-tcl/tcl/utilities-procs.xql

[ hide source ] | [ make this the default ]
Show another procedure: