unzip: add option to insist on a zip archive

The fact that a non-zip archive has always been silently ignored seems
bad, but adding an error might break code that (probably accidentally)
relies on the behavior. This change makes sane behavior at least
available by adding a `#:must-unzip?` option.

Relevant to #3613
This commit is contained in:
Matthew Flatt 2021-03-03 08:50:03 -07:00
parent 71b7f21fdb
commit a5b61f7ac8
3 changed files with 48 additions and 20 deletions

View File

@ -13,11 +13,15 @@ a function to extract items from a @exec{zip} archive.}
. -> . any)
(bytes? boolean? input-port? . -> . any))
(make-filesystem-entry-reader)]
[#:must-unzip? must-unzip? any/c #f]
[#:preserve-timestamps? preserve-timestamps? any/c #f]
[#:utc-timestamps? utc-timestamps? any/c #f])
void?]{
Unzips an entire @exec{zip} archive from @racket[in].
Unzips an entire @exec{zip} archive from @racket[in]. If @racket[in]
does not start with @exec{zip}-archive magic bytes, an error is
reported only if @racket[must-unzip?] is true, otherwise the result is
@racket[(void)] with no bytes consumed from @racket[in].
For each entry in the archive, the @racket[entry-reader] procedure is
called with three or four arguments: the byte string representing the entry
@ -33,18 +37,24 @@ but if @racket[utc-timestamps?] is true, then the time in the archive
is interpreted as UTC.
@history[#:changed "6.0.0.3" @elem{Added the @racket[#:preserve-timestamps?] argument.}
#:changed "6.0.1.12" @elem{Added the @racket[#:utc-timestamps?] argument.}]}
#:changed "6.0.1.12" @elem{Added the @racket[#:utc-timestamps?] argument.}
#:changed "8.0.0.10" @elem{Added the @racket[#:must-unzip?] argument.}]}
@defproc[(call-with-unzip [in (or/c path-string? input-port?)]
[proc (-> path-string? any)])
[proc (-> path-string? any)]
[#:must-unzip? must-unzip? any/c #f])
any]{
Unpacks @racket[in] to a temporary directory, calls @racket[proc] on
the temporary directory's path, and then deletes the temporary
directory while returning the result of @racket[proc].
@history[#:added "6.0.1.6"]}
Like @racket[unzip], no error is reported in the case @racket[in] is
not a @exec{zip} archive, unless @racket[must-unzip?] is true.
@history[#:added "6.0.1.6"
#:changed "8.0.0.10" @elem{Added the @racket[#:must-unzip?] argument.}]}
@defproc[(make-filesystem-entry-reader

View File

@ -62,7 +62,14 @@
(break-thread t)
(sync t)
'done))
=> 'done))
=> 'done)
(test (call-with-unzip (open-input-bytes #"not a zip stream") void)
=> (void))
(test (call-with-unzip (open-input-bytes #"not a zip stream")
void
#:must-unzip? #t)
=error> "input does not appear to be an archive"))
(provide tests)

View File

@ -18,7 +18,8 @@
(or/c (bytes? boolean? input-port? (or/c #f exact-integer?) . -> . any)
(bytes? boolean? input-port? . -> . any))
#:preserve-timestamps? any/c
#:utc-timestamps? any/c)
#:utc-timestamps? any/c
#:must-unzip? any/c)
. ->* . any)]
[make-filesystem-entry-reader (() (#:dest
@ -48,9 +49,10 @@
. ->* .
any)]
[call-with-unzip (-> (or/c path-string? input-port?)
(-> path-string? any)
any)]
[call-with-unzip (((or/c path-string? input-port?)
(-> path-string? any))
(#:must-unzip? any/c)
. ->* . any)]
[call-with-unzip-entry (-> (or/c path-string? input-port?)
path-string?
(-> path-string? any)
@ -309,17 +311,22 @@
;; unzip : [(or/c path-string? input-port) (bytes boolean input-port -> any)] -> any
(define unzip
(lambda (in [read-entry (make-filesystem-entry-reader)]
#:preserve-timestamps? [preserve-timestamps? #f]
#:utc-timestamps? [utc? #f])
(lambda (orig-in [read-entry (make-filesystem-entry-reader)]
#:must-unzip? [must-unzip? #f]
#:preserve-timestamps? [preserve-timestamps? #f]
#:utc-timestamps? [utc? #f])
(call-with-input
in
orig-in
(lambda (in)
(when (= (peek-integer 4 #f in #f) *local-file-header*)
(unzip-one-entry in read-entry preserve-timestamps? utc?)
(unzip in read-entry
#:preserve-timestamps? preserve-timestamps?
#:utc-timestamps? utc?))))))
(cond
[(= (peek-integer 4 #f in #f) *local-file-header*)
(unzip-one-entry in read-entry preserve-timestamps? utc?)
(unzip in read-entry
#:preserve-timestamps? preserve-timestamps?
#:utc-timestamps? utc?)]
[must-unzip?
(error 'unzip "input does not appear to be an archive\n input: ~e" orig-in)]
[else (void)])))))
(define (input-size in)
(file-position in eof)
@ -403,13 +410,17 @@
(lambda ()
(delete-directory/files temp-dir)))))
(define (call-with-unzip zip-file user-proc)
(define (call-with-unzip zip-file user-proc
#:must-unzip? [must-unzip? #f])
(let ([temp-dir #f])
(dynamic-wind
(lambda ()
(set! temp-dir (make-temporary-file "ziptmp~a" 'directory)))
(lambda ()
(unzip zip-file (make-filesystem-entry-reader #:dest temp-dir #:exists 'replace))
(unzip zip-file (make-filesystem-entry-reader
#:dest temp-dir
#:exists 'replace)
#:must-unzip? must-unzip?)
(user-proc temp-dir))
(lambda ()
(delete-directory/files temp-dir)))))