All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cognitect.aws.util.clj Maven / Gradle / Ivy

The newest version!
;; Copyright (c) Cognitect, Inc.
;; All rights reserved.

(ns ^:skip-wiki cognitect.aws.util
  "Impl, don't call directly."
  (:require [clojure.string :as str]
            [clojure.data.xml :as xml]
            [clojure.data.json :as json]
            [clojure.java.io :as io]
            [clojure.core.async :as a])
  (:import [java.time ZoneOffset ZonedDateTime]
           [java.time.format DateTimeFormatter]
           [java.util Date]
           [java.util UUID]
           [java.io InputStream]
           [java.security MessageDigest]
           [javax.crypto Mac]
           [javax.crypto.spec SecretKeySpec]
           [java.nio ByteBuffer]
           [java.io ByteArrayInputStream ByteArrayOutputStream]
           [java.net URLEncoder]
           [java.util Base64]))

(set! *warn-on-reflection* true)

(defn format-date
  ([fmt]
   (format-date fmt (Date.)))
  ([^DateTimeFormatter fmt ^Date inst]
   (.format fmt (.atZone (.toInstant inst) ZoneOffset/UTC))))

(defn format-timestamp
  "Format a timestamp in milliseconds."
  [inst]
  (str (long (/ (.getTime ^Date inst) 1000))))

(defn parse-date
  [^DateTimeFormatter fmt ^String s]
  (Date/from (.toInstant (ZonedDateTime/parse s fmt))))

(def ^DateTimeFormatter x-amz-date-format
  "Custom formatter for x-amz-date header. Used exclusively for formatting.

  https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-signing-elements.html#date

  e.g. '20220830T123600Z'"
  (DateTimeFormatter/ofPattern "yyyyMMdd'T'HHmmssXXX"))

(def ^DateTimeFormatter iso8601-date-format
  "ISO 8601 formatter. Used exclusively for formatting. Fractions of seconds are always omitted.

  e.g. '2008-06-03T11:05:30Z'"
  (DateTimeFormatter/ofPattern "yyyy-MM-dd'T'HH:mm:ssXXX"))

(def ^DateTimeFormatter iso8601-msecs-date-format
  "ISO 8601 formatter. Used exclusively for parsing. Fractions of seconds are optional.

  e.g. '2008-06-03T11:05:30.123Z'"
  DateTimeFormatter/ISO_OFFSET_DATE_TIME)

(def ^DateTimeFormatter rfc822-date-format
  "RFC 1123 formatter. Used for formatting and parsing.

  e.g. 'Tue, 3 Jun 2008 11:05:30 GMT'"
  DateTimeFormatter/RFC_1123_DATE_TIME)

(let [hex-chars (char-array [\0 \1 \2 \3 \4 \5 \6 \7 \8 \9 \a \b \c \d \e \f])]
  (defn hex-encode
    [^bytes bytes]
    (let [bl (alength bytes)
          ca (char-array (* 2 bl))]
      (loop [i (int 0)
             c (int 0)]
        (if (< i bl)
          (let [b (long (bit-and (long (aget bytes i)) 255))]
            (aset ca c ^char (aget hex-chars (unsigned-bit-shift-right b 4)))
            (aset ca (unchecked-inc-int c) (aget hex-chars (bit-and b 15)))
            (recur (unchecked-inc-int i) (unchecked-add-int c 2)))
          (String. ca))))))

(defprotocol ByteSource
  (slurp-bytes [_] "Returns copy unless owner always treats arrays as values. Does not consume owner."))

(extend-protocol ByteSource
  ByteBuffer
  (slurp-bytes
    [buff]
    (let [buff (.duplicate buff)
          n (.remaining buff)
          bytes (byte-array n)]
      (.get buff bytes)
      bytes)))

(defn bbuff->byte-array
  "Returns a byte array with the content that is between the ByteBuffer current position and limit."
  [^ByteBuffer buff]
  (if (and (.hasArray buff)
           (= (.remaining buff) (alength (.array buff))))
    (.array buff)
    (slurp-bytes buff)))

(defn sha-256
  "Returns the sha-256 digest (bytes) of data, which can be a
  byte-array, an input-stream, or nil, in which case returns the
  sha-256 of the empty string."
  [data]
  (cond (string? data)
        (sha-256 (.getBytes ^String data "UTF-8"))
        (instance? ByteBuffer data)
        (sha-256 (bbuff->byte-array ^ByteBuffer data))
        :else
        (let [digest (MessageDigest/getInstance "SHA-256")]
          (when data
            (.update digest ^bytes data))
          (.digest digest))))

(defn hmac-sha-256
  [key ^String data]
  (let [mac (Mac/getInstance "HmacSHA256")]
    (.init mac (SecretKeySpec. key "HmacSHA256"))
    (.doFinal mac (.getBytes data "UTF-8"))))

(defn input-stream->byte-array ^bytes [is]
  (let [os (ByteArrayOutputStream.)]
    (io/copy is os)
    (.toByteArray os)))

(defn bbuf->bytes
  [^ByteBuffer bbuf]
  (when bbuf
    (let [bytes (byte-array (.remaining bbuf))]
      (.get (.duplicate bbuf) bytes)
      bytes)))

