Blame


1 1d126b15 2021-10-13 op (ns gemini.core
2 1d126b15 2021-10-13 op (:require
3 1d126b15 2021-10-13 op [clojure.java.io :as io])
4 5243b9ac 2021-10-13 op (:import
5 0c76ea8b 2021-10-15 op (java.net URI)
6 5243b9ac 2021-10-13 op (com.omarpolo.gemini Request)))
7 1d126b15 2021-10-13 op
8 0c76ea8b 2021-10-15 op (comment
9 0c76ea8b 2021-10-15 op (set! *warn-on-reflection* true)
10 0c76ea8b 2021-10-15 op )
11 1d126b15 2021-10-13 op
12 1d126b15 2021-10-13 op
13 1d126b15 2021-10-13 op ;; helpers
14 1d126b15 2021-10-13 op
15 1d126b15 2021-10-13 op (def code-description
16 1d126b15 2021-10-13 op "Human description for every response code."
17 1d126b15 2021-10-13 op {10 "input"
18 1d126b15 2021-10-13 op 11 "sensitive input"
19 1d126b15 2021-10-13 op 20 "success"
20 1d126b15 2021-10-13 op 30 "temporary redirect"
21 1d126b15 2021-10-13 op 31 "permanent redirect"
22 1d126b15 2021-10-13 op 40 "temporary failure"
23 1d126b15 2021-10-13 op 41 "server unavailable"
24 1d126b15 2021-10-13 op 42 "CGI error"
25 1d126b15 2021-10-13 op 43 "proxy error"
26 1d126b15 2021-10-13 op 44 "slow down"
27 1d126b15 2021-10-13 op 50 "permanent failure"
28 1d126b15 2021-10-13 op 51 "not found"
29 1d126b15 2021-10-13 op 52 "gone"
30 1d126b15 2021-10-13 op 53 "proxy request refused"
31 1d126b15 2021-10-13 op 59 "bad request"
32 1d126b15 2021-10-13 op 60 "client certificate required"
33 1d126b15 2021-10-13 op 61 "certificate not authorized"
34 1d126b15 2021-10-13 op 62 "certificate not valid"})
35 1d126b15 2021-10-13 op
36 62094a4e 2021-10-15 op (defn is-input? [{c :code}] (= 1 (quot c 10)))
37 62094a4e 2021-10-15 op (defn is-success? [{c :code}] (= 2 (quot c 10)))
38 62094a4e 2021-10-15 op (defn is-redirect? [{c :code}] (= 3 (quot c 10)))
39 62094a4e 2021-10-15 op (defn is-temporary-failure? [{c :code}] (= 4 (quot c 10)))
40 62094a4e 2021-10-15 op (defn is-permanent-failure? [{c :code}] (= 5 (quot c 10)))
41 62094a4e 2021-10-15 op (defn is-client-cert-required? [{c :code}] (= 6 (quot c 10)))
42 0c76ea8b 2021-10-15 op
43 0c76ea8b 2021-10-15 op
44 0c76ea8b 2021-10-15 op
45 0c76ea8b 2021-10-15 op (defn- parse-params [{:keys [proxy request follow-redirects?]}]
46 0c76ea8b 2021-10-15 op (when (and (:host proxy)
47 0c76ea8b 2021-10-15 op (not (:port proxy)))
48 0c76ea8b 2021-10-15 op (throw (ex-info "invalid proxy definition" {:got proxy
49 0c76ea8b 2021-10-15 op :reason "missing proxy host"})))
50 0c76ea8b 2021-10-15 op {:host (:host proxy)
51 0c76ea8b 2021-10-15 op :port (:port proxy)
52 0c76ea8b 2021-10-15 op :request (or request
53 0c76ea8b 2021-10-15 op (throw (ex-info ":request is nil" {})))
54 0c76ea8b 2021-10-15 op :follow-redirects? (case follow-redirects?
55 0c76ea8b 2021-10-15 op nil 0
56 0c76ea8b 2021-10-15 op false 0
57 0c76ea8b 2021-10-15 op true 5
58 0c76ea8b 2021-10-15 op follow-redirects?)})
59 0c76ea8b 2021-10-15 op
60 0c76ea8b 2021-10-15 op (defn- resolve-uri [request meta]
61 0c76ea8b 2021-10-15 op (let [uri (URI. request)
62 0c76ea8b 2021-10-15 op rel (URI. meta)]
63 0c76ea8b 2021-10-15 op (str (.resolve uri rel))))
64 0c76ea8b 2021-10-15 op
65 0c76ea8b 2021-10-15 op (defn- fetch' [host port uri]
66 0c76ea8b 2021-10-15 op (try
67 0c76ea8b 2021-10-15 op (let [req (cond
68 0c76ea8b 2021-10-15 op host (Request. ^String host ^int port ^String uri)
69 0c76ea8b 2021-10-15 op :else (Request. ^String uri))]
70 0c76ea8b 2021-10-15 op {:uri uri
71 0c76ea8b 2021-10-15 op :request req
72 0c76ea8b 2021-10-15 op :code (.getCode req)
73 0c76ea8b 2021-10-15 op :meta (.getMeta req)
74 0c76ea8b 2021-10-15 op :body (.body req)})
75 0c76ea8b 2021-10-15 op (catch Throwable e
76 0c76ea8b 2021-10-15 op {:error e})))
77 0c76ea8b 2021-10-15 op
78 0c76ea8b 2021-10-15 op (defn fetch
79 0c76ea8b 2021-10-15 op "Make a gemini request. `params` is a map with the following
80 0c76ea8b 2021-10-15 op keys (only `:request` is mandatory):
81 0c76ea8b 2021-10-15 op
82 0c76ea8b 2021-10-15 op - `:proxy`: a map of `:host` and `:port`, identifies the server to
83 0c76ea8b 2021-10-15 op send the requests to. This allows to use a gemini server as a
84 0c76ea8b 2021-10-15 op proxy, it doesn't do any other kind of proxying (e.g. SOCK5.)
85 0c76ea8b 2021-10-15 op
86 0c76ea8b 2021-10-15 op - `:request` the URI (as string) to require.
87 0c76ea8b 2021-10-15 op
88 0c76ea8b 2021-10-15 op - `:follow-redirects?` if `false` or `nil` don't follow redirects,
89 0c76ea8b 2021-10-15 op if `true` follow up to 5 redirects, or the number of redirects to
90 0c76ea8b 2021-10-15 op follow.
91 0c76ea8b 2021-10-15 op
92 0c76ea8b 2021-10-15 op Return a map with `:request`, `:code`, `:meta`, `:body` on success
93 0c76ea8b 2021-10-15 op or `:error` on failure. The request needs to be closed when done
94 0c76ea8b 2021-10-15 op usign `close`."
95 0c76ea8b 2021-10-15 op [params]
96 0c76ea8b 2021-10-15 op (let [{:keys [host port request follow-redirects?] :as orig} (parse-params params)]
97 0c76ea8b 2021-10-15 op (loop [n follow-redirects?
98 0c76ea8b 2021-10-15 op request request
99 0c76ea8b 2021-10-15 op redirected? false]
100 0c76ea8b 2021-10-15 op (let [res (fetch' host port request)
101 0c76ea8b 2021-10-15 op redirect? (and (not (:error res))
102 0c76ea8b 2021-10-15 op (is-redirect? res))]
103 0c76ea8b 2021-10-15 op (cond
104 0c76ea8b 2021-10-15 op (:error res) res
105 0c76ea8b 2021-10-15 op (= follow-redirects? 0) res
106 0c76ea8b 2021-10-15 op (and (= 0 n)
107 0c76ea8b 2021-10-15 op redirect?) (do (.close ^Request (:request res))
108 0c76ea8b 2021-10-15 op (throw (ex-info "too many redirects"
109 0c76ea8b 2021-10-15 op {:original orig
110 0c76ea8b 2021-10-15 op :redirects follow-redirects?
111 0c76ea8b 2021-10-15 op :code (:code res)
112 0c76ea8b 2021-10-15 op :meta (:meta res)})))
113 0c76ea8b 2021-10-15 op redirect? (do (.close ^Request (:request res))
114 0c76ea8b 2021-10-15 op (recur (dec n)
115 0c76ea8b 2021-10-15 op (resolve-uri request (:meta res))
116 0c76ea8b 2021-10-15 op true))
117 9142afc2 2021-10-15 op :else (assoc res :redirected? redirected?))))))
118 0c76ea8b 2021-10-15 op
119 0c76ea8b 2021-10-15 op (defn body-as-string!
120 0c76ea8b 2021-10-15 op "Read all the response into a strings and returns it. The request
121 0c76ea8b 2021-10-15 op will be closed."
122 0c76ea8b 2021-10-15 op [{r :request}]
123 0c76ea8b 2021-10-15 op (let [sw (java.io.StringWriter.)]
124 0c76ea8b 2021-10-15 op (with-open [r ^Request r]
125 0c76ea8b 2021-10-15 op (io/copy (.body r) sw)
126 0c76ea8b 2021-10-15 op (.toString sw))))
127 0c76ea8b 2021-10-15 op
128 0c76ea8b 2021-10-15 op (defn close
129 0c76ea8b 2021-10-15 op "Close a request."
130 0c76ea8b 2021-10-15 op [{r :request}]
131 0c76ea8b 2021-10-15 op (.close ^Request r))
132 0c76ea8b 2021-10-15 op
133 0c76ea8b 2021-10-15 op (defmacro with-request
134 0c76ea8b 2021-10-15 op "Make a request, eval `body` when it succeed and automatically close
135 0c76ea8b 2021-10-15 op the request, or throw an exception if the request fails."
136 0c76ea8b 2021-10-15 op [[var req] & body]
137 0c76ea8b 2021-10-15 op `(let [~var ~req]
138 0c76ea8b 2021-10-15 op (when-let [e# (:error ~var)]
139 0c76ea8b 2021-10-15 op (throw e#))
140 0c76ea8b 2021-10-15 op (with-open [req# (:request ~var)]
141 0c76ea8b 2021-10-15 op ~@body)))