👻
GoでCRC-32Cハッシュでダウンロードファイルの整合性をチェックする
APIのレスポンスからファイルを保存して、CRC32C整合性をハッシュチェックするサンプル実装
func downloadFile(res *http.Response, path string) error {
var buf bytes.Buffer
tr := io.TeeReader(res.Body, &buf)
writeFile(tr, path)
if err := writeFile(tr, path); err != nil {
return err
}
if err := checkCRC32c(res, path); err != nil {
return err
}
}
func writeFile(r io.Reader, path string) error {
dir := filepath.Dir(path)
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
return errors.Wrapf(err, "failed to create directory: %s", dir)
}
out, err := os.Create(path)
if err != nil {
return errors.Wrapf(err, "failed to create file: %s", path)
}
defer out.Close()
if _, err := io.Copy(out, r); err != nil {
return errors.Wrapf(err, "failed to write raw file: %s", path)
}
if err := out.Sync(); err != nil {
return errors.Wrapf(err, "failed to sync raw file: %s", path)
}
return nil
}
func checkCRC32c(res *http.Response, path string) error {
hash, checked := parseCRC32c(res)
if !checked {
return nil
}
bytes, err := ioutil.ReadFile(path)
if err != nil {
return errors.Wrapf(err, "failed to read file: %s", path)
}
if hash != crc32.Checksum(bytes, crc32cTable) {
return errors.Errorf("CRC32C hash is incorrect and the file may be missing: %s", path)
}
return nil
}
func parseCRC32c(res *http.Response) (uint32, bool) {
const prefix = "crc32c="
for _, spec := range res.Header["X-Goog-Hash"] {
if strings.HasPrefix(spec, prefix) {
c, err := decodeUint32(spec[len(prefix):])
if err != nil {
log.L().Warn("failed to parse CRC32c hash", zap.Error(err))
break
}
return c, true
}
}
return 0, false
}
func decodeUint32(b64 string) (uint32, error) {
d, err := base64.StdEncoding.DecodeString(b64)
if err != nil {
return 0, errors.Wrapf(err, "failed to base64 decode string: %s", b64)
}
if len(d) != 4 {
return 0, errors.Errorf("%q does not encode a 32-bit value", d)
}
return uint32(d[0])<<24 + uint32(d[1])<<16 + uint32(d[2])<<8 + uint32(d[3]), nil
}
Discussion