Add compatibility with hato and clj-http-lite insecure feature (#1080)

Added classes:

- java.net.CookiePolicy
- java.net.http.HttpTimeoutException
- javax.net.ssl.HostnameVerifier
- javax.net.ssl.HttpsURLConnection
- javax.net.ssl.KeyManagerFactory
- javax.net.ssl.SSLSession
- javax.net.ssl.TrustManagerFactory
- java.security.KeyStore
- java.util.zip.Inflater
- java.util.zip.ZipException
This commit is contained in:
Michiel Borkent 2021-11-27 15:58:24 +01:00 committed by GitHub
parent a50930fc14
commit 4566905a5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 514 additions and 106 deletions

View file

@ -51,11 +51,9 @@
:main-opts ["-m" "babashka.profile"]}
:lib-tests
{:extra-paths ["process/src" "process/test" "test-resources/lib_tests"]
:extra-deps {babashka/clj-http-lite
{:git/url "https://github.com/babashka/clj-http-lite"
:sha "f44ebe45446f0f44f2b73761d102af3da6d0a13e"}
:extra-deps {org.clj-commons/clj-http-lite {:mvn/version "0.4.392"}
borkdude/spartan.spec {:git/url "https://github.com/borkdude/spartan.spec"
:sha "16f7eec4b6589c77c96c9fcf989f78fffcee7c4c"}
:sha "7689f85d4c79de6c7b5ee3ab62e3e72eeccd3318"}
lambdaisland/regal {:git/url "https://github.com/lambdaisland/regal"
:sha "f902d2c43121f9e1c48603d6eb99f5900eb6a9f6"}
weavejester/medley {:git/url "https://github.com/weavejester/medley"
@ -92,7 +90,8 @@
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
com.stuartsierra/component {:mvn/version "1.0.0"}
org.clojars.askonomm/ruuter {:mvn/version "1.2.2"}
org.clj-commons/digest {:mvn/version "1.4.100"}}
org.clj-commons/digest {:mvn/version "1.4.100"}
hato/hato {:mvn/version "0.8.2"}}
:classpath-overrides {org.clojure/clojure nil
org.clojure/spec.alpha nil
org.clojure/core.specs.alpha nil}}

2
sci

@ -1 +1 @@
Subproject commit 3b62462112bde39d166914d0e424075ed18195cf
Subproject commit f3d2764b8f9fc14026c89e23d94fa2071ededcea

View file

