golang.org/x/playground@v0.0.0-20230418134305-14ebe15bcd59/play.go (about)

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/binary"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"sync"
    14  	"time"
    15  	"unicode/utf8"
    16  )
    17  
    18  // When sandbox time begins.
    19  var epoch = time.Unix(1257894000, 0)
    20  
    21  // Recorder records the standard and error outputs of a sandbox program
    22  // (comprised of playback headers) and converts it to a sequence of Events.
    23  // It sanitizes each Event's Message to ensure it is valid UTF-8.
    24  //
    25  // Playground programs precede all their writes with a header (described
    26  // below) that describes the time the write occurred (in playground time) and
    27  // the length of the data that will be written. If a non-header is
    28  // encountered where a header is expected, the output is scanned for the next
    29  // header and the intervening text string is added to the sequence an event
    30  // occurring at the same time as the preceding event.
    31  //
    32  // A playback header has this structure:
    33  //
    34  //	4 bytes: "\x00\x00PB", a magic header
    35  //	8 bytes: big-endian int64, unix time in nanoseconds
    36  //	4 bytes: big-endian int32, length of the next write
    37  type Recorder struct {
    38  	stdout, stderr recorderWriter
    39  }
    40  
    41  func (r *Recorder) Stdout() io.Writer { return &r.stdout }
    42  func (r *Recorder) Stderr() io.Writer { return &r.stderr }
    43  
    44  type recorderWriter struct {
    45  	mu     sync.Mutex
    46  	writes []byte
    47  }
    48  
    49  func (w *recorderWriter) bytes() []byte {
    50  	w.mu.Lock()
    51  	defer w.mu.Unlock()
    52  	return w.writes[0:len(w.writes):len(w.writes)]
    53  }
    54  
    55  func (w *recorderWriter) Write(b []byte) (n int, err error) {
    56  	w.mu.Lock()
    57  	defer w.mu.Unlock()
    58  	w.writes = append(w.writes, b...)
    59  	return len(b), nil
    60  }
    61  
    62  type Event struct {
    63  	Message string
    64  	Kind    string        // "stdout" or "stderr"
    65  	Delay   time.Duration // time to wait before printing Message
    66  }
    67  
    68  func (r *Recorder) Events() ([]Event, error) {
    69  	stdout, stderr := r.stdout.bytes(), r.stderr.bytes()
    70  
    71  	evOut, err := decode("stdout", stdout)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	evErr, err := decode("stderr", stderr)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	events := sortedMerge(evOut, evErr)
    81  
    82  	var (
    83  		out []Event
    84  		now = epoch
    85  	)
    86  
    87  	for _, e := range events {
    88  		delay := e.time.Sub(now)
    89  		if delay < 0 {
    90  			delay = 0
    91  		}
    92  		out = append(out, Event{
    93  			Message: string(sanitize(e.msg)),
    94  			Kind:    e.kind,
    95  			Delay:   delay,
    96  		})
    97  		if delay > 0 {
    98  			now = e.time
    99  		}
   100  	}
   101  	return out, nil
   102  }
   103  
   104  type event struct {
   105  	msg  []byte
   106  	kind string
   107  	time time.Time
   108  }
   109  
   110  func decode(kind string, output []byte) ([]event, error) {
   111  	var (
   112  		magic     = []byte{0, 0, 'P', 'B'}
   113  		headerLen = 8 + 4
   114  		last      = epoch
   115  		events    []event
   116  	)
   117  	add := func(t time.Time, b []byte) {
   118  		var prev *event
   119  		if len(events) > 0 {
   120  			prev = &events[len(events)-1]
   121  		}
   122  		if prev != nil && t.Equal(prev.time) {
   123  			// Merge this event with previous event, to avoid
   124  			// sending a lot of events for a big output with no
   125  			// significant timing information.
   126  			prev.msg = append(prev.msg, b...)
   127  		} else {
   128  			e := event{msg: b, kind: kind, time: t}
   129  			events = append(events, e)
   130  		}
   131  		last = t
   132  	}
   133  	for i := 0; i < len(output); {
   134  		if !bytes.HasPrefix(output[i:], magic) {
   135  			// Not a header; find next header.
   136  			j := bytes.Index(output[i:], magic)
   137  			if j < 0 {
   138  				// No more headers; bail.
   139  				add(last, output[i:])
   140  				break
   141  			}
   142  			add(last, output[i:i+j])
   143  			i += j
   144  		}
   145  		i += len(magic)
   146  
   147  		// Decode header.
   148  		if len(output)-i < headerLen {
   149  			return nil, errors.New("short header")
   150  		}
   151  		header := output[i : i+headerLen]
   152  		nanos := int64(binary.BigEndian.Uint64(header[0:]))
   153  		t := time.Unix(0, nanos)
   154  		if t.Before(last) {
   155  			// Force timestamps to be monotonic. (This could
   156  			// be an encoding error, which we ignore now but will
   157  			// will likely be picked up when decoding the length.)
   158  			t = last
   159  		}
   160  		n := int(binary.BigEndian.Uint32(header[8:]))
   161  		if n < 0 {
   162  			return nil, fmt.Errorf("bad length: %v", n)
   163  		}
   164  		i += headerLen
   165  
   166  		// Slurp output.
   167  		// Truncated output is OK (probably caused by sandbox limits).
   168  		end := i + n
   169  		if end > len(output) {
   170  			end = len(output)
   171  		}
   172  		add(t, output[i:end])
   173  		i += n
   174  	}
   175  	return events, nil
   176  }
   177  
   178  // Sorted merge of two slices of events into one slice.
   179  func sortedMerge(a, b []event) []event {
   180  	if len(a) == 0 {
   181  		return b
   182  	}
   183  	if len(b) == 0 {
   184  		return a
   185  	}
   186  
   187  	sorted := make([]event, 0, len(a)+len(b))
   188  	i, j := 0, 0
   189  	for i < len(a) && j < len(b) {
   190  		if a[i].time.Before(b[j].time) {
   191  			sorted = append(sorted, a[i])
   192  			i++
   193  		} else {
   194  			sorted = append(sorted, b[j])
   195  			j++
   196  		}
   197  	}
   198  	sorted = append(sorted, a[i:]...)
   199  	sorted = append(sorted, b[j:]...)
   200  	return sorted
   201  }
   202  
   203  // sanitize scans b for invalid utf8 code points. If found, it reconstructs
   204  // the slice replacing the invalid codes with \uFFFD, properly encoded.
   205  func sanitize(b []byte) []byte {
   206  	if utf8.Valid(b) {
   207  		return b
   208  	}
   209  	var buf bytes.Buffer
   210  	for len(b) > 0 {
   211  		r, size := utf8.DecodeRune(b)
   212  		b = b[size:]
   213  		buf.WriteRune(r)
   214  	}
   215  	return buf.Bytes()
   216  }