<commit_before>package gottyclient

import (
	"bufio"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"unicode/utf8"
	"unsafe"

	"github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
	"golang.org/x/crypto/ssh/terminal"
)

// GetWebsocketURL transforms a GoTTY http URL to its WebSocket URL
func GetWebsocketURL(httpURL string) (*url.URL, *http.Header, error) {
	header := http.Header{}
	target, err := url.Parse(httpURL)
	if err != nil {
		return nil, nil, err
	}

	if target.Scheme == "https" {
		target.Scheme = "wss"
	} else {
		target.Scheme = "ws"
	}

	target.Path = strings.TrimLeft(target.Path+"ws", "/")

	if target.User != nil {
		header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
		target.User = nil
	}

	return target, &header, nil
}

type Client struct {
	Dialer    *websocket.Dialer
	Conn      *websocket.Conn
	Headers   *http.Header
	Target    string
	Connected bool
}

// Connect tries to dial a websocket server
func (c *Client) Connect() error {
	conn, _, err := c.Dialer.Dial(c.Target, *c.Headers)
	if err != nil {
		return err
	}
	c.Conn = conn
	return nil
}

// Close will nicely close the dialer
func (c *Client) Close() {
	c.Conn.Close()
}

// Loop will look indefinitely for new messages
func (c *Client) Loop() error {
	if !c.Connected {
		err := c.Connect()
		if err != nil {
			return err
		}
	}

	done := make(chan bool)
	go c.readLoop(done)
	go c.writeLoop(done)
	go c.termsizeLoop(done)
	<-done
	return nil
}

type winsize struct {
	Rows    uint16 `json:"rows"`
	Columns uint16 `json:"columns"`
	// unused
	x uint16
	y uint16
}

func (c *Client) termsizeLoop(done chan bool) {
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGWINCH)
	ws := winsize{}

	for {
		syscall.Syscall(syscall.SYS_IOCTL,
			uintptr(0), uintptr(syscall.TIOCGWINSZ),
			uintptr(unsafe.Pointer(&ws)))

		b, err := json.Marshal(ws)
		if err != nil {
			logrus.Warnf("json.Marshal error: %v", err)
		}

		err = c.Conn.WriteMessage(websocket.TextMessage, append([]byte("1"), b...))
		if err != nil {
			logrus.Warnf("ws.WriteMessage failed: %v", err)
		}

		<-ch
	}
}

func (c *Client) writeLoop(done chan bool) {
	oldState, err := terminal.MakeRaw(0)
	if err == nil {
		defer terminal.Restore(0, oldState)
	}

	reader := bufio.NewReader(os.Stdin)
	for {
		x, size, err := reader.ReadRune()
		if size <= 0 || err != nil {
			done <- true
			return
		}

		p := make([]byte, size)
		utf8.EncodeRune(p, x)

		err = c.Conn.WriteMessage(websocket.TextMessage, append([]byte("0"), p...))
		if err != nil {
			done <- true
			return
		}
	}
}

func (c *Client) readLoop(done chan bool) {
	for {
		_, data, err := c.Conn.ReadMessage()
		if err != nil {
			done <- true
			logrus.Warnf("c.Conn.ReadMessage: %v", err)
			return
		}

		switch data[0] {
		case '0': // data
			fmt.Print(string(data[1:]))
		case '1': // new title
			newTitle := string(data[1:])
			fmt.Printf("\033]0;%s\007", newTitle)
		case '2': // json prefs
			logrus.Debugf("Unhandled protocol message: json pref: %s", string(data))
		default:
			logrus.Warnf("Unhandled protocol message: %s", string(data))
		}
	}
}

// NewClient returns a GoTTY client object
func NewClient(httpURL string) (*Client, error) {
	target, header, err := GetWebsocketURL(httpURL)
	if err != nil {
		return nil, err
	}

	return &Client{
		Dialer:  &websocket.Dialer{},
		Target:  target.String(),
		Headers: header,
	}, nil
}<commit_msg>Bumped GoTTY protocol for GoTTY@0.10<commit_after>package gottyclient

import (
	"bufio"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"regexp"
	"strings"
	"syscall"
	"time"
	"unicode/utf8"
	"unsafe"

	"github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
	"golang.org/x/crypto/ssh/terminal"
)

// GetAuthTokenURL transforms a GoTTY http URL to its AuthToken file URL
func GetAuthTokenURL(httpURL string) (*url.URL, *http.Header, error) {
	header := http.Header{}
	target, err := url.Parse(httpURL)
	if err != nil {
		return nil, nil, err
	}

	target.Path = strings.TrimLeft(target.Path+"auth_token.js", "/")

	if target.User != nil {
		header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
		target.User = nil
	}

	return target, &header, nil
}

