aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2018-02-08 18:45:03 +0100
committerLudovic Courtès <ludo@gnu.org>2018-02-08 18:45:03 +0100
commit8c7c93922bbe0513ff4c4ff3a6e554e3a72635b6 (patch)
treed2194e82cb737261a7f8169bb30a830c4a2f5d58
parentb0c39b31f61cfc494e0dfbe823b3fe4275efbc7a (diff)
downloadcuirass-8c7c93922bbe0513ff4c4ff3a6e554e3a72635b6.tar
cuirass-8c7c93922bbe0513ff4c4ff3a6e554e3a72635b6.tar.gz
database: Use argument binding in 'db-get-builds' queries.
That makes it safe from SQL injection. * src/cuirass/database.scm (db-get-builds): Rewrite to use question marks in SQL queries and binding through '%sqlite-exec'. * tests/database.scm ("database")["db-get-builds"]: Exercise 'WHERE' clauses.
-rw-r--r--src/cuirass/database.scm111
-rw-r--r--tests/database.scm5
2 files changed, 71 insertions, 45 deletions
diff --git a/src/cuirass/database.scm b/src/cuirass/database.scm
index a9f1c2d..d3e2666 100644
--- a/src/cuirass/database.scm
+++ b/src/cuirass/database.scm
@@ -377,32 +377,55 @@ INNER JOIN Specifications ON Evaluations.specification = Specifications.repo_nam
FILTERS is an assoc list which possible keys are 'project | 'jobset | 'job |
'system | 'nr | 'order | 'status."
- (define (format-where-clause filters)
- (let ((where-clause
- (filter-map
- (lambda (param)
- (match param
- (('project project)
- (format #f "Specifications.repo_name='~A'" project))
- (('jobset jobset)
- (format #f "Specifications.branch='~A'" jobset))
- (('job job)
- (format #f "Derivations.job_name='~A'" job))
- (('system system)
- (format #f "Derivations.system='~A'" system))
- (('status 'done)
- "Builds.status >= 0")
- (('status 'pending)
- "Builds.status < 0")
- (_ #f)))
- filters)))
- (if (> (length where-clause) 0)
- (string-append
- "WHERE "
- (string-join where-clause " AND "))
- "")))
-
- (define (format-order-clause filters)
+ (define (clauses->query+arguments clauses)
+ ;; Given CLAUSES, return two values: a SQL query string, and a list of
+ ;; arguments to bind. Each element of CLAUSES must be either a string, or
+ ;; a (SQL ARGUMENT) tuple, where SQL is a query fragment and ARGUMENT is
+ ;; the argument to be bound for that fragment.
+ (let loop ((clauses clauses)
+ (query '())
+ (arguments '()))
+ (match clauses
+ (()
+ (values (string-concatenate-reverse query)
+ (reverse arguments)))
+ (((? string? clause) . rest)
+ (loop rest
+ (cons clause query)
+ arguments))
+ ((((? string? clause) argument) . rest)
+ (loop rest
+ (cons clause query)
+ (cons argument arguments))))))
+
+ (define (where-clauses filters)
+ (match (filter-map (match-lambda
+ (('project project)
+ (list "Specifications.repo_name=?" project))
+ (('jobset jobset)
+ (list "Specifications.branch=?" jobset))
+ (('job job)
+ (list "Derivations.job_name=?" job))
+ (('system system)
+ (list "Derivations.system=?" system))
+ (('status 'done)
+ "Builds.status >= 0")
+ (('status 'pending)
+ "Builds.status < 0")
+ (_ #f))
+ filters)
+ (()
+ '(""))
+ ((clause)
+ (list "WHERE " clause))
+ ((clause0 rest ...)
+ (cons* "WHERE " clause0
+ (fold-right (lambda (clause result)
+ `(" AND " ,clause ,@result))
+ '()
+ rest)))))
+
+ (define (order-clause filters)
(or (any (match-lambda
(('order 'build-id)
"ORDER BY Builds.id ASC")
@@ -422,31 +445,29 @@ FILTERS is an assoc list which possible keys are 'project | 'jobset | 'job |
filters)
"ORDER BY Builds.id DESC")) ;default order
- (define (format-limit-clause filters)
+ (define (limit-clause filters)
(or (any (match-lambda
(('nr number)
- (format #f "LIMIT '~A'" number))
+ (list "LIMIT ?" number))
(_ #f))
filters)
""))
- (let loop ((rows
- (sqlite-exec db (string-append
- db-build-request
- " "
- (format-where-clause filters)
- " "
- (format-order-clause filters)
- " "
- (format-limit-clause filters)
- ";")))
- (outputs '()))
- (match rows
- (()
- (reverse outputs))
- ((row . rest)
- (loop rest
- (cons (db-format-build db row) outputs))))))
+ (call-with-values
+ (lambda ()
+ (clauses->query+arguments (append (list db-build-request " ")
+ (where-clauses filters) '(" ")
+ (list (order-clause filters) " ")
+ (list (limit-clause filters) " "))))
+ (lambda (sql arguments)
+ (let loop ((rows (apply %sqlite-exec db sql arguments))
+ (outputs '()))
+ (match rows
+ (()
+ (reverse outputs))
+ ((row . rest)
+ (loop rest
+ (cons (db-format-build db row) outputs))))))))
(define (db-get-stamp db spec)
"Return a stamp corresponding to specification SPEC in database DB."
diff --git a/tests/database.scm b/tests/database.scm
index 2382292..306068b 100644
--- a/tests/database.scm
+++ b/tests/database.scm
@@ -121,6 +121,8 @@ INSERT INTO Evaluations (specification, revision) VALUES (3, 3);")
(test-equal "db-get-builds"
#(((1 "/foo.drv") (2 "/bar.drv") (3 "/baz.drv")) ;ascending order
((3 "/baz.drv") (2 "/bar.drv") (1 "/foo.drv")) ;descending order
+ ((3 "/baz.drv") (2 "/bar.drv") (1 "/foo.drv")) ;ditto
+ ((3 "/baz.drv") (2 "/bar.drv") (1 "/foo.drv")) ;ditto
((3 "/baz.drv"))) ;nr = 1
(with-temporary-database db
;; Populate the 'Builds', 'Derivations', 'Evaluations', and
@@ -145,6 +147,9 @@ INSERT INTO Evaluations (specification, revision) VALUES (3, 3);")
(assq-ref alist #:derivation)))))
(vector (map summarize (db-get-builds db '((nr 3) (order build-id))))
(map summarize (db-get-builds db '()))
+ (map summarize (db-get-builds db '((project "guix"))))
+ (map summarize (db-get-builds db '((project "guix")
+ (jobset "master"))))
(map summarize (db-get-builds db '((nr 1))))))))
(test-equal "db-update-build-status!"