Add Content-Length and Content-Disposition Header
for GetFileContent route.
This commit is contained in:
59
pkg/nullio/content_length.go
Normal file
59
pkg/nullio/content_length.go
Normal file
@ -0,0 +1,59 @@
|
||||
package nullio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var ErrRegexMatching = errors.New("could not match content length")
|
||||
|
||||
// ContentLengthWriter implements io.Writer.
|
||||
// It parses the size from the first line as Content Length Header and streams the following data to the Target.
|
||||
// The first line is expected to follow the format headerLineRegex.
|
||||
type ContentLengthWriter struct {
|
||||
Target http.ResponseWriter
|
||||
contentLengthSet bool
|
||||
firstLine []byte
|
||||
}
|
||||
|
||||
func (w *ContentLengthWriter) Write(p []byte) (count int, err error) {
|
||||
if w.contentLengthSet {
|
||||
count, err = w.Target.Write(p)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not write to target: %w", err)
|
||||
}
|
||||
return count, err
|
||||
}
|
||||
|
||||
for i, char := range p {
|
||||
if char != '\n' {
|
||||
continue
|
||||
}
|
||||
|
||||
w.firstLine = append(w.firstLine, p[:i]...)
|
||||
matches := headerLineRegex.FindSubmatch(w.firstLine)
|
||||
if len(matches) < headerLineGroupName {
|
||||
log.WithField("line", string(w.firstLine)).Error(ErrRegexMatching.Error())
|
||||
return 0, ErrRegexMatching
|
||||
}
|
||||
size := string(matches[headerLineGroupSize])
|
||||
w.Target.Header().Set("Content-Length", size)
|
||||
w.contentLengthSet = true
|
||||
|
||||
if i < len(p)-1 {
|
||||
count, err = w.Target.Write(p[i+1:])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not write to target: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return len(p[:i]) + 1 + count, err
|
||||
}
|
||||
|
||||
if !w.contentLengthSet {
|
||||
w.firstLine = append(w.firstLine, p...)
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
48
pkg/nullio/content_length_test.go
Normal file
48
pkg/nullio/content_length_test.go
Normal file
@ -0,0 +1,48 @@
|
||||
package nullio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type responseWriterStub struct {
|
||||
bytes.Buffer
|
||||
header http.Header
|
||||
}
|
||||
|
||||
func (r *responseWriterStub) Header() http.Header {
|
||||
return r.header
|
||||
}
|
||||
func (r *responseWriterStub) WriteHeader(_ int) {
|
||||
}
|
||||
|
||||
func TestContentLengthWriter_Write(t *testing.T) {
|
||||
header := http.Header(make(map[string][]string))
|
||||
buf := &responseWriterStub{header: header}
|
||||
writer := &ContentLengthWriter{Target: buf}
|
||||
part1 := []byte("-rw-rw-r-- 1 kali ka")
|
||||
contentLength := "42"
|
||||
part2 := []byte("li " + contentLength + " 1660763446 flag\nFL")
|
||||
part3 := []byte("AG")
|
||||
|
||||
count, err := writer.Write(part1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(part1), count)
|
||||
assert.Empty(t, buf.String())
|
||||
assert.Equal(t, "", header.Get("Content-Length"))
|
||||
|
||||
count, err = writer.Write(part2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(part2), count)
|
||||
assert.Equal(t, "FL", buf.String())
|
||||
assert.Equal(t, contentLength, header.Get("Content-Length"))
|
||||
|
||||
count, err = writer.Write(part3)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(part3), count)
|
||||
assert.Equal(t, "FLAG", buf.String())
|
||||
assert.Equal(t, contentLength, header.Get("Content-Length"))
|
||||
}
|
@ -18,11 +18,22 @@ var (
|
||||
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
|
||||
jsonStartSend bool
|
||||
jsonStartSent bool
|
||||
setCommaPrefix bool
|
||||
remaining []byte
|
||||
latestPath []byte
|
||||
@ -64,20 +75,20 @@ func (w *Ls2JsonWriter) Write(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (w *Ls2JsonWriter) initializeJSONObject() (count int, err error) {
|
||||
if !w.jsonStartSend {
|
||||
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.jsonStartSend = true
|
||||
w.jsonStartSent = true
|
||||
}
|
||||
}
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (w *Ls2JsonWriter) Close() {
|
||||
if w.jsonStartSend {
|
||||
if w.jsonStartSent {
|
||||
count, err := w.Target.Write([]byte("]}"))
|
||||
if count == 0 || err != nil {
|
||||
log.WithError(err).Warn("Could not Close ls2json writer")
|
||||
@ -118,22 +129,20 @@ func (w *Ls2JsonWriter) writeLine(line []byte) (count int, err error) {
|
||||
}
|
||||
|
||||
func (w *Ls2JsonWriter) parseFileHeader(matches [][]byte) ([]byte, error) {
|
||||
entryType := dto.EntryType(matches[1][0])
|
||||
permissions := string(matches[2])
|
||||
acl := string(matches[3])
|
||||
entryType := dto.EntryType(matches[headerLineGroupEntryType][0])
|
||||
permissions := string(matches[headerLineGroupPermissions])
|
||||
acl := string(matches[headerLineGroupACL])
|
||||
if acl == "+" {
|
||||
permissions += "+"
|
||||
}
|
||||
|
||||
owner := string(matches[4])
|
||||
group := string(matches[5])
|
||||
size, err1 := strconv.Atoi(string(matches[6]))
|
||||
timestamp, err2 := strconv.Atoi(string(matches[7]))
|
||||
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[8]...))
|
||||
name := dto.FilePath(append(w.latestPath, matches[headerLineGroupName]...))
|
||||
linkTarget := dto.FilePath("")
|
||||
if entryType == dto.EntryTypeLink {
|
||||
parts := strings.Split(string(name), " -> ")
|
||||
@ -153,8 +162,8 @@ func (w *Ls2JsonWriter) parseFileHeader(matches [][]byte) ([]byte, error) {
|
||||
Size: size,
|
||||
ModificationTime: timestamp,
|
||||
Permissions: permissions,
|
||||
Owner: owner,
|
||||
Group: group,
|
||||
Owner: string(matches[headerLineGroupOwner]),
|
||||
Group: string(matches[headerLineGroupGroup]),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not marshal file header: %w", err)
|
||||
|
Reference in New Issue
Block a user