fix(audio): give ffprobe its own 45s deadline, a timeout is the file's fault (#49)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,8 +22,14 @@ type FFprobe struct {
|
|||||||
Bin string
|
Bin string
|
||||||
// MaxOutput bounds how much of ffprobe's stdout is read; zero means DefaultMaxOutput.
|
// MaxOutput bounds how much of ffprobe's stdout is read; zero means DefaultMaxOutput.
|
||||||
MaxOutput int
|
MaxOutput int
|
||||||
|
// Timeout bounds one probe; zero means DefaultProbeTimeout. A probe is milliseconds of
|
||||||
|
// work on a real file, so one that runs this long is the FILE's doing (#49).
|
||||||
|
Timeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultProbeTimeout is the probe deadline when FFprobe.Timeout is unset.
|
||||||
|
const DefaultProbeTimeout = 45 * time.Second
|
||||||
|
|
||||||
// DefaultMaxOutput is far above the narrowed probe output of a legitimate file (a few
|
// DefaultMaxOutput is far above the narrowed probe output of a legitimate file (a few
|
||||||
// hundred bytes) and far below what a hostile one can make ffprobe print.
|
// hundred bytes) and far below what a hostile one can make ffprobe print.
|
||||||
const DefaultMaxOutput = 1 << 20
|
const DefaultMaxOutput = 1 << 20
|
||||||
@@ -102,7 +108,13 @@ func (f *FFprobe) Probe(ctx context.Context, path string) (uploads.Probe, error)
|
|||||||
}
|
}
|
||||||
args := append([]string{"-v", "error"}, inputRestrictions...)
|
args := append([]string{"-v", "error"}, inputRestrictions...)
|
||||||
args = append(args, "-print_format", "json", "-show_entries", probeEntries, path)
|
args = append(args, "-print_format", "json", "-show_entries", probeEntries, path)
|
||||||
cmd := exec.CommandContext(ctx, bin, args...)
|
timeout := f.Timeout
|
||||||
|
if timeout <= 0 {
|
||||||
|
timeout = DefaultProbeTimeout
|
||||||
|
}
|
||||||
|
probeCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
|
defer cancel()
|
||||||
|
cmd := exec.CommandContext(probeCtx, bin, args...)
|
||||||
cmd.WaitDelay = killGrace
|
cmd.WaitDelay = killGrace
|
||||||
maxOut := f.MaxOutput
|
maxOut := f.MaxOutput
|
||||||
if maxOut <= 0 {
|
if maxOut <= 0 {
|
||||||
@@ -111,9 +123,12 @@ func (f *FFprobe) Probe(ctx context.Context, path string) (uploads.Probe, error)
|
|||||||
stdout, stderr := &capWriter{max: maxOut}, &headWriter{max: maxStderr}
|
stdout, stderr := &capWriter{max: maxOut}, &headWriter{max: maxStderr}
|
||||||
cmd.Stdout, cmd.Stderr = stdout, stderr
|
cmd.Stdout, cmd.Stderr = stdout, stderr
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
if ctxErr := ctx.Err(); ctxErr != nil { // the caller gave up: not a verdict on the file
|
||||||
return uploads.Probe{}, fmt.Errorf("audio: ffprobe: %w", ctxErr)
|
return uploads.Probe{}, fmt.Errorf("audio: ffprobe: %w", ctxErr)
|
||||||
}
|
}
|
||||||
|
if probeCtx.Err() != nil { // our own deadline passed while the caller was still waiting
|
||||||
|
return uploads.Probe{}, fmt.Errorf("%w: ffprobe did not finish within %s", uploads.ErrUnreadable, timeout)
|
||||||
|
}
|
||||||
if errors.Is(err, errOutputCap) {
|
if errors.Is(err, errOutputCap) {
|
||||||
return uploads.Probe{}, fmt.Errorf("%w: ffprobe printed more than %d bytes", uploads.ErrUnreadable, maxOut)
|
return uploads.Probe{}, fmt.Errorf("%w: ffprobe printed more than %d bytes", uploads.ErrUnreadable, maxOut)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user