diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e0cc879..5cd634d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,6 +29,8 @@ jobs: go-version: ${{ matrix.go }} - name: Checkout code uses: actions/checkout@v6 + with: + persist-credentials: false - name: Test run: go test -v ./... lint: @@ -36,11 +38,15 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v6 + with: + persist-credentials: false - name: go mod tidy run: | go mod tidy git diff --exit-code - name: Lint run: | - docker run --rm -v ./:/go/src/github.com/moby/term -w /go/src/github.com/moby/term \ - golangci/golangci-lint:v2.8-alpine golangci-lint run -v + docker run --rm \ + -v ./:/go/src/github.com/moby/term \ + -w /go/src/github.com/moby/term \ + golangci/golangci-lint:v2.11-alpine golangci-lint run -v diff --git a/term.go b/term.go index f9d8988..d083c98 100644 --- a/term.go +++ b/term.go @@ -10,9 +10,8 @@ type Winsize struct { Height uint16 Width uint16 - // Only used on Unix - x uint16 - y uint16 + x uint16 //nolint:unused // only used on Unix + y uint16 //nolint:unused // only used on Unix } // StdStreams returns the standard streams (stdin, stdout, stderr). diff --git a/term_unix.go b/term_unix.go index f184bb8..093d1f3 100644 --- a/term_unix.go +++ b/term_unix.go @@ -6,6 +6,7 @@ package term import ( "errors" "io" + "math" "os" "golang.org/x/sys/unix" @@ -31,12 +32,18 @@ func getFdInfo(in interface{}) (uintptr, bool) { } func getWinsize(fd uintptr) (*Winsize, error) { + if fd > math.MaxInt { + return nil, errors.New("invalid file descriptor") + } uws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ) ws := &Winsize{Height: uws.Row, Width: uws.Col, x: uws.Xpixel, y: uws.Ypixel} return ws, err } func setWinsize(fd uintptr, ws *Winsize) error { + if fd > math.MaxInt { + return errors.New("invalid file descriptor") + } return unix.IoctlSetWinsize(int(fd), unix.TIOCSWINSZ, &unix.Winsize{ Row: ws.Height, Col: ws.Width, @@ -81,6 +88,9 @@ func setRawTerminalOutput(uintptr) (*State, error) { } func tcget(fd uintptr) (*unix.Termios, error) { + if fd > math.MaxInt { + return nil, errors.New("invalid file descriptor") + } p, err := unix.IoctlGetTermios(int(fd), getTermios) if err != nil { return nil, err @@ -89,5 +99,8 @@ func tcget(fd uintptr) (*unix.Termios, error) { } func tcset(fd uintptr, p *unix.Termios) error { + if fd > math.MaxInt { + return errors.New("invalid file descriptor") + } return unix.IoctlSetTermios(int(fd), setTermios, p) } diff --git a/term_windows.go b/term_windows.go index ab5bd85..4506319 100644 --- a/term_windows.go +++ b/term_windows.go @@ -3,6 +3,7 @@ package term import ( "errors" "io" + "math" "os" "os/signal" @@ -94,13 +95,19 @@ func getWinsize(fd uintptr) (*Winsize, error) { return nil, err } + w := int32(info.Window.Right) - int32(info.Window.Left) + 1 + h := int32(info.Window.Bottom) - int32(info.Window.Top) + 1 + if w < 0 || w > math.MaxUint16 || h < 0 || h > math.MaxUint16 { + return nil, errors.New("invalid console window size") + } + return &Winsize{ - Width: uint16(info.Window.Right - info.Window.Left + 1), - Height: uint16(info.Window.Bottom - info.Window.Top + 1), + Width: uint16(w), + Height: uint16(h), }, nil } -func setWinsize(fd uintptr, ws *Winsize) error { +func setWinsize(uintptr, *Winsize) error { return errors.New("not implemented on Windows") } @@ -167,7 +174,7 @@ func restoreAtInterrupt(fd uintptr, state *State) { signal.Notify(sigchan, os.Interrupt) go func() { - _ = <-sigchan + <-sigchan _ = RestoreTerminal(fd, state) os.Exit(0) }() diff --git a/windows/ansi_reader.go b/windows/ansi_reader.go index fb34c54..c236069 100644 --- a/windows/ansi_reader.go +++ b/windows/ansi_reader.go @@ -22,11 +22,10 @@ const ( // ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation. type ansiReader struct { - file *os.File - fd uintptr - buffer []byte - cbBuffer int - command []byte + file *os.File + fd uintptr + buffer []byte + command []byte } // NewAnsiReader returns an io.ReadCloser that provides VT100 terminal emulation on top of a @@ -99,11 +98,8 @@ func (ar *ansiReader) Read(p []byte) (int, error) { // readInputEvents polls until at least one event is available. func readInputEvents(ar *ansiReader, maxBytes int) ([]winterm.INPUT_RECORD, error) { - // Determine the maximum number of records to retrieve - // -- Cast around the type system to obtain the size of a single INPUT_RECORD. - // unsafe.Sizeof requires an expression vs. a type-reference; the casting - // tricks the type system into believing it has such an expression. - recordSize := int(unsafe.Sizeof(*((*winterm.INPUT_RECORD)(unsafe.Pointer(&maxBytes))))) + // Determine the size of a single INPUT_RECORD. + recordSize := int(unsafe.Sizeof(winterm.INPUT_RECORD{})) countRecords := maxBytes / recordSize if countRecords > ansiterm.MAX_INPUT_EVENTS { countRecords = ansiterm.MAX_INPUT_EVENTS @@ -167,9 +163,10 @@ var keyMapPrefix = map[uint16]string{ // translateKeyEvents converts the input events into the appropriate ANSI string. func translateKeyEvents(events []winterm.INPUT_RECORD, escapeSequence []byte) []byte { var buffer bytes.Buffer - for _, event := range events { + for i := range events { + event := events[i] if event.EventType == winterm.KEY_EVENT && event.KeyEvent.KeyDown != 0 { - buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence)) + buffer.WriteString(keyToString(&events[i].KeyEvent, escapeSequence)) } } @@ -181,9 +178,10 @@ func keyToString(keyEvent *winterm.KEY_EVENT_RECORD, escapeSequence []byte) stri if keyEvent.UnicodeChar == 0 { return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence) } - + key := string(rune(keyEvent.UnicodeChar)) _, alt, control := getControlKeys(keyEvent.ControlKeyState) - if control { + switch { + case control && !alt: // TODO(azlinux): Implement following control sequences // -D Signals the end of input from the keyboard; also exits current shell. // -H Deletes the first character to the left of the cursor. Also called the ERASE key. @@ -191,14 +189,13 @@ func keyToString(keyEvent *winterm.KEY_EVENT_RECORD, escapeSequence []byte) stri // -S Suspends printing on the screen (does not stop the program). // -U Deletes all characters on the current line. Also called the KILL key. // -E Quits current command and creates a core + return key + case !control && alt: + // +Key generates ESC N Key + return ansiterm.KEY_ESC_N + strings.ToLower(key) + default: + return key } - - // +Key generates ESC N Key - if !control && alt { - return ansiterm.KEY_ESC_N + strings.ToLower(string(rune(keyEvent.UnicodeChar))) - } - - return string(rune(keyEvent.UnicodeChar)) } // formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string. @@ -219,9 +216,9 @@ func formatVirtualKey(key uint16, controlState uint32, escapeSequence []byte) st // getControlKeys extracts the shift, alt, and ctrl key states. func getControlKeys(controlState uint32) (shift, alt, control bool) { - shift = 0 != (controlState & winterm.SHIFT_PRESSED) - alt = 0 != (controlState & (winterm.LEFT_ALT_PRESSED | winterm.RIGHT_ALT_PRESSED)) - control = 0 != (controlState & (winterm.LEFT_CTRL_PRESSED | winterm.RIGHT_CTRL_PRESSED)) + shift = controlState&winterm.SHIFT_PRESSED != 0 + alt = controlState&(winterm.LEFT_ALT_PRESSED|winterm.RIGHT_ALT_PRESSED) != 0 + control = controlState&(winterm.LEFT_CTRL_PRESSED|winterm.RIGHT_CTRL_PRESSED) != 0 return shift, alt, control } diff --git a/windows/ansi_writer.go b/windows/ansi_writer.go index 4243307..6ccc38f 100644 --- a/windows/ansi_writer.go +++ b/windows/ansi_writer.go @@ -18,7 +18,6 @@ type ansiWriter struct { infoReset *winterm.CONSOLE_SCREEN_BUFFER_INFO command []byte escapeSequence []byte - inAnsiSequence bool parser *ansiterm.AnsiParser }