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