From 1c787b5f26f96af98103fe72966bf733cae09b0a Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 19 Oct 2020 18:13:52 -0700 Subject: [PATCH] Fix handling of super long lines to not hit a scanner error; closes pterodactyl/panel#2549 Also fixes return handling in games like Minecraft to properly display the full line output. --- environment/docker/container.go | 51 +++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/environment/docker/container.go b/environment/docker/container.go index 61c18aa..eb143d9 100644 --- a/environment/docker/container.go +++ b/environment/docker/container.go @@ -2,6 +2,7 @@ package docker import ( "bufio" + "bytes" "context" "encoding/json" "fmt" @@ -286,15 +287,53 @@ func (e *Environment) followOutput() error { reader, err := e.client.ContainerLogs(context.Background(), e.Id, opts) - go func(r io.ReadCloser) { - defer r.Close() + go func(reader io.ReadCloser) { + defer reader.Close() - s := bufio.NewScanner(r) - for s.Scan() { - e.Events().Publish(environment.ConsoleOutputEvent, s.Text()) + r := bufio.NewReader(reader) + ParentLoop: + for { + var b bytes.Buffer + var line []byte + var isPrefix bool + + for { + // Read the line and write it to the buffer. + line, isPrefix, err = r.ReadLine() + + // Certain games like Minecraft output absolutely random carriage returns in the output seemingly + // in line with that it thinks is the terminal size. Those returns break a lot of output handling, + // so we'll just replace them with proper new-lines and then split it later and send each line as + // its own event in the response. + b.Write(bytes.ReplaceAll(line, []byte(" \r"), []byte("\r\n"))) + + // Finish this loop and begin outputting the line if there is no prefix (the line fit into + // the default buffer), or if we hit the end of the line. + if !isPrefix || err == io.EOF { + break + } + + // If we encountered an error with something in ReadLine that was not an EOF just abort + // the entire process here. + if err != nil { + break ParentLoop + } + } + + // Publish the line for this loop. Break on new-line characters so every line is sent as a single + // output event, otherwise you get funky handling in the browser console. + for _, line := range strings.Split(b.String(), "\r\n") { + e.Events().Publish(environment.ConsoleOutputEvent, line) + } + + // If the error we got previously that lead to the line being output is an io.EOF we want to + // exit the entire looping process. + if err == io.EOF { + break + } } - if err := s.Err(); err != nil { + if err != nil && err != io.EOF { log.WithField("error", err).WithField("container_id", e.Id).Warn("error processing scanner line in console output") } }(reader)