<commit_before>package app

import (
	"encoding/json"
	"log"
	"net/http"
	"os/exec"
	"strings"
	"syscall"
	"unsafe"

	"github.com/elazarl/go-bindata-assetfs"
	"github.com/gorilla/websocket"
	"github.com/kr/pty"
)

type App struct {
	Address     string
	Port        string
	PermitWrite bool
	Command     []string
}

func New(address string, port string, permitWrite bool, command []string) *App {
	return &App{
		Address:     address,
		Port:        port,
		PermitWrite: permitWrite,
		Command:     command,
	}
}

func (app *App) Run() error {
	http.Handle("/",
		http.FileServer(
			&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "bindata"},
		),
	)
	http.HandleFunc("/ws", app.generateHandler())

	url := app.Address + ":" + app.Port
	log.Printf("Sever is running at %s, command: %s", url, strings.Join(app.Command, " "))
	err := http.ListenAndServe(url, nil)
	if err != nil {
		return err
	}

	return nil
}

func (app *App) generateHandler() func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Printf("New client connected: %s", r.RemoteAddr)

		upgrader := websocket.Upgrader{
			ReadBufferSize:  0,
			WriteBufferSize: 0,
			Subprotocols:    []string{"gotty"},
		}

		if r.Method != "GET" {
			http.Error(w, "Method not allowed", 405)
			return
		}

		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			log.Print("Failed to upgrade connection")
			return
		}

		cmd := exec.Command(app.Command[0], app.Command[1:]...)
		fio, err := pty.Start(cmd)
		if err != nil {
			log.Print("Failed to execute command")
			return
		}

		exit := make(chan bool, 2)

		go func() {
			defer func() { exit <- true }()

			buf := make([]byte, 512)
			for {
				len, err := fio.Read(buf)
				if err != nil {
					log.Printf("command exited for: %s", r.RemoteAddr)
					return
				}
				writer, err := conn.NextWriter(1)
				if err != nil {
					return
				}
				writer.Write(buf[0:len])
				writer.Close()
			}
		}()

		go func() {
			defer func() { exit <- true }()

			for {
				_, data, err := conn.ReadMessage()
				if err != nil {
					return
				}

				switch data[0] {
				case '0':
					if !app.PermitWrite {<commit_msg>Don't send invalid UTF-8 string<commit_after>package app

import (
	"encoding/json"
	"log"
	"net/http"
	"os/exec"
	"strings"
	"syscall"
	"unicode/utf8"
	"unsafe"

	"github.com/elazarl/go-bindata-assetfs"
	"github.com/gorilla/websocket"
	"github.com/kr/pty"
)

type App struct {
	Address     string
	Port        string
	PermitWrite bool
	Command     []string
}

func New(address string, port string, permitWrite bool, command []string) *App {
	return &App{
		Address:     address,
		Port:        port,
		PermitWrite: permitWrite,
		Command:     command,
	}
}

func (app *App) Run() error {
	http.Handle("/",
		http.FileServer(
			&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "bindata"},
		),
	)
	http.HandleFunc("/ws", app.generateHandler())

	url := app.Address + ":" + app.Port
	log.Printf("Sever is running at %s, command: %s", url, strings.Join(app.Command, " "))
	err := http.ListenAndServe(url, nil)
	if err != nil {
		return err
	}

	return nil
}

func (app *App) generateHandler() func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Printf("New client connected: %s", r.RemoteAddr)

		upgrader := websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
			Subprotocols:    []string{"gotty"},
		}

		if r.Method != "GET" {
			http.Error(w, "Method not allowed", 405)
			return
		}

		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			log.Print("Failed to upgrade connection")
			return
		}

		cmd := exec.Command(app.Command[0], app.Command[1:]...)
		fio, err := pty.Start(cmd)
		if err != nil {
			log.Print("Failed to execute command")
			return
		}

		exit := make(chan bool, 2)

		go func() {
			defer func() { exit <- true }()

			buf := make([]byte, 1024)
			leftOver := 0
			for {
				size, err := fio.Read(buf[leftOver:])
				size += leftOver

				if err != nil {
					log.Printf("command exited for: %s", r.RemoteAddr)
					return
				}

				writer, err := conn.NextWriter(websocket.TextMessage)
				if err != nil {
					return
				}

				// UTF-8 Boundary check
				for leftOver = 0; leftOver < utf8.UTFMax; leftOver++ {
					re, _ := utf8.DecodeLastRune(
						buf[:size-leftOver],
					)

					if re != utf8.RuneError {
						break
					}
					// Invalid UTF rune
				}

				if leftOver == utf8.UTFMax-1 {
					re, _ := utf8.DecodeLastRune(buf[:size-leftOver])
					if re == utf8.RuneError {
						log.Fatal("UTF8 Boundary error.")
					}
				}

				writer.Write(buf[:size-leftOver])
				writer.Close()

				for i := 0; i < leftOver; i++ {
					buf[i] = buf[size-leftOver+i]
				}
			}
		}()

		go func() {
			defer func() { exit <- true }()

			for {
				_, data, err := conn.ReadMessage()
				if err != nil {
					return
				}

				switch data[0] {
				case '0':
					if !app.PermitWrite {
{'fraction_non_alphanumeric': 0.15583116623324664, 'fraction_numerical': 0.009801960392078415, 'mean_word_length': 2.7369207772795217, 'pattern_counts': {'":': 2, '<': 9, '<?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': '440425', 'n_tokens_mistral': 2205, 'n_tokens_neox': 1815, 'n_words': 443}