untar, untgz, and unzip: add #:permissive?
Also, strengthen the checking that `#:permissive?` (off by default) performs for `untar` and `untgz` to disallow a link whose target is an absolute path or has an up-directory element.
This commit is contained in:
parent
c7fac6e98e
commit
0d3b5b61f0
|
@ -9,6 +9,7 @@ a function to extract items from a TAR/USTAR archive.}
|
||||||
@defproc[(untar [in (or/c path-string? input-port?)]
|
@defproc[(untar [in (or/c path-string? input-port?)]
|
||||||
[#:dest dest-path (or/c path-string? #f) #f]
|
[#:dest dest-path (or/c path-string? #f) #f]
|
||||||
[#:strip-count strip-count exact-nonnegative-integer? 0]
|
[#:strip-count strip-count exact-nonnegative-integer? 0]
|
||||||
|
[#:permissive? permissive? any/c #f]
|
||||||
[#:filter filter-proc
|
[#:filter filter-proc
|
||||||
(path? (or/c path? #f)
|
(path? (or/c path? #f)
|
||||||
symbol? exact-integer? (or/c path? #f)
|
symbol? exact-integer? (or/c path? #f)
|
||||||
|
@ -28,6 +29,12 @@ elements are removed from the item path from the archive (before
|
||||||
prefixing the path with @racket[dest-path]); if the item's path
|
prefixing the path with @racket[dest-path]); if the item's path
|
||||||
contains @racket[strip-count] elements, then it is not extracted.
|
contains @racket[strip-count] elements, then it is not extracted.
|
||||||
|
|
||||||
|
Unless @racket[permissive?] is true, then archive items with paths containing
|
||||||
|
an up-directory indicator are disallowed, and a link item whose target
|
||||||
|
is an absolute path or contains an up-directory indicator is also
|
||||||
|
disallowed. Absolute paths are always disallowed. A disallowed
|
||||||
|
path triggers an exception.
|
||||||
|
|
||||||
For each item in the archive, @racket[filter-proc] is applied to
|
For each item in the archive, @racket[filter-proc] is applied to
|
||||||
|
|
||||||
@itemlist[
|
@itemlist[
|
||||||
|
@ -60,4 +67,6 @@ For each item in the archive, @racket[filter-proc] is applied to
|
||||||
]
|
]
|
||||||
|
|
||||||
If the result of @racket[filter-proc] is @racket[#f], then the item is
|
If the result of @racket[filter-proc] is @racket[#f], then the item is
|
||||||
not unpacked.}
|
not unpacked.
|
||||||
|
|
||||||
|
@history[#:changed "6.2.900.17" @elem{Added the @racket[#:permissive?] argument.}]}
|
||||||
|
|
|
@ -11,6 +11,7 @@ a function to extract items from a possible @exec{gzip}ped TAR/USTAR archive.}
|
||||||
@defproc[(untgz [in (or/c path-string? input-port?)]
|
@defproc[(untgz [in (or/c path-string? input-port?)]
|
||||||
[#:dest dest-path (or/c path-string? #f) #f]
|
[#:dest dest-path (or/c path-string? #f) #f]
|
||||||
[#:strip-count strip-count exact-nonnegative-integer? 0]
|
[#:strip-count strip-count exact-nonnegative-integer? 0]
|
||||||
|
[#:permissive? permissive? any/c #f]
|
||||||
[#:filter filter-proc
|
[#:filter filter-proc
|
||||||
(path? (or/c path? #f)
|
(path? (or/c path? #f)
|
||||||
symbol? exact-integer? (or/c path? #f)
|
symbol? exact-integer? (or/c path? #f)
|
||||||
|
@ -21,4 +22,6 @@ a function to extract items from a possible @exec{gzip}ped TAR/USTAR archive.}
|
||||||
void?]{
|
void?]{
|
||||||
|
|
||||||
The same as @racket[untar], but if @racket[in] is in @exec{gzip} form,
|
The same as @racket[untar], but if @racket[in] is in @exec{gzip} form,
|
||||||
it is @racket[gunzip]ped as it is unpacked.}
|
it is @racket[gunzip]ped as it is unpacked.
|
||||||
|
|
||||||
|
@history[#:changed "6.2.900.17" @elem{Added the @racket[#:permissive?] argument.}]}
|
||||||
|
|
|
@ -50,6 +50,7 @@ directory while returning the result of @racket[proc].
|
||||||
@defproc[(make-filesystem-entry-reader
|
@defproc[(make-filesystem-entry-reader
|
||||||
[#:dest dest-path (or/c path-string? #f) #f]
|
[#:dest dest-path (or/c path-string? #f) #f]
|
||||||
[#:strip-count strip-count exact-nonnegative-integer? 0]
|
[#:strip-count strip-count exact-nonnegative-integer? 0]
|
||||||
|
[#:permissive? permissive? any/c #f]
|
||||||
[#:exists exists (or/c 'skip 'error 'replace 'truncate
|
[#:exists exists (or/c 'skip 'error 'replace 'truncate
|
||||||
'truncate/replace 'append 'update
|
'truncate/replace 'append 'update
|
||||||
'can-update 'must-truncate)
|
'can-update 'must-truncate)
|
||||||
|
@ -72,13 +73,21 @@ elements are removed from the entry path from the archive (before
|
||||||
prefixing the path with @racket[dest-path]); if the item's path
|
prefixing the path with @racket[dest-path]); if the item's path
|
||||||
contains @racket[strip-count] elements, then it is not extracted.
|
contains @racket[strip-count] elements, then it is not extracted.
|
||||||
|
|
||||||
|
Unless @racket[permissive?] is true, then entries with paths containing
|
||||||
|
an up-directory indicator are disallowed, and a link entry whose target
|
||||||
|
is an absolute path or contains an up-directory indicator is also
|
||||||
|
disallowed. Absolute paths are always disallowed. A disallowed
|
||||||
|
path triggers an exception.
|
||||||
|
|
||||||
If @racket[exists] is @racket['skip] and the file for an entry already
|
If @racket[exists] is @racket['skip] and the file for an entry already
|
||||||
exists, then the entry is skipped. Otherwise, @racket[exists] is
|
exists, then the entry is skipped. Otherwise, @racket[exists] is
|
||||||
passed on to @racket[open-output-file] for writing the entry's
|
passed on to @racket[open-output-file] for writing the entry's
|
||||||
inflated content.
|
inflated content.
|
||||||
|
|
||||||
@history[#:changed "6.0.0.3"
|
@history[#:changed "6.0.0.3"
|
||||||
@elem{Added support for the optional timestamp argument in the result function.}]}
|
@elem{Added support for the optional timestamp argument in the result function.}
|
||||||
|
#:changed "6.2.900.17"
|
||||||
|
@elem{Added the @racket[#:permissive?] argument.}]}
|
||||||
|
|
||||||
|
|
||||||
@defproc[(read-zip-directory [in (or/c path-string? input-port?)]) zip-directory?]{
|
@defproc[(read-zip-directory [in (or/c path-string? input-port?)]) zip-directory?]{
|
||||||
|
|
|
@ -98,7 +98,6 @@
|
||||||
(delete-directory/files "sub")
|
(delete-directory/files "sub")
|
||||||
(file-or-directory-permissions* more-dir "rwx")
|
(file-or-directory-permissions* more-dir "rwx")
|
||||||
|
|
||||||
|
|
||||||
;; make sure top-level file extraction works
|
;; make sure top-level file extraction works
|
||||||
(untgz (open-input-bytes
|
(untgz (open-input-bytes
|
||||||
;; bytes gotten from 'tar' and 'gzip' command-line tools
|
;; bytes gotten from 'tar' and 'gzip' command-line tools
|
||||||
|
@ -112,7 +111,28 @@
|
||||||
(test (file-exists? "L1c"))
|
(test (file-exists? "L1c"))
|
||||||
(test (file-exists? "helper.rkt"))
|
(test (file-exists? "helper.rkt"))
|
||||||
(delete-file "L1c")
|
(delete-file "L1c")
|
||||||
(delete-file "helper.rkt"))
|
(delete-file "helper.rkt")
|
||||||
|
|
||||||
|
;; check on [non-]permissive unpacking
|
||||||
|
(unless (eq? (system-type) 'windows)
|
||||||
|
(for ([target (in-list '("../x" "/tmp/abs" "ok"))])
|
||||||
|
(define ok? (equal? target "ok"))
|
||||||
|
(make-directory* "ex2")
|
||||||
|
(make-file-or-directory-link target (build-path "ex2" "link"))
|
||||||
|
(tar "ex2" a.tar)
|
||||||
|
(make-directory "sub")
|
||||||
|
(test (with-handlers ([exn:fail? (lambda (exn)
|
||||||
|
(regexp-match? #rx"up-directory|absolute" (exn-message exn)))])
|
||||||
|
(untar "a.tar" #:dest "sub")
|
||||||
|
ok?))
|
||||||
|
(test (equal? ok? (link-exists? (build-path "sub" "ex2" "link"))))
|
||||||
|
(delete-directory/files "sub")
|
||||||
|
|
||||||
|
(make-directory "sub")
|
||||||
|
(untar "a.tar" #:dest "sub" #:permissive? #t)
|
||||||
|
(test (link-exists? (build-path "sub" "ex2" "link")))
|
||||||
|
(delete-directory/files "sub")
|
||||||
|
(delete-directory/files "ex2"))))
|
||||||
|
|
||||||
(define ((make-unzip-tests* preserve-timestamps?))
|
(define ((make-unzip-tests* preserve-timestamps?))
|
||||||
(make-directory* "ex1")
|
(make-directory* "ex1")
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
(provide check-unpack-path)
|
(provide check-unpack-path)
|
||||||
|
|
||||||
(define (check-unpack-path who filename)
|
(define (check-unpack-path who filename
|
||||||
|
#:allow-up? [allow-up? #f])
|
||||||
(when (absolute-path? filename)
|
(when (absolute-path? filename)
|
||||||
(error who "won't extract a file with an absolute path\n path: ~e" filename))
|
(error who "won't extract a file with an absolute path\n path: ~e" filename))
|
||||||
|
(unless allow-up?
|
||||||
(for ([e (in-list (explode-path filename))])
|
(for ([e (in-list (explode-path filename))])
|
||||||
(when (eq? e 'up)
|
(when (eq? e 'up)
|
||||||
(error who "won't extract a file with an up-directory element\n path: ~e" filename))))
|
(error who "won't extract a file with an up-directory element\n path: ~e" filename)))))
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
(#:dest
|
(#:dest
|
||||||
(or/c #f path-string?)
|
(or/c #f path-string?)
|
||||||
#:strip-count exact-nonnegative-integer?
|
#:strip-count exact-nonnegative-integer?
|
||||||
|
#:permissive? any/c
|
||||||
#:filter (path? (or/c path? #f)
|
#:filter (path? (or/c path? #f)
|
||||||
symbol? exact-integer? (or/c path? #f)
|
symbol? exact-integer? (or/c path? #f)
|
||||||
exact-nonnegative-integer? exact-nonnegative-integer?
|
exact-nonnegative-integer? exact-nonnegative-integer?
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
(define (untar in
|
(define (untar in
|
||||||
#:dest [dest #f]
|
#:dest [dest #f]
|
||||||
#:strip-count [strip-count 0]
|
#:strip-count [strip-count 0]
|
||||||
|
#:permissive? [permissive? #f]
|
||||||
#:filter [filter void])
|
#:filter [filter void])
|
||||||
((if (input-port? in)
|
((if (input-port? in)
|
||||||
(lambda (in f) (f in))
|
(lambda (in f) (f in))
|
||||||
|
@ -34,7 +36,8 @@
|
||||||
(for ([delay (in-list (reverse delays))])
|
(for ([delay (in-list (reverse delays))])
|
||||||
(delay))
|
(delay))
|
||||||
(loop (untar-one-from-port in delays
|
(loop (untar-one-from-port in delays
|
||||||
dest strip-count filter)))))))
|
dest strip-count filter
|
||||||
|
permissive?)))))))
|
||||||
|
|
||||||
(define (read-bytes* n in)
|
(define (read-bytes* n in)
|
||||||
(define s (read-bytes n in))
|
(define s (read-bytes n in))
|
||||||
|
@ -44,7 +47,8 @@
|
||||||
s)
|
s)
|
||||||
|
|
||||||
(define (untar-one-from-port in delays
|
(define (untar-one-from-port in delays
|
||||||
dest strip-count filter)
|
dest strip-count filter
|
||||||
|
permissive?)
|
||||||
(define name-bytes (read-bytes* 100 in))
|
(define name-bytes (read-bytes* 100 in))
|
||||||
(define mode (tar-bytes->number (read-bytes* 8 in) in))
|
(define mode (tar-bytes->number (read-bytes* 8 in) in))
|
||||||
(define owner (tar-bytes->number (read-bytes* 8 in) in))
|
(define owner (tar-bytes->number (read-bytes* 8 in) in))
|
||||||
|
@ -79,7 +83,7 @@
|
||||||
name
|
name
|
||||||
(bytes-append prefix #"/" name)))
|
(bytes-append prefix #"/" name)))
|
||||||
name))))
|
name))))
|
||||||
(check-unpack-path 'untar base-filename)
|
(check-unpack-path 'untar base-filename #:allow-up? permissive?)
|
||||||
(define stripped-filename (strip-prefix base-filename strip-count))
|
(define stripped-filename (strip-prefix base-filename strip-count))
|
||||||
(define filename (and stripped-filename
|
(define filename (and stripped-filename
|
||||||
(if dest
|
(if dest
|
||||||
|
@ -87,6 +91,8 @@
|
||||||
stripped-filename)))
|
stripped-filename)))
|
||||||
(define link-target (and (eq? type 'link)
|
(define link-target (and (eq? type 'link)
|
||||||
(bytes->path (nul-terminated link-target-bytes))))
|
(bytes->path (nul-terminated link-target-bytes))))
|
||||||
|
(when (and link-target (not permissive?))
|
||||||
|
(check-unpack-path 'untar link-target))
|
||||||
(read-bytes* 12 in) ; padding
|
(read-bytes* 12 in) ; padding
|
||||||
(define create?
|
(define create?
|
||||||
(filter base-filename filename type size link-target mod-time mode))
|
(filter base-filename filename type size link-target mod-time mode))
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
(#:dest
|
(#:dest
|
||||||
(or/c #f path-string?)
|
(or/c #f path-string?)
|
||||||
#:strip-count exact-nonnegative-integer?
|
#:strip-count exact-nonnegative-integer?
|
||||||
|
#:permissive? any/c
|
||||||
#:filter (path? (or/c path? #f)
|
#:filter (path? (or/c path? #f)
|
||||||
symbol? exact-integer? (or/c path? #f)
|
symbol? exact-integer? (or/c path? #f)
|
||||||
exact-nonnegative-integer? exact-nonnegative-integer?
|
exact-nonnegative-integer? exact-nonnegative-integer?
|
||||||
|
@ -20,6 +21,7 @@
|
||||||
(define (untgz in
|
(define (untgz in
|
||||||
#:dest [dest #f]
|
#:dest [dest #f]
|
||||||
#:strip-count [strip-count 0]
|
#:strip-count [strip-count 0]
|
||||||
|
#:permissive? [permissive? #f]
|
||||||
#:filter [filter void])
|
#:filter [filter void])
|
||||||
((if (input-port? in)
|
((if (input-port? in)
|
||||||
(lambda (in f) (f in))
|
(lambda (in f) (f in))
|
||||||
|
@ -44,7 +46,7 @@
|
||||||
(thread-wait t)))]
|
(thread-wait t)))]
|
||||||
[else (values in void)]))
|
[else (values in void)]))
|
||||||
(begin0
|
(begin0
|
||||||
(untar in2 #:dest dest #:strip-count strip-count #:filter filter)
|
(untar in2 #:dest dest #:strip-count strip-count #:permissive? permissive? #:filter filter)
|
||||||
(wait)))))
|
(wait)))))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
(or/c #f path-string?)
|
(or/c #f path-string?)
|
||||||
#:strip-count
|
#:strip-count
|
||||||
exact-nonnegative-integer?
|
exact-nonnegative-integer?
|
||||||
|
#:permissive?
|
||||||
|
any/c
|
||||||
#:exists
|
#:exists
|
||||||
(or/c 'skip
|
(or/c 'skip
|
||||||
'error 'replace 'truncate 'truncate/replace 'append 'update
|
'error 'replace 'truncate 'truncate/replace 'append 'update
|
||||||
|
@ -358,10 +360,10 @@
|
||||||
|
|
||||||
;; make-filesystem-entry-reader : [output-flag] -> (bytes boolean input-port -> any)
|
;; make-filesystem-entry-reader : [output-flag] -> (bytes boolean input-port -> any)
|
||||||
(define make-filesystem-entry-reader
|
(define make-filesystem-entry-reader
|
||||||
(lambda (#:dest [dest-dir #f] #:strip-count [strip-count 0] #:exists [flag 'error])
|
(lambda (#:dest [dest-dir #f] #:strip-count [strip-count 0] #:permissive? [permissive? #f] #:exists [flag 'error])
|
||||||
(lambda (name dir? in [timestamp #f])
|
(lambda (name dir? in [timestamp #f])
|
||||||
(define path (bytes->path name))
|
(define path (bytes->path name))
|
||||||
(check-unpack-path 'unzip path)
|
(check-unpack-path 'unzip path #:allow-up? permissive?)
|
||||||
(let* ([base-path (strip-prefix path strip-count)]
|
(let* ([base-path (strip-prefix path strip-count)]
|
||||||
[path (and base-path
|
[path (and base-path
|
||||||
(if dest-dir
|
(if dest-dir
|
||||||
|
|
Loading…
Reference in New Issue
Block a user