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