mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-07 17:10:29 +00:00
328 lines
8.2 KiB
Go
328 lines
8.2 KiB
Go
package http
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
gohttp "net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/GopeedLab/gopeed/internal/test"
|
|
"github.com/GopeedLab/gopeed/pkg/base"
|
|
"github.com/GopeedLab/gopeed/pkg/protocol/http"
|
|
)
|
|
|
|
func TestFetcher_ChecksumVerification(t *testing.T) {
|
|
content := []byte("Hello, Gopeed Checksum Verification Test Data!")
|
|
md5Hasher := md5.New()
|
|
md5Hasher.Write(content)
|
|
correctMd5 := hex.EncodeToString(md5Hasher.Sum(nil))
|
|
|
|
sha256Hasher := sha256.New()
|
|
sha256Hasher.Write(content)
|
|
correctSha256 := hex.EncodeToString(sha256Hasher.Sum(nil))
|
|
|
|
wrongHash := "0123456789abcdef0123456789abcdef"
|
|
|
|
server := httptest.NewServer(gohttp.HandlerFunc(func(w gohttp.ResponseWriter, r *gohttp.Request) {
|
|
w.Header().Set(base.HttpHeaderContentLength, fmt.Sprintf("%d", len(content)))
|
|
w.Header().Set(base.HttpHeaderAcceptRanges, base.HttpHeaderBytes)
|
|
w.WriteHeader(gohttp.StatusOK)
|
|
_, _ = w.Write(content)
|
|
}))
|
|
defer server.Close()
|
|
|
|
t.Run("Valid MD5", func(t *testing.T) {
|
|
f := buildFetcher()
|
|
defer f.Close()
|
|
|
|
opts := &base.Options{
|
|
Path: t.TempDir(),
|
|
Name: "test_valid_md5.txt",
|
|
Extra: &http.OptsExtra{
|
|
Connections: 1,
|
|
Checksum: &http.ChecksumOption{
|
|
Algorithm: "md5",
|
|
Expected: correctMd5,
|
|
},
|
|
},
|
|
}
|
|
|
|
if err := f.Resolve(&base.Request{URL: server.URL + "/test.txt"}); err != nil {
|
|
t.Fatalf("Resolve error: %v", err)
|
|
}
|
|
|
|
f.Meta().Opts = opts
|
|
if err := f.Start(); err != nil {
|
|
t.Fatalf("Start error: %v", err)
|
|
}
|
|
if err := f.Wait(); err != nil {
|
|
t.Fatalf("Wait error (expected success): %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Valid SHA256", func(t *testing.T) {
|
|
f := buildFetcher()
|
|
defer f.Close()
|
|
|
|
opts := &base.Options{
|
|
Path: t.TempDir(),
|
|
Name: "test_valid_sha256.txt",
|
|
Extra: &http.OptsExtra{
|
|
Connections: 1,
|
|
Checksum: &http.ChecksumOption{
|
|
Algorithm: "sha256",
|
|
Expected: correctSha256,
|
|
},
|
|
},
|
|
}
|
|
|
|
if err := f.Resolve(&base.Request{URL: server.URL + "/test.txt"}); err != nil {
|
|
t.Fatalf("Resolve error: %v", err)
|
|
}
|
|
|
|
f.Meta().Opts = opts
|
|
if err := f.Start(); err != nil {
|
|
t.Fatalf("Start error: %v", err)
|
|
}
|
|
if err := f.Wait(); err != nil {
|
|
t.Fatalf("Wait error (expected success): %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Wrong MD5 mismatch", func(t *testing.T) {
|
|
f := buildFetcher()
|
|
defer f.Close()
|
|
|
|
opts := &base.Options{
|
|
Path: t.TempDir(),
|
|
Name: "test_wrong_md5.txt",
|
|
Extra: &http.OptsExtra{
|
|
Connections: 1,
|
|
Checksum: &http.ChecksumOption{
|
|
Algorithm: "md5",
|
|
Expected: wrongHash,
|
|
},
|
|
},
|
|
}
|
|
|
|
if err := f.Resolve(&base.Request{URL: server.URL + "/test.txt"}); err != nil {
|
|
t.Fatalf("Resolve error: %v", err)
|
|
}
|
|
|
|
f.Meta().Opts = opts
|
|
if err := f.Start(); err != nil {
|
|
t.Fatalf("Start error: %v", err)
|
|
}
|
|
err := f.Wait()
|
|
if err == nil {
|
|
t.Fatal("Wait expected checksum mismatch error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "checksum mismatch") {
|
|
t.Fatalf("expected 'checksum mismatch' in error, got %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Unsupported Algorithm", func(t *testing.T) {
|
|
f := buildFetcher()
|
|
defer f.Close()
|
|
|
|
opts := &base.Options{
|
|
Path: t.TempDir(),
|
|
Name: "test_unsupported.txt",
|
|
Extra: &http.OptsExtra{
|
|
Connections: 1,
|
|
Checksum: &http.ChecksumOption{
|
|
Algorithm: "invalid-algo",
|
|
Expected: wrongHash,
|
|
},
|
|
},
|
|
}
|
|
|
|
if err := f.Resolve(&base.Request{URL: server.URL + "/test.txt"}); err != nil {
|
|
t.Fatalf("Resolve error: %v", err)
|
|
}
|
|
f.Meta().Opts = opts
|
|
err := f.Start()
|
|
if err == nil {
|
|
t.Fatal("Start expected error for unsupported algorithm, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "unsupported checksum algorithm") {
|
|
t.Fatalf("expected 'unsupported checksum algorithm' in error, got %v", err)
|
|
}
|
|
if f.checksumFailed.Load() {
|
|
t.Fatal("checksumFailed flag should NOT be set on unsupported algorithm error")
|
|
}
|
|
})
|
|
|
|
t.Run("Case-Insensitive Hash Comparison", func(t *testing.T) {
|
|
f := buildFetcher()
|
|
defer f.Close()
|
|
|
|
opts := &base.Options{
|
|
Path: t.TempDir(),
|
|
Name: "test_case_insensitive.txt",
|
|
Extra: &http.OptsExtra{
|
|
Connections: 1,
|
|
Checksum: &http.ChecksumOption{
|
|
Algorithm: "sha256",
|
|
Expected: strings.ToUpper(correctSha256),
|
|
},
|
|
},
|
|
}
|
|
|
|
if err := f.Resolve(&base.Request{URL: server.URL + "/test.txt"}); err != nil {
|
|
t.Fatalf("Resolve error: %v", err)
|
|
}
|
|
|
|
f.Meta().Opts = opts
|
|
if err := f.Start(); err != nil {
|
|
t.Fatalf("Start error: %v", err)
|
|
}
|
|
if err := f.Wait(); err != nil {
|
|
t.Fatalf("Wait error (expected success with upper case hash): %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Empty Expected Checksum (Skipped)", func(t *testing.T) {
|
|
f := buildFetcher()
|
|
defer f.Close()
|
|
|
|
opts := &base.Options{
|
|
Path: t.TempDir(),
|
|
Name: "test_empty_expected.txt",
|
|
Extra: &http.OptsExtra{
|
|
Connections: 1,
|
|
Checksum: &http.ChecksumOption{
|
|
Algorithm: "",
|
|
Expected: "",
|
|
},
|
|
},
|
|
}
|
|
|
|
if err := f.Resolve(&base.Request{URL: server.URL + "/test.txt"}); err != nil {
|
|
t.Fatalf("Resolve error: %v", err)
|
|
}
|
|
|
|
f.Meta().Opts = opts
|
|
if err := f.Start(); err != nil {
|
|
t.Fatalf("Start error: %v", err)
|
|
}
|
|
if err := f.Wait(); err != nil {
|
|
t.Fatalf("Wait error (expected skip and success): %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Large File Multi-Chunk Streaming (>64KB)", func(t *testing.T) {
|
|
largeData := make([]byte, 128*1024)
|
|
for i := range largeData {
|
|
largeData[i] = byte(i % 256)
|
|
}
|
|
largeSha256Hasher := sha256.New()
|
|
largeSha256Hasher.Write(largeData)
|
|
largeSha256 := hex.EncodeToString(largeSha256Hasher.Sum(nil))
|
|
|
|
largeServer := httptest.NewServer(gohttp.HandlerFunc(func(w gohttp.ResponseWriter, r *gohttp.Request) {
|
|
w.Header().Set(base.HttpHeaderContentLength, fmt.Sprintf("%d", len(largeData)))
|
|
w.Header().Set(base.HttpHeaderAcceptRanges, base.HttpHeaderBytes)
|
|
w.WriteHeader(gohttp.StatusOK)
|
|
_, _ = w.Write(largeData)
|
|
}))
|
|
defer largeServer.Close()
|
|
|
|
f := buildFetcher()
|
|
defer f.Close()
|
|
|
|
opts := &base.Options{
|
|
Path: t.TempDir(),
|
|
Name: "test_large_file.bin",
|
|
Extra: &http.OptsExtra{
|
|
Connections: 1,
|
|
Checksum: &http.ChecksumOption{
|
|
Algorithm: "sha256",
|
|
Expected: largeSha256,
|
|
},
|
|
},
|
|
}
|
|
|
|
if err := f.Resolve(&base.Request{URL: largeServer.URL + "/large.bin"}); err != nil {
|
|
t.Fatalf("Resolve error: %v", err)
|
|
}
|
|
|
|
f.Meta().Opts = opts
|
|
if err := f.Start(); err != nil {
|
|
t.Fatalf("Start error: %v", err)
|
|
}
|
|
if err := f.Wait(); err != nil {
|
|
t.Fatalf("Wait error (expected large file checksum success): %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("No Checksum (Opt-in backward compatibility)", func(t *testing.T) {
|
|
f := buildFetcher()
|
|
defer f.Close()
|
|
|
|
opts := &base.Options{
|
|
Path: t.TempDir(),
|
|
Name: test.DownloadName,
|
|
Extra: &http.OptsExtra{
|
|
Connections: 1,
|
|
},
|
|
}
|
|
|
|
if err := f.Resolve(&base.Request{URL: server.URL + "/test.txt"}); err != nil {
|
|
t.Fatalf("Resolve error: %v", err)
|
|
}
|
|
|
|
f.Meta().Opts = opts
|
|
if err := f.Start(); err != nil {
|
|
t.Fatalf("Start error: %v", err)
|
|
}
|
|
if err := f.Wait(); err != nil {
|
|
t.Fatalf("Wait error: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Invalid Checksum In Start Makes No Additional Requests", func(t *testing.T) {
|
|
var reqReceived atomic.Int32
|
|
customServer := httptest.NewServer(gohttp.HandlerFunc(func(w gohttp.ResponseWriter, r *gohttp.Request) {
|
|
reqReceived.Add(1)
|
|
w.WriteHeader(gohttp.StatusOK)
|
|
_, _ = w.Write([]byte("some data"))
|
|
}))
|
|
defer customServer.Close()
|
|
|
|
f := buildFetcher()
|
|
defer f.Close()
|
|
|
|
opts := &base.Options{
|
|
Path: t.TempDir(),
|
|
Name: "test_invalid_checksum.txt",
|
|
Extra: &http.OptsExtra{
|
|
Connections: 1,
|
|
Checksum: &http.ChecksumOption{
|
|
Algorithm: "sha256",
|
|
Expected: "invalid-hex-chars-not-valid-hex-00000000000000000000000000000000",
|
|
},
|
|
},
|
|
}
|
|
|
|
if err := f.Resolve(&base.Request{URL: customServer.URL + "/test.txt"}); err != nil {
|
|
t.Fatalf("Resolve error: %v", err)
|
|
}
|
|
requestsAfterResolve := reqReceived.Load()
|
|
f.Meta().Opts = opts
|
|
err := f.Start()
|
|
if err == nil {
|
|
t.Fatal("Start expected error for invalid checksum hash, got nil")
|
|
}
|
|
if got := reqReceived.Load(); got != requestsAfterResolve {
|
|
t.Fatalf("invalid checksum in Start issued additional requests: got %d, had %d after Resolve", got, requestsAfterResolve)
|
|
}
|
|
})
|
|
}
|