8e500ea0d9ff

Add :allow-print and :allow-read

These can be useful to restrict reading/printing to make sure
you don't accidentally serialize the wrong thing and send an
internal data structure out over the wire.
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Tue, 18 Aug 2020 21:50:45 -0400
parents 24d3163b1f64
children c9eb52bb4d0a
branches/tags (none)
files .TODO.done TODO src/mop.lisp src/package.lisp test/tests.lisp

Changes

--- a/.TODO.done	Sat Aug 15 00:27:30 2020 -0400
+++ b/.TODO.done	Tue Aug 18 21:50:45 2020 -0400
@@ -1,3 +1,4 @@
+Add read/print disabling mechanism | id:1268bf0b10c13aec23aafbd8f5e236db1e485fa5
 Optimize discarding | id:93105ef9d21d33bfb10c67fcac36bc60434d3fb4
 Add opaque-json type | id:a1a380eb9782d088693dfb75402b99c2b30cf039
 Add size and depth limits | id:ab9b49ec993f1e46c34b9d627549f41cad80609d
--- a/TODO	Sat Aug 15 00:27:30 2020 -0400
+++ b/TODO	Tue Aug 18 21:50:45 2020 -0400
@@ -1,4 +1,4 @@
-Add read/print disabling mechanism | id:1268bf0b10c13aec23aafbd8f5e236db1e485fa5
+Clean up error hierarchy | id:3d3efa4af649474151661a9d294080ab24e22ff7
 Add basic wrapper definition | id:861f048b3b69079dedf8779be1cb73c05e6fc732
 Add after-read and before-print functions | id:9f982ca45b68d644159e0c64f0dc1b185f72a2f8
 Add extra key preservation | id:cfb63b37d87893083fc98477ec3d488fb909a984
--- a/src/mop.lisp	Sat Aug 15 00:27:30 2020 -0400
+++ b/src/mop.lisp	Tue Aug 18 21:50:45 2020 -0400
@@ -13,7 +13,9 @@
                   :initarg :unknown-slots
                   :initform :discard)
    (name-initarg-map :accessor name-initarg-map)
-   (slot-name-alist :accessor slot-name-alist)))
+   (slot-name-alist :accessor slot-name-alist)
+   (allow-print :accessor allow-print :initarg :allow-print :initform t)
+   (allow-read :accessor allow-read :initarg :allow-read :initform t)))
 
 (defmethod c2mop:validate-superclass ((class json-class) (superclass standard-class))
   t)
@@ -89,15 +91,17 @@
 
 (defmethod shared-initialize ((class json-class) slot-names
                               &rest initargs
-                              &key slot-name-to-json-name unknown-slots
+                              &key slot-name-to-json-name unknown-slots allow-print allow-read
                               &allow-other-keys)
-  (apply #'call-next-method class slot-names
-         (append
-           (when slot-name-to-json-name ; todo assert length = 1
-             (list :slot-name-to-json-name (first slot-name-to-json-name)))
-           (when unknown-slots ; todo assert length = 1
-             (list :unknown-slots (first unknown-slots)))
-           initargs)))
+  (flet ((arg (initarg args)
+           (when args ; todo assert length = 1
+             (list initarg (first args)))))
+    (apply #'call-next-method class slot-names
+           (append (arg :slot-name-to-json-name slot-name-to-json-name)
+                   (arg :unknown-slots unknown-slots)
+                   (arg :allow-read allow-read)
+                   (arg :allow-print allow-print)
+                   initargs))))
 
 (defmethod c2mop:finalize-inheritance :after ((class json-class))
   (setf (name-initarg-map class) (make-name-initarg-map class)
@@ -106,6 +110,8 @@
 
 ;;;; Read ---------------------------------------------------------------------
 (defun parse-json-class (class-name class input)
+  (unless (allow-read class)
+    (error "Class ~S does not allow reading." class))
   (let ((ch (r input)))
     (unless (eql ch #\{)
       (e class-name input "expected ~S but got ~S" #\{ ch)))
@@ -152,18 +158,21 @@
 ;;;; Printing -----------------------------------------------------------------
 (defmethod print% (thing stream)
   (let ((class (class-of thing)))
-    (if (typep class 'json-class)
-      (progn
-        (write-char #\{ stream)
-        (loop :with first = t
-              :for (slot . name) :in (slot-name-alist class)
-              :when (slot-boundp thing slot)
-              :do (progn (if first
-                           (setf first nil)
-                           (write-char #\, stream))
-                         (print% name stream)
-                         (write-char #\: stream)
-                         (print% (slot-value thing slot) stream)))
-        (write-char #\} stream))
-      (error "Don't know how to print object ~S of class ~S as JSON." thing class))))
+    (cond
+      ((not (typep class 'json-class))
+       (error "Don't know how to print object ~S of class ~S as JSON." thing class))
+      ((not (allow-print class))
+       (error "Class ~S does not allow printing." class))
+      (t
+       (write-char #\{ stream)
+       (loop :with first = t
+             :for (slot . name) :in (slot-name-alist class)
+             :when (slot-boundp thing slot)
+             :do (progn (if first
+                          (setf first nil)
+                          (write-char #\, stream))
+                        (print% name stream)
+                        (write-char #\: stream)
+                        (print% (slot-value thing slot) stream)))
+       (write-char #\} stream)))))
 
--- a/src/package.lisp	Sat Aug 15 00:27:30 2020 -0400
+++ b/src/package.lisp	Tue Aug 18 21:50:45 2020 -0400
@@ -4,4 +4,8 @@
   (:export
     :read :print
 
-    :json-parsing-error :line :column))
+    :json-parsing-error :line :column
+
+    :json-class
+
+    ))
--- a/test/tests.lisp	Sat Aug 15 00:27:30 2020 -0400
+++ b/test/tests.lisp	Tue Aug 18 21:50:45 2020 -0400
@@ -310,6 +310,33 @@
     (signals jarl::json-depth-limit-exceeded-error (jarl:read nil (json "[{'foo': [[1]]}]")))))
 
 
+;;;; Allow Print/Read ---------------------------------------------------------
+(defclass foo ()
+  ((id :json number :initarg :id))
+  (:metaclass jarl:json-class))
+
+(defclass foo/deny-read ()
+  ((id :json number :initarg :id))
+  (:metaclass jarl:json-class)
+  (:allow-read nil))
+
+(defclass foo/deny-print ()
+  ((id :json number :initarg :id))
+  (:metaclass jarl:json-class)
+  (:allow-print nil))
+
+
+(define-test allow-read
+  (is (= 1 (slot-value (jarl:read 'foo (json "{'id': 1}")) 'id)))
+  (is (= 1 (slot-value (jarl:read 'foo/deny-print (json "{'id': 1}")) 'id)))
+  (signals error (jarl:read 'foo/deny-read (json "{'id': 1}"))))
+
+(define-test allow-print
+  (is (string= (json "{'id':1}") (jarl:print (make-instance 'foo :id 1) nil)))
+  (is (string= (json "{'id':1}") (jarl:print (make-instance 'foo/deny-read :id 1) nil)))
+  (signals error (jarl:print (make-instance 'foo/deny-print :id 1) nil)))
+
+
 ;;;; Fuzz Utilities -----------------------------------------------------------
 (defparameter *basic-chars*
   " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_,'[]{}+=")