mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-02 09:08:35 +02:00
fix(download): avoid unsafe automatic range resume
This commit is contained in:
@@ -129,6 +129,7 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value {
|
||||
var onProgress goja.Callable
|
||||
var headers map[string]string
|
||||
var chunkedDownload bool
|
||||
var resumeDownload bool
|
||||
trackItemBytes := true
|
||||
var chunkSize int64
|
||||
if len(call.Arguments) > 2 && !goja.IsUndefined(call.Arguments[2]) && !goja.IsNull(call.Arguments[2]) {
|
||||
@@ -170,6 +171,11 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value {
|
||||
}
|
||||
}
|
||||
}
|
||||
if resume, ok := opts["resume"]; ok {
|
||||
if v, ok := resume.(bool); ok {
|
||||
resumeDownload = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,14 +334,14 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value {
|
||||
}
|
||||
}
|
||||
|
||||
// Mid-body network failures resume from the current offset instead of
|
||||
// failing the whole download. Only attempted when the server gave a
|
||||
// validator (If-Range guards against splicing two versions of the file)
|
||||
// and the caller did not set its own Range. A server that ignores Range
|
||||
// answers 200 and the download restarts from zero — same as today.
|
||||
// Mid-body resume is opt-in because switching networks can route a stable
|
||||
// URL to a different CDN object even when its validator is unchanged. The
|
||||
// safe default is to fail and delete the staged partial file. Extensions
|
||||
// that know their origin supports byte-identical Range resumes can request
|
||||
// it explicitly with { resume: true }.
|
||||
validator := resumeValidator(resp.Header)
|
||||
_, callerSetRange := headers["Range"]
|
||||
canResume := validator != "" && !callerSetRange
|
||||
canResume := resumeDownload && validator != "" && !callerSetRange
|
||||
|
||||
const maxResumes = 3
|
||||
resumes := 0
|
||||
@@ -401,7 +407,7 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value {
|
||||
}
|
||||
}
|
||||
validator = resumeValidator(resp.Header)
|
||||
canResume = validator != ""
|
||||
canResume = resumeDownload && validator != ""
|
||||
default:
|
||||
code := resp.StatusCode
|
||||
resp.Body.Close()
|
||||
|
||||
@@ -267,7 +267,42 @@ func TestFileDownloadFailureLeavesNoFinalFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileDownloadResumesAfterMidBodyCut(t *testing.T) {
|
||||
func TestFileDownloadDoesNotResumeMidBodyCutByDefault(t *testing.T) {
|
||||
const full = "hello-world!"
|
||||
var attempts int
|
||||
runtime := newFileDownloadTestRuntime(t, func(req *http.Request) (*http.Response, error) {
|
||||
attempts++
|
||||
h := make(http.Header)
|
||||
h.Set("ETag", `"v1"`)
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Header: h,
|
||||
Body: io.NopCloser(&failingBodyReader{data: []byte(full[:6])}),
|
||||
ContentLength: int64(len(full)),
|
||||
Request: req,
|
||||
}, nil
|
||||
})
|
||||
|
||||
result := runtime.fileDownload(goja.FunctionCall{Arguments: []goja.Value{
|
||||
runtime.vm.ToValue("https://cdn.example.com/track.flac"),
|
||||
runtime.vm.ToValue("out/track.flac"),
|
||||
}}).Export().(map[string]any)
|
||||
if result["success"] != false {
|
||||
t.Fatalf("expected failed download, got %#v", result)
|
||||
}
|
||||
if attempts != 1 {
|
||||
t.Fatalf("attempts = %d, want no automatic resume", attempts)
|
||||
}
|
||||
finalPath := filepath.Join(runtime.dataDir, "out", "track.flac")
|
||||
if _, err := os.Stat(finalPath); !os.IsNotExist(err) {
|
||||
t.Fatalf("partial download visible at final path: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(stagedDownloadPath(finalPath)); !os.IsNotExist(err) {
|
||||
t.Fatalf("staged file left behind: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileDownloadResumesAfterMidBodyCutWhenEnabled(t *testing.T) {
|
||||
const full = "hello-world!"
|
||||
var attempts int
|
||||
var resumeRange, resumeIfRange string
|
||||
@@ -300,6 +335,7 @@ func TestFileDownloadResumesAfterMidBodyCut(t *testing.T) {
|
||||
result := runtime.fileDownload(goja.FunctionCall{Arguments: []goja.Value{
|
||||
runtime.vm.ToValue("https://cdn.example.com/track.flac"),
|
||||
runtime.vm.ToValue("out/track.flac"),
|
||||
runtime.vm.ToValue(map[string]any{"resume": true}),
|
||||
}}).Export().(map[string]any)
|
||||
if result["success"] != true {
|
||||
t.Fatalf("download result = %#v", result)
|
||||
@@ -342,6 +378,7 @@ func TestFileDownloadResumeRestartsWhenRangeIgnored(t *testing.T) {
|
||||
result := runtime.fileDownload(goja.FunctionCall{Arguments: []goja.Value{
|
||||
runtime.vm.ToValue("https://cdn.example.com/track.flac"),
|
||||
runtime.vm.ToValue("out/track.flac"),
|
||||
runtime.vm.ToValue(map[string]any{"resume": true}),
|
||||
}}).Export().(map[string]any)
|
||||
if result["success"] != true {
|
||||
t.Fatalf("download result = %#v", result)
|
||||
|
||||
Reference in New Issue
Block a user