001  (ns dog-and-duck.quack.quack
002    "Validator for ActivityPub objects: if it walks like a duck, and it quacks 
003     like a duck...
004     
005     **NOTE THAT the ActivityPub spec 
006     [says](https://www.w3.org/TR/activitypub/#obj)
007     
008     > Servers SHOULD validate the content they receive to avoid content 
009     > spoofing attacks
010     
011     but in practice ActivityPub content collected in the wild bears only 
012     a hazy relationship to the spec, so this is difficult. I suspect that
013     I may have to implement a `*strict*` dynamic variable, so that users can 
014     toggle some checks off."
015    
016    ;;(:require [clojure.spec.alpha as s])
017    (:require [dog-and-duck.quack.picky :refer [filter-severity has-context? 
018                                                object-faults]])
019    (:import [java.net URI URISyntaxException]))
020  
021  ;;;     Copyright (C) Simon Brooke, 2022
022  
023  ;;;     This program is free software; you can redistribute it and/or
024  ;;;     modify it under the terms of the GNU General Public License
025  ;;;     as published by the Free Software Foundation; either version 2
026  ;;;     of the License, or (at your option) any later version.
027  
028  ;;;     This program is distributed in the hope that it will be useful,
029  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
030  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031  ;;;     GNU General Public License for more details.
032  
033  ;;;     You should have received a copy of the GNU General Public License
034  ;;;     along with this program; if not, write to the Free Software
035  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
036  
037  (defn object?
038    "Returns `true` iff `x` is recognisably an ActivityStreams object.
039     
040     **NOTE THAT** The ActivityStreams spec 
041     [says](https://www.w3.org/TR/activitystreams-core/#object):
042     
043     > All properties are optional (including the id and type)
044     
045     But we are *just not having that*, because otherwise we're flying blind.
046     We *shall* reject objects lacking at least `:type`. Missing `:id` keys are
047     tolerable because they represent transient objects, which we expect to 
048     handle.
049     
050     **NOTE THAT** The ActivityPub spec [says](https://www.w3.org/TR/activitypub/#obj)
051     
052     > Implementers SHOULD include the ActivityPub context in their object 
053     > definitions
054     
055     but in samples found in the wild they typically don't."
056    ([x]
057    (and (map? x) (:type x) true))
058    ([x severity]
059     (empty? (filter-severity (object-faults x) severity))))
060  
061  (defn persistent-object?
062    "`true` iff `x` is a persistent object.
063  
064     Transient objects in ActivityPub are not required to have an `id` key, but persistent
065     ones must have a key, and it must be an IRI (but normally a URI)."
066    [x]
067    (try
068      (and (object? x) (uri? (URI. (:id x))))
069      (catch URISyntaxException _ false)))
070  
071  ;; (persistent-object? {:type "test" :id "https://mastodon.scot/@barfilfarm"})
072  
073  (def ^:const actor-types
074    "The set of types we will accept as actors.
075     
076     There's an [explicit set of allowed actor types]
077     (https://www.w3.org/TR/activitystreams-vocabulary/#actor-types)."
078    #{"Application"
079      "Group"
080      "Organization"
081      "Person"
082      "Service"})
083  
084  (defmacro actor-type?
085    "Return `true` iff the `x` is a recognised actor type, else `false`."
086    [^String x]
087    `(if (actor-types ~x) true false))
088  
089  ;; (actor-type? "Group")
090  
091  (def ^:const verb-types
092    "The set of types we will accept as verbs.
093     
094     There's an [explicit set of allowed verb types]
095     (https://www.w3.org/TR/activitystreams-vocabulary/#activity-types)."
096    #{"Accept" "Add" "Announce" "Arrive" "Block" "Create" "Delete" "Dislike"
097      "Flag" "Follow" "Ignore" "Invite" "Join" "Leave" "Like" "Listen" "Move"
098      "Offer" "Question" "Reject" "Read" "Remove" "TentativeAccept"
099      "TentativeReject" "Travel" "Undo" "Update" "View"})
100  
101  (defmacro verb-type?
102    ;; TODO: better as a macro
103    [^String x]
104    `(if (verb-types ~x) true false))
105  
106  
107  (defn actor?
108    "Returns `true` if `x` quacks like an actor, else false.
109     
110     **NOTE THAT** [Section 4.1 of the spec]
111     (https://www.w3.org/TR/activitypub/#actor-objects) says explicitly that
112     
113     >  Actor objects MUST have, in addition to the properties mandated by 3.1 Object Identifiers, the following properties:
114     >
115     >  inbox
116     >    A reference to an [ActivityStreams] OrderedCollection comprised of all the messages received by the actor; see 5.2 Inbox. 
117     > outbox
118     >    An [ActivityStreams] OrderedCollection comprised of all the messages produced by the actor; see 5.1 Outbox. 
119     
120     However, none of the provided examples in the [activitystreams-test-documents repository]() does in fact have these properties"
121    [x]
122    (and
123     (object? x)
124     (has-context? x)
125     (uri? (URI. (:inbox x)))
126     (uri? (URI. (:outbox x)))
127     (actor-type? (:type x))
128     true))
129  
130  (defn actor-or-uri?
131    "`true` if `x` is either a URI or an actor.
132     
133     **TODO**: I need to decide about whether to reify referenced objects
134     before validation or after. After reification, every reference to an actor
135     *must be* to an actor object, but before, may only be to a URI pointing to 
136     one."
137    [x]
138    (and 
139     (cond (string? x) (uri? (URI. x))
140          :else (actor? x)) 
141         true))
142  
143  (defn activity?
144    "`true` iff `x` quacks like an activity, else false."
145    [x]
146    (try
147      (and (object? x)
148           (has-context? x)
149           (string? (:summary x))
150           (actor-or-uri? (:actor x))
151           (verb-type? (:type x))
152           (or (object? (:object x)) (uri? (URI. (:object x))))
153           true)
154      (catch URISyntaxException _ false)))
155  
156  (defn link?
157    "`true` iff `x` quacks like a link, else false."
158    [x]
159    (and (object? x)
160         (= (:type x) "Link")
161         (uri? (URI. (:href x)))
162         true))
163  
164  (defn link-or-uri?
165    "`true` iff `x` is either a URI or a link, else false.
166     
167     There are several points in the specification where e.g. the `:image`
168     property (if present) may be either a link or a URI."
169    [x]
170    (and
171     (cond (string? x) (uri? (URI. x))
172           :else (link? x))
173     true))
174  
175  (defn collection?
176    "`true` iff `x` quacks like a collection of type `object-type`, else `false`.
177     
178     With one argument, will recognise plain collections and ordered collections,
179     but (currently) not collection pages."
180    ([x ^String object-type]
181     (let [items (or (:items x) (:orderedItems x))]
182       (and
183        (cond
184          (:items x) (nil? (:orderedItems x))
185          (:orderedItems x) (nil? (:items x)) ;; can't have both properties
186          (integer? (:totalItems x)) true ;; can have neither, provided it has totalItems.
187          :else false) 
188        (object? x)
189        (= (:type x) object-type)
190        (if items
191          (and (coll? items)
192               (every? object? items) ;; if there are items, they must form a
193                                      ;; collection of objects.
194               true)
195          true) ;; but it's OK if there aren't.
196        true)
197       ;; test for totalItems not done here, because collection pages don't
198       ;; have it.
199       ))
200    ([x]
201     (and
202      (or (collection? x "Collection")
203          (collection? x "OrderedCollection"))
204      (integer? (:totalItems x))
205      true)))
206  
207  (defn unordered-collection?
208    "`true` iff `x` quacks like an unordered collection, else `false`."
209    [x]
210    (and (collection? x "Collection") (integer? (:totalItems x)) true))
211  
212  (defn ordered-collection?
213    "`true` iff `x` quacks like an ordered collection, else `false`."
214    [x]
215    (and (collection? x "OrderedCollection") (integer? (:totalItems x)) true))
216  
217  (defn collection-page?
218    "`true` iff `x` quacks like a page in a paged collection, else `false`."
219    [x]
220    (collection? x "CollectionPage"))
221  
222  (defn ordered-collection-page?
223    "`true` iff `x` quacks like a page in an ordered paged collection, else `false`."
224    [x]
225    (collection? x "OrderedCollectionPage"))
226  
227