fix(keys): preserve invalid split alt utf8 input
- commit
- de4decf704b65137395f76223b798160d3e2eb42
- author
- brice · · 2026-07-03 21:28 UTC
- parent
- 3a2ff56
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +20 -3
M internal/usecase/keys/router.go +8 -3
| @@ -71,7 +71,7 @@ func (r *Router) Route(data []byte) { | |||
| 71 | 71 | r.pending = false | |
| 72 | 72 | r.pendingAlt = nil | |
| 73 | 73 | combined := append(append([]byte(nil), pendingAlt...), data...) | |
| 74 | - | if consumed := r.routeAfterPendingESC(combined); consumed > len(pendingAlt) { | |
| 74 | + | if consumed := r.routeAfterPendingESC(combined, len(pendingAlt)); consumed > len(pendingAlt) { | |
| 75 | 75 | data = data[consumed-len(pendingAlt):] | |
| 76 | 76 | } | |
| 77 | 77 | } | |
| @@ -122,7 +122,7 @@ func (r *Router) route(data []byte) { | |||
| 122 | 122 | flush() | |
| 123 | 123 | } | |
| 124 | 124 | ||
| 125 | - | func (r *Router) routeAfterPendingESC(data []byte) int { | |
| 125 | + | func (r *Router) routeAfterPendingESC(data []byte, pendingAltLen int) int { | |
| 126 | 126 | next := data[0] | |
| 127 | 127 | if passThroughPrefix(next) { | |
| 128 | 128 | r.forward([]byte{ESC, next}) | |
| @@ -136,10 +136,15 @@ func (r *Router) routeAfterPendingESC(data []byte) int { | |||
| 136 | 136 | r.retainESC(data) | |
| 137 | 137 | return len(data) | |
| 138 | 138 | } | |
| 139 | - | if _, size := utf8.DecodeRune(data); size > 1 { | |
| 139 | + | key, size := utf8.DecodeRune(data) | |
| 140 | + | if size > 1 { | |
| 140 | 141 | r.forward(append([]byte{ESC}, data[:size]...)) | |
| 141 | 142 | return size | |
| 142 | 143 | } | |
| 144 | + | if key == utf8.RuneError && pendingAltLen > 0 { | |
| 145 | + | r.forward(append([]byte{ESC}, data[:pendingAltLen]...)) | |
| 146 | + | return pendingAltLen | |
| 147 | + | } | |
| 143 | 148 | r.forward([]byte{ESC}) | |
| 144 | 149 | return 0 | |
| 145 | 150 | } | |
| @@ -71,7 +71,7 @@ func (r *Router) Route(data []byte) { | |||||
| 71 | r.pending = false | 71 | r.pending = false | ||
| 72 | r.pendingAlt = nil | 72 | r.pendingAlt = nil | ||
| 73 | combined := append(append([]byte(nil), pendingAlt...), data...) | 73 | combined := append(append([]byte(nil), pendingAlt...), data...) | ||
| 74 | - | if consumed := r.routeAfterPendingESC(combined); consumed > len(pendingAlt) { | 74 | + | if consumed := r.routeAfterPendingESC(combined, len(pendingAlt)); consumed > len(pendingAlt) { |
| 75 | data = data[consumed-len(pendingAlt):] | 75 | data = data[consumed-len(pendingAlt):] | ||
| 76 | } | 76 | } | ||
| 77 | } | 77 | } | ||
| @@ -122,7 +122,7 @@ func (r *Router) route(data []byte) { | |||||
| 122 | flush() | 122 | flush() | ||
| 123 | } | 123 | } | ||
| 124 | 124 | ||||
| 125 | - | func (r *Router) routeAfterPendingESC(data []byte) int { | 125 | + | func (r *Router) routeAfterPendingESC(data []byte, pendingAltLen int) int { |
| 126 | next := data[0] | 126 | next := data[0] | ||
| 127 | if passThroughPrefix(next) { | 127 | if passThroughPrefix(next) { | ||
| 128 | r.forward([]byte{ESC, next}) | 128 | r.forward([]byte{ESC, next}) | ||
| @@ -136,10 +136,15 @@ func (r *Router) routeAfterPendingESC(data []byte) int { | |||||
| 136 | r.retainESC(data) | 136 | r.retainESC(data) | ||
| 137 | return len(data) | 137 | return len(data) | ||
| 138 | } | 138 | } | ||
| 139 | - | if _, size := utf8.DecodeRune(data); size > 1 { | 139 | + | key, size := utf8.DecodeRune(data) |
| 140 | + | if size > 1 { | |||
| 140 | r.forward(append([]byte{ESC}, data[:size]...)) | 141 | r.forward(append([]byte{ESC}, data[:size]...)) | ||
| 141 | return size | 142 | return size | ||
| 142 | } | 143 | } | ||
| 144 | + | if key == utf8.RuneError && pendingAltLen > 0 { | |||
| 145 | + | r.forward(append([]byte{ESC}, data[:pendingAltLen]...)) | |||
| 146 | + | return pendingAltLen | |||
| 147 | + | } | |||
| 143 | r.forward([]byte{ESC}) | 148 | r.forward([]byte{ESC}) | ||
| 144 | return 0 | 149 | return 0 | ||
| 145 | } | 150 | } | ||
M internal/usecase/keys/router_test.go +12 -0
| @@ -119,6 +119,18 @@ func TestRouterForwardsUnboundAltUTF8SplitAcrossReads(t *testing.T) { | |||
| 119 | 119 | require.Equal(t, [][]byte{{ESC, 0xc3, 0xb1}}, h.forwards) | |
| 120 | 120 | } | |
| 121 | 121 | ||
| 122 | + | func TestRouterForwardsInvalidSplitAltUTF8WithoutDroppingBytes(t *testing.T) { | |
| 123 | + | clk := &fakeClock{} | |
| 124 | + | h := &captureHandler{} | |
| 125 | + | r := NewRouter(clk, h) | |
| 126 | + | ||
| 127 | + | r.Route([]byte{ESC, 0xc3}) | |
| 128 | + | r.Route([]byte("X")) | |
| 129 | + | ||
| 130 | + | require.Empty(t, h.actions) | |
| 131 | + | require.Equal(t, [][]byte{{ESC, 0xc3}, []byte("X")}, h.forwards) | |
| 132 | + | } | |
| 133 | + | ||
| 122 | 134 | func TestTopRowDigitIndexAcceptsQWERTYAndAZERTYVariants(t *testing.T) { | |
| 123 | 135 | cases := []struct { | |
| 124 | 136 | key rune | |
| @@ -119,6 +119,18 @@ func TestRouterForwardsUnboundAltUTF8SplitAcrossReads(t *testing.T) { | |||||
| 119 | require.Equal(t, [][]byte{{ESC, 0xc3, 0xb1}}, h.forwards) | 119 | require.Equal(t, [][]byte{{ESC, 0xc3, 0xb1}}, h.forwards) | ||
| 120 | } | 120 | } | ||
| 121 | 121 | ||||
| 122 | + | func TestRouterForwardsInvalidSplitAltUTF8WithoutDroppingBytes(t *testing.T) { | |||
| 123 | + | clk := &fakeClock{} | |||
| 124 | + | h := &captureHandler{} | |||
| 125 | + | r := NewRouter(clk, h) | |||
| 126 | + | ||||
| 127 | + | r.Route([]byte{ESC, 0xc3}) | |||
| 128 | + | r.Route([]byte("X")) | |||
| 129 | + | ||||
| 130 | + | require.Empty(t, h.actions) | |||
| 131 | + | require.Equal(t, [][]byte{{ESC, 0xc3}, []byte("X")}, h.forwards) | |||
| 132 | + | } | |||
| 133 | + | ||||
| 122 | func TestTopRowDigitIndexAcceptsQWERTYAndAZERTYVariants(t *testing.T) { | 134 | func TestTopRowDigitIndexAcceptsQWERTYAndAZERTYVariants(t *testing.T) { | ||
| 123 | cases := []struct { | 135 | cases := []struct { | ||
| 124 | key rune | 136 | key rune | ||