summaryrefslogtreecommitdiff
path: root/src/weepinbell/processor/verifier.clj
blob: e6415f82d37734a642f58315fc7a41dd8e7dd787 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
(ns weepinbell.processor.verifier
  "Verifies in-coming webmentions after receiver checks URL.
   This makes sure: 
   1) source contains target URL.
   2) source respond with 2** status code.
   3) source's Content-Type is either HTML or plain text."
  (:require [clojure.string :refer [trim lower-case split]]
            [weepinbell.utils.url :as url]
            [weepinbell.http.client :as client]
            [taoensso.timbre :as timbre]))

(def text-content-types #{"text/html" "text/plain"})
(defn- match-content-type?
  "Return true if "
  [types headers]
  (when-let [content-type (:content-type headers)]
    (-> content-type (split #";") first trim lower-case types)))
(def text-content-types?
  (partial match-content-type? text-content-types))

(defn- includes-url?
  "Return true if text includes a certain URL. URLs are normalized before comparison."
  [text url]
  (some #(url/url= % url) (url/extract text)))

(defn- verify-mention
  "Get source and follow redirects to check if target link is present."
  [id source-resp target-resp source target]
  ;; get first 1MB of source URL and verify
  (let [{:keys [status body headers error]} source-resp
        target-headers (:headers target-resp)]
    (timbre/info "verifying webmention" id
                 (str "(" "source: " source ", target: " target ")"))
    (cond
      ;; handle error 
      error
      (do
        (timbre/error "error verifying webmention:" error)
        false)
      ;; verify HTTP status
      (or (not status) (< status 200) (>= status 300))
      (do
        (timbre/warn "invalid source of" id ", status:" status)
        false)
      ;; verify Content-Type 
      (not (every? text-content-types? [target-headers headers]))
      (do
        (timbre/warn "source or target of" id "is neither HTML nor plain text content.")
        false)
      ;; verify inclusion of target URL
      (not (includes-url? (str body) target))
      (do
        (timbre/warn "invalid source of" id ": target not found in source body.")
        false)
      ;; true if all verified 
      :else true)))

(def retry-times 3)

(defn verify
  [data]
  (when (:ok? data)
    (let [{:keys [id wm new?]} data
          {:keys [s t]} wm
          source-resp (client/with-retry retry-times 5
                        (client/get s {:headers {"Range" "bytes=0-1048575"}}))
          target-resp (client/with-retry retry-times 5 (client/head t))
          verified? (verify-mention id @source-resp @target-resp s t)]
      (-> data
          (assoc :verified? verified?)
          (assoc :source-html (:body @source-resp))
          ;; don't continue if new webmention request is not verified
          (assoc :ok? (or (not new?) verified?))))))