(defn bbuf->str
  "Creates a string from java.nio.ByteBuffer object.
   The encoding is fixed to UTF-8."
  [^ByteBuffer bbuf]
  (when-let [bytes (bbuf->bytes bbuf)]
    (String. ^bytes bytes "UTF-8")))

(defn bbuf->input-stream
  [^ByteBuffer bbuf]
  (when bbuf
    (io/input-stream (bbuf->bytes bbuf))))

(defprotocol BBuffable
  (->bbuf [data]))

(extend-protocol BBuffable
  (Class/forName "[B")
  (->bbuf [bs] (ByteBuffer/wrap bs))

  String
  (->bbuf [s] (->bbuf (.getBytes s "UTF-8")))

  InputStream
  (->bbuf [is] (->bbuf (input-stream->byte-array is)))

  ByteBuffer
  (->bbuf [bb] bb)

  nil
  (->bbuf [_]))

(defn xml-read
  "Parse the UTF-8 XML string."
  [s]
  (xml/parse (ByteArrayInputStream. (.getBytes ^String s "UTF-8"))
             :namespace-aware false
             :skip-whitespace true))

(defn xml->map [element]
  (cond
    (nil? element)        nil
    (string? element)     element
    (sequential? element) (if (> (count element) 1)
                            (into {} (map xml->map) element)
                            (xml->map (first element)))
    (map? element)
    (cond
      (empty? element) {}
      (:attrs element) {(:tag element)                                (xml->map (:content element))
                        (keyword (str (name (:tag element)) "Attrs")) (:attrs element)}
      :else            {(:tag element) (xml->map  (:content element))})
    :else                 nil))

(defn xml-write
  [e]
  (if (instance? String e)
    (print e)
    (do
      (print (str "<" (name (:tag e))))
      (when (:attrs e)
        (doseq [attr (:attrs e)]
          (print (str " " (name (key attr)) "=\"" (val attr) "\""))))
      (if-not (empty? (:content e))
        (do
          (print ">")
          (doseq [c (:content e)]
            (xml-write c))
          (print (str "")))
        (print " />")))))

(defn url-encode
  "Percent encode the string to put in a URL."
  [^String s]
  (-> s
      (URLEncoder/encode "UTF-8")
      ;; https://github.com/aws/aws-sdk-java-v2/blob/61d16e0/utils/src/main/java/software/amazon/awssdk/utils/http/SdkHttpUtils.java#L170-L176
      (.replace "+" "%20")
      (.replace "*" "%2A")))

(defn query-string
  "Create a query string from a list of parameters. Values must all be
  strings."
  [params]
  (when-not (empty? params)
    (str/join "&" (map (fn [[k v]]
                         (str (url-encode (name k))
                              "="
                              (url-encode v)))
                       params))))

(defprotocol Base64Encodable
  (base64-encode [data]))

(extend-protocol Base64Encodable
  (Class/forName "[B")
  (base64-encode [ba] (.encodeToString (Base64/getEncoder) ba))

  ByteBuffer
  (base64-encode [bb] (base64-encode (bbuff->byte-array bb)))

  java.lang.String
  (base64-encode [s] (base64-encode (.getBytes s))))

(defn base64-decode
  "base64 decode a base64-encoded string to an input stream"
  [s]
  (io/input-stream (.decode (Base64/getDecoder) ^String s)))

(defn encode-jsonvalue [data]
  (base64-encode (.getBytes ^String (json/write-str data))))

(defn parse-jsonvalue [data]
  (-> data
      base64-decode
      io/reader
      slurp
      (json/read-str :key-fn keyword)))

(defn md5
  "returns an MD5 hash of the content of bb as a byte array"
  ^bytes [^ByteBuffer bb]
  (let [ba     (bbuff->byte-array bb)
        hasher (MessageDigest/getInstance "MD5")]
    (.update hasher ^bytes ba)
    (.digest hasher)))

(defn uuid-string
  "returns a string representation of a randomly generated UUID"
  []
  (str (UUID/randomUUID)))

(defn with-defaults
  "Given a shape and data of that shape, add defaults for the
  following required keys if they are missing or bound to nil

      :idempotencyToken"
  [shape data]
  (reduce (fn [m [member-name member-spec]]
            (cond
              (not (nil? (get data member-name)))
              m

              (:idempotencyToken member-spec)
              (assoc m member-name (uuid-string))

              :else
              m))
          (or data {})
          (:members shape)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; used to fetch creds and region

(defn fetch-async
  "Internal use. Do not call directly."
  [fetch provider item]
  (a/thread
    (try
      ;; lock on the provider to avoid redundant concurrent requests
      ;; before the provider has a chance to cache the results of the
      ;; first fetch.
      (or (locking provider
            (fetch provider))
          {:cognitect.anomalies/category :cognitect.anomalies/fault
           :cognitect.anomalies/message (format "Unable to fetch %s. See log for more details." item)})
      (catch Throwable t
        {:cognitect.anomalies/category :cognitect.anomalies/fault
         ::throwable t
         :cognitect.anomalies/message (format "Unable to fetch %s." item)}))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Wrappers - here to support testing with-redefs since
;;;;            we can't redef static methods

(defn getenv
  ([] (System/getenv))
  ([k] (System/getenv k)))

(defn getProperty [k]
  (System/getProperty k))




© 2015 - 2025 Weber Informatics LLC | Privacy Policy