Confine untrusted spec strings and identifiers inside Kotlin/Gradle literals

ober

ccbaa93cdb7bc0ea9a7b43966bd2268cb55cb2e9

diff --git a/jandroid.ss b/jandroid.ss
index 86922c1..bcbcd92 100644
--- a/jandroid.ss
+++ b/jandroid.ss
@@ -281,7 +281,27 @@
         (if (state-entry? item) #f item))
       '(column)))
 
-(def (kotlin-ident sym) (symbol->string sym))
+(def (kotlin-identifier? text)
+  (and (string? text)
+       (> (string-length text) 0)
+       (<= (string-length text) 128)
+       (let ((first (string-ref text 0)))
+         (or (char-alphabetic? first) (char=? first #\_)))
+       (every (lambda (ch)
+                (or (char-alphabetic? ch)
+                    (char-numeric? ch)
+                    (char=? ch #\_)))
+              (string->list text))))
+
+(def (kotlin-ident sym)
+  (let ((text (symbol->string sym)))
+    (unless (kotlin-identifier? text)
+      (error 'kotlin-ident "unsafe Kotlin identifier" text))
+    text))
+
+(def (control-char? ch)
+  (let ((code (char->integer ch)))
+    (or (< code 32) (= code 127))))
 
 (def (escape-string s quote-ch)
   (with-output-to-string
@@ -292,6 +312,11 @@
           ((char=? ch #\\) (display "\\\\"))
           ((char=? ch #\newline) (display "\\n"))
           ((char=? ch #\tab) (display "\\t"))
+          ((char=? ch #\return) (display "\\r"))
+          ((char=? ch #\$) (display "${'$'}"))
+          ((control-char? ch)
+           (error 'escape-string "control character in string literal"
+                  (char->integer ch)))
           (else (display ch)))))))
 
 (def (kotlin-string s) (string-append "\"" (escape-string s #\") "\""))
@@ -560,12 +585,37 @@
                       deps))
           "}\n"))))
 
+(def (gradle-property-key? value)
+  (and (string? value)
+       (> (string-length value) 0)
+       (<= (string-length value) 128)
+       (every (lambda (ch)
+                (or (char-alphabetic? ch)
+                    (char-numeric? ch)
+                    (char=? ch #\.)
+                    (char=? ch #\_)
+                    (char=? ch #\-)))
+              (string->list value))))
+
+(def (gradle-property-value? value)
+  (and (string? value)
+       (<= (string-length value) 4096)
+       (every (lambda (ch)
+                (let ((code (char->integer ch)))
+                  (and (>= code 32) (not (= code 127)))))
+              (string->list value))))
+
 (def (gradle-property-line property-form)
   (unless (and (= (length property-form) 3)
                (string? (cadr property-form))
                (string? (caddr property-form)))
     (error 'gradle-property "expected `(gradle-property key value)`" property-form))
-  (string-append (cadr property-form) "=" (caddr property-form) "\n"))
+  (let ((key (cadr property-form)) (value (caddr property-form)))
+    (unless (gradle-property-key? key)
+      (error 'gradle-property "unsafe gradle property key" key))
+    (unless (gradle-property-value? value)
+      (error 'gradle-property "unsafe gradle property value" value))
+    (string-append key "=" value "\n")))
 
 (def (gradle-properties-file spec)
   (apply string-append (map gradle-property-line (gradle-property-forms spec))))