diff --git a/src/taoensso/nippy.clj b/src/taoensso/nippy.clj index 65fac03..5cbb194 100644 --- a/src/taoensso/nippy.clj +++ b/src/taoensso/nippy.clj @@ -4,6 +4,7 @@ (:require [clojure.string :as str] [clojure.java.io :as jio] + [taoensso.truss :as truss] [taoensso.encore :as enc] [taoensso.nippy [impl :as impl] @@ -695,7 +696,7 @@ (sm-count? len) (do (write-id out id-kw-sm) (write-sm-count out len)) (md-count? len) (do (write-id out id-kw-md) (write-md-count out len)) ;; :else (do (write-id out id-kw-lg) (write-lg-count out len)) ; Unrealistic - :else (throw (ex-info "Keyword too long" {:name s}))) + :else (truss/ex-info! "Keyword too long" {:name s})) (.write out ba 0 len))) @@ -707,7 +708,7 @@ (sm-count? len) (do (write-id out id-sym-sm) (write-sm-count out len)) (md-count? len) (do (write-id out id-sym-md) (write-md-count out len)) ;; :else (do (write-id out id-sym-lg) (write-lg-count out len)) ; Unrealistic - :else (throw (ex-info "Symbol too long" {:name s}))) + :else (truss/ex-info! "Symbol too long" {:name s})) (.write out ba 0 len))) @@ -906,7 +907,7 @@ (sm-count? len) (do (write-id out id-sz-quarantined-sm) (write-bytes-sm out class-name-ba)) (md-count? len) (do (write-id out id-sz-quarantined-md) (write-bytes-md out class-name-ba)) ;; :else (do (write-id out id-sz-quarantined-lg) (write-bytes-md out class-name-ba)) ; Unrealistic - :else (throw (ex-info "Serializable class name too long" {:name class-name}))) + :else (truss/ex-info! "Serializable class name too long" {:name class-name})) ;; Legacy: write object directly to out. ;; (.writeObject (ObjectOutputStream. out) x) @@ -931,8 +932,8 @@ :else (do (write-id out id-reader-lg) (write-bytes-lg out edn-ba))) true))) -(defn ^:deprecated try-write-serializable [out x] (enc/catching (write-serializable out x))) -(defn ^:deprecated try-write-readable [out x] (enc/catching (write-readable out x))) +(defn ^:deprecated try-write-serializable [out x] (truss/catching :all (write-serializable out x))) +(defn ^:deprecated try-write-readable [out x] (truss/catching :all (write-readable out x))) (defn- try-pr-edn [x] (try @@ -1028,7 +1029,7 @@ (when first-occurance? (-freeze-with-meta! x-val out))) :else - ;; (throw (ex-info "Max cache size exceeded" {:idx idx})) + ;; (truss/ex-info! "Max cache size exceeded" {:idx idx}) (-freeze-with-meta! x-val out) ; Just freeze uncached )) @@ -1045,7 +1046,7 @@ (vswap! cache_ assoc idx x) x) v)) - (throw (ex-info "Can't thaw without cache available. See `with-cache`." {})))))) + (truss/ex-info! "Can't thaw without cache available. See `with-cache`." {}))))) (comment (thaw (freeze [(cache "foo") (cache "foo") (cache "foo")])) @@ -1129,7 +1130,7 @@ (sm-count? len) (do (write-id out id-record-sm) (write-bytes-sm out class-name-ba)) (md-count? len) (do (write-id out id-record-md) (write-bytes-md out class-name-ba)) ;; :else (do (write-id out id-record-lg) (write-bytes-md out class-name-ba)) ; Unrealistic - :else (throw (ex-info "Record class name too long" {:name class-name}))) + :else (truss/ex-info! "Record class name too long" {:name class-name})) (-freeze-without-meta! (into {} x) out))) @@ -1191,14 +1192,13 @@ :if-let [fff *final-freeze-fallback*] (fff out x) ; Deprecated :else (let [t (type x)] - (throw - (ex-info (str "Failed to freeze type: " t) - (enc/assoc-some - {:type t - :as-str (try-pr-edn x)} - {:serializable-error e1 - :readable-error e2}) - (or e1 e2)))))))) + (truss/ex-info! (str "Failed to freeze type: " t) + (enc/assoc-some + {:type t + :as-str (try-pr-edn x)} + {:serializable-error e1 + :readable-error e2}) + (or e1 e2))))))) ;;;; @@ -1212,9 +1212,8 @@ (defn- wrap-header [data-ba head-meta] (if-let [head-ba (get-head-ba head-meta)] (enc/ba-concat head-ba data-ba) - (throw - (ex-info (str "Unrecognized header meta: " head-meta) - {:head-meta head-meta})))) + (truss/ex-info! (str "Unrecognized header meta: " head-meta) + {:head-meta head-meta}))) (comment (wrap-header (.getBytes "foo") {:compressor-id :lz4 :encryptor-id nil})) @@ -1377,7 +1376,7 @@ (defmacro ^:private editable? [coll] `(instance? clojure.lang.IEditableCollection ~coll)) -(defn- xform* [xform] (enc/catching-xform {:error/msg "Error thrown via `*thaw-xform*`"} xform)) +(defn- xform* [xform] (truss/catching-xform {:error/msg "Error thrown via `*thaw-xform*`"} xform)) (let [rf! (fn rf! ([x] (persistent! x)) ([acc x] (conj! acc x))) rf* (fn rf* ([x] x) ([acc x] (conj acc x)))] @@ -1414,14 +1413,12 @@ (try (custom-reader in) (catch Exception e - (throw - (ex-info - (str "Reader exception for custom type id: " type-id) - {:type-id type-id, :prefixed? prefixed?} e)))) - (throw - (ex-info - (str "No reader provided for custom type id: " type-id) - {:type-id type-id, :prefixed? prefixed?})))) + (truss/ex-info! + (str "Reader exception for custom type id: " type-id) + {:type-id type-id, :prefixed? prefixed?} e))) + (truss/ex-info! + (str "No reader provided for custom type id: " type-id) + {:type-id type-id, :prefixed? prefixed?}))) (defn- read-edn [edn] (try @@ -1498,9 +1495,9 @@ [^DataInput in class-name] (if (thaw-serializable-allowed? class-name) (read-object in class-name) - (throw ; No way to skip bytes, so best we can do is throw - (ex-info "Cannot thaw object: `taoensso.nippy/*thaw-serializable-allowlist*` check failed. This is a security feature. See `*thaw-serializable-allowlist*` docstring or https://github.com/ptaoussanis/nippy/issues/130 for details!" - {:class-name class-name})))) + (truss/ex-info! ; No way to skip bytes, so best we can do is throw + "Cannot thaw object: `taoensso.nippy/*thaw-serializable-allowlist*` check failed. This is a security feature. See `*thaw-serializable-allowlist*` docstring or https://github.com/ptaoussanis/nippy/issues/130 for details!" + {:class-name class-name}))) (defn- read-record [in class-name] (let [content (thaw-from-in! in)] @@ -1753,15 +1750,13 @@ (if (neg? type-id) (read-custom! in nil type-id) ; Unprefixed custom type - (throw - (ex-info - (str "Unrecognized type id (" type-id "). Data frozen with newer Nippy version?") - {:type-id type-id})))) + (truss/ex-info! + (str "Unrecognized type id (" type-id "). Data frozen with newer Nippy version?") + {:type-id type-id}))) (catch Throwable t - (throw - (ex-info (str "Thaw failed against type-id: " type-id) - {:type-id type-id} t)))))) + (truss/ex-info! (str "Thaw failed against type-id: " type-id) + {:type-id type-id} t))))) (let [head-sig head-sig] ; Not ^:const (defn- try-parse-header [^bytes ba] @@ -1781,20 +1776,20 @@ :lzma2 lzma2-compressor :lz4 lz4-compressor :zstd zstd-compressor - :no-header (throw (ex-info ":auto not supported on headerless data." {})) - :else (throw (ex-info ":auto not supported for non-standard compressors." {})) - (do (throw (ex-info (str "Unrecognized :auto compressor id: " compressor-id) - {:compressor-id compressor-id}))))) + :no-header (truss/ex-info! ":auto not supported on headerless data." {}) + :else (truss/ex-info! ":auto not supported for non-standard compressors." {}) + (do (truss/ex-info! (str "Unrecognized :auto compressor id: " compressor-id) + {:compressor-id compressor-id})))) (defn- get-auto-encryptor [encryptor-id] (case encryptor-id nil nil :aes128-gcm-sha512 aes128-gcm-encryptor :aes128-cbc-sha512 aes128-cbc-encryptor - :no-header (throw (ex-info ":auto not supported on headerless data." {})) - :else (throw (ex-info ":auto not supported for non-standard encryptors." {})) - (do (throw (ex-info (str "Unrecognized :auto encryptor id: " encryptor-id) - {:encryptor-id encryptor-id}))))) + :no-header (truss/ex-info! ":auto not supported on headerless data." {}) + :else (truss/ex-info! ":auto not supported for non-standard encryptors." {}) + (do (truss/ex-info! (str "Unrecognized :auto encryptor id: " encryptor-id) + {:encryptor-id encryptor-id})))) (def ^:private err-msg-unknown-thaw-failure "Possible decryption/decompression error, unfrozen/damaged data, etc.") (def ^:private err-msg-unrecognized-header "Unrecognized (but apparently well-formed) header. Data frozen with newer Nippy version?") @@ -1841,24 +1836,23 @@ (fn ex ([ msg] (ex nil msg)) ([e msg] - (throw - (ex-info (str "Thaw failed. " msg) - {:opts - (assoc opts - :compressor compressor - :encryptor encryptor) + (truss/ex-info! (str "Thaw failed. " msg) + {:opts + (assoc opts + :compressor compressor + :encryptor encryptor) - :bindings - (enc/assoc-some {} - '*freeze-fallback* *freeze-fallback* - '*final-freeze-fallback* *final-freeze-fallback* - '*auto-freeze-compressor* *auto-freeze-compressor* - '*custom-readers* *custom-readers* - '*incl-metadata?* *incl-metadata?* - '*thaw-serializable-allowlist* *thaw-serializable-allowlist* - '*thaw-xform* *thaw-xform*)} + :bindings + (enc/assoc-some {} + '*freeze-fallback* *freeze-fallback* + '*final-freeze-fallback* *final-freeze-fallback* + '*auto-freeze-compressor* *auto-freeze-compressor* + '*custom-readers* *custom-readers* + '*incl-metadata?* *incl-metadata?* + '*thaw-serializable-allowlist* *thaw-serializable-allowlist* + '*thaw-xform* *thaw-xform*)} - e)))) + e))) thaw-data (fn [data-ba compressor-id encryptor-id ex-fn] @@ -2243,5 +2237,5 @@ (alter-var-root *thaw-serializable-allowlist* f) and/or (alter-var-root *freeze-serializable-allow-list* f) instead." [f] - (alter-var-root *freeze-serializable-allowlist* (fn [old] (f (enc/have set? old)))) - (alter-var-root *thaw-serializable-allowlist* (fn [old] (f (enc/have set? old)))))) + (alter-var-root *freeze-serializable-allowlist* (fn [old] (f (truss/have set? old)))) + (alter-var-root *thaw-serializable-allowlist* (fn [old] (f (truss/have set? old)))))) diff --git a/src/taoensso/nippy/compression.clj b/src/taoensso/nippy/compression.clj index 562ab6a..06e8115 100644 --- a/src/taoensso/nippy/compression.clj +++ b/src/taoensso/nippy/compression.clj @@ -1,6 +1,8 @@ (ns ^:no-doc taoensso.nippy.compression "Private, implementation detail." - (:require [taoensso.encore :as enc]) + (:require + [taoensso.truss :as truss] + [taoensso.encore :as enc]) (:import [java.nio ByteBuffer] [java.io @@ -156,7 +158,7 @@ (.read xzs ba 0 len-decomp) (if (== -1 (.read xzs)) ; Good practice as extra safety measure nil - (throw (ex-info "LZMA2 Decompress failed: corrupt data?" {:ba ba}))) + (truss/ex-info! "LZMA2 Decompress failed: corrupt data?" {:ba ba})) ba))) ;;;; Public API diff --git a/src/taoensso/nippy/crypto.clj b/src/taoensso/nippy/crypto.clj index 88c5005..7b73c05 100644 --- a/src/taoensso/nippy/crypto.clj +++ b/src/taoensso/nippy/crypto.clj @@ -2,7 +2,9 @@ "Low-level crypto utils. Private & alpha, very likely to change!" (:refer-clojure :exclude [rand-nth]) - (:require [taoensso.encore :as enc])) + (:require + [taoensso.truss :as truss] + [taoensso.encore :as enc])) ;; Note that AES128 may be preferable to AES256 due to known attack ;; vectors specific to AES256, Ref. @@ -45,7 +47,7 @@ (defn take-ba ^bytes [n ^bytes ba] (java.util.Arrays/copyOf ba ^int n)) ; Pads if ba too small (defn utf8->ba ^bytes [^String s] (.getBytes s "UTF-8")) (defn- add-salt ^bytes [?salt-ba ba] (if ?salt-ba (enc/ba-concat ?salt-ba ba) ba)) -(defn pwd-as-ba ^bytes [utf8-or-ba] (if (string? utf8-or-ba) (utf8->ba utf8-or-ba) (enc/have enc/bytes? utf8-or-ba))) +(defn pwd-as-ba ^bytes [utf8-or-ba] (if (string? utf8-or-ba) (utf8->ba utf8-or-ba) (truss/have enc/bytes? utf8-or-ba))) (comment (seq (pwd-as-ba "foo"))) diff --git a/src/taoensso/nippy/encryption.clj b/src/taoensso/nippy/encryption.clj index 448a028..2620d1b 100644 --- a/src/taoensso/nippy/encryption.clj +++ b/src/taoensso/nippy/encryption.clj @@ -1,6 +1,7 @@ (ns ^:no-doc taoensso.nippy.encryption "Private, implementation detail." (:require + [taoensso.truss :as truss] [taoensso.encore :as enc] [taoensso.nippy.crypto :as crypto])) @@ -14,11 +15,11 @@ (decrypt ^bytes [encryptor pwd ba])) (defn- throw-destructure-ex [typed-password] - (throw (ex-info - (str "Expected password form: " - "[<#{:salted :cached}> ].\n " - "See `aes128-encryptor` docstring for details!") - {:typed-password typed-password}))) + (truss/ex-info! + (str "Expected password form: " + "[<#{:salted :cached}> ].\n " + "See `aes128-encryptor` docstring for details!") + {:typed-password typed-password})) (defn- destructure-typed-pwd [typed-password] (if (vector? typed-password) diff --git a/src/taoensso/nippy/impl.clj b/src/taoensso/nippy/impl.clj index 014c288..7593b82 100644 --- a/src/taoensso/nippy/impl.clj +++ b/src/taoensso/nippy/impl.clj @@ -2,6 +2,7 @@ "Private, implementation detail." (:require [clojure.string :as str] + [taoensso.truss :as truss] [taoensso.encore :as enc])) ;;;; Fallback type tests @@ -63,7 +64,7 @@ (when x (if (string? x) (if (= x "") #{} (set (mapv str/trim (str/split x #"[,:]")))) - (enc/have set? x)))) + (truss/have set? x)))) (comment (mapv classname-set [nil #{"foo"} "" "foo, bar:baz"]) diff --git a/test/taoensso/nippy_tests.clj b/test/taoensso/nippy_tests.clj index 319c6ac..884bd13 100644 --- a/test/taoensso/nippy_tests.clj +++ b/test/taoensso/nippy_tests.clj @@ -4,6 +4,7 @@ [clojure.test.check :as tc] [clojure.test.check.generators :as tc-gens] [clojure.test.check.properties :as tc-props] + [taoensso.truss :as truss :refer [throws?]] [taoensso.encore :as enc :refer [ba=]] [taoensso.nippy :as nippy :refer [freeze thaw]] [taoensso.nippy.compression :as compr] @@ -75,9 +76,9 @@ #(freeze % {:compressor nippy/zstd-compressor})) test-data))) - (is (enc/throws? Exception (thaw (freeze test-data {:password "malformed"})))) - (is (enc/throws? Exception (thaw (freeze test-data {:password [:salted "p"]})))) - (is (enc/throws? Exception (thaw (freeze test-data {:password [:salted "p"]})))) + (is (throws? Exception (thaw (freeze test-data {:password "malformed"})))) + (is (throws? Exception (thaw (freeze test-data {:password [:salted "p"]})))) + (is (throws? Exception (thaw (freeze test-data {:password [:salted "p"]})))) (is (= "payload" @@ -94,12 +95,12 @@ (let [n range-uint+] (= (thaw (freeze n)) n)) (let [n (- range-uint+)] (= (thaw (freeze n)) n))])) - (is (enc/throws? :ex-info "Failed to freeze type" (nippy/freeze (fn [])))) + (is (throws? :ex-info "Failed to freeze type" (nippy/freeze (fn [])))) (testing "Clojure v1.10+ metadata protocol extensions" - [(is (enc/throws? :ex-info "Failed to freeze type" (nippy/freeze (with-meta [] {:a :A, 'b (fn [])})))) - (is (= {:a :A} (meta (nippy/thaw (nippy/freeze (with-meta [] {:a :A, 'b/c (fn [])})))))) - (is (= nil (meta (nippy/thaw (nippy/freeze (with-meta [] { 'b/c (fn [])}))))) + [(is (throws? :ex-info "Failed to freeze type" (nippy/freeze (with-meta [] {:a :A, 'b (fn [])})))) + (is (= {:a :A} (meta (nippy/thaw (nippy/freeze (with-meta [] {:a :A, 'b/c (fn [])})))))) + (is (= nil (meta (nippy/thaw (nippy/freeze (with-meta [] { 'b/c (fn [])}))))) "Don't attach empty metadata")]) (is (gen-test 1600 [gen-data] (= gen-data (thaw (freeze gen-data)))) "Generative")]) @@ -112,7 +113,7 @@ (deftest _types [(testing "Extend to custom type" [(is - (enc/throws? Exception ; No thaw extension yet + (throws? Exception ; No thaw extension yet (do (alter-var-root #'nippy/*custom-readers* (constantly {})) (nippy/extend-freeze MyType 1 [x s] @@ -323,7 +324,7 @@ [(is (= nippy/*thaw-serializable-allowlist* #{"base.1" "base.2" "add.1" "add.2"}) "JVM properties override initial allowlist values") - (is (enc/throws? Exception (nippy/freeze sem {:serializable-allowlist #{}})) + (is (throws? Exception (nippy/freeze sem {:serializable-allowlist #{}})) "Can't freeze Serializable objects unless approved by allowlist") (is (sem? @@ -435,8 +436,8 @@ (is (= (binding [nippy/*thaw-xform* (map (fn [x] (/ 1 0)))] (thaw (freeze []))) []) "rf not run on empty colls") - (let [ex (enc/throws :default (binding [nippy/*thaw-xform* (map (fn [x] (/ 1 0)))] (thaw (freeze [:a :b]))))] - (is (= (-> ex enc/ex-cause enc/ex-cause ex-data :call) '(rf acc in)) "Error thrown via `*thaw-xform*`"))]) + (let [ex (truss/throws :default (binding [nippy/*thaw-xform* (map (fn [x] (/ 1 0)))] (thaw (freeze [:a :b]))))] + (is (= (-> ex ex-cause ex-cause ex-data :call) '(rf acc in)) "Error thrown via `*thaw-xform*`"))]) ;;;; Compressors @@ -453,7 +454,7 @@ (print ".") (flush) (dotimes [_ 1000] (is - (nil? (enc/catching (compr/decompress c (crypto/rand-bytes 1024)))) + (nil? (truss/catching :all (compr/decompress c (crypto/rand-bytes 1024)))) "Decompression never crashes JVM, even against invalid data"))) (println)))