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 }