Skip to content

Commit ec7a164

Browse files
Merge pull request rudolfochrist#13 from bo-tato/check-sha1
Check sha1
2 parents a028be0 + 6705878 commit ec7a164

3 files changed

Lines changed: 176 additions & 5 deletions

File tree

content-hash.lisp

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
;;; Copyright (c) 2013 Zachary Beane <xach@xach.com>, All Rights Reserved
2+
;;;
3+
;;; Redistribution and use in source and binary forms, with or without
4+
;;; modification, are permitted provided that the following conditions
5+
;;; are met:
6+
;;;
7+
;;; * Redistributions of source code must retain the above copyright
8+
;;; notice, this list of conditions and the following disclaimer.
9+
;;;
10+
;;; * Redistributions in binary form must reproduce the above
11+
;;; copyright notice, this list of conditions and the following
12+
;;; disclaimer in the documentation and/or other materials
13+
;;; provided with the distribution.
14+
;;;
15+
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
16+
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17+
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18+
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
19+
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20+
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
21+
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22+
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23+
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24+
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
27+
;;; Commentary:
28+
;;;
29+
;;; copied from tarhash.lisp from quicklisp-controller: https://github.com/quicklisp/quicklisp-controller
30+
;;; changed to use openssl to compute sha1 digest rather than depending on ironclad
31+
32+
(in-package #:ql-https)
33+
34+
(defconstant +block-octet-count+ 512)
35+
36+
(defun make-block-buffer ()
37+
(make-array +block-octet-count+
38+
:element-type '(unsigned-byte 8)
39+
:initial-element 0))
40+
41+
(defun read-header-block (buffer stream)
42+
"Read a tar header block from STREAM into BUFFER. Returns NIL when
43+
at the terminating block of the end of input, BUFFER otherwise."
44+
(let ((size (read-sequence buffer stream)))
45+
(cond ((zerop size)
46+
nil)
47+
((/= size 0 +block-octet-count+)
48+
(error "Short block (only ~D bytes)" size))
49+
((every #'zerop buffer)
50+
nil)
51+
(t
52+
buffer))))
53+
54+
(defun ascii-subseq (vector start end)
55+
(let ((string (make-string (- end start))))
56+
(loop for i from 0
57+
for j from start below end
58+
do (setf (char string i) (code-char (aref vector j))))
59+
string))
60+
61+
(defun block-asciiz-string (block start length)
62+
(let* ((end (+ start length))
63+
(eos (or (position 0 block :start start :end end)
64+
end)))
65+
(ascii-subseq block start eos)))
66+
67+
(defun payload-size (header)
68+
(values (parse-integer (block-asciiz-string header 124 12) :radix 8)))
69+
70+
(defun file-payload-p (header)
71+
(member (aref header 156) '(0 48)))
72+
73+
(defparameter *ignored-path-substrings*
74+
'("/_darcs/" "/CVS/" "/.git/" "/CVS/" "/.hg/"))
75+
76+
(defun ignored-path-p (path)
77+
(dolist (substring *ignored-path-substrings*)
78+
(when (search substring path)
79+
(return t))))
80+
81+
(defun prefix (header)
82+
(when (plusp (aref header 345))
83+
(block-asciiz-string header 345 155)))
84+
85+
(defun name (header)
86+
(block-asciiz-string header 0 100))
87+
88+
(defun full-path (header)
89+
(let ((prefix (prefix header))
90+
(name (name header)))
91+
(if prefix
92+
(format nil "~A/~A" prefix name)
93+
name)))
94+
95+
(defun skip-n-octets-blocks (n stream)
96+
(let ((count (ceiling n +block-octet-count+))
97+
(block (make-block-buffer)))
98+
(dotimes (i count)
99+
(read-sequence block stream))))
100+
101+
(defun content-info (stream)
102+
"Return a list of file info for the POSIX tar stream STREAM. Each
103+
element in the result is a list of a filename, the position of its
104+
starting storage block in STREAM, and the total file size."
105+
(file-position stream :start)
106+
(let ((buffer (make-block-buffer))
107+
(result '()))
108+
(loop
109+
(let ((header (read-header-block buffer stream))
110+
(position (file-position stream)))
111+
(when (not header)
112+
(return result))
113+
(let ((size (payload-size header)))
114+
(when (file-payload-p header)
115+
(let ((path (full-path header)))
116+
(unless (ignored-path-p path)
117+
(push (list path
118+
position
119+
size)
120+
result))))
121+
(skip-n-octets-blocks size stream))))))
122+
123+
(defun read-binary-line (stream)
124+
"Read a line from a binary stream and return it as an ascii string."
125+
(with-output-to-string (string)
126+
(loop for byte = (read-byte stream)
127+
until (= byte (char-code #\Newline))
128+
do (write-char (code-char byte) string))))
129+
130+
(defun content-hash (tarfile)
131+
"Return a hash string of TARFILE. The hash is computed by creating
132+
the digest of the files in TARFILE in order of their name."
133+
(uiop:with-temporary-file (:pathname temp)
134+
(setf tarfile (gunzip tarfile temp))
135+
(unwind-protect
136+
(with-open-file (stream tarfile :element-type '(unsigned-byte 8))
137+
(let* ((openssl (uiop:launch-program "openssl dgst -sha1"
138+
:input :stream
139+
:element-type '(unsigned-byte 8)
140+
:output :stream))
141+
(digest-stream (uiop:process-info-input openssl))
142+
(buffer (make-block-buffer)))
143+
(flet ((add-file-content (position size)
144+
(file-position stream position)
145+
(multiple-value-bind (complete partial)
146+
(truncate size +block-octet-count+)
147+
(dotimes (i complete)
148+
(read-sequence buffer stream)
149+
(write-sequence buffer digest-stream))
150+
(read-sequence buffer stream)
151+
(write-sequence buffer digest-stream :end partial))))
152+
(let ((contents (content-info stream)))
153+
(setf contents (sort contents #'string< :key #'first))
154+
(dolist (info contents)
155+
(destructuring-bind (position size)
156+
(rest info)
157+
(add-file-content position size))))
158+
(close (uiop:process-info-input openssl))
159+
(unless (zerop (uiop:wait-process openssl))
160+
(error "openssl failed to calculate sha1"))
161+
(extract-openssl-digest (read-binary-line (uiop:process-info-output openssl))))))
162+
(when (probe-file temp)
163+
(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)