# dog_and_duck/quack/quack.clj

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