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