fix(gopher): reject resume when caller supplies Writer
This commit is contained in:
@@ -165,6 +165,16 @@ func (p *Protocol) downloadText(ctx context.Context, reader *bufio.Reader, req *
|
|||||||
// fresh downloads (create) and resume (append). When req.Writer is
|
// fresh downloads (create) and resume (append). When req.Writer is
|
||||||
// set (caller manages the file), we use it as-is.
|
// set (caller manages the file), we use it as-is.
|
||||||
if req.Output != "" && req.Output != "-" {
|
if req.Output != "" && req.Output != "-" {
|
||||||
|
// Resume is meaningful only when we own the output file. If the
|
||||||
|
// caller already supplied a Writer we cannot append to Output
|
||||||
|
// behind their back, so disable resume to keep the partial-state
|
||||||
|
// sidecar consistent with what actually lands on disk.
|
||||||
|
if req.Resume && req.Writer != nil && req.Verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "[gopher] warning: --resume ignored because a Writer is set; output goes to the Writer only\n")
|
||||||
|
}
|
||||||
|
if req.Resume && req.Writer != nil {
|
||||||
|
req.Resume = false
|
||||||
|
}
|
||||||
if req.Resume {
|
if req.Resume {
|
||||||
canResume, resumeMeta, rerr := output.CanResume(req.Output, req.URL.String())
|
canResume, resumeMeta, rerr := output.CanResume(req.Output, req.URL.String())
|
||||||
if rerr != nil {
|
if rerr != nil {
|
||||||
|
|||||||
@@ -438,3 +438,53 @@ func TestGopherResumeStaleMetadata(t *testing.T) {
|
|||||||
t.Errorf("content = %q, want %q", string(data), "Fresh\n")
|
t.Errorf("content = %q, want %q", string(data), "Fresh\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestGopherResumeIgnoredWhenWriterSet verifies that --resume is silently
|
||||||
|
// disabled when the caller also supplies a Writer: the resume metadata
|
||||||
|
// sidecar would otherwise lie about bytes written to Output, since the
|
||||||
|
// real data ends up in the Writer.
|
||||||
|
func TestGopherResumeIgnoredWhenWriterSet(t *testing.T) {
|
||||||
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
port := listener.Addr().(*net.TCPAddr).Port
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
conn, _ := listener.Accept()
|
||||||
|
if conn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
r := bufio.NewReader(conn)
|
||||||
|
r.ReadString('\n')
|
||||||
|
conn.Write([]byte("0Line one\t/foo\thost\t70\r\n.\r\n"))
|
||||||
|
}()
|
||||||
|
|
||||||
|
proto := NewProtocol()
|
||||||
|
u, _ := url.Parse("gopher://127.0.0.1:" + strconv.Itoa(port) + "/foo")
|
||||||
|
outPath := t.TempDir() + "/ignored-output.txt"
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
res, dlErr := proto.Download(context.Background(), &core.DownloadRequest{
|
||||||
|
URL: u,
|
||||||
|
Output: outPath,
|
||||||
|
Writer: &stringWriter{&buf},
|
||||||
|
Resume: true,
|
||||||
|
})
|
||||||
|
if dlErr != nil {
|
||||||
|
t.Fatalf("download: %v", dlErr)
|
||||||
|
}
|
||||||
|
if res.Resumed {
|
||||||
|
t.Error("expected Resumed=false when Writer is set (resume must be ignored)")
|
||||||
|
}
|
||||||
|
if !strings.Contains(buf.String(), "Line one") {
|
||||||
|
t.Errorf("writer should receive the body, got %q", buf.String())
|
||||||
|
}
|
||||||
|
// Output is supplied but a Writer takes precedence; the file must
|
||||||
|
// not be created or touched by resume machinery.
|
||||||
|
if _, statErr := os.Stat(outPath); statErr == nil {
|
||||||
|
t.Error("Output file should not be created when Writer is set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user