Files
poseidon/pkg/nullio/ls2json.go
Maximilian Paß 3509109b6f Fix Ls2JsonWriter
by allowing more spaces in the ls response.
by sending the error response of the list file system route only when no content has been written.
2022-10-05 12:11:47 +01:00

178 lines
4.2 KiB
Go

package nullio
import (
"bytes"
"encoding/json"
"fmt"
"github.com/openHPI/poseidon/pkg/dto"
"github.com/openHPI/poseidon/pkg/logging"
"io"
"regexp"
"strconv"
"strings"
)
var (
log = logging.GetLogger("nullio")
pathLineRegex = regexp.MustCompile(`(.*):$`)
headerLineRegex = regexp.
MustCompile(`([-aAbcCdDlMnpPsw?])([-rwxXsStT]{9})(\+?) +\d+ +(.+?) +(.+?) +(\d+) +(\d+) +(.*)$`)
)
const (
headerLineGroupEntryType = 1
headerLineGroupPermissions = 2
headerLineGroupACL = 3
headerLineGroupOwner = 4
headerLineGroupGroup = 5
headerLineGroupSize = 6
headerLineGroupTimestamp = 7
headerLineGroupName = 8
)
// Ls2JsonWriter implements io.Writer.
// It streams the passed data to the Target and transforms the data into the json format.
type Ls2JsonWriter struct {
Target io.Writer
jsonStartSent bool
setCommaPrefix bool
remaining []byte
latestPath []byte
}
func (w *Ls2JsonWriter) HasStartedWriting() bool {
return w.jsonStartSent
}
func (w *Ls2JsonWriter) Write(p []byte) (int, error) {
i, err := w.initializeJSONObject()
if err != nil {
return i, err
}
start := 0
for i, char := range p {
if char != '\n' {
continue
}
line := p[start:i]
if len(w.remaining) > 0 {
line = append(w.remaining, line...)
w.remaining = []byte("")
}
if len(line) != 0 {
count, err := w.writeLine(line)
if err != nil {
log.WithError(err).Warn("Could not write line to Target")
return count, err
}
}
start = i + 1
}
if start < len(p) {
w.remaining = p[start:]
}
return len(p), nil
}
func (w *Ls2JsonWriter) initializeJSONObject() (count int, err error) {
if !w.jsonStartSent {
count, err = w.Target.Write([]byte("{\"files\": ["))
if count == 0 || err != nil {
log.WithError(err).Warn("Could not write to target")
err = fmt.Errorf("could not write to target: %w", err)
} else {
w.jsonStartSent = true
}
}
return count, err
}
func (w *Ls2JsonWriter) Close() {
if w.jsonStartSent {
count, err := w.Target.Write([]byte("]}"))
if count == 0 || err != nil {
log.WithError(err).Warn("Could not Close ls2json writer")
}
}
}
func (w *Ls2JsonWriter) writeLine(line []byte) (count int, err error) {
matches := pathLineRegex.FindSubmatch(line)
if matches != nil {
w.latestPath = append(bytes.TrimSuffix(matches[1], []byte("/")), '/')
return 0, nil
}
matches = headerLineRegex.FindSubmatch(line)
if matches != nil {
response, err1 := w.parseFileHeader(matches)
if err1 != nil {
return 0, err1
}
// Skip the first leading comma
if w.setCommaPrefix {
response = append([]byte{','}, response...)
} else {
w.setCommaPrefix = true
}
count, err1 = w.Target.Write(response)
if err1 != nil {
err = fmt.Errorf("could not write to target: %w", err1)
} else if count == len(response) {
count = len(line)
}
}
return count, err
}
func (w *Ls2JsonWriter) parseFileHeader(matches [][]byte) ([]byte, error) {
entryType := dto.EntryType(matches[headerLineGroupEntryType][0])
permissions := string(matches[headerLineGroupPermissions])
acl := string(matches[headerLineGroupACL])
if acl == "+" {
permissions += "+"
}
size, err1 := strconv.Atoi(string(matches[headerLineGroupSize]))
timestamp, err2 := strconv.Atoi(string(matches[headerLineGroupTimestamp]))
if err1 != nil || err2 != nil {
return nil, fmt.Errorf("could not parse file details: %w %+v", err1, err2)
}
name := dto.FilePath(append(w.latestPath, matches[headerLineGroupName]...))
linkTarget := dto.FilePath("")
if entryType == dto.EntryTypeLink {
parts := strings.Split(string(name), " -> ")
const NumberOfPartsInALink = 2
if len(parts) == NumberOfPartsInALink {
name = dto.FilePath(parts[0])
linkTarget = dto.FilePath(parts[1])
} else {
log.Error("could not split link into name and target")
}
}
response, err := json.Marshal(dto.FileHeader{
Name: name,
EntryType: entryType,
LinkTarget: linkTarget,
Size: size,
ModificationTime: timestamp,
Permissions: permissions,
Owner: string(matches[headerLineGroupOwner]),
Group: string(matches[headerLineGroupGroup]),
})
if err != nil {
return nil, fmt.Errorf("could not marshal file header: %w", err)
}
return response, nil
}