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