Skip to content

Commit 10362c2

Browse files
committed
check sha1
1 parent a028be0 commit 10362c2

3 files changed

Lines changed: 141 additions & 5 deletions

File tree

content-hash.lisp

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
;; copied from tarhash.lisp from quicklisp-controller: https://github.com/quicklisp/quicklisp-controller
2+
;; changed to use openssl to compute sha1 digest rather than depending on ironclad
3+
4+
(in-package #:ql-https)
5+
6+
(defconstant +block-octet-count+ 512)
7+
8+
(defun make-block-buffer ()
9+
(make-array +block-octet-count+
10+
:element-type '(unsigned-byte 8)
11+
:initial-element 0))
12+
13+
(defun read-header-block (buffer stream)
14+
"Read a tar header block from STREAM into BUFFER. Returns NIL when
15+
at the terminating block of the end of input, BUFFER otherwise."
16+
(let ((size (read-sequence buffer stream)))
17+
(cond ((zerop size)
18+
nil)
19+
((/= size 0 +block-octet-count+)
20+
(error "Short block (only ~D bytes)" size))
21+
((every #'zerop buffer)
22+
nil)
23+
(t
24+
buffer))))
25+
26+
(defun ascii-subseq (vector start end)
27+
(let ((string (make-string (- end start))))
28+
(loop for i from 0
29+
for j from start below end
30+
do (setf (char string i) (code-char (aref vector j))))
31+
string))
32+
33+
(defun block-asciiz-string (block start length)
34+
(let* ((end (+ start length))
35+
(eos (or (position 0 block :start start :end end)
36+
end)))
37+
(ascii-subseq block start eos)))
38+
39+
(defun payload-size (header)
40+
(values (parse-integer (block-asciiz-string header 124 12) :radix 8)))
41+
42+
(defun file-payload-p (header)
43+
(member (aref header 156) '(0 48)))
44+
45+
(defparameter *ignored-path-substrings*
46+
'("/_darcs/" "/CVS/" "/.git/" "/CVS/" "/.hg/"))
47+
48+
(defun ignored-path-p (path)
49+
(dolist (substring *ignored-path-substrings*)
50+
(when (search substring path)
51+
(return t))))
52+
53+
(defun prefix (header)
54+
(when (plusp (aref header 345))
55+
(block-asciiz-string header 345 155)))
56+
57+
(defun name (header)
58+
(block-asciiz-string header 0 100))
59+
60+
(defun full-path (header)
61+
(let ((prefix (prefix header))
62+
(name (name header)))
63+
(if prefix
64+
(format nil "~A/~A" prefix name)
65+
name)))
66+
67+
(defun skip-n-octets-blocks (n stream)
68+
(let ((count (ceiling n +block-octet-count+))
69+
(block (make-block-buffer)))
70+
(dotimes (i count)
71+
(read-sequence block stream))))
72+
73+
(defun content-info (stream)
74+
"Return a list of file info for the POSIX tar stream STREAM. Each
75+
element in the result is a list of a filename, the position of its
76+
starting storage block in STREAM, and the total file size."
77+
(file-position stream :start)
78+
(let ((buffer (make-block-buffer))
79+
(result '()))
80+
(loop
81+
(let ((header (read-header-block buffer stream))
82+
(position (file-position stream)))
83+
(when (not header)
84+
(return result))
85+
(let ((size (payload-size header)))
86+
(when (file-payload-p header)
87+
(let ((path (full-path header)))
88+
(unless (ignored-path-p path)
89+
(push (list path
90+
position
91+
size)
92+
result))))
93+
(skip-n-octets-blocks size stream))))))
94+
95+
(defun content-hash (tarfile)
96+
"Return a hash string of TARFILE. The hash is computed by creating
97+
the digest of the files in TARFILE in order of their name."
98+
(let ((temp "quicklisp-controller:tmp;tarhash.tar"))
99+
(ensure-directories-exist temp)
100+
(setf tarfile (gunzip tarfile temp))
101+
(unwind-protect
102+
(with-open-file (stream tarfile :element-type '(unsigned-byte 8))
103+
(let* ((openssl (uiop:launch-program "openssl dgst -sha1"
104+
:input :stream
105+
:output :stream))
106+
(digest-stream (uiop:process-info-input openssl))
107+
(buffer (make-block-buffer)))
108+
(flet ((add-file-content (position size)
109+
(file-position stream position)
110+
(multiple-value-bind (complete partial)
111+
(truncate size +block-octet-count+)
112+
(dotimes (i complete)
113+
(read-sequence buffer stream)
114+
(write-sequence buffer digest-stream))
115+
(read-sequence buffer stream)
116+
(write-sequence buffer digest-stream :end partial))))
117+
(let ((contents (content-info stream)))
118+
(setf contents (sort contents #'string< :key #'first))
119+
(dolist (info contents)
120+
(destructuring-bind (position size)
121+
(rest info)
122+
(add-file-content position size))))
123+
(close (uiop:process-info-input openssl))
124+
(unless (zerop (uiop:wait-process openssl))
125+
(error "openssl failed to calculate sha1"))
126+
(extract-openssl-digest (read-line (uiop:process-info-output openssl))))))
127+
(when (probe-file temp)
128+
(ignore-errors (delete-file temp))))))

ql-https.asd

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
:source-control (:git "https://github.com/rudolfochrist/ql-https.git")
1111
:version (:read-file-line "version")
1212
:depends-on ((:require "uiop") (:feature :sbcl :sb-md5))
13-
:components ((:file "ql-https"))
13+
:components ((:file "ql-https")
14+
(:file "content-hash"))
1415
:description "Enable HTTPS in Quicklisp"
1516
:long-description
1617
#.(uiop:read-file-string

ql-https.lisp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
(defpackage #:ql-https
44
(:use :cl)
5+
(:import-from #:ql-gunzipper #:gunzip)
56
(:export
67
#:fetcher
78
#:*quietly-use-https*
@@ -53,13 +54,17 @@
5354
"Returns md5sum of FILE"
5455
(format nil "~{~2,'0x~}" (coerce (sb-md5:md5sum-file file) 'list)))
5556

57+
(defun extract-openssl-digest (output)
58+
"Extracts digest from output of `openssl dgst'"
59+
(let ((space-pos (position #\Space output)))
60+
(subseq output (1+ space-pos)))) ; exclude space itself
61+
5662
#-sbcl
5763
(defun md5 (file)
5864
"Returns md5sum of FILE"
59-
(let* ((output (uiop:run-program (list "openssl" "dgst" "-md5" (namestring file))
60-
:output '(:string :stripped t)))
61-
(space-pos (position #\Space output)))
62-
(subseq output (1+ space-pos)))) ; exclude space itself
65+
(extract-openssl-digest
66+
(uiop:run-program (list "openssl" "dgst" "-md5" (namestring file))
67+
:output '(:string :stripped t))))
6368

6469
(defun file-size (file)
6570
"Returns the size of FILE in bytes"
@@ -72,6 +77,8 @@ dist."
7277
(let ((release (ql-dist:find-release name)))
7378
(unless (string-equal (ql-dist:archive-md5 release) (md5 file))
7479
(error "md5 mismatch for ~A" name))
80+
(unless (string-equal (ql-dist:archive-content-sha1 release) (content-hash file))
81+
(error "sha1 mismatch for ~A" name))
7582
(unless (= (ql-dist:archive-size release) (file-size file))
7683
(error "file size mismatch for ~A" name))))
7784

0 commit comments

Comments
 (0)