From 97618cbaa3a63eaf30ba1d720f3b126c3551992c Mon Sep 17 00:00:00 2001 From: beck-8 <1504068285@qq.com> Date: Thu, 26 Mar 2026 19:41:09 +0800 Subject: [PATCH] fix: check HTTP status code and fix concurrent fetch race --- paramfetch.go | 10 ++++++++++ paramfetch_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/paramfetch.go b/paramfetch.go index d87b20a..1fba778 100644 --- a/paramfetch.go +++ b/paramfetch.go @@ -108,6 +108,12 @@ func (ft *fetch) maybeFetchAsync(ctx context.Context, name string, info paramFil ft.fetchLk.Lock() defer ft.fetchLk.Unlock() + // Re-check after acquiring the in-process mutex — another goroutine + // may have already fetched the file while we waited. + if err := ft.checkFile(path, info); err == nil { + return + } + var lockfail bool var unlocker io.Closer for { @@ -275,6 +281,10 @@ func doFetch(ctx context.Context, out string, info paramFile) error { } defer resp.Body.Close() + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent { + return xerrors.Errorf("fetching file from %s: %s", url, resp.Status) + } + bar := pb.New64(fStat.Size() + resp.ContentLength). SetCurrent(fStat.Size()).Start() diff --git a/paramfetch_test.go b/paramfetch_test.go index 7c0df51..0a506fd 100644 --- a/paramfetch_test.go +++ b/paramfetch_test.go @@ -3,8 +3,12 @@ package paramfetch import ( "context" "encoding/json" + "fmt" + "net/http" + "net/http/httptest" "os" "path/filepath" + "strings" "testing" logging "github.com/ipfs/go-log/v2" @@ -93,3 +97,45 @@ func TestCheckFileIgnoresUntrustableExtension(t *testing.T) { err = ft.checkFile(filepath.Join(".", "also_check_and_fail.srs"), mockParamInfo) assert.Error(t, err) } + +func TestDoFetchBadStatusCode(t *testing.T) { + cases := []struct { + name string + code int + }{ + {"BadRequest", http.StatusBadRequest}, + {"Forbidden", http.StatusForbidden}, + {"NotFound", http.StatusNotFound}, + {"InternalServerError", http.StatusInternalServerError}, + {"BadGateway", http.StatusBadGateway}, + {"ServiceUnavailable", http.StatusServiceUnavailable}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(tc.code) + })) + defer ts.Close() + + t.Setenv("IPFS_GATEWAY", ts.URL+"/ipfs/") + + tmpDir := t.TempDir() + outPath := filepath.Join(tmpDir, "test-param-file") + + info := paramFile{ + Cid: "QmFakeCid", + Digest: "0000000000000000", + } + + err := doFetch(context.Background(), outPath, info) + require.Error(t, err) + + errMsg := fmt.Sprintf("%v", err) + assert.True(t, strings.Contains(errMsg, fmt.Sprintf("%d", tc.code)), + "error should contain HTTP status code %d, got: %s", tc.code, errMsg) + assert.False(t, strings.Contains(errMsg, "checksum"), + "error should not mention checksum mismatch, got: %s", errMsg) + }) + } +}