Compare commits

..

No commits in common. 'master' and 'v2.2.1' have entirely different histories.

@ -0,0 +1,22 @@
name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.15
- name: Build
run: go build -x -v -mod=vendor -ldflags "-X main.version=0.0.1" && test $(./tty-share --version) = 0.0.1

@ -1,61 +0,0 @@
---
name: check the code builds
on:
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
# build targets
include:
- goos: linux
goarch: amd64
- goos: linux
goarch: '386'
- goos: darwin
goarch: amd64
steps:
- name: Checkout source code
uses: actions/checkout@v2.3.4
with:
lfs: true
fetch-depth: 0
- name: Set up Golang
uses: actions/setup-go@v2
with:
go-version: 1.18
- uses: actions/setup-node@v3
with:
node-version: 16.13.0
- name: Build stage
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: |
# TODO: figure out how to get go-bindata here
# make -C server
v=$(echo ${GITHUB_REF} | awk -F/ '{print substr($3,2,10);}')
go build -x -v -mod=vendor -ldflags "-X main.version=${v} -w -s" -o "tty-share_${GOOS}-${GOARCH}"
- name: Upload to artifact storage
uses: actions/upload-artifact@v2
with:
path: tty-share_${{ matrix.goos }}-${{ matrix.goarch }}
if-no-files-found: error
# only meant for sharing with the publish job
retention-days: 1
publish:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v2
with:
path: tty-share_*

@ -29,7 +29,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.18
go-version: 1.15
- name: Build for Linux-amd64
env:

