![JAR search and dependency download from the Maven repository](/logo.png)
com.github.jlangch.venice.zipvault.venice Maven / Gradle / Ivy
;;;; __ __ _
;;;; \ \ / /__ _ __ (_) ___ ___
;;;; \ \/ / _ \ '_ \| |/ __/ _ \
;;;; \ / __/ | | | | (_| __/
;;;; \/ \___|_| |_|_|\___\___|
;;;;
;;;;
;;;; Copyright 2017-2024 Venice
;;;;
;;;; Licensed under the Apache License, Version 2.0 (the "License");
;;;; you may not use this file except in compliance with the License.
;;;; You may obtain a copy of the License at
;;;;
;;;; http://www.apache.org/licenses/LICENSE-2.0
;;;;
;;;; Unless required by applicable law or agreed to in writing, software
;;;; distributed under the License is distributed on an "AS IS" BASIS,
;;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;;; See the License for the specific language governing permissions and
;;;; limitations under the License.
;;;; ZIP Vault, AES-256 encrypted and password protected
(ns zipvault)
(import :org.repackage.net.lingala.zip4j.ZipFile)
(import :org.repackage.net.lingala.zip4j.io.outputstream.ZipOutputStream)
(import :org.repackage.net.lingala.zip4j.io.inputstream.ZipInputStream)
(import :org.repackage.net.lingala.zip4j.model.ZipParameters)
(import :org.repackage.net.lingala.zip4j.model.ExcludeFileFilter)
(import :org.repackage.net.lingala.zip4j.model.enums.EncryptionMethod)
(defn
^{ :arglists '("(zipvault/zip out passphrase & entries)")
:doc """
Creates an AES-256 encrypted and password protected zip form the
entries and writes it to out. out may be a file or an output stream.
An entry is given by a name and data. The entry data may be nil, a
bytebuf, a string, a file, an input stream, or a producer function.
An entry name with a trailing '/' creates a directory.
Entry value types:
| nil | an empty file is written to the zip entry |
| bytebuf | the bytes are written to the zip entry |
| string | the string is written to the zip entry |
| file | the content of the file is written to the zip entry |
| input stream | the slurped input stream data is written to the \
zip entry |
| function | a producer function with a single output stream \
argument. All data written to the stream is written \
to the zip entry. The stream can be flushed but \
must not be closed! |
**Passphrases:**
The AES-256 algorithm requires a 256-bit key as input.
One should use a passphrase with at least 128 bits of entropy
(that's roughly a 20-character passphrase of random
upper/lower/digits/symbols).
Less is dropping below general limits of safety, and more than
256 bits won't accomplish anything.
See function: `zipvault/entropy`
"""
:examples '(
"""
(do
(load-module :zipvault)
(zipvault/zip (io/file "vault.zip") "pwd")) ; empty zip
""",
"""
(do
(load-module :zipvault)
(zipvault/zip (io/file "vault.zip") "pwd" "a.txt" "abc"))
""",
"""
(do
(load-module :zipvault)
(zipvault/zip (io/file-out-stream "vault.zip")
"pwd"
"a.txt" "abc"
"b.txt" (bytebuf [100 101 102])))
""",
"""
(do
(load-module :zipvault)
(let [file (io/file (io/tmp-dir) "c.txt")]
(io/spit file "1234")
(io/delete-file-on-exit c-tmp)
;; create "vault.zip"
;; ├── a.txt
;; ├── b.txt
;; ├── c.txt
;; ├── d.txt
;; ├── e.txt
;; ├── empty.txt
;; └── xx
;; └── g.txt
(zipvault/zip
(io/file "vault.zip")
"pwd"
"a.txt" "abc"
"b.txt" (bytebuf "def")
file file ; aquivalent: (io/file-basename file) file
"d.txt" (io/string-in-stream "ghi")
"e.txt" (fn [os]
(let [wr (io/wrap-os-with-buffered-writer os)]
(println wr "200")
(flush wr)))
"empty.txt" nil
"xx/g.txt" "jkl")))
""" )
:see-also '(
"zipvault/zip-folder"
"zipvault/entries"
"zipvault/add-files"
"zipvault/add-folder"
"zipvault/add-stream"
"zipvault/remove-files"
"zipvault/extract-file"
"zipvault/extract-all"
"zipvault/extract-file-data"
"zipvault/entropy" ) }
zip [out passphrase & entries]
{ :pre [(or (string? out) (io/file? out) (instance-of? :java.io.OutputStream out))
(string? passphrase)] }
(let [params (create-params)
entries (apply hash-map entries)
os (if (file-or-string? out)
(io/file-out-stream out :append false)
out)]
(zip-to-os params passphrase os entries)))
(defn
^{ :arglists '(
"(zipvault/zip-folder out passphrase folder)"
"(zipvault/zip-folder out passphrase folder include-root-folder)"
"(zipvault/zip-folder out passphrase folder include-root-folder exclude-fn)")
:doc """
Creates an AES-256 encrypted and password protected zip from the
folder.
If 'include-root-folder' (default true) is true the root folder name
will be added to the entry name as folder.
The 'exclude-fn' filters the files in the folder that are to be
excluded from the zip. 'exclude-fn' is a single argument function
that receives a file and returns true if the files is to be excluded
otherwise it returns false.
"""
:examples '(
"""
(do
(load-module :zipvault)
(let [zip (io/file "vault.zip")
tmp-folder (io/file (io/tmp-dir) "ziptest")
tmp-1 (io/file tmp-folder "a1.txt")
tmp-2 (io/file tmp-folder "a2.txt")]
(io/mkdir tmp-folder)
(io/spit tmp-1 "1234")
(io/spit tmp-2 "2345")
(io/delete-file-on-exit tmp-folder)
(zipvault/zip-folder zip "pwd" tmp-folder)))
""",
"""
(do
(load-module :zipvault)
(defn exclude-fn [file] (io/file-ext? file "log"))
(let [zip (io/file "vault.zip")
tmp-folder (io/file (io/tmp-dir) "ziptest")
tmp-1 (io/file tmp-folder "a.txt")
tmp-2 (io/file tmp-folder "b.txt")
tmp-3 (io/file tmp-folder "c.log")]
(io/mkdir tmp-folder)
(io/spit tmp-1 "12")
(io/spit tmp-2 "23")
(io/spit tmp-3 "34")
(io/delete-file-on-exit tmp-folder)
(zipvault/zip -folder zip "pwd" tmp-folder true exclude-fn)))
""" )
:see-also '(
"zipvault/zip"
"zipvault/add-folder" ) }
zip-folder
([out passphrase folder]
(zip out passphrase)
(add-folder out passphrase folder))
([out passphrase folder include-root-folder]
(zip out passphrase)
(add-folder out passphrase folder include-root-folder))
([out passphrase folder include-root-folder exclude-fn]
(zip out passphrase)
(add-folder out passphrase folder include-root-folder exclude-fn)))
(defn
^{ :arglists '(
"(zipvault/entropy passphrase)")
:doc """
Returns the passphrase's entropy in bits.
The password entropy using the formula: **E = log2(RL)**
* **E** stands for password entropy, measured in bits
* **Log2** is a mathematical formula that converts the total number \
of possible character combinations to bits
* **R** stands for the range of characters
* **L** stands for the number of characters in a password
The entropy is calculated based on 26 lower and upper case letters,
10 digits, and 24 symbols like °+*%&/()=?'`^:_,.-$£!#~;
Note: The function just calculates the entropy. A strong passphrase
does not rely on the entropy solely. Avoid passphrases containing
words from the dictionary ("admin_passw0rd"), dates (birthdate,
...), repetitions ("aaaaa"), or sequences ("123456")!
"""
:examples '(
"""
(do
(load-module :zipvault)
(zipvault/entropy "uibsd6b38hs7b_La'sdgk898wbver"))
""" )
:see-also '(
"zipvault/zip" ) }
entropy [passphrase]
{ :pre [(string? passphrase)] }
(let [letters 26, digits 10, symbols 24]
(log2 (pow (+ letters letters digits symbols) (count passphrase)))))
(defn
^{ :arglists '(
"(zipvault/entries zip passphrase)")
:doc """
Returns a list of the entry names in the zip.
"""
:examples '(
"""
(do
(load-module :zipvault)
(zipvault/zip (io/file "vault.zip")
"pwd"
"a.txt" "abc"
"b.txt" "def")
(zipvault/entries (io/file "vault.zip") "pwd"))
""" )
:see-also '(
"zipvault/zip" ) }
entries [zip passphrase]
{ :pre [(file-or-string? zip)
(string? passphrase)] }
(try-with [zf (. :ZipFile :new (io/file zip) passphrase)]
(->> (. zf :getFileHeaders)
(map (fn [h] { :file-name (. h :getFileName)
:compressed-size (. h :getCompressedSize)
:uncompressed-size (. h :getUncompressedSize)
:encrypted? (. h :isEncrypted)
:directory? (. h :isDirectory) })))))
(defn
^{ :arglists '("(zipvault/add-files zip passphrase & files)")
:doc """
Adds a list of files to the zip.
"""
:examples '(
"""
(do
(load-module :zipvault)
(let [zip (io/file "vault.zip")
tmp-1 (io/file (io/tmp-dir) "a1.txt")
tmp-2 (io/file (io/tmp-dir) "a2.txt")]
(io/spit tmp-1 "1234")
(io/spit tmp-2 "2345")
(io/delete-file-on-exit tmp-1)
(io/delete-file-on-exit tmp-2)
(zipvault/zip zip "pwd" "a.txt" "A")
(zipvault/add-files zip "pwd" tmp-1 tmp-2)))
""" )
:see-also '(
"zipvault/zip"
"zipvault/add-folder"
"zipvault/add-stream"
"zipvault/remove-files" ) }
add-files [zip passphrase & files]
{ :pre [(file-or-string? zip)
(string? passphrase)
(every? file-or-string? files)] }
(let [params (create-params)]
(try-with [zf (. :ZipFile :new (io/file zip) passphrase)]
(. zf :addFiles files params))))
(defn
^{ :arglists '(
"(zipvault/add-folder zip passphrase folder)"
"(zipvault/add-folder zip passphrase folder include-root-folder)"
"(zipvault/add-folder zip passphrase folder include-root-folder exclude-fn)")
:doc """
Adds a folder to the zip file.
If 'include-root-folder' (default true) is true the root folder name
will be added to the entry name as folder.
The 'exclude-fn' filters the files in the folder that are to be
excluded from the zip. 'exclude-fn' is a single argument function
that receives a file and returns true if the files is to be excluded
otherwise it returns false.
"""
:examples '(
"""
(do
(load-module :zipvault)
(let [zip (io/file "vault.zip")
tmp-folder (io/file (io/tmp-dir) "ziptest")
tmp-1 (io/file tmp-folder "a1.txt")
tmp-2 (io/file tmp-folder "a2.txt")]
(io/mkdir tmp-folder)
(io/spit tmp-1 "1234")
(io/spit tmp-2 "2345")
(io/delete-file-on-exit tmp-folder)
(zipvault/zip zip "pwd" "a.txt" "A")
(zipvault/add-folder zip "pwd" tmp-folder)))
""",
"""
(do
(load-module :zipvault)
(defn exclude-fn [file] (io/file-ext? file "log"))
(let [zip (io/file "vault.zip")
tmp-folder (io/file (io/tmp-dir) "ziptest")
tmp-1 (io/file tmp-folder "a.txt")
tmp-2 (io/file tmp-folder "b.txt")
tmp-3 (io/file tmp-folder "c.log")]
(io/mkdir tmp-folder)
(io/spit tmp-1 "12")
(io/spit tmp-2 "23")
(io/spit tmp-3 "34")
(io/delete-file-on-exit tmp-folder)
(zipvault/zip zip "pwd")
(zipvault/add-folder zip "pwd" tmp-folder true exclude-fn)))
""" )
:see-also '(
"zipvault/zip"
"zipvault/add-files"
"zipvault/add-stream"
"zipvault/remove-files" ) }
add-folder
([zip passphrase folder]
{ :pre [(file-or-string? zip)
(string? passphrase)
(file-or-string? folder)] }
(add-folder (io/file zip) passphrase folder true))
([zip passphrase folder include-root-folder]
{ :pre [(file-or-string? zip)
(string? passphrase)
(file-or-string? folder)
(boolean? include-root-folder)] }
(let [params (create-params)]
(. params :setIncludeRootFolder include-root-folder)
(try-with [zf (. :ZipFile :new (io/file zip) passphrase)]
(. zf :addFolder folder params))))
([zip passphrase folder include-root-folder exclude-fn]
{ :pre [(file-or-string? zip)
(string? passphrase)
(file-or-string? folder)
(boolean? include-root-folder)
(fn? exclude-fn)] }
(let [params (create-params)
exc-fn (proxify :ExcludeFileFilter {:isExcluded exclude-fn})]
(. params :setIncludeRootFolder include-root-folder)
(. params :setExcludeFileFilter exc-fn)
(try-with [zf (. :ZipFile :new (io/file zip) passphrase)]
(. zf :addFolder folder params)))))
(defn
^{ :arglists '("(zipvault/add-stream zip passphrase name is)")
:doc """
Creates a new entry in the zip file and adds the content of the
input stream to the zip file.
"""
:examples '(
"""
(do
(load-module :zipvault)
(let [zip (io/file "vault.zip")
is (io/string-in-stream "abc")]
(zipvault/zip zip "pwd" "a.txt" "A")
(zipvault/add-stream zip "pwd" "a.txt" is)))
""" )
:see-also '(
"zipvault/zip"
"zipvault/add-files"
"zipvault/add-folder"
"zipvault/remove-files" ) }
add-stream [zip passphrase name is]
{ :pre [(file-or-string? zip)
(string? passphrase)
(string? name)
(instance-of? :java.io.InputStream is)] }
(let [params (create-params)]
(. params :setFileNameInZip name)
(try-with [zf (. :ZipFile :new (io/file zip) passphrase)]
(. zf :addStream is params))))
(defn
^{ :arglists '("(zipvault/remove-files zip passphrase & files)")
:doc """
Removes all files from the zip file that match the names in the input
list.
If any of the file is a directory, all the files and directories under
this directory will be removed as well.
"""
:examples '(
"""
(do
(load-module :zipvault)
(let [zip (io/file "vault.zip")]
(zipvault/zip zip "pwd" "a.txt" "A" "b.txt" "B")
(zipvault/remove-files zip "pwd" "a.txt")))
""" )
:see-also '(
"zipvault/zip"
"zipvault/add-files"
"zipvault/add-folder"
"zipvault/add-stream" ) }
remove-files [zip passphrase & files]
{ :pre [(file-or-string? zip)
(string? passphrase)
(or (empty? files) (every? string? files))] }
(when-not (empty? files)
(try-with [zf (. :ZipFile :new (io/file zip) passphrase)]
(. zf :removeFiles files))))
(defn
^{ :arglists '("(zipvault/encrypted? zip)")
:doc """
Extracts a specific file from the zip file to the destination path.
"""
:examples '(
"""
(do
(load-module :zipvault)
(zipvault/zip (io/file "vault.zip") "pwd" "a.txt" "abc")
(zipvault/encrypted? (io/file "vault.zip")))
""" ) }
encrypted? [zip]
{ :pre [(file-or-string? zip)] }
(assert (io/exists-file? zip))
(try-with [zf (. :ZipFile :new (io/file zip))]
(. zf :isEncrypted)))
(defn
^{ :arglists '("(zipvault/valid-zip-file? zip)")
:doc """
Returns true if the zip is a valid zip file else false.
"""
:examples '(
"""
(do
(load-module :zipvault)
(zipvault/zip (io/file "vault.zip") "pwd" "a.txt" "abc")
(zipvault/valid-zip-file? (io/file "vault.zip")))
""" ) }
valid-zip-file? [zip]
{ :pre [(file-or-string? zip)] }
(try-with [zf (. :ZipFile :new (io/file zip))]
(. zf :isValidZipFile)))
(defn
^{ :arglists '("(zipvault/extract-file zip password filename destpath)")
:doc """
Extracts a specific file or folder from the zip file to the
destination path.
"""
:examples '(
"""
(do
(load-module :zipvault)
(zipvault/zip (io/file "vault.zip")
"pwd"
"a.txt" "abc"
"b.txt" "def")
;; extract a file
(zipvault/extract-file (io/file "vault.zip")
"pwd"
"a.txt"
"."))
""",
"""
(do
(load-module :zipvault)
(zipvault/zip (io/file "vault.zip")
"pwd"
"words/one.txt" "one"
"words/two.txt" "two"
"logs/001.log" "xxx")
;; extract a folder
(zipvault/extract-file (io/file "vault.zip")
"pwd"
"words/"
"."))
""" )
:see-also '(
"zipvault/zip"
"zipvault/extract-all"
"zipvault/extract-file-data" ) }
extract-file [zip passphrase filename destpath]
{ :pre [(file-or-string? zip)
(string? passphrase)
(string? filename)
(or (string? destpath) (io/file? destpath))] }
(assert (io/exists-file? zip))
(assert (io/file-can-read? zip))
(assert (io/exists-dir? destpath))
(assert (io/file-can-write? destpath))
(let [destpath (if (string? destpath) destpath (io/file-path destpath))]
(try-with [zf (. :ZipFile :new (io/file zip) passphrase)]
(. zf :extractFile filename destpath))))
(defn
^{ :arglists '(
"(zipvault/extract-all zip destpath)"
"(zipvault/extract-all zip passphrase destpath)")
:doc """
Extracts all files from the zip file to the destination path.
"""
:examples '(
"""
(do
(load-module :zipvault)
(zipvault/zip (io/file "vault.zip")
"pwd"
"a.txt" "abc"
"b.txt" "def")
(zipvault/extract-all (io/file "vault.zip")
"pwd"
"."))
""" )
:see-also '(
"zipvault/zip"
"zipvault/extract-file"
"zipvault/extract-file-data" ) }
extract-all
([zip destpath]
{ :pre [(file-or-string? zip) (file-or-string? destpath)] }
(assert (io/exists-file? zip))
(assert (io/file-can-read? zip))
(assert (io/exists-dir? destpath))
(assert (io/file-can-write? destpath))
(let [destpath (if (string? destpath) destpath (io/file-path destpath))]
(try-with [zf (. :ZipFile :new (io/file zip))]
(. zf :extractAll destpath))))
([zip passphrase destpath]
{ :pre [(file-or-string? zip)
(string? passphrase)
(file-or-string? destpath)] }
(assert (io/exists-file? zip))
(assert (io/file-can-read? zip))
(assert (io/exists-dir? destpath))
(assert (io/file-can-write? destpath))
(let [destpath (if (string? destpath) destpath (io/file-path destpath))]
(try-with [zf (. :ZipFile :new (io/file zip) passphrase)]
(. zf :extractAll destpath)))))
(defn
^{ :arglists '("(zipvault/extract-file-data in passphrase filename)")
:doc """
Extracts a specific file from the zip file and returns it as binary
data. in may be a file or an input stream.
Returns `nil` if the file does not exist.
"""
:examples '(
"""
(do
(load-module :zipvault)
(zipvault/zip (io/file "vault.zip")
"pwd"
"a.txt" "abc"
"b.txt" "def")
(zipvault/extract-file-data (io/file "vault.zip")
"pwd"
"a.txt"))
""" )
:see-also '(
"zipvault/zip"
"zipvault/extract-file"
"zipvault/extract-all" ) }
extract-file-data [in passphrase filename]
{ :pre [(or (file-or-string? in) (instance-of? :java.io.InputStream in))
(string? passphrase)
(string? filename)] }
(let [is (if (file-or-string? in) (io/file-in-stream in) in)]
(try-with [zis (. :ZipInputStream :new is passphrase)]
(loop [header (. zis :getNextEntry)]
(if (nil? header)
nil ; file not found -> return no data
(let [name (. header :getFileName)]
(if (= filename name)
(io/slurp zis :binary true) ; return binary file data
(recur (. zis :getNextEntry)))))))))
(defn- zip-to-os [params passphrase os entries]
(try-with [zos (. :ZipOutputStream :new os passphrase)]
(docoll (fn [[k v]] (add-entry params zos k v)) entries)))
(defn- add-entry [params zos name value]
(let [name (if (io/file? name) (io/file-name name) name)]
(cond
(str/ends-with? name "/")
(add-entry-folder params zos name)
(nil? value)
(add-entry-data params zos name (bytebuf))
(fn? value)
(add-entry-producer params zos name value)
(instance-of? :java.io.InputStream value)
(add-entry-data params zos name (io/slurp value :binary true))
(= (type value) :java.io.File)
(add-entry-data params zos name (io/slurp value :binary true))
(= (type value) :core/string)
(add-entry-data params zos name (bytebuf value))
(= (type value) :core/bytebuf)
(add-entry-data params zos name value)
:else
(throw (ex :VncException
"""
Invalid zip entry value type ~(type value)! \
Expected a file, string, or bytebuf.
""")))))
(defn- add-entry-folder [params zos name]
(. params :setFileNameInZip name)
(. zos :putNextEntry params)
(. zos :closeEntry))
(defn- add-entry-data [params zos name data]
(. params :setFileNameInZip name)
(. zos :putNextEntry params)
(. zos :write data)
(. zos :closeEntry))
(defn- add-entry-producer [params zos name producer]
(. params :setFileNameInZip name)
(. zos :putNextEntry params)
(producer zos)
(. zos :closeEntry))
(defn- create-params []
(doto (. :ZipParameters :new)
(. :setEncryptFiles true)
(. :setEncryptionMethod :AES)
(. :setAesKeyStrength :KEY_STRENGTH_256)
(. :setAesVersion :TWO)
(. :setCompressionLevel :NORMAL)))
(defn- file-or-string? [x]
(or (io/file? x) (string? x)))
© 2015 - 2025 Weber Informatics LLC | Privacy Policy