// GetWebsocketURL transforms a GoTTY http URL to its WebSocket URL
func GetWebsocketURL(httpURL string) (*url.URL, *http.Header, error) {
	header := http.Header{}
	target, err := url.Parse(httpURL)
	if err != nil {
		return nil, nil, err
	}

	if target.Scheme == "https" {
		target.Scheme = "wss"
	} else {
		target.Scheme = "ws"
	}

	target.Path = strings.TrimLeft(target.Path+"ws", "/")

	if target.User != nil {
		header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
		target.User = nil
	}

	return target, &header, nil
}

type Client struct {
	Dialer    *websocket.Dialer
	Conn      *websocket.Conn
	URL       string
	Connected bool
}

// GetAuthToken retrieves an Auth Token from dynamic auth_token.js file
func (c *Client) GetAuthToken() (string, error) {
	target, header, err := GetAuthTokenURL(c.URL)
	if err != nil {
		return "", err
	}

	req, err := http.NewRequest("GET", target.String(), nil)
	req.Header = *header
	client := http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	re := regexp.MustCompile("var gotty_auth_token = '(.*)'")
	return re.FindStringSubmatch(string(body))[1], nil
}

// Connect tries to dial a websocket server
func (c *Client) Connect() error {
	// Retrieve AuthToken
	authToken, err := c.GetAuthToken()
	if err != nil {
		return err
	}

	// Open WebSocket connection
	target, header, err := GetWebsocketURL(c.URL)
	if err != nil {
		return err
	}
	conn, _, err := c.Dialer.Dial(target.String(), *header)
	if err != nil {
		return err
	}
	c.Conn = conn

	// Send AuthToken
	err = c.Conn.WriteMessage(websocket.TextMessage, []byte(authToken))
	if err != nil {
		return err
	}

	go c.pingLoop()

	return nil
}

func (c *Client) pingLoop() {
	for {
		c.Conn.WriteMessage(websocket.TextMessage, []byte("1"))
		time.Sleep(30 * time.Second)
	}
}

// Close will nicely close the dialer
func (c *Client) Close() {
	c.Conn.Close()
}

// Loop will look indefinitely for new messages
func (c *Client) Loop() error {
	if !c.Connected {
		err := c.Connect()
		if err != nil {
			return err
		}
	}

	done := make(chan bool)
	go c.readLoop(done)
	go c.writeLoop(done)
	go c.termsizeLoop(done)
	<-done
	return nil
}

type winsize struct {
	Rows    uint16 `json:"rows"`
	Columns uint16 `json:"columns"`
	// unused
	x uint16
	y uint16
}

func (c *Client) termsizeLoop(done chan bool) {
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGWINCH)
	ws := winsize{}

	for {
		syscall.Syscall(syscall.SYS_IOCTL,
			uintptr(0), uintptr(syscall.TIOCGWINSZ),
			uintptr(unsafe.Pointer(&ws)))

		b, err := json.Marshal(ws)
		if err != nil {
			logrus.Warnf("json.Marshal error: %v", err)
		}

		err = c.Conn.WriteMessage(websocket.TextMessage, append([]byte("2"), b...))
		if err != nil {
			logrus.Warnf("ws.WriteMessage failed: %v", err)
		}

		<-ch
	}
}

func (c *Client) writeLoop(done chan bool) {
	oldState, err := terminal.MakeRaw(0)
	if err == nil {
		defer terminal.Restore(0, oldState)
	}

	reader := bufio.NewReader(os.Stdin)
	for {
		x, size, err := reader.ReadRune()
		if size <= 0 || err != nil {
			done <- true
			return
		}

		p := make([]byte, size)
		utf8.EncodeRune(p, x)

		err = c.Conn.WriteMessage(websocket.TextMessage, append([]byte("0"), p...))
		if err != nil {
			done <- true
			return
		}
	}
}

func (c *Client) readLoop(done chan bool) {
	for {
		_, data, err := c.Conn.ReadMessage()
		if err != nil {
			done <- true
			logrus.Warnf("c.Conn.ReadMessage: %v", err)
			return
		}

		switch data[0] {
		case '0': // data
			buf, err := base64.StdEncoding.DecodeString(string(data[1:]))
			if err != nil {
				logrus.Warnf("Invalid base64 content: %q", data[1:])
			}
			fmt.Print(string(buf))
		case '1': // pong
		case '2': // new title
			newTitle := string(data[1:])
			fmt.Printf("\033]0;%s\007", newTitle)
		case '3': // json prefs
			logrus.Debugf("Unhandled protocol message: json pref: %s", string(data))
		case '4': // autoreconnect
			logrus.Debugf("Unhandled protocol message: autoreconnect: %s", string(data))
		default:
			logrus.Warnf("Unhandled protocol message: %s", string(data))
		}
	}
}

// NewClient returns a GoTTY client object
func NewClient(httpURL string) (*Client, error) {
	return &Client{
		Dialer: &websocket.Dialer{},
		URL:    httpURL,
	}, nil
}
{'fraction_non_alphanumeric': 0.15984027381631488, 'fraction_numerical': 0.009583571021106674, 'mean_word_length': 3.474732006125574, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3658555', 'n_tokens_mistral': 3605, 'n_tokens_neox': 3293, 'n_words': 819}