refactor(headless): share the screenshot security gate
- commit
- b9704414c3d166506625aba3985d014e6588f008
- author
- brice · · 2026-10-22 04:53 UTC
- parent
- 9ccafd9
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +14 -7
M internal/adapters/headless/output.go +14 -7
| @@ -642,9 +642,10 @@ func writePNGSecure(path string, img image.Image, security ports.SessionSecurity | |||
| 642 | 642 | return err | |
| 643 | 643 | } | |
| 644 | 644 | ||
| 645 | - | // screenshotPNG encodes screenshot frames: encoding is by far the largest cost | |
| 646 | - | // of a headless frame, so it trades file size for speed. Its buffers are | |
| 647 | - | // reused between frames. | |
| 645 | + | // screenshotPNG encodes the PNGs written by the headless output (frames, the | |
| 646 | + | // latest.png fallback, raw HDR latest-pq.png): encoding is by far the largest | |
| 647 | + | // cost of a headless frame, so it trades file size for speed. Its buffers are | |
| 648 | + | // reused between files. | |
| 648 | 649 | var screenshotPNG = png.Encoder{CompressionLevel: png.BestSpeed, BufferPool: new(pngBuffers)} | |
| 649 | 650 | ||
| 650 | 651 | // pngBuffers is a png.EncoderBufferPool. | |
| @@ -657,9 +658,15 @@ func (p *pngBuffers) Get() *png.EncoderBuffer { | |||
| 657 | 658 | ||
| 658 | 659 | func (p *pngBuffers) Put(b *png.EncoderBuffer) { p.pool.Put(b) } | |
| 659 | 660 | ||
| 661 | + | // gated reports whether a screenshot must not be published: the session is | |
| 662 | + | // protected, or its security state is no longer the one the frame was drawn in. | |
| 663 | + | func gated(security ports.SessionSecurity, state ports.SecurityState) bool { | |
| 664 | + | return security != nil && (state.Protected || security.Snapshot() != state) | |
| 665 | + | } | |
| 666 | + | ||
| 660 | 667 | // writePNGSecureResult distinguishes a security skip from a published PNG. | |
| 661 | 668 | func writePNGSecureResult(path string, img image.Image, security ports.SessionSecurity, state ports.SecurityState) (bool, error) { | |
| 662 | - | if security != nil && (state.Protected || security.Snapshot() != state) { | |
| 669 | + | if gated(security, state) { | |
| 663 | 670 | return false, nil | |
| 664 | 671 | } | |
| 665 | 672 | f, err := os.CreateTemp(filepath.Dir(path), ".frame-*.png") | |
| @@ -674,7 +681,7 @@ func writePNGSecureResult(path string, img image.Image, security ports.SessionSe | |||
| 674 | 681 | if err := f.Close(); err != nil { | |
| 675 | 682 | return false, err | |
| 676 | 683 | } | |
| 677 | - | if security != nil && security.Snapshot() != state { | |
| 684 | + | if gated(security, state) { | |
| 678 | 685 | return false, nil | |
| 679 | 686 | } | |
| 680 | 687 | if err := os.Rename(f.Name(), path); err != nil { | |
| @@ -690,7 +697,7 @@ func writePNGSecureResult(path string, img image.Image, security ports.SessionSe | |||
| 690 | 697 | // It gates on security like writePNGSecureResult, and falls back to encoding | |
| 691 | 698 | // img where the file system cannot link. | |
| 692 | 699 | func linkPNGSecure(src, dst string, img image.Image, security ports.SessionSecurity, state ports.SecurityState) (bool, error) { | |
| 693 | - | if security != nil && (state.Protected || security.Snapshot() != state) { | |
| 700 | + | if gated(security, state) { | |
| 694 | 701 | return false, nil | |
| 695 | 702 | } | |
| 696 | 703 | tmp := dst + ".tmp" | |
| @@ -699,7 +706,7 @@ func linkPNGSecure(src, dst string, img image.Image, security ports.SessionSecur | |||
| 699 | 706 | return writePNGSecureResult(dst, img, security, state) | |
| 700 | 707 | } | |
| 701 | 708 | defer os.Remove(tmp) | |
| 702 | - | if security != nil && security.Snapshot() != state { | |
| 709 | + | if gated(security, state) { | |
| 703 | 710 | return false, nil | |
| 704 | 711 | } | |
| 705 | 712 | if err := os.Rename(tmp, dst); err != nil { | |
| @@ -642,9 +642,10 @@ func writePNGSecure(path string, img image.Image, security ports.SessionSecurity | |||||
| 642 | return err | 642 | return err | ||
| 643 | } | 643 | } | ||
| 644 | 644 | ||||
| 645 | - | // screenshotPNG encodes screenshot frames: encoding is by far the largest cost | 645 | + | // screenshotPNG encodes the PNGs written by the headless output (frames, the |
| 646 | - | // of a headless frame, so it trades file size for speed. Its buffers are | 646 | + | // latest.png fallback, raw HDR latest-pq.png): encoding is by far the largest |
| 647 | - | // reused between frames. | 647 | + | // cost of a headless frame, so it trades file size for speed. Its buffers are |
| 648 | + | // reused between files. | |||
| 648 | var screenshotPNG = png.Encoder{CompressionLevel: png.BestSpeed, BufferPool: new(pngBuffers)} | 649 | var screenshotPNG = png.Encoder{CompressionLevel: png.BestSpeed, BufferPool: new(pngBuffers)} | ||
| 649 | 650 | ||||
| 650 | // pngBuffers is a png.EncoderBufferPool. | 651 | // pngBuffers is a png.EncoderBufferPool. | ||
| @@ -657,9 +658,15 @@ func (p *pngBuffers) Get() *png.EncoderBuffer { | |||||
| 657 | 658 | ||||
| 658 | func (p *pngBuffers) Put(b *png.EncoderBuffer) { p.pool.Put(b) } | 659 | func (p *pngBuffers) Put(b *png.EncoderBuffer) { p.pool.Put(b) } | ||
| 659 | 660 | ||||
| 661 | + | // gated reports whether a screenshot must not be published: the session is | |||
| 662 | + | // protected, or its security state is no longer the one the frame was drawn in. | |||
| 663 | + | func gated(security ports.SessionSecurity, state ports.SecurityState) bool { | |||
| 664 | + | return security != nil && (state.Protected || security.Snapshot() != state) | |||
| 665 | + | } | |||
| 666 | + | ||||
| 660 | // writePNGSecureResult distinguishes a security skip from a published PNG. | 667 | // writePNGSecureResult distinguishes a security skip from a published PNG. | ||
| 661 | func writePNGSecureResult(path string, img image.Image, security ports.SessionSecurity, state ports.SecurityState) (bool, error) { | 668 | func writePNGSecureResult(path string, img image.Image, security ports.SessionSecurity, state ports.SecurityState) (bool, error) { | ||
| 662 | - | if security != nil && (state.Protected || security.Snapshot() != state) { | 669 | + | if gated(security, state) { |
| 663 | return false, nil | 670 | return false, nil | ||
| 664 | } | 671 | } | ||
| 665 | f, err := os.CreateTemp(filepath.Dir(path), ".frame-*.png") | 672 | f, err := os.CreateTemp(filepath.Dir(path), ".frame-*.png") | ||
| @@ -674,7 +681,7 @@ func writePNGSecureResult(path string, img image.Image, security ports.SessionSe | |||||
| 674 | if err := f.Close(); err != nil { | 681 | if err := f.Close(); err != nil { | ||
| 675 | return false, err | 682 | return false, err | ||
| 676 | } | 683 | } | ||
| 677 | - | if security != nil && security.Snapshot() != state { | 684 | + | if gated(security, state) { |
| 678 | return false, nil | 685 | return false, nil | ||
| 679 | } | 686 | } | ||
| 680 | if err := os.Rename(f.Name(), path); err != nil { | 687 | if err := os.Rename(f.Name(), path); err != nil { | ||
| @@ -690,7 +697,7 @@ func writePNGSecureResult(path string, img image.Image, security ports.SessionSe | |||||
| 690 | // It gates on security like writePNGSecureResult, and falls back to encoding | 697 | // It gates on security like writePNGSecureResult, and falls back to encoding | ||
| 691 | // img where the file system cannot link. | 698 | // img where the file system cannot link. | ||
| 692 | func linkPNGSecure(src, dst string, img image.Image, security ports.SessionSecurity, state ports.SecurityState) (bool, error) { | 699 | func linkPNGSecure(src, dst string, img image.Image, security ports.SessionSecurity, state ports.SecurityState) (bool, error) { | ||
| 693 | - | if security != nil && (state.Protected || security.Snapshot() != state) { | 700 | + | if gated(security, state) { |
| 694 | return false, nil | 701 | return false, nil | ||
| 695 | } | 702 | } | ||
| 696 | tmp := dst + ".tmp" | 703 | tmp := dst + ".tmp" | ||
| @@ -699,7 +706,7 @@ func linkPNGSecure(src, dst string, img image.Image, security ports.SessionSecur | |||||
| 699 | return writePNGSecureResult(dst, img, security, state) | 706 | return writePNGSecureResult(dst, img, security, state) | ||
| 700 | } | 707 | } | ||
| 701 | defer os.Remove(tmp) | 708 | defer os.Remove(tmp) | ||
| 702 | - | if security != nil && security.Snapshot() != state { | 709 | + | if gated(security, state) { |
| 703 | return false, nil | 710 | return false, nil | ||
| 704 | } | 711 | } | ||
| 705 | if err := os.Rename(tmp, dst); err != nil { | 712 | if err := os.Rename(tmp, dst); err != nil { | ||