remote_preview.go RawHistoryFolder
1package ports
2
3import (
4 "errors"
5 "math"
6 "unicode"
7 "unicode/utf8"
8
9 renderer "github.com/bnema/vev-vt"
10 "github.com/bnema/vev/internal/domain"
11)
12
13const (
14 RemotePreviewMaxWidth = 256
15 RemotePreviewMaxHeight = 128
16 RemotePreviewMaxCells = RemotePreviewMaxWidth * RemotePreviewMaxHeight
17 RemotePreviewMaxBytes = 1 << 20
18)
19
20var (
21 ErrInvalidRemotePreviewRequest = errors.New("ports: invalid remote preview request")
22 ErrInvalidRemotePreview = errors.New("ports: invalid remote preview")
23 ErrRemotePreviewTooLarge = errors.New("ports: remote preview exceeds size limit")
24 ErrRemotePreviewTimeout = errors.New("ports: remote preview command timed out")
25 ErrRemotePreviewUnsupportedStyle = errors.New("ports: remote preview has unsupported style")
26)
27
28func ValidateRemotePreviewRequest(request RemotePreviewRequest) error {
29 if request.Version != RemotePreviewSchemaVersion || request.Target.Stopped || request.Target.Validate() != nil {
30 return ErrInvalidRemotePreviewRequest
31 }
32 // These fields are encoded with uint16 byte lengths. Reject an oversized
33 // nested route before putString truncates it on the wire.
34 if len(request.Target.Endpoint) > math.MaxUint16 ||
35 len(request.Target.DisplayOrigin) > math.MaxUint16 ||
36 len(request.Target.SessionName) > math.MaxUint16 ||
37 len(request.Target.LiveTabID) > math.MaxUint16 {
38 return ErrInvalidRemotePreviewRequest
39 }
40 if request.Width == 0 || request.Height == 0 || int(request.Width) > RemotePreviewMaxWidth || int(request.Height) > RemotePreviewMaxHeight {
41 return ErrInvalidRemotePreviewRequest
42 }
43 return nil
44}
45
46func ValidateRemotePreview(preview RemotePreview) error {
47 if preview.Version != RemotePreviewSchemaVersion || preview.Status > RemotePreviewTooLarge {
48 return ErrInvalidRemotePreview
49 }
50 if preview.Status != RemotePreviewOK {
51 if preview.Width != 0 || preview.Height != 0 || len(preview.Cells) != 0 {
52 return ErrInvalidRemotePreview
53 }
54 return nil
55 }
56 if preview.LifecycleID == (domain.SessionLifecycleID{}) || domain.ValidateTabStableID(preview.TabID) != nil || preview.Revision == 0 {
57 return ErrInvalidRemotePreview
58 }
59 if preview.Width == 0 || preview.Height == 0 || int(preview.Width) > RemotePreviewMaxWidth || int(preview.Height) > RemotePreviewMaxHeight {
60 return ErrInvalidRemotePreview
61 }
62 want := int(preview.Width) * int(preview.Height)
63 if want > RemotePreviewMaxCells || len(preview.Cells) != want {
64 return ErrRemotePreviewTooLarge
65 }
66 width := int(preview.Width)
67 for i, cell := range preview.Cells {
68 if !utf8.ValidRune(cell.Rune) || (cell.Rune != 0 && unicode.IsControl(cell.Rune)) || !validRemotePreviewStyle(cell.Style) {
69 return ErrInvalidRemotePreview
70 }
71 rowStart := (i / width) * width
72 rowEnd := rowStart + width
73 if cell.Continuation {
74 if cell.Rune != 0 || i == rowStart || preview.Cells[i-1].Continuation || renderer.RuneWidth(preview.Cells[i-1].Rune) != 2 {
75 return ErrInvalidRemotePreview
76 }
77 } else if renderer.RuneWidth(cell.Rune) == 2 && (i+1 >= rowEnd || !preview.Cells[i+1].Continuation) {
78 return ErrInvalidRemotePreview
79 }
80 }
81 return nil
82}
83
84func validRemotePreviewStyle(style renderer.Style) bool {
85 return style.Attrs&^(renderer.AttrDim|renderer.AttrUnderline|renderer.AttrBlink|renderer.AttrStrikethrough) == 0 &&
86 style.UnderlineStyle <= renderer.UnderlineDashed &&
87 style.Foreground >= math.MinInt16 && style.Foreground <= math.MaxInt16 &&
88 style.Background >= math.MinInt16 && style.Background <= math.MaxInt16 &&
89 style.UnderlineColor >= math.MinInt16 && style.UnderlineColor <= math.MaxInt16
90}
91
92func (p RemotePreview) FrameRows() [][]renderer.Cell {
93 if p.Width == 0 || p.Height == 0 || len(p.Cells) != int(p.Width)*int(p.Height) {
94 return nil
95 }
96 rows := make([][]renderer.Cell, p.Height)
97 width := int(p.Width)
98 for y := range rows {
99 rows[y] = append([]renderer.Cell(nil), p.Cells[y*width:(y+1)*width]...)
100 }
101 return rows
102}