5824: Merge branch 'master' into 5824-keep-web-workbench
[arvados.git] / sdk / go / keepclient / hashcheck.go
1 package keepclient
2
3 import (
4         "errors"
5         "fmt"
6         "hash"
7         "io"
8 )
9
10 var BadChecksum = errors.New("Reader failed checksum")
11
12 // HashCheckingReader is an io.ReadCloser that checks the contents
13 // read from the underlying io.Reader against the provided hash.
14 type HashCheckingReader struct {
15         // The underlying data source
16         io.Reader
17
18         // The hash function to use
19         hash.Hash
20
21         // The hash value to check against.  Must be a hex-encoded lowercase string.
22         Check string
23 }
24
25 // Reads from the underlying reader, update the hashing function, and
26 // pass the results through. Returns BadChecksum (instead of EOF) on
27 // the last read if the checksum doesn't match.
28 func (this HashCheckingReader) Read(p []byte) (n int, err error) {
29         n, err = this.Reader.Read(p)
30         if n > 0 {
31                 this.Hash.Write(p[:n])
32         }
33         if err == io.EOF {
34                 sum := this.Hash.Sum(make([]byte, 0, this.Hash.Size()))
35                 if fmt.Sprintf("%x", sum) != this.Check {
36                         err = BadChecksum
37                 }
38         }
39         return n, err
40 }
41
42 // WriteTo writes the entire contents of this.Reader to dest.  Returns
43 // BadChecksum if the checksum doesn't match.
44 func (this HashCheckingReader) WriteTo(dest io.Writer) (written int64, err error) {
45         if writeto, ok := this.Reader.(io.WriterTo); ok {
46                 written, err = writeto.WriteTo(io.MultiWriter(dest, this.Hash))
47         } else {
48                 written, err = io.Copy(io.MultiWriter(dest, this.Hash), this.Reader)
49         }
50
51         sum := this.Hash.Sum(make([]byte, 0, this.Hash.Size()))
52
53         if fmt.Sprintf("%x", sum) != this.Check {
54                 err = BadChecksum
55         }
56
57         return written, err
58 }
59
60 // Close reads all remaining data from the underlying Reader and
61 // returns BadChecksum if the checksum doesn't match. It also closes
62 // the underlying Reader if it implements io.ReadCloser.
63 func (this HashCheckingReader) Close() (err error) {
64         _, err = io.Copy(this.Hash, this.Reader)
65
66         if closer, ok := this.Reader.(io.ReadCloser); ok {
67                 err = closer.Close()
68         }
69
70         sum := this.Hash.Sum(make([]byte, 0, this.Hash.Size()))
71         if fmt.Sprintf("%x", sum) != this.Check {
72                 err = BadChecksum
73         }
74
75         return err
76 }