Merge branch 'master' of github.com:tus/tusd
This commit is contained in:
commit
bea4d10ae6
|
@ -4,9 +4,6 @@
|
||||||
[submodule "vendor/github.com/nightlyone/lockfile"]
|
[submodule "vendor/github.com/nightlyone/lockfile"]
|
||||||
path = vendor/github.com/nightlyone/lockfile
|
path = vendor/github.com/nightlyone/lockfile
|
||||||
url = https://github.com/nightlyone/lockfile
|
url = https://github.com/nightlyone/lockfile
|
||||||
[submodule "vendor/github.com/aws/aws-sdk-go"]
|
|
||||||
path = vendor/github.com/aws/aws-sdk-go
|
|
||||||
url = https://github.com/aws/aws-sdk-go
|
|
||||||
[submodule "vendor/github.com/go-ini/ini"]
|
[submodule "vendor/github.com/go-ini/ini"]
|
||||||
path = vendor/github.com/go-ini/ini
|
path = vendor/github.com/go-ini/ini
|
||||||
url = https://github.com/go-ini/ini
|
url = https://github.com/go-ini/ini
|
||||||
|
@ -25,9 +22,6 @@
|
||||||
[submodule "vendor/github.com/pmezard/go-difflib"]
|
[submodule "vendor/github.com/pmezard/go-difflib"]
|
||||||
path = vendor/github.com/pmezard/go-difflib
|
path = vendor/github.com/pmezard/go-difflib
|
||||||
url = https://github.com/pmezard/go-difflib
|
url = https://github.com/pmezard/go-difflib
|
||||||
[submodule "vendor/github.com/hashicorp/consul"]
|
|
||||||
path = vendor/github.com/hashicorp/consul
|
|
||||||
url = https://github.com/hashicorp/consul
|
|
||||||
[submodule "vendor/github.com/hashicorp/go-cleanhttp"]
|
[submodule "vendor/github.com/hashicorp/go-cleanhttp"]
|
||||||
path = vendor/github.com/hashicorp/go-cleanhttp
|
path = vendor/github.com/hashicorp/go-cleanhttp
|
||||||
url = https://github.com/hashicorp/go-cleanhttp
|
url = https://github.com/hashicorp/go-cleanhttp
|
||||||
|
|
|
@ -4,6 +4,7 @@ go:
|
||||||
- 1.4
|
- 1.4
|
||||||
- 1.5
|
- 1.5
|
||||||
- 1.6
|
- 1.6
|
||||||
|
- 1.7
|
||||||
- tip
|
- tip
|
||||||
sudo: required
|
sudo: required
|
||||||
cache:
|
cache:
|
||||||
|
@ -22,6 +23,7 @@ matrix:
|
||||||
install:
|
install:
|
||||||
- export PACKAGES=$(find ./ -maxdepth 1 -type d -not \( -name ".git" -or -name "cmd" -or -name ".infra" -or -name "vendor" -or -name "data" -or -name ".hooks" \))
|
- export PACKAGES=$(find ./ -maxdepth 1 -type d -not \( -name ".git" -or -name "cmd" -or -name ".infra" -or -name "vendor" -or -name "data" -or -name ".hooks" \))
|
||||||
- rsync -r ./vendor/ $GOPATH/src
|
- rsync -r ./vendor/ $GOPATH/src
|
||||||
|
- go get $PACKAGES
|
||||||
script:
|
script:
|
||||||
- go test $PACKAGES
|
- go test $PACKAGES
|
||||||
before_deploy:
|
before_deploy:
|
||||||
|
|
|
@ -25,7 +25,7 @@ func CreateComposer() {
|
||||||
dir := Flags.UploadDir
|
dir := Flags.UploadDir
|
||||||
|
|
||||||
stdout.Printf("Using '%s' as directory storage.\n", dir)
|
stdout.Printf("Using '%s' as directory storage.\n", dir)
|
||||||
if err := os.MkdirAll(dir, os.FileMode(0775)); err != nil {
|
if err := os.MkdirAll(dir, os.FileMode(0774)); err != nil {
|
||||||
stderr.Fatalf("Unable to ensure directory exists: %s", err)
|
stderr.Fatalf("Unable to ensure directory exists: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ var Flags struct {
|
||||||
HooksDir string
|
HooksDir string
|
||||||
ShowVersion bool
|
ShowVersion bool
|
||||||
ExposeMetrics bool
|
ExposeMetrics bool
|
||||||
|
MetricsPath string
|
||||||
BehindProxy bool
|
BehindProxy bool
|
||||||
|
|
||||||
HooksInstalled bool
|
HooksInstalled bool
|
||||||
|
@ -34,6 +35,7 @@ func ParseFlags() {
|
||||||
flag.StringVar(&Flags.HooksDir, "hooks-dir", "", "Directory to search for available hooks scripts")
|
flag.StringVar(&Flags.HooksDir, "hooks-dir", "", "Directory to search for available hooks scripts")
|
||||||
flag.BoolVar(&Flags.ShowVersion, "version", false, "Print tusd version information")
|
flag.BoolVar(&Flags.ShowVersion, "version", false, "Print tusd version information")
|
||||||
flag.BoolVar(&Flags.ExposeMetrics, "expose-metrics", true, "Expose metrics about tusd usage")
|
flag.BoolVar(&Flags.ExposeMetrics, "expose-metrics", true, "Expose metrics about tusd usage")
|
||||||
|
flag.StringVar(&Flags.MetricsPath, "metrics-path", "/metrics", "Path under which the metrics endpoint will be accessible")
|
||||||
flag.BoolVar(&Flags.BehindProxy, "behind-proxy", false, "Respect X-Forwarded-* and similar headers which may be set by proxies")
|
flag.BoolVar(&Flags.BehindProxy, "behind-proxy", false, "Respect X-Forwarded-* and similar headers which may be set by proxies")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
|
@ -12,19 +12,23 @@ func PrepareGreeting() {
|
||||||
`Welcome to tusd
|
`Welcome to tusd
|
||||||
===============
|
===============
|
||||||
|
|
||||||
Congratulations for setting up tusd! You are now part of the chosen elite and
|
Congratulations on setting up tusd! Thanks for joining our cause, you have taken
|
||||||
able to experience the feeling of resumable uploads! We hope you are as excited
|
the first step towards making the future of resumable uploading a reality! We
|
||||||
as we are (a lot)!
|
hope you are as excited about this as we are!
|
||||||
|
|
||||||
However, there is something you should be aware of: While you got tusd
|
While you did an awesome job on getting tusd running, this is just the welcome
|
||||||
running (you did an awesome job!), this is the root directory of the server
|
message, so let's talk about the places that really matter:
|
||||||
and tus requests are only accepted at the %s route.
|
|
||||||
|
|
||||||
So don't waste time, head over there and experience the future!
|
- %s - send your tus uploads to this endpoint
|
||||||
|
- %s - gather statistics to keep tusd running smoothly
|
||||||
|
- https://github.com/tus/tusd/issues - report your bugs here
|
||||||
|
|
||||||
|
So quit lollygagging, send over your files and experience the future!
|
||||||
|
|
||||||
Version = %s
|
Version = %s
|
||||||
GitCommit = %s
|
GitCommit = %s
|
||||||
BuildDate = %s`, Flags.Basepath, VersionName, GitCommit, BuildDate)
|
BuildDate = %s
|
||||||
|
`, Flags.Basepath, Flags.MetricsPath, VersionName, GitCommit, BuildDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DisplayGreeting(w http.ResponseWriter, r *http.Request) {
|
func DisplayGreeting(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -18,5 +18,6 @@ func SetupMetrics(handler *tusd.Handler) {
|
||||||
prometheus.MustRegister(MetricsOpenConnections)
|
prometheus.MustRegister(MetricsOpenConnections)
|
||||||
prometheus.MustRegister(prometheuscollector.New(handler.Metrics))
|
prometheus.MustRegister(prometheuscollector.New(handler.Metrics))
|
||||||
|
|
||||||
http.Handle("/metrics", prometheus.Handler())
|
stdout.Printf("Using %s as the metrics path.\n", Flags.MetricsPath)
|
||||||
|
http.Handle(Flags.MetricsPath, prometheus.Handler())
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ func Serve() {
|
||||||
|
|
||||||
stdout.Printf("Using %s as address to listen.\n", address)
|
stdout.Printf("Using %s as address to listen.\n", address)
|
||||||
stdout.Printf("Using %s as the base path.\n", basepath)
|
stdout.Printf("Using %s as the base path.\n", basepath)
|
||||||
stdout.Printf(Composer.Capabilities())
|
|
||||||
|
|
||||||
SetupPostHooks(handler)
|
SetupPostHooks(handler)
|
||||||
|
|
||||||
|
@ -35,6 +34,8 @@ func Serve() {
|
||||||
SetupMetrics(handler)
|
SetupMetrics(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stdout.Printf(Composer.Capabilities())
|
||||||
|
|
||||||
// Do not display the greeting if the tusd handler will be mounted at the root
|
// Do not display the greeting if the tusd handler will be mounted at the root
|
||||||
// path. Else this would cause a "multiple registrations for /" panic.
|
// path. Else this would cause a "multiple registrations for /" panic.
|
||||||
if basepath != "/" {
|
if basepath != "/" {
|
||||||
|
|
|
@ -46,7 +46,7 @@ func TestConcatPartial(t *testing.T) {
|
||||||
Method: "OPTIONS",
|
Method: "OPTIONS",
|
||||||
URL: "",
|
URL: "",
|
||||||
ResHeader: map[string]string{
|
ResHeader: map[string]string{
|
||||||
"Tus-Extension": "creation,concatenation",
|
"Tus-Extension": "creation,creation-with-upload,concatenation",
|
||||||
},
|
},
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
}).Run(handler, t)
|
}).Run(handler, t)
|
||||||
|
@ -69,7 +69,7 @@ func TestConcatPartial(t *testing.T) {
|
||||||
ReqHeader: map[string]string{
|
ReqHeader: map[string]string{
|
||||||
"Tus-Resumable": "1.0.0",
|
"Tus-Resumable": "1.0.0",
|
||||||
},
|
},
|
||||||
Code: http.StatusNoContent,
|
Code: http.StatusOK,
|
||||||
ResHeader: map[string]string{
|
ResHeader: map[string]string{
|
||||||
"Upload-Concat": "partial",
|
"Upload-Concat": "partial",
|
||||||
},
|
},
|
||||||
|
@ -165,7 +165,7 @@ func TestConcatFinal(t *testing.T) {
|
||||||
ReqHeader: map[string]string{
|
ReqHeader: map[string]string{
|
||||||
"Tus-Resumable": "1.0.0",
|
"Tus-Resumable": "1.0.0",
|
||||||
},
|
},
|
||||||
Code: http.StatusNoContent,
|
Code: http.StatusOK,
|
||||||
ResHeader: map[string]string{
|
ResHeader: map[string]string{
|
||||||
"Upload-Concat": "final; http://tus.io/files/a http://tus.io/files/b",
|
"Upload-Concat": "final; http://tus.io/files/a http://tus.io/files/b",
|
||||||
"Upload-Length": "10",
|
"Upload-Length": "10",
|
||||||
|
|
|
@ -28,7 +28,7 @@ import (
|
||||||
"github.com/nightlyone/lockfile"
|
"github.com/nightlyone/lockfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultFilePerm = os.FileMode(0775)
|
var defaultFilePerm = os.FileMode(0664)
|
||||||
|
|
||||||
// See the tusd.DataStore interface for documentation about the different
|
// See the tusd.DataStore interface for documentation about the different
|
||||||
// methods.
|
// methods.
|
||||||
|
|
|
@ -40,7 +40,7 @@ func TestHead(t *testing.T) {
|
||||||
ReqHeader: map[string]string{
|
ReqHeader: map[string]string{
|
||||||
"Tus-Resumable": "1.0.0",
|
"Tus-Resumable": "1.0.0",
|
||||||
},
|
},
|
||||||
Code: http.StatusNoContent,
|
Code: http.StatusOK,
|
||||||
ResHeader: map[string]string{
|
ResHeader: map[string]string{
|
||||||
"Upload-Offset": "11",
|
"Upload-Offset": "11",
|
||||||
"Upload-Length": "44",
|
"Upload-Length": "44",
|
||||||
|
|
|
@ -20,7 +20,7 @@ func TestOptions(t *testing.T) {
|
||||||
Method: "OPTIONS",
|
Method: "OPTIONS",
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
ResHeader: map[string]string{
|
ResHeader: map[string]string{
|
||||||
"Tus-Extension": "creation",
|
"Tus-Extension": "creation,creation-with-upload",
|
||||||
"Tus-Version": "1.0.0",
|
"Tus-Version": "1.0.0",
|
||||||
"Tus-Resumable": "1.0.0",
|
"Tus-Resumable": "1.0.0",
|
||||||
"Tus-Max-Size": "400",
|
"Tus-Max-Size": "400",
|
||||||
|
|
114
post_test.go
114
post_test.go
|
@ -1,44 +1,57 @@
|
||||||
package tusd_test
|
package tusd_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
. "github.com/tus/tusd"
|
. "github.com/tus/tusd"
|
||||||
)
|
)
|
||||||
|
|
||||||
type postStore struct {
|
type postStore struct {
|
||||||
t *testing.T
|
t *assert.Assertions
|
||||||
zeroStore
|
zeroStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s postStore) NewUpload(info FileInfo) (string, error) {
|
func (s postStore) NewUpload(info FileInfo) (string, error) {
|
||||||
if info.Size != 300 {
|
s.t.Equal(int64(300), info.Size)
|
||||||
s.t.Errorf("Expected size to be 300 (got %v)", info.Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
metaData := info.MetaData
|
metaData := info.MetaData
|
||||||
if len(metaData) != 2 {
|
s.t.Equal(2, len(metaData))
|
||||||
s.t.Errorf("Expected two elements in metadata")
|
s.t.Equal("hello", metaData["foo"])
|
||||||
}
|
s.t.Equal("world", metaData["bar"])
|
||||||
|
|
||||||
if v := metaData["foo"]; v != "hello" {
|
|
||||||
s.t.Errorf("Expected foo element to be 'hello' but got %s", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v := metaData["bar"]; v != "world" {
|
|
||||||
s.t.Errorf("Expected bar element to be 'world' but got %s", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return "foo", nil
|
return "foo", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s postStore) WriteChunk(id string, offset int64, src io.Reader) (int64, error) {
|
||||||
|
s.t.Equal(int64(0), offset)
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(src)
|
||||||
|
s.t.Nil(err)
|
||||||
|
s.t.Equal("hello", string(data))
|
||||||
|
|
||||||
|
return 5, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s postStore) ConcatUploads(id string, uploads []string) error {
|
||||||
|
s.t.True(false, "concatenation should not be attempted")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestPost(t *testing.T) {
|
func TestPost(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
handler, _ := NewHandler(Config{
|
handler, _ := NewHandler(Config{
|
||||||
MaxSize: 400,
|
MaxSize: 400,
|
||||||
BasePath: "files",
|
BasePath: "files",
|
||||||
DataStore: postStore{
|
DataStore: postStore{
|
||||||
t: t,
|
t: a,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -87,7 +100,7 @@ func TestPost(t *testing.T) {
|
||||||
MaxSize: 400,
|
MaxSize: 400,
|
||||||
BasePath: "files",
|
BasePath: "files",
|
||||||
DataStore: postStore{
|
DataStore: postStore{
|
||||||
t: t,
|
t: a,
|
||||||
},
|
},
|
||||||
RespectForwardedHeaders: true,
|
RespectForwardedHeaders: true,
|
||||||
})
|
})
|
||||||
|
@ -141,3 +154,70 @@ func TestPost(t *testing.T) {
|
||||||
},
|
},
|
||||||
}).Run(handler, t)
|
}).Run(handler, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPostWithUpload(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
handler, _ := NewHandler(Config{
|
||||||
|
MaxSize: 400,
|
||||||
|
BasePath: "files",
|
||||||
|
DataStore: postStore{
|
||||||
|
t: a,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
(&httpTest{
|
||||||
|
Name: "Successful request",
|
||||||
|
Method: "POST",
|
||||||
|
ReqHeader: map[string]string{
|
||||||
|
"Tus-Resumable": "1.0.0",
|
||||||
|
"Upload-Length": "300",
|
||||||
|
"Content-Type": "application/offset+octet-stream",
|
||||||
|
"Upload-Metadata": "foo aGVsbG8=, bar d29ybGQ=",
|
||||||
|
},
|
||||||
|
ReqBody: strings.NewReader("hello"),
|
||||||
|
Code: http.StatusCreated,
|
||||||
|
ResHeader: map[string]string{
|
||||||
|
"Location": "http://tus.io/files/foo",
|
||||||
|
"Upload-Offset": "5",
|
||||||
|
},
|
||||||
|
}).Run(handler, t)
|
||||||
|
|
||||||
|
(&httpTest{
|
||||||
|
Name: "Exceeding upload size",
|
||||||
|
Method: "POST",
|
||||||
|
ReqHeader: map[string]string{
|
||||||
|
"Tus-Resumable": "1.0.0",
|
||||||
|
"Upload-Length": "300",
|
||||||
|
"Content-Type": "application/offset+octet-stream",
|
||||||
|
"Upload-Metadata": "foo aGVsbG8=, bar d29ybGQ=",
|
||||||
|
},
|
||||||
|
ReqBody: bytes.NewReader(make([]byte, 400)),
|
||||||
|
Code: http.StatusRequestEntityTooLarge,
|
||||||
|
}).Run(handler, t)
|
||||||
|
|
||||||
|
(&httpTest{
|
||||||
|
Name: "Incorrect content type",
|
||||||
|
Method: "POST",
|
||||||
|
ReqHeader: map[string]string{
|
||||||
|
"Tus-Resumable": "1.0.0",
|
||||||
|
"Content-Type": "application/false",
|
||||||
|
},
|
||||||
|
ReqBody: strings.NewReader("hello"),
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
}).Run(handler, t)
|
||||||
|
|
||||||
|
(&httpTest{
|
||||||
|
Name: "Upload and final concatenation",
|
||||||
|
Method: "POST",
|
||||||
|
ReqHeader: map[string]string{
|
||||||
|
"Tus-Resumable": "1.0.0",
|
||||||
|
"Upload-Length": "300",
|
||||||
|
"Content-Type": "application/offset+octet-stream",
|
||||||
|
"Upload-Metadata": "foo aGVsbG8=, bar d29ybGQ=",
|
||||||
|
"Upload-Concat": "final; http://tus.io/files/a http://tus.io/files/b",
|
||||||
|
},
|
||||||
|
ReqBody: strings.NewReader("hello"),
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
}).Run(handler, t)
|
||||||
|
}
|
||||||
|
|
|
@ -907,6 +907,16 @@ func (_mr *_MockS3APIRecorder) ListObjectsV2(arg0 interface{}) *gomock.Call {
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "ListObjectsV2", arg0)
|
return _mr.mock.ctrl.RecordCall(_mr.mock, "ListObjectsV2", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (_m *MockS3API) ListObjectsV2Pages(_param0 *s3.ListObjectsV2Input, _param1 func(*s3.ListObjectsV2Output, bool) bool) error {
|
||||||
|
ret := _m.ctrl.Call(_m, "ListObjectsV2Pages", _param0, _param1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_mr *_MockS3APIRecorder) ListObjectsV2Pages(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
return _mr.mock.ctrl.RecordCall(_mr.mock, "ListObjectsV2Pages", arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
func (_m *MockS3API) ListObjectsV2Request(_param0 *s3.ListObjectsV2Input) (*request.Request, *s3.ListObjectsV2Output) {
|
func (_m *MockS3API) ListObjectsV2Request(_param0 *s3.ListObjectsV2Input) (*request.Request, *s3.ListObjectsV2Output) {
|
||||||
ret := _m.ctrl.Call(_m, "ListObjectsV2Request", _param0)
|
ret := _m.ctrl.Call(_m, "ListObjectsV2Request", _param0)
|
||||||
ret0, _ := ret[0].(*request.Request)
|
ret0, _ := ret[0].(*request.Request)
|
||||||
|
|
|
@ -44,7 +44,7 @@ func TestTerminate(t *testing.T) {
|
||||||
Method: "OPTIONS",
|
Method: "OPTIONS",
|
||||||
URL: "",
|
URL: "",
|
||||||
ResHeader: map[string]string{
|
ResHeader: map[string]string{
|
||||||
"Tus-Extension": "creation,termination",
|
"Tus-Extension": "creation,creation-with-upload,termination",
|
||||||
},
|
},
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
}).Run(handler, t)
|
}).Run(handler, t)
|
||||||
|
|
|
@ -89,7 +89,7 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only promote extesions using the Tus-Extension header which are implemented
|
// Only promote extesions using the Tus-Extension header which are implemented
|
||||||
extensions := "creation"
|
extensions := "creation,creation-with-upload"
|
||||||
if config.StoreComposer.UsesTerminater {
|
if config.StoreComposer.UsesTerminater {
|
||||||
extensions += ",termination"
|
extensions += ",termination"
|
||||||
}
|
}
|
||||||
|
@ -191,6 +191,16 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
|
||||||
// PostFile creates a new file upload using the datastore after validating the
|
// PostFile creates a new file upload using the datastore after validating the
|
||||||
// length and parsing the metadata.
|
// length and parsing the metadata.
|
||||||
func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) {
|
func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Check for presence of application/offset+octet-stream
|
||||||
|
containsChunk := false
|
||||||
|
if contentType := r.Header.Get("Content-Type"); contentType != "" {
|
||||||
|
if contentType != "application/offset+octet-stream" {
|
||||||
|
handler.sendError(w, r, ErrInvalidContentType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
containsChunk = true
|
||||||
|
}
|
||||||
|
|
||||||
// Only use the proper Upload-Concat header if the concatenation extension
|
// Only use the proper Upload-Concat header if the concatenation extension
|
||||||
// is even supported by the data store.
|
// is even supported by the data store.
|
||||||
var concatHeader string
|
var concatHeader string
|
||||||
|
@ -210,6 +220,12 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request)
|
||||||
// Upload-Length header)
|
// Upload-Length header)
|
||||||
var size int64
|
var size int64
|
||||||
if isFinal {
|
if isFinal {
|
||||||
|
// A final upload must not contain a chunk within the creation request
|
||||||
|
if containsChunk {
|
||||||
|
handler.sendError(w, r, ErrModifyFinal)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
size, err = handler.sizeOfUploads(partialUploads)
|
size, err = handler.sizeOfUploads(partialUploads)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
|
@ -246,6 +262,13 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the Location header directly after creating the new resource to even
|
||||||
|
// include it in cases of failure when an error is returned
|
||||||
|
url := handler.absFileURL(r, id)
|
||||||
|
w.Header().Set("Location", url)
|
||||||
|
|
||||||
|
go handler.Metrics.incUploadsCreated()
|
||||||
|
|
||||||
if isFinal {
|
if isFinal {
|
||||||
if err := handler.composer.Concater.ConcatUploads(id, partialUploads); err != nil {
|
if err := handler.composer.Concater.ConcatUploads(id, partialUploads); err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
|
@ -257,15 +280,26 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request)
|
||||||
info.ID = id
|
info.ID = id
|
||||||
handler.CompleteUploads <- info
|
handler.CompleteUploads <- info
|
||||||
}
|
}
|
||||||
|
|
||||||
go handler.Metrics.incUploadsFinished()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
url := handler.absFileURL(r, id)
|
if containsChunk {
|
||||||
w.Header().Set("Location", url)
|
if handler.composer.UsesLocker {
|
||||||
w.WriteHeader(http.StatusCreated)
|
locker := handler.composer.Locker
|
||||||
|
if err := locker.LockUpload(id); err != nil {
|
||||||
|
handler.sendError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
go handler.Metrics.incUploadsCreated()
|
defer locker.UnlockUpload(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := handler.writeChunk(id, info, w, r); err != nil {
|
||||||
|
handler.sendError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeadFile returns the length and offset for the HEAD request
|
// HeadFile returns the length and offset for the HEAD request
|
||||||
|
@ -313,7 +347,7 @@ func (handler *UnroutedHandler) HeadFile(w http.ResponseWriter, r *http.Request)
|
||||||
w.Header().Set("Cache-Control", "no-store")
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
w.Header().Set("Upload-Length", strconv.FormatInt(info.Size, 10))
|
w.Header().Set("Upload-Length", strconv.FormatInt(info.Size, 10))
|
||||||
w.Header().Set("Upload-Offset", strconv.FormatInt(info.Offset, 10))
|
w.Header().Set("Upload-Offset", strconv.FormatInt(info.Offset, 10))
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PatchFile adds a chunk to an upload. Only allowed enough space is left.
|
// PatchFile adds a chunk to an upload. Only allowed enough space is left.
|
||||||
|
@ -372,13 +406,23 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := handler.writeChunk(id, info, w, r); err != nil {
|
||||||
|
handler.sendError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchFile adds a chunk to an upload. Only allowed enough space is left.
|
||||||
|
func (handler *UnroutedHandler) writeChunk(id string, info FileInfo, w http.ResponseWriter, r *http.Request) error {
|
||||||
// Get Content-Length if possible
|
// Get Content-Length if possible
|
||||||
length := r.ContentLength
|
length := r.ContentLength
|
||||||
|
offset := info.Offset
|
||||||
|
|
||||||
// Test if this upload fits into the file's size
|
// Test if this upload fits into the file's size
|
||||||
if offset+length > info.Size {
|
if offset+length > info.Size {
|
||||||
handler.sendError(w, r, ErrSizeExceeded)
|
return ErrSizeExceeded
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
maxSize := info.Size - offset
|
maxSize := info.Size - offset
|
||||||
|
@ -386,13 +430,18 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request
|
||||||
maxSize = length
|
maxSize = length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bytesWritten int64
|
||||||
|
// Prevent a nil pointer derefernce when accessing the body which may not be
|
||||||
|
// available in the case of a malicious request.
|
||||||
|
if r.Body != nil {
|
||||||
// Limit the data read from the request's body to the allowed maxiumum
|
// Limit the data read from the request's body to the allowed maxiumum
|
||||||
reader := io.LimitReader(r.Body, maxSize)
|
reader := io.LimitReader(r.Body, maxSize)
|
||||||
|
|
||||||
bytesWritten, err := handler.composer.Core.WriteChunk(id, offset, reader)
|
var err error
|
||||||
|
bytesWritten, err = handler.composer.Core.WriteChunk(id, offset, reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.sendError(w, r, err)
|
return err
|
||||||
return
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send new offset to client
|
// Send new offset to client
|
||||||
|
@ -405,8 +454,7 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request
|
||||||
// ... allow custom mechanism to finish and cleanup the upload
|
// ... allow custom mechanism to finish and cleanup the upload
|
||||||
if handler.composer.UsesFinisher {
|
if handler.composer.UsesFinisher {
|
||||||
if err := handler.composer.Finisher.FinishUpload(id); err != nil {
|
if err := handler.composer.Finisher.FinishUpload(id); err != nil {
|
||||||
handler.sendError(w, r, err)
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,7 +467,7 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request
|
||||||
go handler.Metrics.incUploadsFinished()
|
go handler.Metrics.incUploadsFinished()
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFile handles requests to download a file using a GET request. This is not
|
// GetFile handles requests to download a file using a GET request. This is not
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit c76e8918e8f08490e3bb154178a84a0b2bdf8d6e
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit f6fef66e1bf17be4f3c9855fbec6de802ca6bd7d
|
|
Loading…
Reference in New Issue