2026-05-03 10:36:28 +01:00
|
|
|
package handler
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
2026-08-17 09:20:11 +01:00
|
|
|
|
|
|
|
|
"github.com/git-pkgs/integrity"
|
2026-05-03 10:36:28 +01:00
|
|
|
)
|
|
|
|
|
|
2026-08-17 09:20:11 +01:00
|
|
|
type integrityChecks struct {
|
|
|
|
|
contentHash integrity.SRI
|
|
|
|
|
native integrity.SRI
|
|
|
|
|
algorithms []integrity.Algorithm
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newIntegrityChecks(contentHash, native string) (integrityChecks, error) {
|
|
|
|
|
checks := integrityChecks{}
|
|
|
|
|
|
|
|
|
|
if contentHash != "" {
|
|
|
|
|
digest, err := integrity.ParseHex(integrity.SHA256, contentHash)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return integrityChecks{}, fmt.Errorf("parse content_hash: %w", err)
|
|
|
|
|
}
|
|
|
|
|
checks.contentHash = integrity.SRI{digest}
|
|
|
|
|
checks.algorithms = append(checks.algorithms, integrity.SHA256)
|
2026-05-03 10:36:28 +01:00
|
|
|
}
|
2026-08-17 09:20:11 +01:00
|
|
|
|
|
|
|
|
if native != "" {
|
|
|
|
|
digests, err := integrity.ParseSRI(native)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return integrityChecks{}, fmt.Errorf("parse integrity: %w", err)
|
|
|
|
|
}
|
|
|
|
|
checks.native = digests
|
|
|
|
|
for _, digest := range digests {
|
|
|
|
|
checks.algorithms = append(checks.algorithms, digest.Algorithm())
|
|
|
|
|
}
|
2026-05-03 10:36:28 +01:00
|
|
|
}
|
2026-08-17 09:20:11 +01:00
|
|
|
|
|
|
|
|
return checks, nil
|
2026-05-03 10:36:28 +01:00
|
|
|
}
|
|
|
|
|
|
2026-08-17 09:20:11 +01:00
|
|
|
func (c integrityChecks) wrap(source io.ReadCloser, onMismatch func(string)) (io.ReadCloser, error) {
|
|
|
|
|
if len(c.algorithms) == 0 {
|
|
|
|
|
return source, nil
|
|
|
|
|
}
|
|
|
|
|
reader, err := integrity.NewReader(source, c.algorithms...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("create integrity reader: %w", err)
|
2026-05-03 10:36:28 +01:00
|
|
|
}
|
2026-08-17 09:20:11 +01:00
|
|
|
return &verifyingReader{
|
|
|
|
|
source: source,
|
|
|
|
|
reader: reader,
|
|
|
|
|
checks: c,
|
|
|
|
|
onMismatch: onMismatch,
|
|
|
|
|
}, nil
|
2026-05-03 10:36:28 +01:00
|
|
|
}
|
|
|
|
|
|
2026-08-17 09:20:11 +01:00
|
|
|
// verifyingReader forwards Close to its source and reports completed digest
|
|
|
|
|
// mismatches after its shared integrity reader observes EOF.
|
2026-05-03 10:36:28 +01:00
|
|
|
type verifyingReader struct {
|
2026-08-17 09:20:11 +01:00
|
|
|
source io.ReadCloser
|
|
|
|
|
reader *integrity.Reader
|
|
|
|
|
checks integrityChecks
|
2026-05-03 10:36:28 +01:00
|
|
|
onMismatch func(reason string)
|
|
|
|
|
verified bool
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-17 09:20:11 +01:00
|
|
|
func (r *verifyingReader) Read(p []byte) (int, error) {
|
|
|
|
|
n, err := r.reader.Read(p)
|
2026-05-03 10:36:28 +01:00
|
|
|
if err == io.EOF {
|
2026-08-17 09:20:11 +01:00
|
|
|
r.verify()
|
2026-05-03 10:36:28 +01:00
|
|
|
}
|
|
|
|
|
return n, err
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-17 09:20:11 +01:00
|
|
|
func (r *verifyingReader) Close() error {
|
|
|
|
|
return r.source.Close()
|
2026-05-03 10:36:28 +01:00
|
|
|
}
|
|
|
|
|
|
2026-08-17 09:20:11 +01:00
|
|
|
func (r *verifyingReader) verify() {
|
|
|
|
|
if r.verified {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
r.verified = true
|
|
|
|
|
result := r.reader.Result()
|
|
|
|
|
if !result.Complete {
|
2026-05-03 10:36:28 +01:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-17 09:20:11 +01:00
|
|
|
if len(r.checks.contentHash) > 0 {
|
|
|
|
|
if err := result.Verify(r.checks.contentHash); err != nil {
|
|
|
|
|
r.onMismatch("content_hash: " + err.Error())
|
2026-05-03 10:36:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-08-17 09:20:11 +01:00
|
|
|
if len(r.checks.native) > 0 {
|
|
|
|
|
if err := result.Verify(r.checks.native); err != nil {
|
|
|
|
|
r.onMismatch("integrity: " + err.Error())
|
2026-05-03 10:36:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|