zone.lisp: Split a single `:txt' string into small enough pieces.
authorMark Wooding <mdw@distorted.org.uk>
Fri, 3 May 2024 00:14:43 +0000 (01:14 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 4 May 2024 23:56:24 +0000 (00:56 +0100)
The substrings of a `:txt' record can be at most 255 bytes long.  If the
argument is a single string that's too long then split it into pieces;
prefer to split at semicolons, or spaces.

If the argument is a list of strings, then respect their split.
Theoretically, the split positions are semantically transparent, but
it's possible that some programs are sensitive to the boundaries.

zone.lisp

index 031a15d..5589361 100644 (file)
--- a/zone.lisp
+++ b/zone.lisp
@@ -26,7 +26,7 @@
 
 (defpackage #:zone
   (:use #:common-lisp
-       #:mdw.base #:mdw.str #:collect #:safely
+       #:mdw.base #:mdw.str #:anaphora #:collect #:safely
        #:net #:services)
   (:import-from #:net #:round-down #:round-up))
 
   (rec-name (zr-data zr))
   5)
 
+(defun split-txt-data (data)
+  (collecting ()
+    (let ((i 0) (n (length data)))
+      (loop
+       (let ((end (+ i 255)))
+         (when (<= n end) (return))
+         (let ((split (acond ((position #\; data :from-end t
+                                        :start i :end end)
+                              (+ it 1))
+                             ((position #\space data :from-end t
+                                        :start i :end end)
+                              (+ it 1))
+                             (t end))))
+           (loop
+             (when (or (>= split end)
+                       (char/= (char data split) #\space))
+               (return))
+             (incf split))
+           (collect (subseq data i split))
+           (setf i split))))
+      (collect (subseq data i)))))
+
 (defzoneparse :txt (name data rec)
   ":txt (TEXT*)"
-  (rec :data (listify data)))
+  (rec :data (cond ((stringp data) (split-txt-data data))
+                  (t
+                   (dolist (piece data)
+                     (unless (<= (length piece) 255)
+                       (error "`:txt' record piece `~A' too long" piece)))
+                   data))))
 
 (defmethod zone-record-rrdata ((type (eql :txt)) zr)
   (mapc #'rec-string (zr-data zr))