@ -2,11 +2,11 @@
{:no-doc true}
(:require
[babashka.impl.features :as features]
[babashka.impl.proxy :as proxy]
[cheshire.core :as json]
[sci.impl.types :as t]))
(def custom-map
(cond->
(def base-custom-map
`{clojure.lang.LineNumberingPushbackReader {:allPublicConstructors true
:allPublicMethods true}
java.lang.Thread
@ -82,7 +82,12 @@
java.net.Inet4Address
{:methods [{:name "getHostAddress"}]}
java.net.Inet6Address
{:methods [{:name "getHostAddress"}]}}
{:methods [{:name "getHostAddress"}]}})
(def custom-map
(cond->
(merge base-custom-map
proxy/custom-reflect-map)
features/hsqldb? (assoc `org.hsqldb.dbinfo.DatabaseInformationFull
{:methods [{:name "<init>"
:parameterTypes ["org.hsqldb.Database"]}]}
@ -189,6 +194,7 @@
java.net.CookieHandler
java.net.CookieManager
java.net.CookieStore
java.net.CookiePolicy
java.net.HttpCookie
java.net.PasswordAuthentication
java.net.ProxySelector
@ -204,15 +210,21 @@
java.net.http.HttpResponse
java.net.http.HttpResponse$BodyHandler
java.net.http.HttpResponse$BodyHandlers
java.net.http.HttpTimeoutException
java.net.http.WebSocket
java.net.http.WebSocket$Builder
java.net.http.WebSocket$Listener
java.security.cert.X509Certificate
javax.crypto.Mac
javax.crypto.spec.SecretKeySpec
javax.net.ssl.HostnameVerifier ;; clj-http-lite
javax.net.ssl.HttpsURLConnection ;; clj-http-lite
javax.net.ssl.KeyManagerFactory
javax.net.ssl.SSLContext
javax.net.ssl.SSLParameters
javax.net.ssl.SSLSession ;; clj-http-lite
javax.net.ssl.TrustManager
javax.net.ssl.TrustManagerFactory
javax.net.ssl.X509TrustManager
jdk.internal.net.http.HttpClientBuilderImpl
jdk.internal.net.http.HttpClientFacade
@ -259,6 +271,7 @@
java.security.MessageDigest
java.security.DigestInputStream
java.security.Provider
java.security.KeyStore
java.security.SecureRandom
java.security.Security
java.sql.Date
@ -337,6 +350,7 @@
java.util.concurrent.TimeUnit
java.util.function.Function
java.util.function.Supplier
java.util.zip.Inflater
java.util.zip.InflaterInputStream
java.util.zip.DeflaterInputStream
java.util.zip.GZIPInputStream
@ -344,6 +358,7 @@
java.util.zip.ZipInputStream
java.util.zip.ZipOutputStream
java.util.zip.ZipEntry
java.util.zip.ZipException
java.util.zip.ZipFile
~(symbol "[B")
~(symbol "[I")

View file

@ -61,9 +61,32 @@
["java.net.Authenticator" #{}]
(proxy [java.net.Authenticator] []
(getPasswordAuthentication [] ((method-or-bust methods 'getPasswordAuthentication) this)))
(getPasswordAuthentication []
((method-or-bust methods 'getPasswordAuthentication) this)))
["java.net.ProxySelector" #{}]
(proxy [java.net.ProxySelector] []
(connectFailed [_ _ _] ((method-or-bust methods 'connectFailed) this))
(select [_ _] ((method-or-bust methods 'select) this))))))
(connectFailed [uri socket-address ex]
((method-or-bust methods 'connectFailed) this uri socket-address ex))
(select [uri] ((method-or-bust methods 'select) this uri)))
["javax.net.ssl.HostnameVerifier" #{}]
(proxy [javax.net.ssl.HostnameVerifier] []
(verify [host-name session] ((method-or-bust methods 'verify) this host-name session))))))
(defn class-sym [c] (symbol (class-name c)))
(def custom-reflect-map
{(class-sym (class (proxy-fn {:class java.net.Authenticator})))
{:methods [{:name "getPasswordAuthentication"}]}
(class-sym (class (proxy-fn {:class java.net.ProxySelector})))
{:methods [{:name "connectFailed"}
{:name "select"}]}
(class-sym (class (proxy-fn {:class javax.net.ssl.HostnameVerifier})))
{:methods [{:name "verify"}]}})
;;; Scratch
(comment
)

View file

@ -1,7 +1,10 @@
(ns babashka.run-all-libtests
(:require [clojure.java.io :as io]
[clojure.string :as str]
[clojure.test :as t]))
[clojure.test :as t]
[spartan.spec]))
(require 'clojure.spec.alpha)
(def ns-args (set (map symbol *command-line-args*)))
@ -260,6 +263,8 @@
(test-namespaces 'clj-commons.digest-test)
(test-namespaces 'hato.client-test)
;;;; final exit code
(let [{:keys [:test :fail :error] :as m} @status]

View file

@ -1,25 +1,34 @@
(ns clj-http.lite.client-test
(:require [cheshire.core :as json]
[clj-http.lite.client :as client]
[clojure.test :as t :refer [deftest]]))
[clojure.test :as t :refer [deftest is]]))
(deftest client-test
(prn (:status (client/get "https://www.clojure.org" {:throw-exceptions false})))
(is (= 200 (:status (client/get "https://www.clojure.org" {:throw-exceptions false}))))
(prn (:status (client/get "https://postman-echo.com/get?foo1=bar1&foo2=bar2" {:throw-exceptions false})))
(is (= 200 (:status (client/get "https://postman-echo.com/get?foo1=bar1&foo2=bar2" {:throw-exceptions false}))))
(prn (:status (client/post "https://postman-echo.com/post" {:throw-exceptions false})))
(is (= 200 (:status (client/post "https://postman-echo.com/post" {:throw-exceptions false}))))
(prn (:status (client/post "https://postman-echo.com/post"
{:body (json/generate-string {:a 1})
:headers {"X-Hasura-Role" "admin"}
:content-type :json
:accept :json
:throw-exceptions false})))
(prn (:status (client/put "https://postman-echo.com/put"
(is (= 200 (:status (client/post "https://postman-echo.com/post"
{:body (json/generate-string {:a 1})
:headers {"X-Hasura-Role" "admin"}
:content-type :json
:accept :json
:throw-exceptions false}))))
(is (= 200 (:status (client/put "https://postman-echo.com/put"
{:body (json/generate-string {:a 1})
:headers {"X-Hasura-Role" "admin"}
:content-type :json
:accept :json
:throw-exceptions false})))))
(deftest insecure-test
(is (= 200 (:status (client/get "https://self-signed.badssl.com/" {:insecure? true})))))
(deftest exception-test
(try (client/get "https://site.com/broken")
(is false "should not reach here")
(catch Exception e
(is (:headers (ex-data e))))))

View file

@ -0,0 +1,348 @@
(ns hato.client-test
(:refer-clojure :exclude [get])
(:require [clojure.test :refer :all]
[hato.client :refer :all]
[clojure.java.io :as io]
[org.httpkit.server :as http-kit]
[cheshire.core :as json]
[cognitect.transit :as transit]
#_[promesa.exec :as pexec]
#_[ring.middleware.multipart-params])
(:import (java.io InputStream ByteArrayOutputStream)
(java.net ProxySelector CookieHandler CookieManager)
(java.net.http HttpClient$Redirect HttpClient$Version HttpClient)
(java.time Duration)
(javax.net.ssl SSLContext)
(java.util UUID)))
(defmacro with-server
"Spins up a local HTTP server with http-kit."
[handler & body]
`(let [s# (http-kit/run-server ~handler {:port 1234})]
(try ~@body
(finally
(s# :timeout 100)))))
(deftest test-build-http-client
(testing "authenticator"
(is (.isEmpty (.authenticator (build-http-client {}))) "not set by default")
(is (= "user" (-> (build-http-client {:authenticator {:user "user" :pass "pass"}})
.authenticator .get .getPasswordAuthentication .getUserName)))
(is (.isEmpty (.authenticator (build-http-client {:authenticator :some-invalid-value}))) "ignore invalid input"))
(testing "connect-timeout"
(is (.isEmpty (.connectTimeout (build-http-client {}))) "not set by default")
(is (= 5 (-> (build-http-client {:connect-timeout 5}) (.connectTimeout) ^Duration (.get) (.toMillis))))
(is (thrown? Exception (build-http-client {:connect-timeout :not-a-number}))))
(testing "cookie-manager and cookie-policy"
(is (.isEmpty (.cookieHandler (build-http-client {}))) "not set by default")
(are [x] (instance? CookieHandler (-> ^HttpClient (build-http-client {:cookie-policy x}) (.cookieHandler) (.get)))
:none
:all
:original-server
:any-random-thing ; Invalid values are ignored, so the default :original-server will be in effect
)
(let [cm (CookieManager.)]
(is (= cm (-> (build-http-client {:cookie-handler cm :cookie-policy :all}) (.cookieHandler) (.get)))
":cookie-handler takes precedence over :cookie-policy")))
(testing "redirect-policy"
(is (= HttpClient$Redirect/NEVER (.followRedirects (build-http-client {}))) "NEVER by default")
(are [expected option] (= expected (.followRedirects (build-http-client {:redirect-policy option})))
HttpClient$Redirect/ALWAYS :always
HttpClient$Redirect/NEVER :never
HttpClient$Redirect/NORMAL :normal)
(is (thrown? Exception (build-http-client {:redirect-policy :not-valid-value}))))
(testing "priority"
(is (build-http-client {:priority 1}))
(is (build-http-client {:priority 256}))
(is (thrown? Exception (build-http-client {:priority :not-a-number})))
(are [x] (thrown? Exception (build-http-client {:priority x}))
:not-a-number
0
257))
(testing "proxy"
(is (.isEmpty (.proxy (build-http-client {}))) "not set by default")
(is (.isPresent (.proxy (build-http-client {:proxy :no-proxy}))))
(is (.isPresent (.proxy (build-http-client {:proxy (ProxySelector/getDefault)})))))
#_(testing "executor"
(let [executor (pexec/fixed-pool 1)
client (build-http-client {:executor executor})
stored-executor (.orElse (.executor client) nil)]
(is (instance? java.util.concurrent.ThreadPoolExecutor stored-executor) "executor has proper type")
(is (= executor stored-executor) "executor set properly")))
(testing "ssl-context"
(is (= (SSLContext/getDefault) (.sslContext (build-http-client {}))))
(is (not= (SSLContext/getDefault) (.sslContext (build-http-client {:ssl-context {:keystore (io/resource "keystore.p12")
:keystore-pass "borisman"
:trust-store (io/resource "keystore.p12")
:trust-store-pass "borisman"}})))))
(testing "version"
(is (= HttpClient$Version/HTTP_2 (.version (build-http-client {}))) "HTTP_2 by default")
(are [expected option] (= expected (.version (build-http-client {:version option})))
HttpClient$Version/HTTP_1_1 :http-1.1
HttpClient$Version/HTTP_2 :http-2)
(is (thrown? Exception (build-http-client {:version :not-valid-value})))))
(deftest ^:integration test-basic-response
(testing "basic get request returns response map"
(let [r (get "https://httpbin.org/get")]
(is (pos-int? (:request-time r)))
(is (= 200 (:status r)))
(is (= "https://httpbin.org/get" (:uri r)))
(is (= :http-2 (:version r)))
(is (= :get (-> r :request :request-method)))
(is (= "gzip, deflate" (get-in r [:request :headers "accept-encoding"])))))
#_(testing "setting executor works"
(let [c (build-http-client {:executor (pexec/fixed-pool 1)})
r (get "https://httpbin.org/get" {:http-client c})]
(is (= 200 (:status r)))))
(testing "query encoding"
(let [r (get "https://httpbin.org/get?foo=bar<bee")]
(is (= "https://httpbin.org/get?foo=bar%3Cbee" (:uri r)) "encodes illegals"))
(let [r (get "https://httpbin.org/get?foo=bar%3Cbee")]
(is (= "https://httpbin.org/get?foo=bar%3Cbee" (:uri r)) "does not double encode")))
(testing "verbs exist"
(are [fn] (= 200 (:status (fn "https://httpbin.org/status/200")))
get
post
put
patch
delete
head
options)))
#_(deftest ^:integration test-multipart-response
(testing "basic post request returns response map"
(with-server (ring.middleware.multipart-params/wrap-multipart-params
(fn app [req]
{:status 200
:headers {"Content-Type" "application/json"}
:body (let [params (clojure.walk/keywordize-keys (:multipart-params req))]
(json/generate-string {:files {:file (-> params :file :tempfile slurp)}
:form (select-keys params [:Content/type :eggplant :title])}))}))
(let [uuid (.toString (UUID/randomUUID))
_ (spit (io/file ".test-data") uuid)
r (post "http://localhost:1234" {:as :json
:multipart [{:name "title" :content "My Awesome Picture"}
{:name "Content/type" :content "image/jpeg"}
{:name "foo.txt" :part-name "eggplant" :content "Eggplants"}
{:name "file" :content (io/file ".test-data")}]})]
(is (= {:files {:file uuid}
:form {:Content/type "image/jpeg"
:eggplant "Eggplants"
:title "My Awesome Picture"}} (:body r)))))))
(deftest ^:integration test-basic-response-async
(testing "basic request returns something"
(let [r @(request {:url "https://httpbin.org/get" :async? true})]
(is (= 200 (:status r)))))
(testing "basic get request returns response map"
(let [r @(get "https://httpbin.org/get" {:async? true})]
(is (pos-int? (:request-time r)))
(is (= 200 (:status r)))
(is (= "https://httpbin.org/get" (:uri r)))
(is (= :http-2 (:version r)))
(is (= :get (-> r :request :request-method)))
(is (= "gzip, deflate" (get-in r [:request :headers "accept-encoding"])))))
(testing "callback"
(is (= 200 @(get "https://httpbin.org/status/200" {:async? true} :status identity))
"returns status on success")
(is (= 400 @(get "https://httpbin.org/status/400" {:async? true} identity #(-> % ex-data :status)))
"extracts response map from ex-info on fail")))
(deftest ^:integration test-exceptions
(testing "throws on exceptional status"
(is (thrown? Exception (get "https://httpbin.org/status/500"))))
(testing "can opt out"
(is (= 500 (:status (get "https://httpbin.org/status/500" {:throw-exceptions false})))))
(testing "async"
(is (thrown? Exception @(get "https://httpbin.org/status/400" {:async? true}))
"default callbacks throws on error")
(is (= 400 (:status @(get "https://httpbin.org/status/400" {:async? true :throw-exceptions? false})))
"default callbacks does not throw if :throw-exceptions? is false")))
(deftest ^:integration test-coercions
(testing "as default"
(let [r (get "https://httpbin.org/get")]
(is (string? (:body r)))))
(testing "as byte array"
(let [r (get "https://httpbin.org/get" {:as :byte-array})]
(is (instance? (Class/forName "[B") (:body r)))
(is (string? (String. ^bytes (:body r))))))
(testing "as stream"
(let [r (get "https://httpbin.org/get" {:as :stream})]
(is (instance? InputStream (:body r)))
(is (string? (-> r :body slurp)))))
(testing "as string"
(let [r (get "https://httpbin.org/get" {:as :string})]
(is (string? (:body r)))))
#_(testing "as auto"
(let [r (get "https://httpbin.org/get" {:as :auto})]
(is (coll? (:body r)))))
(testing "as json"
(let [r (get "https://httpbin.org/get" {:as :json})]
(is (coll? (:body r)))))
(testing "large json"
; Ensure large json can be parsed without the stream closing prematurely.
(with-server (fn app [_]
{:status 200
:headers {"Content-Type" "application/json"}
:body (json/generate-string (take 1000 (repeatedly (constantly {:a 1}))))})
(let [b (:body (get "http://localhost:1234" {:as :json}))]
(is (coll? b))
(is (= 1000 (count b))))))
(doseq [content-type [#_"msgpack" "json"]]
(testing (str "decode " content-type)
(let [body {:a [1 2]}]
(with-server (fn app [_]
{:status 200
:headers {"Content-Type" (str "application/transit+" content-type)}
:body (let [output (ByteArrayOutputStream.)]
(transit/write (transit/writer output (keyword content-type)) body)
(clojure.java.io/input-stream (.toByteArray output)))})
(let [r (get "http://localhost:1234" {:as :auto})]
(is (= body (:body r)))))))
(testing (str "empty stream when decoding " content-type)
(with-server (fn app [_]
{:status 200
:headers {"Content-Type" (str "application/transit+" content-type)}
:body nil})
(let [r (get "http://localhost:1234" {:as :auto})]
(is (nil? (:body r))))))))
(deftest ^:integration test-auth
(testing "authenticator basic auth (non-preemptive) via client option"
(let [r (get "https://httpbin.org/basic-auth/user/pass" {:http-client {:authenticator {:user "user" :pass "pass"}}})]
(is (= 200 (:status r))))
(is (thrown? Exception (get "https://httpbin.org/basic-auth/user/pass" {:http-client {:authenticator {:user "user" :pass "invalid"}}}))))
(testing "basic auth"
(let [r (get "https://httpbin.org/basic-auth/user/pass" {:basic-auth {:user "user" :pass "pass"}})]
(is (= 200 (:status r))))
(let [r (get "https://user:pass@httpbin.org/basic-auth/user/pass")]
(is (= 200 (:status r))))
(is (thrown? Exception (get "https://httpbin.org/basic-auth/user/pass" {:basic-auth {:user "user" :pass "invalid"}})))))
(deftest ^:integration test-redirects
; Changed provider due to https://github.com/postmanlabs/httpbin/issues/617
(let [redirect-to "https://httpbingo.org/get"
uri (format "https://httpbingo.org/redirect-to?url=%s" redirect-to)]
(testing "no redirects (default)"
(let [r (get uri {:as :string})]
(is (= 302 (:status r)))
(is (= uri (:uri r)))))
(testing "explicitly never"
(let [r (get uri {:as :string :http-client {:redirect-policy :never}})]
(is (= 302 (:status r)))
(is (= uri (:uri r)))))
(testing "always redirect"
(let [r (get uri {:as :string :http-client {:redirect-policy :always}})]
(is (= 200 (:status r)))
(is (= redirect-to (:uri r)))))
(testing "normal redirect (same protocol - accepted)"
(let [r (get uri {:as :string :http-client {:redirect-policy :normal}})]
(is (= 200 (:status r)))
(is (= redirect-to (:uri r)))))
(testing "normal redirect (different protocol - denied)"
(let [https-tp-http-uri (format "https://httpbingo.org/redirect-to?url=%s" "http://httpbingo.org/get")
r (get https-tp-http-uri {:as :string :http-client {:redirect-policy :normal}})]
(is (= 302 (:status r)))
(is (= https-tp-http-uri (:uri r)))))
(testing "default max redirects"
(are [status redirects] (= status (:status (get (str "https://httpbingo.org/redirect/" redirects) {:http-client {:redirect-policy :normal}})))
200 4
302 5))))
(deftest ^:integration test-cookies
(testing "no cookie manager"
(let [r (get "https://httpbin.org/cookies/set/moo/cow" {:as :json :http-client {:redirect-policy :always}})]
(is (= 200 (:status r)))
(is (nil? (-> r :body :cookies :moo)))))
(testing "with cookie manager"
(let [r (get "https://httpbin.org/cookies/set/moo/cow" {:as :json :http-client {:redirect-policy :always :cookie-policy :all}})]
(is (= 200 (:status r)))
(is (= "cow" (-> r :body :cookies :moo)))))
(testing "persists over requests"
(let [c (build-http-client {:redirect-policy :always :cookie-policy :all})
_ (get "https://httpbin.org/cookies/set/moo/cow" {:http-client c})
r (get "https://httpbin.org/cookies" {:as :json :http-client c})]
(is (= 200 (:status r)))
(is (= "cow" (-> r :body :cookies :moo))))))
(deftest ^:integration test-decompression
(testing "gzip via byte array"
(let [r (get "https://httpbin.org/gzip" {:as :json})]
(is (= 200 (:status r)))
(is (true? (-> r :body :gzipped)))))
(testing "gzip via stream"
(let [r (get "https://httpbin.org/gzip" {:as :stream})]
(is (= 200 (:status r)))
(is (instance? InputStream (:body r)))))
(testing "deflate via byte array"
(let [r (get "https://httpbin.org/deflate" {:as :json})]
(is (= 200 (:status r)))
(is (true? (-> r :body :deflated)))))
(testing "deflate via stream"
(let [r (get "https://httpbin.org/deflate" {:as :stream})]
(is (= 200 (:status r)))
(is (instance? InputStream (:body r))))))
(deftest ^:integration test-http2
(testing "can make an http2 request"
(let [r (get "https://nghttp2.org/httpbin/get" {:as :json})]
(is (= :http-2 (:version r))))))
(deftest custom-middleware
(let [r (get "" {:middleware [(fn [client]
(fn [req]
::response))]})]
(is (= ::response r))))
(try (get "https://httpbin.org/gzip" {:timeout 1000})
(catch java.net.http.HttpTimeoutException _
(doseq [v (vals (ns-publics *ns*))]
(when (:integration (meta v))
(println "Removing test from" v "because httpbin is slow.")
(alter-meta! v dissoc :test)))))

View file

@ -1,8 +1,8 @@
(ns spartan.spec-test
(:require [clojure.test :as t :refer [deftest is]]))
(time (require '[spartan.spec :as s]))
(require '[spartan.spec :as s])
(time (require '[spartan.spec]))
(require '[clojure.spec.alpha :as s])
(deftest spec-test
(time (s/explain (s/cat :i int? :s string?) [1 :foo]))

View file

@ -793,10 +793,19 @@ true")))
(is (= "192.168.2.2" (bb nil "(-> (java.net.InetAddress/getByName \"192.168.2.2\") (.getHostAddress))"))))
(deftest satisfies-protocols-test
#_(is (true? (bb nil "(satisfies? clojure.core.protocols/Datafiable {})")))
#_(is (true? (bb nil "(satisfies? clojure.core.protocols/Navigable {})")))
(is (true? (bb nil "(satisfies? clojure.core.protocols/Datafiable {})")))
(is (true? (bb nil "(satisfies? clojure.core.protocols/Navigable {})")))
(is (true? (bb nil "(satisfies? clojure.core.protocols/IKVReduce {})"))))
(deftest interop-on-proxy
(is (true? (bb nil (pr-str
'(instance? java.net.PasswordAuthentication
(.getPasswordAuthentication
(proxy [java.net.Authenticator] []
(getPasswordAuthentication []
(java.net.PasswordAuthentication. "bork"
(char-array "dude")))))))))))
;;;; Scratch
(comment