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 [dog-and-duck.quack.picky :refer [*reject-severity* activity-faults
017                                                actor-faults filter-severity link-faults
018                                                object-faults persistent-object-faults]])
019  
020    (:import [java.net URI URISyntaxException]))
021  
022  ;;;     Copyright (C) Simon Brooke, 2022
023  
024  ;;;     This program is free software; you can redistribute it and/or
025  ;;;     modify it under the terms of the GNU General Public License
026  ;;;     as published by the Free Software Foundation; either version 2
027  ;;;     of the License, or (at your option) any later version.
028  
029  ;;;     This program is distributed in the hope that it will be useful,
030  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
031  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
032  ;;;     GNU General Public License for more details.
033  
034  ;;;     You should have received a copy of the GNU General Public License
035  ;;;     along with this program; if not, write to the Free Software
036  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
037  
038  (defn object?
039    "Returns `true` iff `x` is recognisably an ActivityStreams object.
040     
041     **NOTE THAT** The ActivityStreams spec 
042     [says](https://www.w3.org/TR/activitystreams-core/#object):
043     
044     > All properties are optional (including the id and type)
045     
046     But we are *just not having that*, because otherwise we're flying blind.
047     We *shall* reject objects lacking at least `:type`. Missing `:id` keys are
048     tolerable because they represent transient objects, which we expect to 
049     handle.
050     
051     **NOTE THAT** The ActivityPub spec [says](https://www.w3.org/TR/activitypub/#obj)
052     
053     > Implementers SHOULD include the ActivityPub context in their object 
054     > definitions
055     
056     but in samples found in the wild they typically don't."
057    ([x]
058     (object? x *reject-severity*))
059    ([x severity]
060     (empty? (filter-severity (object-faults x) severity))))
061  
062  (defn persistent-object?
063    "`true` iff `x` is a persistent object.
064  
065     Transient objects in ActivityPub are not required to have an `id` key, but persistent
066     ones must have a key, and it must be an IRI (but normally a URI)."
067    ([x]
068     (persistent-object? x *reject-severity*))
069    ([x severity]
070     (empty? (filter-severity (persistent-object-faults x) severity))))
071  
072  (defn actor?
073    "Returns `true` if `x` quacks like an actor, else false."
074    ([x] (actor? x *reject-severity*))
075    ([x severity]
076     (empty? (filter-severity (actor-faults x) severity))))
077  
078  (defn actor-or-uri?
079    "`true` if `x` is either a URI or an actor.
080     
081     **TODO**: I need to decide about whether to reify referenced objects
082     before validation or after. After reification, every reference to an actor
083     *must be* to an actor object, but before, may only be to a URI pointing to 
084     one."
085    [x]
086    (try
087      (and
088       (cond (string? x) (uri? (URI. x))
089             :else (actor? x))
090       true)
091      (catch URISyntaxException _ false)
092      (catch NullPointerException _ false)))
093  
094  (defn activity?
095    "`true` iff `x` quacks like an activity, else false."
096    ([x] (activity? x *reject-severity*))
097    ([x severity]
098     (empty? (filter-severity (activity-faults x) severity))))
099  
100  (defn link?
101    "`true` iff `x` quacks like a link, else false."
102    ([x] (link? x *reject-severity*))
103    ([x severity]
104     (empty? (filter-severity (link-faults x) severity))))
105  
106  (defn link-or-uri?
107    "`true` iff `x` is either a URI or a link, else false.
108     
109     There are several points in the specification where e.g. the `:image`
110     property (if present) may be either a link or a URI."
111    [x]
112    (and
113     (cond (string? x) (uri? (URI. x))
114           :else (link? x))
115     true))
116  
117  (defn collection?
118    "`true` iff `x` quacks like a collection of type `object-type`, else `false`.
119     
120     With one argument, will recognise plain collections and ordered collections,
121     but (currently) not collection pages."
122    ([x ^String object-type]
123     (let [items (or (:items x) (:orderedItems x))]
124       (and
125        (cond
126          (:items x) (nil? (:orderedItems x))
127          (:orderedItems x) (nil? (:items x)) ;; can't have both properties
128          (integer? (:totalItems x)) true ;; can have neither, provided it has totalItems.
129          :else false)
130        (object? x)
131        (= (:type x) object-type)
132        (if items
133          (and (coll? items)
134               (every? object? items) ;; if there are items, they must form a
135                                      ;; collection of objects.
136               true)
137          true) ;; but it's OK if there aren't.
138        true)
139       ;; test for totalItems not done here, because collection pages don't
140       ;; have it.
141       ))
142    ([x]
143     (and
144      (or (collection? x "Collection")
145          (collection? x "OrderedCollection"))
146      (integer? (:totalItems x))
147      true)))
148  
149  (defn unordered-collection?
150    "`true` iff `x` quacks like an unordered collection, else `false`."
151    [x]
152    (and (collection? x "Collection") (integer? (:totalItems x)) true))
153  
154  (defn ordered-collection?
155    "`true` iff `x` quacks like an ordered collection, else `false`."
156    [x]
157    (and (collection? x "OrderedCollection") (integer? (:totalItems x)) true))
158  
159  (defn collection-page?
160    "`true` iff `x` quacks like a page in a paged collection, else `false`."
161    [x]
162    (collection? x "CollectionPage"))
163  
164  (defn ordered-collection-page?
165    "`true` iff `x` quacks like a page in an ordered paged collection, else `false`."
166    [x]
167    (collection? x "OrderedCollectionPage"))
168  
169