@ -1,3 +1,5 @@
[![Build Status](https://travis-ci.com/elisescu/tty-share.svg?branch=master)](https://travis-ci.com/elisescu/tty-share)
# tty-share
[tty-share](https://tty-share.com) is a very simple tool used to share your Linux/OSX terminal over the Internet. It is written in GO, results in a static cross-platform binary with no dependencies, and therefore will also work on your Raspberry Pi.
@ -30,9 +32,7 @@ If you are on OSX and use [brew](https://brew.sh/), then you can simply do a `br
**Binary releases**
Otherwise, download the latest `tty-share` binary [release](https://github.com/elisescu/tty-share/releases).
See package name for your system package manager at [Repology](https://repology.org/project/tty-share/information).
Otherwise, download the latest `tty-share` binary [release](https://github.com/elisescu/tty-share/releases), and run it:
**Running it**
```
@ -53,16 +53,6 @@ You can join a session by opening the session URLs in the browser, or with anoth
~ $ tty-share https://on.tty-share.com/s/L8d2ECvHLhU8CXEBaEF5WKV8O3jsZkS5sXwG1__--2_jnFSlGonzXBe0qxd7tZeRvQM/
```
**Join a session with TCP port forwarding**
You can use the `-L` option to create a TCP tunnel, similarly to how you would do it with `ssh`:
```
tty-share -L 1234:example.com:4567 https://on.tty-share.com/s/L8d2ECvHLhU8CXEBaEF5WKV8O3jsZkS5sXwG1__--2_jnFSlGonzXBe0qxd7tZeRvQM/
```
This will make `tty-share` listen locally on port `1234` and forward all connections to `example.com:4567` from the remote side.
The server needs to allow this, by using the `-A` flag.
## Building
Simply run

@ -1,53 +1,44 @@
package main
import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"github.com/elisescu/tty-share/server"
"github.com/gorilla/websocket"
"github.com/hashicorp/yamux"
"github.com/moby/term"
log "github.com/sirupsen/logrus"
)
type ttyShareClient struct {
url string
ttyWsConn *websocket.Conn
tunnelWsConn *websocket.Conn
tunnelAddresses *string
detachKeys string
wcChan chan os.Signal
ioFlagAtomic uint32 // used with atomic
winSizes struct {
url string
wsConn *websocket.Conn
detachKeys string
wcChan chan os.Signal
ioFlagAtomic uint32 // used with atomic
winSizes struct {
thisW uint16
thisH uint16
remoteW uint16
remoteH uint16
}
winSizesMutex sync.Mutex
tunnelMuxSession *yamux.Session
winSizesMutex sync.Mutex
}
func newTtyShareClient(url string, detachKeys string, tunnelConfig *string) *ttyShareClient {
func newTtyShareClient(url string, detachKeys string) *ttyShareClient {
return &ttyShareClient{
url: url,
ttyWsConn: nil,
detachKeys: detachKeys,
wcChan: make(chan os.Signal, 1),
ioFlagAtomic: 1,
tunnelAddresses: tunnelConfig,
url: url,
wsConn: nil,
detachKeys: detachKeys,
wcChan: make(chan os.Signal, 1),
ioFlagAtomic: 1,
}
}
@ -112,10 +103,7 @@ func (c *ttyShareClient) Run() (err error) {
}
// Get the path of the websockts route from the header
ttyWsPath := resp.Header.Get("TTYSHARE-TTY-WSPATH")
ttyWSProtocol := resp.Header.Get("TTYSHARE-VERSION")
ttyTunnelPath := resp.Header.Get("TTYSHARE-TUNNEL-WSPATH")
wsPath := resp.Header.Get("TTYSHARE-WSPATH")
// Build the WS URL from the host part of the given http URL and the wsPath
httpURL, err := url.Parse(c.url)
@ -126,98 +114,14 @@ func (c *ttyShareClient) Run() (err error) {
if httpURL.Scheme == "https" {
wsScheme = "wss"
}
ttyWsURL := wsScheme + "://" + httpURL.Host + ttyWsPath
ttyTunnelURL := wsScheme + "://" + httpURL.Host + ttyTunnelPath
wsURL := wsScheme + "://" + httpURL.Host + wsPath
log.Debugf("Built the WS URL from the headers: %s", ttyWsURL)
log.Debugf("Built the WS URL from the headers: %s", wsURL)
c.ttyWsConn, _, err = websocket.DefaultDialer.Dial(ttyWsURL, nil)
c.wsConn, _, err = websocket.DefaultDialer.Dial(wsURL, nil)
if err != nil {
return
}
defer c.ttyWsConn.Close()
tunnelFunc := func() {
if *c.tunnelAddresses == "" {
// Don't build a tunnel
return
}
if ver, err := strconv.Atoi(ttyWSProtocol); err != nil || ver < 2 {
log.Fatalf("Cannot create a tunnel. Server too old (protocol %d, required min. 2)", ver)
}
c.tunnelWsConn, _, err = websocket.DefaultDialer.Dial(ttyTunnelURL, nil)
if err != nil {
log.Errorf("Cannot create a tunnel connection with the server. Server needs to allow that")
return
}
defer c.tunnelWsConn.Close()
a := strings.Split(*c.tunnelAddresses, ":")
tunnelRemoteAddress := fmt.Sprintf("%s:%s", a[1], a[2])
tunnelLocalAddress := fmt.Sprintf(":%s", a[0])
initMsg := server.TunInitMsg{
Address: tunnelRemoteAddress,
}
data, err := json.Marshal(initMsg)
if err != nil {
log.Errorf("Could not marshal the tunnel init message: %s", err.Error())
return
}
err = c.tunnelWsConn.WriteMessage(websocket.TextMessage, data)
if err != nil {
log.Errorf("Could not initiate the tunnel: %s", err.Error())
return
}
wsWRC := server.WSConnReadWriteCloser{
WsConn: c.tunnelWsConn,
}
localListener, err := net.Listen("tcp", tunnelLocalAddress)
if err != nil {
log.Errorf("Could not listen locally for the tunnel: %s", err.Error())
}
c.tunnelMuxSession, err = yamux.Server(&wsWRC, nil)
if err != nil {
log.Errorf("Could not create mux server: %s", err.Error())
}
for {
localTunconn, err := localListener.Accept()
if err != nil {
log.Warnf("Cannot accept local tunnel connections: ", err.Error())
return
}
muxClient, err := c.tunnelMuxSession.Open()
if err != nil {
log.Warnf("Cannot create a muxer to the remote, over ws: ", err.Error())
return
}
go func() {
io.Copy(muxClient, localTunconn)
defer localTunconn.Close()
defer muxClient.Close()
}()
go func() {
io.Copy(localTunconn, muxClient)
defer localTunconn.Close()
defer muxClient.Close()
}()
}
}
detachBytes, err := term.ToBytes(c.detachKeys)
if err != nil {
@ -229,7 +133,7 @@ func (c *ttyShareClient) Run() (err error) {
defer term.RestoreTerminal(os.Stdin.Fd(), state)
clearScreen()
protoWS := server.NewTTYProtocolWSLocked(c.ttyWsConn)
protoWS := server.NewTTYProtocolWSLocked(c.wsConn)
monitorWinChanges := func() {
// start monitoring the size of the terminal
@ -293,7 +197,6 @@ func (c *ttyShareClient) Run() (err error) {
go monitorWinChanges()
go writeLoop()
go tunnelFunc()
readLoop()
clearScreen()
@ -301,11 +204,6 @@ func (c *ttyShareClient) Run() (err error) {
}
func (c *ttyShareClient) Stop() {
// if we had a tunnel, close it
if c.tunnelMuxSession != nil {
c.tunnelMuxSession.Close()
c.tunnelWsConn.Close()
}
c.ttyWsConn.Close()
c.wsConn.Close()
signal.Stop(c.wcChan)
}

@ -1,19 +1,18 @@
module github.com/elisescu/tty-share
go 1.18
go 1.13
require (
github.com/creack/pty v1.1.11
github.com/elisescu/pty v1.0.2
github.com/go-bindata/go-bindata v3.1.2+incompatible // indirect
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0
github.com/hashicorp/yamux v0.1.1
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c
github.com/sirupsen/logrus v1.9.0
golang.org/x/crypto v0.3.0
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/term v0.2.0 // indirect
github.com/gorilla/websocket v1.4.2
github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce
github.com/jroimartin/gocui v0.4.0 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 // indirect
github.com/sirupsen/logrus v1.4.2
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876
)

@ -1,36 +1,56 @@
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/elisescu/pty v1.0.2 h1:I/wkN5bnPyh00j/bnNAy8wll8yvd8wRLUjPnmcmYI+Q=
github.com/elisescu/pty v1.0.2/go.mod h1:tzLUboZf84k7sFZdd2cOvhr/fSxMABV0UTMxnF25R/Y=
github.com/go-bindata/go-bindata v1.0.0 h1:DZ34txDXWn1DyWa+vQf7V9ANc2ILTtrEjtlsdJRF26M=
github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE=
github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c h1:RC8WMpjonrBfyAh6VN/POIPtYD5tRAq0qMqCRjQNK+g=
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c/go.mod h1:9OcmHNQQUTbk4XCffrLgN1NEKc2mh5u++biHVrvHsSU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce h1:7UnVY3T/ZnHUrfviiAgIUjg2PXxsQfs5bphsG8F7Keo=
github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8=
github.com/jroimartin/gocui v0.4.0/go.mod h1:7i7bbj99OgFHzo7kB2zPb8pXLqMBSQegY7azfqXMkyY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2 h1:SPoLlS9qUUnXcIY4pvA4CTwYjk0Is5f4UPEkeESr53k=
github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2/go.mod h1:TjQg8pa4iejrUrjiz0MCtMV38jdMNW4doKSiBrEvCQQ=
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 h1:lh3PyZvY+B9nFliSGTn5uFuqQQJGuNrD0MLCokv09ag=
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7 h1:/W9OPMnnpmFXHYkcp2rQsbFUbRlRzfECQjmAFiOyHE8=
golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a h1:i47hUS795cOydZI4AwJQCKXOr4BvxzvikwDoDtHhP2Y=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=

@ -16,14 +16,12 @@ import (
var version string = "0.0.0"
func createServer(frontListenAddress string, frontendPath string, pty server.PTYHandler, sessionID string, allowTunneling bool, crossOrigin bool) *server.TTYServer {
func createServer(frontListenAddress string, frontendPath string, pty server.PTYHandler, sessionID string) *server.TTYServer {
config := ttyServer.TTYServerConfig{
FrontListenAddress: frontListenAddress,
FrontendPath: frontendPath,
PTY: pty,
SessionID: sessionID,
AllowTunneling: allowTunneling,
CrossOrigin: crossOrigin,
}
server := ttyServer.NewTTYServer(config)
@ -49,7 +47,7 @@ Usage:
[--logfile <file name>] [--listen <[ip]:port>]
[--frontend-path <path>] [--tty-proxy <host:port>]
[--readonly] [--public] [no-tls] [--verbose] [--version]
tty-share [--verbose] [--logfile <file name>] [-L <local_port>:<remote_host>:<remote_port>]
tty-share [--verbose] [--logfile <file name>]
[--detach-keys] <session URL> # connect to an existing session, as a client
Examples:
@ -62,31 +60,21 @@ Examples:
tty-share http://localhost:8000/s/local/
Flags:
[c] - flags that are used only by the client
[s] - flags that are used only by the server
`
commandName := flag.String("command", os.Getenv("SHELL"), "[s] The command to run")
commandName := flag.String("command", os.Getenv("SHELL"), "The command to run")
if *commandName == "" {
*commandName = "bash"
}
commandArgs := flag.String("args", "", "[s] The command arguments")
commandArgs := flag.String("args", "", "The command arguments")
logFileName := flag.String("logfile", "-", "The name of the file to log")
listenAddress := flag.String("listen", "localhost:8000", "[s] tty-server address")
listenAddress := flag.String("listen", "localhost:8000", "tty-server address")
versionFlag := flag.Bool("version", false, "Print the tty-share version")
frontendPath := flag.String("frontend-path", "", "[s] The path to the frontend resources. By default, these resources are included in the server binary, so you only need this path if you don't want to use the bundled ones.")
proxyServerAddress := flag.String("tty-proxy", "on.tty-share.com:4567", "[s] Address of the proxy for public facing connections")
readOnly := flag.Bool("readonly", false, "[s] Start a read only session")
publicSession := flag.Bool("public", false, "[s] Create a public session")
noTLS := flag.Bool("no-tls", false, "[s] Don't use TLS to connect to the tty-proxy server. Useful for local debugging")
noWaitEnter := flag.Bool("no-wait", false, "[s] Don't wait for the Enter press before starting the session")
headless := flag.Bool("headless", false, "[s] Don't expect an interactive terminal at stdin")
headlessCols := flag.Int("headless-cols", 80, "[s] Number of cols for the allocated pty when running headless")
headlessRows := flag.Int("headless-rows", 25, "[s] Number of rows for the allocated pty when running headless")
detachKeys := flag.String("detach-keys", "ctrl-o,ctrl-c", "[c] Sequence of keys to press for closing the connection. Supported: https://godoc.org/github.com/moby/term#pkg-variables.")
allowTunneling := flag.Bool("A", false, "[s] Allow clients to create a TCP tunnel")
tunnelConfig := flag.String("L", "", "[c] TCP tunneling addresses: local_port:remote_host:remote_port. The client will listen on local_port for TCP connections, and will forward those to the from the server side to remote_host:remote_port")
crossOrgin := flag.Bool("cross-origin", false, "[s] Allow cross origin requests to the server")
frontendPath := flag.String("frontend-path", "", "The path to the frontend resources. By default, these resources are included in the server binary, so you only need this path if you don't want to use the bundled ones.")
proxyServerAddress := flag.String("tty-proxy", "on.tty-share.com:4567", "Address of the proxy for public facing connections")
readOnly := flag.Bool("readonly", false, "Start a read only session")
publicSession := flag.Bool("public", false, "Create a public session")
noTLS := flag.Bool("no-tls", false, "Don't use TLS to connect to the tty-proxy server. Useful for local debugging")
detachKeys := flag.String("detach-keys", "ctrl-o,ctrl-c", "Sequence of keys to press for closing the connection. Supported: https://godoc.org/github.com/moby/term#pkg-variables.")
verbose := flag.Bool("verbose", false, "Verbose logging")
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "%s", usageString)
@ -121,8 +109,7 @@ Flags:
args := flag.Args()
if len(args) == 1 {
connectURL := args[0]
client := newTtyShareClient(connectURL, *detachKeys, tunnelConfig)
client := newTtyShareClient(connectURL, *detachKeys)
err := client.Run()
if err != nil {
@ -133,7 +120,7 @@ Flags:
}
// tty-share works as a server, from here on
if !isStdinTerminal() && !*headless {
if !isStdinTerminal() {
fmt.Printf("Input not a tty\n")
os.Exit(1)
}
@ -165,7 +152,7 @@ Flags:
)
}
ptyMaster := ptyMasterNew(*headless, *headlessCols, *headlessRows)
ptyMaster := ptyMasterNew()
err := ptyMaster.Start(*commandName, strings.Fields(*commandArgs), envVars)
if err != nil {
log.Errorf("Cannot start the %s command: %s", *commandName, err.Error())
@ -179,13 +166,10 @@ Flags:
}
fmt.Printf("local session: http://%s/s/local/\n", *listenAddress)
fmt.Printf("Press Enter to continue!\n")
bufio.NewReader(os.Stdin).ReadString('\n')
if !*noWaitEnter && !*headless {
fmt.Printf("Press Enter to continue!\n")
bufio.NewReader(os.Stdin).ReadString('\n')
}
stopPtyAndRestore := func() {
stopPtyAndRestore := func () {
ptyMaster.Stop()
ptyMaster.Restore()
}
@ -197,7 +181,7 @@ Flags:
pty = &nilPTY{}
}
server := createServer(*listenAddress, *frontendPath, pty, sessionID, *allowTunneling, *crossOrgin)
server := createServer(*listenAddress, *frontendPath, pty, sessionID)
if cols, rows, e := ptyMaster.GetWinSize(); e == nil {
server.WindowSize(cols, rows)
}
@ -207,11 +191,8 @@ Flags:
server.WindowSize(cols, rows)
})
var mw io.Writer
mw = server
if !*headless {
mw = io.MultiWriter(os.Stdout, server)
}
mw := io.MultiWriter(os.Stdout, server)
go func() {
err := server.Run()
@ -228,14 +209,12 @@ Flags:
}
}()
if !*headless {
go func() {
_, err := io.Copy(ptyMaster, os.Stdin)
if err != nil {
stopPtyAndRestore()
}
}()
}
go func() {
_, err := io.Copy(ptyMaster, os.Stdin)
if err != nil {
stopPtyAndRestore()
}
}()
ptyMaster.Wait()
fmt.Printf("tty-share finished\n\n\r")

@ -7,7 +7,7 @@ import (
"syscall"
"time"
ptyDevice "github.com/creack/pty"
ptyDevice "github.com/elisescu/pty"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh/terminal"
)
@ -20,13 +20,10 @@ type ptyMaster struct {
ptyFile *os.File
command *exec.Cmd
terminalInitState *terminal.State
headless bool
headlessCols int
headlessRows int
}
func ptyMasterNew(headless bool, headlessCols, headlessRows int) *ptyMaster {
return &ptyMaster{headless: headless, headlessCols: headlessCols, headlessRows: headlessRows}
func ptyMasterNew() *ptyMaster {
return &ptyMaster{}
}
func isStdinTerminal() bool {
@ -43,21 +40,12 @@ func (pty *ptyMaster) Start(command string, args []string, envVars []string) (er
}
// Set the initial window size
cols, rows := pty.headlessCols, pty.headlessRows
if !pty.headless {
cols, rows, err = terminal.GetSize(0)
}
cols, rows, err := terminal.GetSize(0)
pty.SetWinSize(rows, cols)
return
}
func (pty *ptyMaster) MakeRaw() (err error) {
// don't do anything if running headless
if pty.headless {
return nil
}
// Save the initial state of the terminal, before making it RAW. Note that this terminal is the
// terminal under which the tty-share command has been started, and it's identified via the
@ -71,25 +59,20 @@ func (pty *ptyMaster) MakeRaw() (err error) {
}
func (pty *ptyMaster) SetWinChangeCB(winChangedCB onWindowChangedCB) {
// Start listening for window changes if not running headless
if !pty.headless {
go onWindowChanges(func(cols, rows int) {
// TODO:policy: should the server decide here if we care about the size and set it
// right here?
pty.SetWinSize(rows, cols)
// Notify the ptyMaster user of the window changes, to be sent to the remote side
winChangedCB(cols, rows)
})
}
// Start listening for window changes
go onWindowChanges(func(cols, rows int) {
// TODO:policy: should the server decide here if we care about the size and set it
// right here?
pty.SetWinSize(rows, cols)
// Notify the ptyMaster user of the window changes, to be sent to the remote side
winChangedCB(cols, rows)
})
}
func (pty *ptyMaster) GetWinSize() (int, int, error) {
if pty.headless {
return pty.headlessCols, pty.headlessRows, nil
} else {
return terminal.GetSize(0)
}
cols, rows, err := terminal.GetSize(0)
return cols, rows, err
}
func (pty *ptyMaster) Write(b []byte) (int, error) {
@ -101,11 +84,7 @@ func (pty *ptyMaster) Read(b []byte) (int, error) {
}
func (pty *ptyMaster) SetWinSize(rows, cols int) {
winSize := ptyDevice.Winsize{
Rows: uint16(rows),
Cols: uint16(cols),
}
ptyDevice.Setsize(pty.ptyFile, &winSize)
ptyDevice.Setsize(pty.ptyFile, rows, cols)
}
func (pty *ptyMaster) Refresh() {
@ -131,9 +110,7 @@ func (pty *ptyMaster) Wait() (err error) {
}
func (pty *ptyMaster) Restore() {
if !pty.headless {
terminal.Restore(0, pty.terminalInitState)
}
terminal.Restore(0, pty.terminalInitState)
return
}

@ -0,0 +1,25 @@
package main
import (
"io"
)
type combiner struct {
r io.Reader
w io.Writer
}
func newReadWriter(r io.Reader, w io.Writer) io.ReadWriter {
return &combiner{
r: r,
w: w,
}
}
func (c *combiner) Read(p []byte) (n int, err error) {
return c.r.Read(p)
}
func (c *combiner) Write(p []byte) (n int, err error) {
return c.w.Write(p)
}

@ -8,7 +8,7 @@ all: assets_bundle.go
rebuild: clean all
assets_bundle.go: $(TTY_SERVER_ASSETS)
go install github.com/go-bindata/go-bindata/...
go get github.com/go-bindata/go-bindata/...
go-bindata --prefix frontend/public/ -pkg server -o $@ frontend/public/*
frontend: cleanfront frontend/public/index.html assets_bundle.go

File diff suppressed because one or more lines are too long

@ -1 +1,2 @@
v16.13.0
12.4.0

File diff suppressed because it is too large Load Diff

@ -10,13 +10,13 @@
"author": "elisescu",
"license": "elisescu",
"dependencies": {
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.2",
"style-loader": "^3.3.1",
"ts-loader": "^9.4.1",
"typescript": "^4.9.3",
"webpack": "^5.76.0",
"webpack-cli": "^5.0.0",
"xterm": "^5.0.0"
"copy-webpack-plugin": "^6.0.0",
"css-loader": "^3.6.0",
"style-loader": "^0.23.1",
"ts-loader": "^6.2.2",
"typescript": "^3.9.7",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"xterm": "^4.9.0"
}
}

@ -2,6 +2,7 @@ import 'xterm/css/xterm.css';
import './main.css';
import { Terminal } from 'xterm';
import * as pbkdf2 from 'pbkdf2';
import { TTYReceiver } from './tty-receiver';
@ -10,6 +11,9 @@ const term = new Terminal({
macOptionIsMeta: true,
});
const derivedKey = pbkdf2.pbkdf2Sync('password', 'salt', 4096, 32, 'sha256');
console.log(derivedKey);
let wsAddress = "";
if (window.location.protocol === "https:") {
wsAddress = 'wss://';

@ -31,7 +31,7 @@ class TTYReceiver {
connection.onclose = (evt: CloseEvent) => {
this.xterminal.blur();
this.xterminal.options.cursorBlink = false
this.xterminal.setOption('cursorBlink', false);
this.xterminal.clear();
setTimeout(() => {
@ -43,16 +43,17 @@ class TTYReceiver {
const containerPixSize = this.getElementPixelsSize(container);
const newFontSize = this.guessNewFontSize(this.xterminal.cols, this.xterminal.rows, containerPixSize.width, containerPixSize.height);
this.xterminal.options.fontSize = newFontSize
this.xterminal.options.fontFamily= 'SauceCodePro MonoWindows, courier-new, monospace'
this.xterminal.setOption('fontSize', newFontSize);
connection.onmessage = (ev: MessageEvent) => {
console.log("Got message: ", ev.data);
let message = JSON.parse(ev.data)
let msgData = base64.decode(message.Data)
if (message.Type === "Write") {
let writeMsg = JSON.parse(msgData)
this.xterminal.write(base64.base64ToArrayBuffer(writeMsg.Data));
this.xterminal.writeUtf8(base64.base64ToArrayBuffer(writeMsg.Data));
}
if (message.Type == "WinSize") {
@ -60,7 +61,7 @@ class TTYReceiver {
const containerPixSize = this.getElementPixelsSize(container);
const newFontSize = this.guessNewFontSize(winSizeMsg.Cols, winSizeMsg.Rows, containerPixSize.width, containerPixSize.height);
this.xterminal.options.fontSize = newFontSize
this.xterminal.setOption('fontSize', newFontSize);
// Now set the new size.
this.xterminal.resize(winSizeMsg.Cols, winSizeMsg.Rows)
@ -98,7 +99,7 @@ class TTYReceiver {
private guessNewFontSize(newCols: number, newRows: number, targetWidth: number, targetHeight: number): number {
const cols = this.xterminal.cols;
const rows = this.xterminal.rows;
const fontSize = this.xterminal.options.fontSize
const fontSize = this.xterminal.getOption('fontSize');
const xtermPixelsSize = this.getElementPixelsSize(this.containerElement.querySelector(".xterm-screen"));
const newHFontSizeMultiplier = (cols / newCols) * (targetWidth / xtermPixelsSize.width);

@ -1,19 +1,15 @@
package server
import (
"encoding/json"
"fmt"
"html/template"
"io"
"mime"
"net"
"net/http"
"os"
"path/filepath"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/hashicorp/yamux"
log "github.com/sirupsen/logrus"
)
@ -40,16 +36,13 @@ type TTYServerConfig struct {
FrontendPath string
PTY PTYHandler
SessionID string
AllowTunneling bool
CrossOrigin bool
}
// TTYServer represents the instance of a tty server
type TTYServer struct {
httpServer *http.Server
config TTYServerConfig
session *ttyShareSession
muxTunnelSession *yamux.Session
httpServer *http.Server
config TTYServerConfig
session *ttyShareSession
}
func (server *TTYServer) serveContent(w http.ResponseWriter, r *http.Request, name string) {
@ -97,44 +90,30 @@ func NewTTYServer(config TTYServerConfig) (server *TTYServer) {
installHandlers := func(session string) {
// This function installs handlers for paths that contain the "session" passed as a
// parameter. The paths are for the static files, websockets, and other.
staticPath := "/s/" + session + "/static/"
ttyWsPath := "/s/" + session + "/ws"
tunnelWsPath := "/s/" + session + "/tws"
pathPrefix := "/s/" + session
routesHandler.PathPrefix(staticPath).Handler(http.StripPrefix(staticPath,
path := fmt.Sprintf("/s/%s/static/", session)
routesHandler.PathPrefix(path).Handler(http.StripPrefix(path,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
server.serveContent(w, r, r.URL.Path)
})))
routesHandler.HandleFunc(pathPrefix+"/", func(w http.ResponseWriter, r *http.Request) {
routesHandler.HandleFunc(fmt.Sprintf("/s/%s/", session), func(w http.ResponseWriter, r *http.Request) {
wsPath := "/s/" + session + "/ws"
pathPrefix := "/s/" + session
// Check the frontend/templates/tty-share.in.html file to see where the template applies
templateModel := struct {
PathPrefix string
WSPath string
}{pathPrefix, ttyWsPath}
}{pathPrefix, wsPath}
// TODO Extract these in constants
w.Header().Add("TTYSHARE-VERSION", "2")
// Deprecated HEADER (from prev version)
// TODO: Find a proper way to stop handling backward versions
w.Header().Add("TTYSHARE-WSPATH", ttyWsPath)
w.Header().Add("TTYSHARE-TTY-WSPATH", ttyWsPath)
w.Header().Add("TTYSHARE-TUNNEL-WSPATH", tunnelWsPath)
w.Header().Add("TTYSHARE-VERSION", "1")
w.Header().Add("TTYSHARE-WSPATH", wsPath)
server.handleWithTemplateHtml(w, r, "tty-share.in.html", templateModel)
})
routesHandler.HandleFunc(ttyWsPath, func(w http.ResponseWriter, r *http.Request) {
server.handleTTYWebsocket(w, r, config.CrossOrigin)
routesHandler.HandleFunc(fmt.Sprintf("/s/%s/ws", session), func(w http.ResponseWriter, r *http.Request) {
server.handleWebsocket(w, r)
})
if server.config.AllowTunneling {
// tunnel websockets connection
routesHandler.HandleFunc(tunnelWsPath, func(w http.ResponseWriter, r *http.Request) {
server.handleTunnelWebsocket(w, r)
})
}
routesHandler.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
templateModel := struct{ PathPrefix string }{fmt.Sprintf("/s/%s", session)}
server.handleWithTemplateHtml(w, r, "404.in.html", templateModel)
@ -152,25 +131,16 @@ func NewTTYServer(config TTYServerConfig) (server *TTYServer) {
return server
}
func (server *TTYServer) handleTTYWebsocket(w http.ResponseWriter, r *http.Request, crossOrigin bool) {
func (server *TTYServer) handleWebsocket(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
w.WriteHeader(http.StatusForbidden)
return
}
upgrader := websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
if crossOrigin {
upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
@ -183,84 +153,6 @@ func (server *TTYServer) handleTTYWebsocket(w http.ResponseWriter, r *http.Reque
server.session.HandleWSConnection(conn)
}
func (server *TTYServer) handleTunnelWebsocket(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
w.WriteHeader(http.StatusForbidden)
return
}
upgrader := websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
wsConn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Error("Cannot upgrade to WS for tunnel route connection: ", err.Error())
return
}
defer wsConn.Close()
// Read the first message on this ws route, and expect it to be a json containing the address
// to tunnel to. After that first message, will follow the raw connection data
_, wsReader, err := wsConn.NextReader()
if err != nil {
log.Error("Cannot read from the tunnel WS connection ", err.Error())
return
}
var tunInitMsg TunInitMsg
err = json.NewDecoder(wsReader).Decode(&tunInitMsg)
if err != nil {
log.Error("Cannot decode the tunnel init message ", err.Error())
return
}
wsRW := &WSConnReadWriteCloser{
WsConn: wsConn,
}
server.muxTunnelSession, err = yamux.Server(wsRW, nil)
if err != nil {
log.Errorf("Could not open a mux server: ", err.Error())
return
}
for {
muxStream, err := server.muxTunnelSession.Accept()
if err != nil {
if err != io.EOF {
log.Warnf("Mux cannot accept new connections: %s", err.Error())
}
return
}
localConn, err := net.Dial("tcp", tunInitMsg.Address)
if err != nil {
log.Error("Cannot create local connection ", err.Error())
return
}
go func() {
io.Copy(muxStream, localConn)
// Not sure yet which of the two io.Copy finishes first, so just close everything in both cases
defer localConn.Close()
defer muxStream.Close()
}()
go func() {
io.Copy(localConn, muxStream)
// Not sure yet which of the two io.Copy finishes first, so just close everything in both cases
defer muxStream.Close()
defer localConn.Close()
}()
}
}
func panicIfErr(err error) {
if err != nil {
panic(err.Error())
@ -301,8 +193,5 @@ func (server *TTYServer) WindowSize(cols, rows int) (err error) {
func (server *TTYServer) Stop() error {
log.Debug("Stopping the server")
if server.muxTunnelSession != nil {
server.muxTunnelSession.Close()
}
return server.httpServer.Close()
}

@ -33,13 +33,13 @@ type OnMsgWrite func(data []byte)
type OnMsgWinSize func(cols, rows int)
type TTYProtocolWSLocked struct {
ws *websocket.Conn
lock sync.Mutex
ws *websocket.Conn
lock sync.Mutex
}
func NewTTYProtocolWSLocked(ws *websocket.Conn) *TTYProtocolWSLocked {
return &TTYProtocolWSLocked{
ws: ws,
ws: ws,
}
}
@ -68,6 +68,7 @@ func marshalMsg(aMessage interface{}) (_ []byte, err error) {
return nil, nil
}
func (handler *TTYProtocolWSLocked) ReadAndHandle(onWrite OnMsgWrite, onWinSize OnMsgWinSize) (err error) {
var msg MsgWrapper

@ -1,64 +0,0 @@
package server
import (
"io"
"github.com/gorilla/websocket"
)
type TunInitMsg struct {
Address string
}
type WSConnReadWriteCloser struct {
WsConn *websocket.Conn
reader io.Reader
}
func (conn *WSConnReadWriteCloser) Read(p []byte) (n int, err error) {
// Weird method here, as we need to do a few things:
// - re-use the WS reader between different calls of this function. If the existing reader
// has no more data, then get another reader (NextReader())
// - if we get a CloseAbnormalClosure, or CloseGoingAway error message from WS, we need to
// transform that into a io.EOF, otherwise yamux will complain. We use yamux on top of this
// reader interface, in order to multiplex multiple streams
// More here:
// https://github.com/hashicorp/yamux/blob/574fd304fd659b0dfdd79e221f4e34f6b7cd9ed2/session.go#L554
// https://github.com/gorilla/websocket/blob/b65e62901fc1c0d968042419e74789f6af455eb9/examples/chat/client.go#L67
// https://stackoverflow.com/questions/61108552/go-websocket-error-close-1006-abnormal-closure-unexpected-eof
filterErr := func() {
if err != nil && !websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
// if we have an error != nil, and it's one of the two, then return EOF
err = io.EOF
}
}
defer filterErr()
if conn.reader != nil {
n, err = conn.reader.Read(p)
if err == io.EOF {
// if this reader has no more data, get the next reader
_, conn.reader, err = conn.WsConn.NextReader()
if err == nil {
// and read in this same call as well
return conn.reader.Read(p)
}
}
} else {
_, conn.reader, err = conn.WsConn.NextReader()
}
return
}
func (conn *WSConnReadWriteCloser) Write(p []byte) (n int, err error) {
return len(p), conn.WsConn.WriteMessage(websocket.BinaryMessage, p)
}
func (conn *WSConnReadWriteCloser) Close() error {
return conn.WsConn.Close()
}

@ -10,7 +10,6 @@ import (
"syscall"
"github.com/Azure/go-ansiterm"
windows "golang.org/x/sys/windows"
)
// Windows keyboard constants
@ -163,28 +162,15 @@ func ensureInRange(n int16, min int16, max int16) int16 {
func GetStdFile(nFile int) (*os.File, uintptr) {
var file *os.File
// syscall uses negative numbers
// windows package uses very big uint32
// Keep these switches split so we don't have to convert ints too much.
switch uint32(nFile) {
case windows.STD_INPUT_HANDLE:
switch nFile {
case syscall.STD_INPUT_HANDLE:
file = os.Stdin
case windows.STD_OUTPUT_HANDLE:
case syscall.STD_OUTPUT_HANDLE:
file = os.Stdout
case windows.STD_ERROR_HANDLE:
case syscall.STD_ERROR_HANDLE:
file = os.Stderr
default:
switch nFile {
case syscall.STD_INPUT_HANDLE:
file = os.Stdin
case syscall.STD_OUTPUT_HANDLE:
file = os.Stdout
case syscall.STD_ERROR_HANDLE:
file = os.Stderr
default:
panic(fmt.Errorf("Invalid standard handle identifier: %v", nFile))
}
panic(fmt.Errorf("Invalid standard handle identifier: %v", nFile))
}
fd, err := syscall.GetStdHandle(nFile)

@ -1,14 +0,0 @@
FROM golang:1.13
# Clone and complie a riscv compatible version of the go compiler.
RUN git clone https://review.gerrithub.io/riscv/riscv-go /riscv-go
# riscvdev branch HEAD as of 2019-06-29.
RUN cd /riscv-go && git checkout 04885fddd096d09d4450726064d06dd107e374bf
ENV PATH=/riscv-go/misc/riscv:/riscv-go/bin:$PATH
RUN cd /riscv-go/src && GOROOT_BOOTSTRAP=$(go env GOROOT) ./make.bash
ENV GOROOT=/riscv-go
# Make sure we compile.
WORKDIR pty
ADD . .
RUN GOOS=linux GOARCH=riscv go build

@ -1,100 +0,0 @@
# pty
Pty is a Go package for using unix pseudo-terminals.
## Install
go get github.com/creack/pty
## Example
### Command
```go
package main
import (
"github.com/creack/pty"
"io"
"os"
"os/exec"
)
func main() {
c := exec.Command("grep", "--color=auto", "bar")
f, err := pty.Start(c)
if err != nil {
panic(err)
}
go func() {
f.Write([]byte("foo\n"))
f.Write([]byte("bar\n"))
f.Write([]byte("baz\n"))
f.Write([]byte{4}) // EOT
}()
io.Copy(os.Stdout, f)
}
```
### Shell
```go
package main
import (
"io"
"log"
"os"
"os/exec"
"os/signal"
"syscall"
"github.com/creack/pty"
"golang.org/x/crypto/ssh/terminal"
)
func test() error {
// Create arbitrary command.
c := exec.Command("bash")
// Start the command with a pty.
ptmx, err := pty.Start(c)
if err != nil {
return err
}
// Make sure to close the pty at the end.
defer func() { _ = ptmx.Close() }() // Best effort.
// Handle pty size.
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for range ch {
if err := pty.InheritSize(os.Stdin, ptmx); err != nil {
log.Printf("error resizing pty: %s", err)
}
}
}()
ch <- syscall.SIGWINCH // Initial resize.
// Set stdin in raw mode.
oldState, err := terminal.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
defer func() { _ = terminal.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
// Copy stdin to the pty and the pty to stdout.
go func() { _, _ = io.Copy(ptmx, os.Stdin) }()
_, _ = io.Copy(os.Stdout, ptmx)
return nil
}
func main() {
if err := test(); err != nil {
log.Fatal(err)
}
}
```

@ -1,30 +0,0 @@
package pty
import (
"golang.org/x/sys/unix"
"unsafe"
)
const (
// see /usr/include/sys/stropts.h
I_PUSH = uintptr((int32('S')<<8 | 002))
I_STR = uintptr((int32('S')<<8 | 010))
I_FIND = uintptr((int32('S')<<8 | 013))
// see /usr/include/sys/ptms.h
ISPTM = (int32('P') << 8) | 1
UNLKPT = (int32('P') << 8) | 2
PTSSTTY = (int32('P') << 8) | 3
ZONEPT = (int32('P') << 8) | 4
OWNERPT = (int32('P') << 8) | 5
)
type strioctl struct {
ic_cmd int32
ic_timout int32
ic_len int32
ic_dp unsafe.Pointer
}
func ioctl(fd, cmd, ptr uintptr) error {
return unix.IoctlSetInt(int(fd), uint(cmd), int(ptr))
}

@ -1,33 +0,0 @@
package pty
import (
"os"
"syscall"
"unsafe"
)
func open() (pty, tty *os.File, err error) {
/*
* from ptm(4):
* The PTMGET command allocates a free pseudo terminal, changes its
* ownership to the caller, revokes the access privileges for all previous
* users, opens the file descriptors for the pty and tty devices and
* returns them to the caller in struct ptmget.
*/
p, err := os.OpenFile("/dev/ptm", os.O_RDWR|syscall.O_CLOEXEC, 0)
if err != nil {
return nil, nil, err
}
defer p.Close()
var ptm ptmget
if err := ioctl(p.Fd(), uintptr(ioctl_PTMGET), uintptr(unsafe.Pointer(&ptm))); err != nil {
return nil, nil, err
}
pty = os.NewFile(uintptr(ptm.Cfd), "/dev/ptm")
tty = os.NewFile(uintptr(ptm.Sfd), "/dev/ptm")
return pty, tty, nil
}

@ -1,139 +0,0 @@
package pty
/* based on:
http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/pt.c
*/
import (
"errors"
"golang.org/x/sys/unix"
"os"
"strconv"
"syscall"
"unsafe"
)
const NODEV = ^uint64(0)
func open() (pty, tty *os.File, err error) {
masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|unix.O_NOCTTY, 0)
//masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC|unix.O_NOCTTY, 0)
if err != nil {
return nil, nil, err
}
p := os.NewFile(uintptr(masterfd), "/dev/ptmx")
sname, err := ptsname(p)
if err != nil {
return nil, nil, err
}
err = grantpt(p)
if err != nil {
return nil, nil, err
}
err = unlockpt(p)
if err != nil {
return nil, nil, err
}
slavefd, err := syscall.Open(sname, os.O_RDWR|unix.O_NOCTTY, 0)
if err != nil {
return nil, nil, err
}
t := os.NewFile(uintptr(slavefd), sname)
// pushing terminal driver STREAMS modules as per pts(7)
for _, mod := range([]string{"ptem", "ldterm", "ttcompat"}) {
err = streams_push(t, mod)
if err != nil {
return nil, nil, err
}
}
return p, t, nil
}
func minor(x uint64) uint64 {
return x & 0377
}
func ptsdev(fd uintptr) uint64 {
istr := strioctl{ISPTM, 0, 0, nil}
err := ioctl(fd, I_STR, uintptr(unsafe.Pointer(&istr)))
if err != nil {
return NODEV
}
var status unix.Stat_t
err = unix.Fstat(int(fd), &status)
if err != nil {
return NODEV
}
return uint64(minor(status.Rdev))
}
func ptsname(f *os.File) (string, error) {
dev := ptsdev(f.Fd())
if dev == NODEV {
return "", errors.New("not a master pty")
}
fn := "/dev/pts/" + strconv.FormatInt(int64(dev), 10)
// access(2) creates the slave device (if the pty exists)
// F_OK == 0 (unistd.h)
err := unix.Access(fn, 0)
if err != nil {
return "", err
}
return fn, nil
}
type pt_own struct {
pto_ruid int32
pto_rgid int32
}
func grantpt(f *os.File) error {
if ptsdev(f.Fd()) == NODEV {
return errors.New("not a master pty")
}
var pto pt_own
pto.pto_ruid = int32(os.Getuid())
// XXX should first attempt to get gid of DEFAULT_TTY_GROUP="tty"
pto.pto_rgid = int32(os.Getgid())
var istr strioctl
istr.ic_cmd = OWNERPT
istr.ic_timout = 0
istr.ic_len = int32(unsafe.Sizeof(istr))
istr.ic_dp = unsafe.Pointer(&pto)
err := ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr)))
if err != nil {
return errors.New("access denied")
}
return nil
}
func unlockpt(f *os.File) error {
istr := strioctl{UNLKPT, 0, 0, nil}
return ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr)))
}
// push STREAMS modules if not already done so
func streams_push(f *os.File, mod string) error {
var err error
buf := []byte(mod)
// XXX I_FIND is not returning an error when the module
// is already pushed even though truss reports a return
// value of 1. A bug in the Go Solaris syscall interface?
// XXX without this we are at risk of the issue
// https://www.illumos.org/issues/9042
// but since we are not using libc or XPG4.2, we should not be
// double-pushing modules
err = ioctl(f.Fd(), I_FIND, uintptr(unsafe.Pointer(&buf[0])))
if err != nil {
return nil
}
err = ioctl(f.Fd(), I_PUSH, uintptr(unsafe.Pointer(&buf[0])))
return err
}

@ -1,74 +0,0 @@
// +build !windows
package pty
import (
"os"
"os/exec"
"syscall"
)
// Start assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
// and c.Stderr, calls c.Start, and returns the File of the tty's
// corresponding pty.
//
// Starts the process in a new session and sets the controlling terminal.
func Start(c *exec.Cmd) (pty *os.File, err error) {
return StartWithSize(c, nil)
}
// StartWithSize assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
// and c.Stderr, calls c.Start, and returns the File of the tty's
// corresponding pty.
//
// This will resize the pty to the specified size before starting the command.
// Starts the process in a new session and sets the controlling terminal.
func StartWithSize(c *exec.Cmd, sz *Winsize) (pty *os.File, err error) {
if c.SysProcAttr == nil {
c.SysProcAttr = &syscall.SysProcAttr{}
}
c.SysProcAttr.Setsid = true
c.SysProcAttr.Setctty = true
return StartWithAttrs(c, sz, c.SysProcAttr)
}
// StartWithAttrs assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
// and c.Stderr, calls c.Start, and returns the File of the tty's
// corresponding pty.
//
// This will resize the pty to the specified size before starting the command if a size is provided.
// The `attrs` parameter overrides the one set in c.SysProcAttr.
//
// This should generally not be needed. Used in some edge cases where it is needed to create a pty
// without a controlling terminal.
func StartWithAttrs(c *exec.Cmd, sz *Winsize, attrs *syscall.SysProcAttr) (pty *os.File, err error) {
pty, tty, err := Open()
if err != nil {
return nil, err
}
defer tty.Close()
if sz != nil {
if err := Setsize(pty, sz); err != nil {
pty.Close()
return nil, err
}
}
if c.Stdout == nil {
c.Stdout = tty
}
if c.Stderr == nil {
c.Stderr = tty
}
if c.Stdin == nil {
c.Stdin = tty
}
c.SysProcAttr = attrs
if err := c.Start(); err != nil {
_ = pty.Close()
return nil, err
}
return pty, err
}

@ -1,50 +0,0 @@
#!/usr/bin/env sh
# Test script checking that all expected os/arch compile properly.
# Does not actually test the logic, just the compilation so we make sure we don't break code depending on the lib.
echo2() {
echo $@ >&2
}
trap end 0
end() {
[ "$?" = 0 ] && echo2 "Pass." || (echo2 "Fail."; exit 1)
}
cross() {
os=$1
shift
echo2 "Build for $os."
for arch in $@; do
echo2 " - $os/$arch"
GOOS=$os GOARCH=$arch go build
done
echo2
}
set -e
cross linux amd64 386 arm arm64 ppc64 ppc64le s390x mips mipsle mips64 mips64le
cross darwin amd64 386 arm arm64
cross freebsd amd64 386 arm
cross netbsd amd64 386 arm
cross openbsd amd64 386 arm arm64
cross dragonfly amd64
cross solaris amd64
# Not expected to work but should still compile.
cross windows amd64 386 arm
# TODO: Fix compilation error on openbsd/arm.
# TODO: Merge the solaris PR.
# Some os/arch require a different compiler. Run in docker.
if ! hash docker; then
# If docker is not present, stop here.
return
fi
echo2 "Build for linux."
echo2 " - linux/riscv"
docker build -t test -f Dockerfile.riscv .

@ -1,64 +0,0 @@
// +build !windows,!solaris
package pty
import (
"os"
"syscall"
"unsafe"
)
// InheritSize applies the terminal size of pty to tty. This should be run
// in a signal handler for syscall.SIGWINCH to automatically resize the tty when
// the pty receives a window size change notification.
func InheritSize(pty, tty *os.File) error {
size, err := GetsizeFull(pty)
if err != nil {
return err
}
err = Setsize(tty, size)
if err != nil {
return err
}
return nil
}
// Setsize resizes t to s.
func Setsize(t *os.File, ws *Winsize) error {
return windowRectCall(ws, t.Fd(), syscall.TIOCSWINSZ)
}
// GetsizeFull returns the full terminal size description.
func GetsizeFull(t *os.File) (size *Winsize, err error) {
var ws Winsize
err = windowRectCall(&ws, t.Fd(), syscall.TIOCGWINSZ)
return &ws, err
}
// Getsize returns the number of rows (lines) and cols (positions
// in each line) in terminal t.
func Getsize(t *os.File) (rows, cols int, err error) {
ws, err := GetsizeFull(t)
return int(ws.Rows), int(ws.Cols), err
}
// Winsize describes the terminal size.
type Winsize struct {
Rows uint16 // ws_row: Number of rows (in cells)
Cols uint16 // ws_col: Number of columns (in cells)
X uint16 // ws_xpixel: Width in pixels
Y uint16 // ws_ypixel: Height in pixels
}
func windowRectCall(ws *Winsize, fd, a2 uintptr) error {
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
fd,
a2,
uintptr(unsafe.Pointer(ws)),
)
if errno != 0 {
return syscall.Errno(errno)
}
return nil
}

@ -1,51 +0,0 @@
//
package pty
import (
"os"
"golang.org/x/sys/unix"
)
const (
TIOCGWINSZ = 21608 // 'T' << 8 | 104
TIOCSWINSZ = 21607 // 'T' << 8 | 103
)
// Winsize describes the terminal size.
type Winsize struct {
Rows uint16 // ws_row: Number of rows (in cells)
Cols uint16 // ws_col: Number of columns (in cells)
X uint16 // ws_xpixel: Width in pixels
Y uint16 // ws_ypixel: Height in pixels
}
// GetsizeFull returns the full terminal size description.
func GetsizeFull(t *os.File) (size *Winsize, err error) {
var wsz *unix.Winsize
wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ)
if err != nil {
return nil, err
} else {
return &Winsize{wsz.Row, wsz.Col, wsz.Xpixel, wsz.Ypixel}, nil
}
}
// Get Windows Size
func Getsize(t *os.File) (rows, cols int, err error) {
var wsz *unix.Winsize
wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ)
if err != nil {
return 80, 25, err
} else {
return int(wsz.Row), int(wsz.Col), nil
}
}
// Setsize resizes t to s.
func Setsize(t *os.File, ws *Winsize) error {
wsz := unix.Winsize{ws.Rows, ws.Cols, ws.X, ws.Y}
return unix.IoctlSetWinsize(int(t.Fd()), TIOCSWINSZ, &wsz)
}

@ -1,13 +0,0 @@
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs types_freebsd.go
package pty
const (
_C_SPECNAMELEN = 0xff
)
type fiodgnameArg struct {
Len int32
Buf *byte
}

@ -1,13 +0,0 @@
// +build openbsd
// +build 386 amd64 arm arm64
package pty
type ptmget struct {
Cfd int32
Sfd int32
Cn [16]int8
Sn [16]int8
}
var ioctl_PTMGET = 0x40287401

@ -1,11 +0,0 @@
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs types.go
// +build riscv riscv64
package pty
type (
_C_int int32
_C_uint uint32
)

@ -0,0 +1,36 @@
# pty
Pty is a Go package for using unix pseudo-terminals.
## Install
go get github.com/kr/pty
## Example
```go
package main
import (
"github.com/kr/pty"
"io"
"os"
"os/exec"
)
func main() {
c := exec.Command("grep", "--color=auto", "bar")
f, err := pty.Start(c)
if err != nil {
panic(err)
}
go func() {
f.Write([]byte("foo\n"))
f.Write([]byte("bar\n"))
f.Write([]byte("baz\n"))
f.Write([]byte{4}) // EOT
}()
io.Copy(os.Stdout, f)
}
```

@ -1,4 +1,4 @@
// +build !windows,!solaris
// +build !windows
package pty

@ -13,7 +13,7 @@ GODEFS="go tool cgo -godefs"
$GODEFS types.go |gofmt > ztypes_$GOARCH.go
case $GOOS in
freebsd|dragonfly|openbsd)
freebsd|dragonfly)
$GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go
;;
esac

@ -13,23 +13,19 @@ func open() (pty, tty *os.File, err error) {
return nil, nil, err
}
p := os.NewFile(uintptr(pFD), "/dev/ptmx")
// In case of error after this point, make sure we close the ptmx fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p)
if err != nil {
return nil, nil, err
}
if err := grantpt(p); err != nil {
err = grantpt(p)
if err != nil {
return nil, nil, err
}
if err := unlockpt(p); err != nil {
err = unlockpt(p)
if err != nil {
return nil, nil, err
}

@ -14,23 +14,19 @@ func open() (pty, tty *os.File, err error) {
if err != nil {
return nil, nil, err
}
// In case of error after this point, make sure we close the ptmx fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p)
if err != nil {
return nil, nil, err
}
if err := grantpt(p); err != nil {
err = grantpt(p)
if err != nil {
return nil, nil, err
}
if err := unlockpt(p); err != nil {
err = unlockpt(p)
if err != nil {
return nil, nil, err
}

@ -7,28 +7,22 @@ import (
"unsafe"
)
func posixOpenpt(oflag int) (fd int, err error) {
func posix_openpt(oflag int) (fd int, err error) {
r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
fd = int(r0)
if e1 != 0 {
err = e1
}
return fd, err
return
}
func open() (pty, tty *os.File, err error) {
fd, err := posixOpenpt(syscall.O_RDWR | syscall.O_CLOEXEC)
fd, err := posix_openpt(syscall.O_RDWR | syscall.O_CLOEXEC)
if err != nil {
return nil, nil, err
}
p := os.NewFile(uintptr(fd), "/dev/pts")
// In case of error after this point, make sure we close the pts fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
p := os.NewFile(uintptr(fd), "/dev/pts")
sname, err := ptsname(p)
if err != nil {
return nil, nil, err
@ -48,7 +42,7 @@ func isptmaster(fd uintptr) (bool, error) {
var (
emptyFiodgnameArg fiodgnameArg
ioctlFIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
ioctl_FIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
)
func ptsname(f *os.File) (string, error) {
@ -65,7 +59,8 @@ func ptsname(f *os.File) (string, error) {
buf = make([]byte, n)
arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))}
)
if err := ioctl(f.Fd(), ioctlFIODGNAME, uintptr(unsafe.Pointer(&arg))); err != nil {
err = ioctl(f.Fd(), ioctl_FIODGNAME, uintptr(unsafe.Pointer(&arg)))
if err != nil {
return "", err
}

@ -12,19 +12,14 @@ func open() (pty, tty *os.File, err error) {
if err != nil {
return nil, nil, err
}
// In case of error after this point, make sure we close the ptmx fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p)
if err != nil {
return nil, nil, err
}
if err := unlockpt(p); err != nil {
err = unlockpt(p)
if err != nil {
return nil, nil, err
}
@ -46,6 +41,6 @@ func ptsname(f *os.File) (string, error) {
func unlockpt(f *os.File) error {
var u _C_int
// use TIOCSPTLCK with a pointer to zero to clear the lock
// use TIOCSPTLCK with a zero valued arg to clear the slave pty lock
return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
}

@ -1,4 +1,4 @@
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd,!solaris
// +build !linux,!darwin,!freebsd,!dragonfly
package pty

@ -0,0 +1,34 @@
// +build !windows
package pty
import (
"os"
"os/exec"
"syscall"
)
// Start assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
// and c.Stderr, calls c.Start, and returns the File of the tty's
// corresponding pty.
func Start(c *exec.Cmd) (pty *os.File, err error) {
pty, tty, err := Open()
if err != nil {
return nil, err
}
defer tty.Close()
c.Stdout = tty
c.Stdin = tty
c.Stderr = tty
if c.SysProcAttr == nil {
c.SysProcAttr = &syscall.SysProcAttr{}
}
c.SysProcAttr.Setctty = true
c.SysProcAttr.Setsid = true
err = c.Start()
if err != nil {
pty.Close()
return nil, err
}
return pty, err
}

@ -0,0 +1,64 @@
// +build !windows
package pty
import (
"os"
"syscall"
"unsafe"
)
// Getsize returns the number of rows (lines) and cols (positions
// in each line) in terminal t.
func Getsize(t *os.File) (rows, cols int, err error) {
var ws winsize
err = getwindowrect(&ws, t.Fd())
return int(ws.ws_row), int(ws.ws_col), err
}
// Setsize sets the number of rows (lines) and cols (positions
// in each line) in terminal t. Both rows and cols have to be
// positive integers.
func Setsize(t *os.File, rows, cols int) error {
ws := winsize{
ws_col: uint16(cols),
ws_row: uint16(rows),
ws_xpixel: uint16(0), // not used
ws_ypixel: uint16(0), // not used
}
return setwindowrect(&ws, t.Fd())
}
type winsize struct {
ws_row uint16
ws_col uint16
ws_xpixel uint16
ws_ypixel uint16
}
func getwindowrect(ws *winsize, fd uintptr) error {
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
fd,
syscall.TIOCGWINSZ,
uintptr(unsafe.Pointer(ws)),
)
if errno != 0 {
return syscall.Errno(errno)
}
return nil
}
func setwindowrect(ws *winsize, fd uintptr) error {
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
fd,
syscall.TIOCSWINSZ,
uintptr(unsafe.Pointer(ws)),
)
if errno != 0 {
return syscall.Errno(errno)
}
return nil
}

@ -0,0 +1,3 @@
module github.com/gorilla/mux
go 1.12

@ -6,13 +6,6 @@
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
---
⚠️ **[The Gorilla WebSocket Package is looking for a new maintainer](https://github.com/gorilla/websocket/issues/370)**
---
### Documentation
* [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc)
@ -37,3 +30,35 @@ The Gorilla WebSocket package passes the server tests in the [Autobahn Test
Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
### Gorilla WebSocket compared with other packages
<table>
<tr>
<th></th>
<th><a href="http://godoc.org/github.com/gorilla/websocket">github.com/gorilla</a></th>
<th><a href="http://godoc.org/golang.org/x/net/websocket">golang.org/x/net</a></th>
</tr>
<tr>
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr>
<tr><td>Passes <a href="https://github.com/crossbario/autobahn-testsuite">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr>
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr>
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr>
<tr><td colspan="3">Other Features</tr></td>
<tr><td><a href="https://tools.ietf.org/html/rfc7692">Compression Extensions</a></td><td>Experimental</td><td>No</td></tr>
<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr>
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
</table>
Notes:
1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html).
2. The application can get the type of a received data message by implementing
a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal)
function.
3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries.
Read returns when the input buffer is full or a frame boundary is
encountered. Each call to Write sends a single frame message. The Gorilla
io.Reader and io.WriteCloser operate on a single WebSocket message.

@ -48,23 +48,15 @@ func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufS
}
// A Dialer contains options for connecting to WebSocket server.
//
// It is safe to call Dialer's methods concurrently.
type Dialer struct {
// NetDial specifies the dial function for creating TCP connections. If
// NetDial is nil, net.Dial is used.
NetDial func(network, addr string) (net.Conn, error)
// NetDialContext specifies the dial function for creating TCP connections. If
// NetDialContext is nil, NetDial is used.
// NetDialContext is nil, net.DialContext is used.
NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
// NetDialTLSContext specifies the dial function for creating TLS/TCP connections. If
// NetDialTLSContext is nil, NetDialContext is used.
// If NetDialTLSContext is set, Dial assumes the TLS handshake is done there and
// TLSClientConfig is ignored.
NetDialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error)
// Proxy specifies a function to return a proxy for a given
// Request. If the function returns a non-nil error, the
// request is aborted with the provided error.
@ -73,8 +65,6 @@ type Dialer struct {
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
// If nil, the default configuration is used.
// If either NetDialTLS or NetDialTLSContext are set, Dial assumes the TLS handshake
// is done there and TLSClientConfig is ignored.
TLSClientConfig *tls.Config
// HandshakeTimeout specifies the duration for the handshake to complete.
@ -186,7 +176,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
}
req := &http.Request{
Method: http.MethodGet,
Method: "GET",
URL: u,
Proto: "HTTP/1.1",
ProtoMajor: 1,
@ -247,32 +237,13 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
// Get network dial function.
var netDial func(network, add string) (net.Conn, error)
switch u.Scheme {
case "http":
if d.NetDialContext != nil {
netDial = func(network, addr string) (net.Conn, error) {
return d.NetDialContext(ctx, network, addr)
}
} else if d.NetDial != nil {
netDial = d.NetDial
}
case "https":
if d.NetDialTLSContext != nil {
netDial = func(network, addr string) (net.Conn, error) {
return d.NetDialTLSContext(ctx, network, addr)
}
} else if d.NetDialContext != nil {
netDial = func(network, addr string) (net.Conn, error) {
return d.NetDialContext(ctx, network, addr)
}
} else if d.NetDial != nil {
netDial = d.NetDial
if d.NetDialContext != nil {
netDial = func(network, addr string) (net.Conn, error) {
return d.NetDialContext(ctx, network, addr)
}
default:
return nil, nil, errMalformedURL
}
if netDial == nil {
} else if d.NetDial != nil {
netDial = d.NetDial
} else {
netDialer := &net.Dialer{}
netDial = func(network, addr string) (net.Conn, error) {
return netDialer.DialContext(ctx, network, addr)
@ -333,9 +304,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
}
}()
if u.Scheme == "https" && d.NetDialTLSContext == nil {
// If NetDialTLSContext is set, assume that the TLS handshake has already been done
if u.Scheme == "https" {
cfg := cloneTLSConfig(d.TLSClientConfig)
if cfg.ServerName == "" {
cfg.ServerName = hostNoPort
@ -343,12 +312,11 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
tlsConn := tls.Client(netConn, cfg)
netConn = tlsConn
if trace != nil && trace.TLSHandshakeStart != nil {
trace.TLSHandshakeStart()
}
err := doHandshake(ctx, tlsConn, cfg)
if trace != nil && trace.TLSHandshakeDone != nil {
trace.TLSHandshakeDone(tlsConn.ConnectionState(), err)
var err error
if trace != nil {
err = doHandshakeWithTrace(trace, tlsConn, cfg)
} else {
err = doHandshake(tlsConn, cfg)
}
if err != nil {
@ -380,8 +348,8 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
}
if resp.StatusCode != 101 ||
!tokenListContainsValue(resp.Header, "Upgrade", "websocket") ||
!tokenListContainsValue(resp.Header, "Connection", "upgrade") ||
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
// Before closing the network connection on return from this
// function, slurp up some of the response to aid application
@ -414,9 +382,14 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
return conn, resp, nil
}
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error {
if err := tlsConn.Handshake(); err != nil {
return err
}
if !cfg.InsecureSkipVerify {
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
return err
}
}
return cfg.Clone()
return nil
}

@ -0,0 +1,16 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.8
package websocket
import "crypto/tls"
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return cfg.Clone()
}

@ -0,0 +1,38 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8
package websocket
import "crypto/tls"
// cloneTLSConfig clones all public fields except the fields
// SessionTicketsDisabled and SessionTicketKey. This avoids copying the
// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a
// config in active use.
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return &tls.Config{
Rand: cfg.Rand,
Time: cfg.Time,
Certificates: cfg.Certificates,
NameToCertificate: cfg.NameToCertificate,
GetCertificate: cfg.GetCertificate,
RootCAs: cfg.RootCAs,
NextProtos: cfg.NextProtos,
ServerName: cfg.ServerName,
ClientAuth: cfg.ClientAuth,
ClientCAs: cfg.ClientCAs,
InsecureSkipVerify: cfg.InsecureSkipVerify,
CipherSuites: cfg.CipherSuites,
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
ClientSessionCache: cfg.ClientSessionCache,
MinVersion: cfg.MinVersion,
MaxVersion: cfg.MaxVersion,
CurvePreferences: cfg.CurvePreferences,
}
}

@ -13,7 +13,6 @@ import (
"math/rand"
"net"
"strconv"
"strings"
"sync"
"time"
"unicode/utf8"
@ -402,12 +401,6 @@ func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error
return nil
}
func (c *Conn) writeBufs(bufs ...[]byte) error {
b := net.Buffers(bufs)
_, err := b.WriteTo(c.conn)
return err
}
// WriteControl writes a control message with the given deadline. The allowed
// message types are CloseMessage, PingMessage and PongMessage.
func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) error {
@ -801,69 +794,47 @@ func (c *Conn) advanceFrame() (int, error) {
}
// 2. Read and parse first two bytes of frame header.
// To aid debugging, collect and report all errors in the first two bytes
// of the header.
var errors []string
p, err := c.read(2)
if err != nil {
return noFrame, err
}
frameType := int(p[0] & 0xf)
final := p[0]&finalBit != 0
rsv1 := p[0]&rsv1Bit != 0
rsv2 := p[0]&rsv2Bit != 0
rsv3 := p[0]&rsv3Bit != 0
frameType := int(p[0] & 0xf)
mask := p[1]&maskBit != 0
c.setReadRemaining(int64(p[1] & 0x7f))
c.readDecompress = false
if rsv1 {
if c.newDecompressionReader != nil {
c.readDecompress = true
} else {
errors = append(errors, "RSV1 set")
}
}
if rsv2 {
errors = append(errors, "RSV2 set")
if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 {
c.readDecompress = true
p[0] &^= rsv1Bit
}
if rsv3 {
errors = append(errors, "RSV3 set")
if rsv := p[0] & (rsv1Bit | rsv2Bit | rsv3Bit); rsv != 0 {
return noFrame, c.handleProtocolError("unexpected reserved bits 0x" + strconv.FormatInt(int64(rsv), 16))
}
switch frameType {
case CloseMessage, PingMessage, PongMessage:
if c.readRemaining > maxControlFramePayloadSize {
errors = append(errors, "len > 125 for control")
return noFrame, c.handleProtocolError("control frame length > 125")
}
if !final {
errors = append(errors, "FIN not set on control")
return noFrame, c.handleProtocolError("control frame not final")
}
case TextMessage, BinaryMessage:
if !c.readFinal {
errors = append(errors, "data before FIN")
return noFrame, c.handleProtocolError("message start before final message frame")
}
c.readFinal = final
case continuationFrame:
if c.readFinal {
errors = append(errors, "continuation after FIN")
return noFrame, c.handleProtocolError("continuation after final message frame")
}
c.readFinal = final
default:
errors = append(errors, "bad opcode "+strconv.Itoa(frameType))
}
if mask != c.isServer {
errors = append(errors, "bad MASK")
}
if len(errors) > 0 {
return noFrame, c.handleProtocolError(strings.Join(errors, ", "))
return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType))
}
// 3. Read and parse frame length as per
@ -901,6 +872,10 @@ func (c *Conn) advanceFrame() (int, error) {
// 4. Handle frame masking.
if mask != c.isServer {
return noFrame, c.handleProtocolError("incorrect mask flag")
}
if mask {
c.readMaskPos = 0
p, err := c.read(len(c.readMaskKey))
@ -960,7 +935,7 @@ func (c *Conn) advanceFrame() (int, error) {
if len(payload) >= 2 {
closeCode = int(binary.BigEndian.Uint16(payload))
if !isValidReceivedCloseCode(closeCode) {
return noFrame, c.handleProtocolError("bad close code " + strconv.Itoa(closeCode))
return noFrame, c.handleProtocolError("invalid close code")
}
closeText = string(payload[2:])
if !utf8.ValidString(closeText) {
@ -977,11 +952,7 @@ func (c *Conn) advanceFrame() (int, error) {
}
func (c *Conn) handleProtocolError(message string) error {
data := FormatCloseMessage(CloseProtocolError, message)
if len(data) > maxControlFramePayloadSize {
data = data[:maxControlFramePayloadSize]
}
c.WriteControl(CloseMessage, data, time.Now().Add(writeWait))
c.WriteControl(CloseMessage, FormatCloseMessage(CloseProtocolError, message), time.Now().Add(writeWait))
return errors.New("websocket: " + message)
}

@ -0,0 +1,15 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.8
package websocket
import "net"
func (c *Conn) writeBufs(bufs ...[]byte) error {
b := net.Buffers(bufs)
_, err := b.WriteTo(c.conn)
return err
}

@ -0,0 +1,18 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8
package websocket
func (c *Conn) writeBufs(bufs ...[]byte) error {
for _, buf := range bufs {
if len(buf) > 0 {
if _, err := c.conn.Write(buf); err != nil {
return err
}
}
}
return nil
}

@ -0,0 +1,3 @@
module github.com/gorilla/websocket
go 1.12

@ -2,7 +2,6 @@
// this source code is governed by a BSD-style license that can be found in the
// LICENSE file.
//go:build !appengine
// +build !appengine
package websocket

@ -2,7 +2,6 @@
// this source code is governed by a BSD-style license that can be found in the
// LICENSE file.
//go:build appengine
// +build appengine
package websocket

@ -48,7 +48,7 @@ func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error)
}
connectReq := &http.Request{
Method: http.MethodConnect,
Method: "CONNECT",
URL: &url.URL{Opaque: addr},
Host: addr,
Header: connectHeader,

@ -23,8 +23,6 @@ func (e HandshakeError) Error() string { return e.message }
// Upgrader specifies parameters for upgrading an HTTP connection to a
// WebSocket connection.
//
// It is safe to call Upgrader's methods concurrently.
type Upgrader struct {
// HandshakeTimeout specifies the duration for the handshake to complete.
HandshakeTimeout time.Duration
@ -117,8 +115,8 @@ func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
//
// The responseHeader is included in the response to the client's upgrade
// request. Use the responseHeader to specify cookies (Set-Cookie). To specify
// subprotocols supported by the server, set Upgrader.Subprotocols directly.
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
// application negotiated subprotocol (Sec-WebSocket-Protocol).
//
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
// response.
@ -133,7 +131,7 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header")
}
if r.Method != http.MethodGet {
if r.Method != "GET" {
return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET")
}

@ -1,21 +0,0 @@
//go:build go1.17
// +build go1.17
package websocket
import (
"context"
"crypto/tls"
)
func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config) error {
if err := tlsConn.HandshakeContext(ctx); err != nil {
return err
}
if !cfg.InsecureSkipVerify {
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
return err
}
}
return nil
}

@ -1,21 +0,0 @@
//go:build !go1.17
// +build !go1.17
package websocket
import (
"context"
"crypto/tls"
)
func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config) error {
if err := tlsConn.Handshake(); err != nil {
return err
}
if !cfg.InsecureSkipVerify {
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
return err
}
}
return nil
}

@ -0,0 +1,19 @@
// +build go1.8
package websocket
import (
"crypto/tls"
"net/http/httptrace"
)
func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error {
if trace.TLSHandshakeStart != nil {
trace.TLSHandshakeStart()
}
err := doHandshake(tlsConn, cfg)
if trace.TLSHandshakeDone != nil {
trace.TLSHandshakeDone(tlsConn.ConnectionState(), err)
}
return err
}

@ -0,0 +1,12 @@
// +build !go1.8
package websocket
import (
"crypto/tls"
"net/http/httptrace"
)
func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error {
return doHandshake(tlsConn, cfg)
}

@ -5,25 +5,6 @@ import (
"fmt"
)
// NetError implements net.Error
type NetError struct {
err error
timeout bool
temporary bool
}
func (e *NetError) Error() string {
return e.err.Error()
}
func (e *NetError) Timeout() bool {
return e.timeout
}
func (e *NetError) Temporary() bool {
return e.temporary
}
var (
// ErrInvalidVersion means we received a frame with an
// invalid version
@ -49,13 +30,7 @@ var (
ErrRecvWindowExceeded = fmt.Errorf("recv window exceeded")
// ErrTimeout is used when we reach an IO deadline
ErrTimeout = &NetError{
err: fmt.Errorf("i/o deadline reached"),
// Error should meet net.Error interface for timeouts for compatability
// with standard library expectations, such as http servers.
timeout: true,
}
ErrTimeout = fmt.Errorf("i/o deadline reached")
// ErrStreamClosed is returned when using a closed stream
ErrStreamClosed = fmt.Errorf("stream closed")

@ -0,0 +1 @@
module github.com/hashicorp/yamux

@ -31,20 +31,6 @@ type Config struct {
// window size that we allow for a stream.
MaxStreamWindowSize uint32
// StreamOpenTimeout is the maximum amount of time that a stream will
// be allowed to remain in pending state while waiting for an ack from the peer.
// Once the timeout is reached the session will be gracefully closed.
// A zero value disables the StreamOpenTimeout allowing unbounded
// blocking on OpenStream calls.
StreamOpenTimeout time.Duration
// StreamCloseTimeout is the maximum time that a stream will allowed to
// be in a half-closed state when `Close` is called before forcibly
// closing the connection. Forcibly closed connections will empty the
// receive buffer, drop any future packets received for that stream,
// and send a RST to the remote side.
StreamCloseTimeout time.Duration
// LogOutput is used to control the log destination. Either Logger or
// LogOutput can be set, not both.
LogOutput io.Writer
@ -62,8 +48,6 @@ func DefaultConfig() *Config {
KeepAliveInterval: 30 * time.Second,
ConnectionWriteTimeout: 10 * time.Second,
MaxStreamWindowSize: initialStreamWindow,
StreamCloseTimeout: 5 * time.Minute,
StreamOpenTimeout: 75 * time.Second,
LogOutput: os.Stderr,
}
}

@ -2,7 +2,6 @@ package yamux
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
@ -64,27 +63,24 @@ type Session struct {
// sendCh is used to mark a stream as ready to send,
// or to send a header out directly.
sendCh chan *sendReady
sendCh chan sendReady
// recvDoneCh is closed when recv() exits to avoid a race
// between stream registration and stream shutdown
recvDoneCh chan struct{}
sendDoneCh chan struct{}
// shutdown is used to safely close a session
shutdown bool
shutdownErr error
shutdownCh chan struct{}
shutdownLock sync.Mutex
shutdownErrLock sync.Mutex
shutdown bool
shutdownErr error
shutdownCh chan struct{}
shutdownLock sync.Mutex
}
// sendReady is used to either mark a stream as ready
// or to directly send a header
type sendReady struct {
Hdr []byte
mu sync.Mutex // Protects Body from unsafe reads.
Body []byte
Body io.Reader
Err chan error
}
@ -105,9 +101,8 @@ func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
inflight: make(map[uint32]struct{}),
synCh: make(chan struct{}, config.AcceptBacklog),
acceptCh: make(chan *Stream, config.AcceptBacklog),
sendCh: make(chan *sendReady, 64),
sendCh: make(chan sendReady, 64),
recvDoneCh: make(chan struct{}),
sendDoneCh: make(chan struct{}),
shutdownCh: make(chan struct{}),
}
if client {
@ -189,10 +184,6 @@ GET_ID:
s.inflight[id] = struct{}{}
s.streamLock.Unlock()
if s.config.StreamOpenTimeout > 0 {
go s.setOpenTimeout(stream)
}
// Send the window update to create
if err := stream.sendWindowUpdate(); err != nil {
select {
@ -205,27 +196,6 @@ GET_ID:
return stream, nil
}
// setOpenTimeout implements a timeout for streams that are opened but not established.
// If the StreamOpenTimeout is exceeded we assume the peer is unable to ACK,
// and close the session.
// The number of running timers is bounded by the capacity of the synCh.
func (s *Session) setOpenTimeout(stream *Stream) {
timer := time.NewTimer(s.config.StreamOpenTimeout)
defer timer.Stop()
select {
case <-stream.establishCh:
return
case <-s.shutdownCh:
return
case <-timer.C:
// Timeout reached while waiting for ACK.
// Close the session to force connection re-establishment.
s.logger.Printf("[ERR] yamux: aborted stream open (destination=%s): %v", s.RemoteAddr().String(), ErrTimeout.err)
s.Close()
}
}
// Accept is used to block until the next available stream
// is ready to be accepted.
func (s *Session) Accept() (net.Conn, error) {
@ -260,15 +230,10 @@ func (s *Session) Close() error {
return nil
}
s.shutdown = true
s.shutdownErrLock.Lock()
if s.shutdownErr == nil {
s.shutdownErr = ErrSessionShutdown
}
s.shutdownErrLock.Unlock()
close(s.shutdownCh)
s.conn.Close()
<-s.recvDoneCh
@ -277,18 +242,17 @@ func (s *Session) Close() error {
for _, stream := range s.streams {
stream.forceClose()
}
<-s.sendDoneCh
return nil
}
// exitErr is used to handle an error that is causing the
// session to terminate.
func (s *Session) exitErr(err error) {
s.shutdownErrLock.Lock()
s.shutdownLock.Lock()
if s.shutdownErr == nil {
s.shutdownErr = err
}
s.shutdownErrLock.Unlock()
s.shutdownLock.Unlock()
s.Close()
}
@ -363,7 +327,7 @@ func (s *Session) keepalive() {
}
// waitForSendErr waits to send a header, checking for a potential shutdown
func (s *Session) waitForSend(hdr header, body []byte) error {
func (s *Session) waitForSend(hdr header, body io.Reader) error {
errCh := make(chan error, 1)
return s.waitForSendErr(hdr, body, errCh)
}
@ -371,7 +335,7 @@ func (s *Session) waitForSend(hdr header, body []byte) error {
// waitForSendErr waits to send a header with optional data, checking for a
// potential shutdown. Since there's the expectation that sends can happen
// in a timely manner, we enforce the connection write timeout here.
func (s *Session) waitForSendErr(hdr header, body []byte, errCh chan error) error {
func (s *Session) waitForSendErr(hdr header, body io.Reader, errCh chan error) error {
t := timerPool.Get()
timer := t.(*time.Timer)
timer.Reset(s.config.ConnectionWriteTimeout)
@ -384,7 +348,7 @@ func (s *Session) waitForSendErr(hdr header, body []byte, errCh chan error) erro
timerPool.Put(t)
}()
ready := &sendReady{Hdr: hdr, Body: body, Err: errCh}
ready := sendReady{Hdr: hdr, Body: body, Err: errCh}
select {
case s.sendCh <- ready:
case <-s.shutdownCh:
@ -393,34 +357,12 @@ func (s *Session) waitForSendErr(hdr header, body []byte, errCh chan error) erro
return ErrConnectionWriteTimeout
}
bodyCopy := func() {
if body == nil {
return // A nil body is ignored.
}
// In the event of session shutdown or connection write timeout,
// we need to prevent `send` from reading the body buffer after
// returning from this function since the caller may re-use the
// underlying array.
ready.mu.Lock()
defer ready.mu.Unlock()
if ready.Body == nil {
return // Body was already copied in `send`.
}
newBody := make([]byte, len(body))
copy(newBody, body)
ready.Body = newBody
}
select {
case err := <-errCh:
return err
case <-s.shutdownCh:
bodyCopy()
return ErrSessionShutdown
case <-timer.C:
bodyCopy()
return ErrConnectionWriteTimeout
}
}
@ -442,7 +384,7 @@ func (s *Session) sendNoWait(hdr header) error {
}()
select {
case s.sendCh <- &sendReady{Hdr: hdr}:
case s.sendCh <- sendReady{Hdr: hdr}:
return nil
case <-s.shutdownCh:
return ErrSessionShutdown
@ -453,59 +395,39 @@ func (s *Session) sendNoWait(hdr header) error {
// send is a long running goroutine that sends data
func (s *Session) send() {
if err := s.sendLoop(); err != nil {
s.exitErr(err)
}
}
func (s *Session) sendLoop() error {
defer close(s.sendDoneCh)
var bodyBuf bytes.Buffer
for {
bodyBuf.Reset()
select {
case ready := <-s.sendCh:
// Send a header if ready
if ready.Hdr != nil {
_, err := s.conn.Write(ready.Hdr)
if err != nil {
s.logger.Printf("[ERR] yamux: Failed to write header: %v", err)
asyncSendErr(ready.Err, err)
return err
sent := 0
for sent < len(ready.Hdr) {
n, err := s.conn.Write(ready.Hdr[sent:])
if err != nil {
s.logger.Printf("[ERR] yamux: Failed to write header: %v", err)
asyncSendErr(ready.Err, err)
s.exitErr(err)
return
}
sent += n
}
}
ready.mu.Lock()
// Send data from a body if given
if ready.Body != nil {
// Copy the body into the buffer to avoid
// holding a mutex lock during the write.
_, err := bodyBuf.Write(ready.Body)
if err != nil {
ready.Body = nil
ready.mu.Unlock()
s.logger.Printf("[ERR] yamux: Failed to copy body into buffer: %v", err)
asyncSendErr(ready.Err, err)
return err
}
ready.Body = nil
}
ready.mu.Unlock()
if bodyBuf.Len() > 0 {
// Send data from a body if given
_, err := s.conn.Write(bodyBuf.Bytes())
_, err := io.Copy(s.conn, ready.Body)
if err != nil {
s.logger.Printf("[ERR] yamux: Failed to write body: %v", err)
asyncSendErr(ready.Err, err)
return err
s.exitErr(err)
return
}
}
// No error, successful send
asyncSendErr(ready.Err, nil)
case <-s.shutdownCh:
return nil
return
}
}
}
@ -692,9 +614,8 @@ func (s *Session) incomingStream(id uint32) error {
// Backlog exceeded! RST the stream
s.logger.Printf("[WARN] yamux: backlog exceeded, forcing connection reset")
delete(s.streams, id)
hdr := header(make([]byte, headerSize))
hdr.encode(typeWindowUpdate, flagRST, id, 0)
return s.sendNoWait(hdr)
stream.sendHdr.encode(typeWindowUpdate, flagRST, id, 0)
return s.sendNoWait(stream.sendHdr)
}
}

@ -2,7 +2,6 @@ package yamux
import (
"bytes"
"errors"
"io"
"sync"
"sync/atomic"
@ -50,13 +49,6 @@ type Stream struct {
readDeadline atomic.Value // time.Time
writeDeadline atomic.Value // time.Time
// establishCh is notified if the stream is established or being closed.
establishCh chan struct{}
// closeTimer is set with stateLock held to honor the StreamCloseTimeout
// setting on Session.
closeTimer *time.Timer
}
// newStream is used to construct a new stream within
@ -74,7 +66,6 @@ func newStream(session *Session, id uint32, state streamState) *Stream {
sendWindow: initialStreamWindow,
recvNotifyCh: make(chan struct{}, 1),
sendNotifyCh: make(chan struct{}, 1),
establishCh: make(chan struct{}, 1),
}
s.readDeadline.Store(time.Time{})
s.writeDeadline.Store(time.Time{})
@ -128,9 +119,6 @@ START:
// Send a window update potentially
err = s.sendWindowUpdate()
if err == ErrSessionShutdown {
err = nil
}
return n, err
WAIT:
@ -173,7 +161,7 @@ func (s *Stream) Write(b []byte) (n int, err error) {
func (s *Stream) write(b []byte) (n int, err error) {
var flags uint16
var max uint32
var body []byte
var body io.Reader
START:
s.stateLock.Lock()
switch s.state {
@ -199,15 +187,11 @@ START:
// Send up to our send window
max = min(window, uint32(len(b)))
body = b[:max]
body = bytes.NewReader(b[:max])
// Send the header
s.sendHdr.encode(typeData, flags, s.id, max)
if err = s.session.waitForSendErr(s.sendHdr, body, s.sendErr); err != nil {
if errors.Is(err, ErrSessionShutdown) || errors.Is(err, ErrConnectionWriteTimeout) {
// Message left in ready queue, header re-use is unsafe.
s.sendHdr = header(make([]byte, headerSize))
}
return 0, err
}
@ -281,10 +265,6 @@ func (s *Stream) sendWindowUpdate() error {
// Send the header
s.controlHdr.encode(typeWindowUpdate, flags, s.id, delta)
if err := s.session.waitForSendErr(s.controlHdr, nil, s.controlErr); err != nil {
if errors.Is(err, ErrSessionShutdown) || errors.Is(err, ErrConnectionWriteTimeout) {
// Message left in ready queue, header re-use is unsafe.
s.controlHdr = header(make([]byte, headerSize))
}
return err
}
return nil
@ -299,10 +279,6 @@ func (s *Stream) sendClose() error {
flags |= flagFIN
s.controlHdr.encode(typeWindowUpdate, flags, s.id, 0)
if err := s.session.waitForSendErr(s.controlHdr, nil, s.controlErr); err != nil {
if errors.Is(err, ErrSessionShutdown) || errors.Is(err, ErrConnectionWriteTimeout) {
// Message left in ready queue, header re-use is unsafe.
s.controlHdr = header(make([]byte, headerSize))
}
return err
}
return nil
@ -336,27 +312,6 @@ func (s *Stream) Close() error {
s.stateLock.Unlock()
return nil
SEND_CLOSE:
// This shouldn't happen (the more realistic scenario to cancel the
// timer is via processFlags) but just in case this ever happens, we
// cancel the timer to prevent dangling timers.
if s.closeTimer != nil {
s.closeTimer.Stop()
s.closeTimer = nil
}
// If we have a StreamCloseTimeout set we start the timeout timer.
// We do this only if we're not already closing the stream since that
// means this was a graceful close.
//
// This prevents memory leaks if one side (this side) closes and the
// remote side poorly behaves and never responds with a FIN to complete
// the close. After the specified timeout, we clean our resources up no
// matter what.
if !closeStream && s.session.config.StreamCloseTimeout > 0 {
s.closeTimer = time.AfterFunc(
s.session.config.StreamCloseTimeout, s.closeTimeout)
}
s.stateLock.Unlock()
s.sendClose()
s.notifyWaiting()
@ -366,23 +321,6 @@ SEND_CLOSE:
return nil
}
// closeTimeout is called after StreamCloseTimeout during a close to
// close this stream.
func (s *Stream) closeTimeout() {
// Close our side forcibly
s.forceClose()
// Free the stream from the session map
s.session.closeStream(s.id)
// Send a RST so the remote side closes too.
s.sendLock.Lock()
defer s.sendLock.Unlock()
hdr := header(make([]byte, headerSize))
hdr.encode(typeWindowUpdate, flagRST, s.id, 0)
s.session.sendNoWait(hdr)
}
// forceClose is used for when the session is exiting
func (s *Stream) forceClose() {
s.stateLock.Lock()
@ -394,27 +332,20 @@ func (s *Stream) forceClose() {
// processFlags is used to update the state of the stream
// based on set flags, if any. Lock must be held
func (s *Stream) processFlags(flags uint16) error {
s.stateLock.Lock()
defer s.stateLock.Unlock()
// Close the stream without holding the state lock
closeStream := false
defer func() {
if closeStream {
if s.closeTimer != nil {
// Stop our close timeout timer since we gracefully closed
s.closeTimer.Stop()
}
s.session.closeStream(s.id)
}
}()
s.stateLock.Lock()
defer s.stateLock.Unlock()
if flags&flagACK == flagACK {
if s.state == streamSYNSent {
s.state = streamEstablished
}
asyncNotify(s.establishCh)
s.session.establishStream(s.id)
}
if flags&flagFIN == flagFIN {
@ -447,7 +378,6 @@ func (s *Stream) processFlags(flags uint16) error {
func (s *Stream) notifyWaiting() {
asyncNotify(s.recvNotifyCh)
asyncNotify(s.sendNotifyCh)
asyncNotify(s.establishCh)
}
// incrSendWindow updates the size of our send window
@ -482,7 +412,6 @@ func (s *Stream) readData(hdr header, flags uint16, conn io.Reader) error {
if length > s.recvWindow {
s.session.logger.Printf("[ERR] yamux: receive window exceeded (stream: %d, remain: %d, recv: %d)", s.id, s.recvWindow, length)
s.recvLock.Unlock()
return ErrRecvWindowExceeded
}
@ -491,15 +420,14 @@ func (s *Stream) readData(hdr header, flags uint16, conn io.Reader) error {
// This way we can read in the whole packet without further allocations.
s.recvBuf = bytes.NewBuffer(make([]byte, 0, length))
}
copiedLength, err := io.Copy(s.recvBuf, conn)
if err != nil {
if _, err := io.Copy(s.recvBuf, conn); err != nil {
s.session.logger.Printf("[ERR] yamux: Failed to read stream data: %v", err)
s.recvLock.Unlock()
return err
}
// Decrement the receive window
s.recvWindow -= uint32(copiedLength)
s.recvWindow -= length
s.recvLock.Unlock()
// Unblock any readers

@ -0,0 +1,9 @@
(The MIT License)
Copyright (c) 2017 marvin + konsorten GmbH (open-source@konsorten.de)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,41 @@
# Windows Terminal Sequences
This library allow for enabling Windows terminal color support for Go.
See [Console Virtual Terminal Sequences](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences) for details.
## Usage
```go
import (
"syscall"
sequences "github.com/konsorten/go-windows-terminal-sequences"
)
func main() {
sequences.EnableVirtualTerminalProcessing(syscall.Stdout, true)
}
```
## Authors
The tool is sponsored by the [marvin + konsorten GmbH](http://www.konsorten.de).
We thank all the authors who provided code to this library:
* Felix Kollmann
* Nicolas Perraut
## License
(The MIT License)
Copyright (c) 2018 marvin + konsorten GmbH (open-source@konsorten.de)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1 @@
module github.com/konsorten/go-windows-terminal-sequences

@ -0,0 +1,36 @@
// +build windows
package sequences
import (
"syscall"
"unsafe"
)
var (
kernel32Dll *syscall.LazyDLL = syscall.NewLazyDLL("Kernel32.dll")
setConsoleMode *syscall.LazyProc = kernel32Dll.NewProc("SetConsoleMode")
)
func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error {
const ENABLE_VIRTUAL_TERMINAL_PROCESSING uint32 = 0x4
var mode uint32
err := syscall.GetConsoleMode(syscall.Stdout, &mode)
if err != nil {
return err
}
if enable {
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING
} else {
mode &^= ENABLE_VIRTUAL_TERMINAL_PROCESSING
}
ret, _, err := setConsoleMode.Call(uintptr(unsafe.Pointer(stream)), uintptr(mode))
if ret == 0 {
return err
}
return nil
}

@ -0,0 +1,11 @@
// +build linux darwin
package sequences
import (
"fmt"
)
func EnableVirtualTerminalProcessing(stream uintptr, enable bool) error {
return fmt.Errorf("windows only package")
}

@ -0,0 +1,12 @@
module github.com/moby/term
go 1.13
require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78
github.com/creack/pty v1.1.9
github.com/google/go-cmp v0.4.0
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a
gotest.tools/v3 v3.0.2
)

@ -0,0 +1,23 @@
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a h1:i47hUS795cOydZI4AwJQCKXOr4BvxzvikwDoDtHhP2Y=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=

@ -1,4 +1,3 @@
//go:build !windows
// +build !windows
package term

@ -1,4 +1,3 @@
//go:build !windows
// +build !windows
// Package term provides structures and helper functions to work with
@ -15,8 +14,10 @@ import (
"golang.org/x/sys/unix"
)
// ErrInvalidState is returned if the state of the terminal is invalid.
var ErrInvalidState = errors.New("Invalid terminal state")
var (
// ErrInvalidState is returned if the state of the terminal is invalid.
ErrInvalidState = errors.New("Invalid terminal state")
)
// State represents the state of the terminal.
type State struct {

@ -66,23 +66,24 @@ func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
}
}
// Temporarily use STD_INPUT_HANDLE, STD_OUTPUT_HANDLE and
// STD_ERROR_HANDLE from syscall rather than x/sys/windows as long as
// go-ansiterm hasn't switch to x/sys/windows.
// TODO: switch back to x/sys/windows once go-ansiterm has switched
if emulateStdin {
h := uint32(windows.STD_INPUT_HANDLE)
stdIn = windowsconsole.NewAnsiReader(int(h))
stdIn = windowsconsole.NewAnsiReader(windows.STD_INPUT_HANDLE)
} else {
stdIn = os.Stdin
}
if emulateStdout {
h := uint32(windows.STD_OUTPUT_HANDLE)
stdOut = windowsconsole.NewAnsiWriter(int(h))
stdOut = windowsconsole.NewAnsiWriter(windows.STD_OUTPUT_HANDLE)
} else {
stdOut = os.Stdout
}
if emulateStderr {
h := uint32(windows.STD_ERROR_HANDLE)
stdErr = windowsconsole.NewAnsiWriter(int(h))
stdErr = windowsconsole.NewAnsiWriter(windows.STD_ERROR_HANDLE)
} else {
stdErr = os.Stderr
}

@ -1,4 +1,3 @@
//go:build !windows
// +build !windows
package term

@ -1,4 +1,3 @@
//go:build darwin || freebsd || openbsd || netbsd
// +build darwin freebsd openbsd netbsd
package term

@ -1,5 +1,4 @@
//go:build !darwin && !freebsd && !netbsd && !openbsd && !windows
// +build !darwin,!freebsd,!netbsd,!openbsd,!windows
//+build !darwin,!freebsd,!netbsd,!openbsd,!windows
package term

@ -1,4 +1,3 @@
//go:build windows
// +build windows
package windowsconsole
@ -191,6 +190,7 @@ func keyToString(keyEvent *winterm.KEY_EVENT_RECORD, escapeSequence []byte) stri
// <Ctrl>-S Suspends printing on the screen (does not stop the program).
// <Ctrl>-U Deletes all characters on the current line. Also called the KILL key.
// <Ctrl>-E Quits current command and creates a core
}
// <Alt>+Key generates ESC N Key

@ -1,4 +1,3 @@
//go:build windows
// +build windows
package windowsconsole

@ -1,4 +1,3 @@
//go:build windows
// +build windows
package windowsconsole
@ -30,7 +29,7 @@ func GetHandleInfo(in interface{}) (uintptr, bool) {
// IsConsole returns true if the given file descriptor is a Windows Console.
// The code assumes that GetConsoleMode will return an error for file descriptors that are not a console.
// Deprecated: use golang.org/x/sys/windows.GetConsoleMode() or golang.org/x/term.IsTerminal()
// Deprecated: use golang.org/x/sys/windows.GetConsoleMode() or golang.org/x/crypto/ssh/terminal.IsTerminal()
var IsConsole = isConsole
func isConsole(fd uintptr) bool {

@ -1,4 +1,3 @@
//go:build !windows
// +build !windows
package term

@ -1,4 +1,2 @@
logrus
vendor
.idea/

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save