diff --git a/cmd/tusd/cli/flags.go b/cmd/tusd/cli/flags.go index c12312a..072d490 100644 --- a/cmd/tusd/cli/flags.go +++ b/cmd/tusd/cli/flags.go @@ -21,6 +21,8 @@ var Flags struct { UploadDir string Basepath string ShowGreeting bool + DisableDownload bool + DisableTermination bool Timeout int64 S3Bucket string S3ObjectPrefix string @@ -68,6 +70,8 @@ func ParseFlags() { flag.StringVar(&Flags.UploadDir, "upload-dir", "./data", "Directory to store uploads in") flag.StringVar(&Flags.Basepath, "base-path", "/files/", "Basepath of the HTTP server") flag.BoolVar(&Flags.ShowGreeting, "show-greeting", true, "Show the greeting message") + flag.BoolVar(&Flags.DisableDownload, "disable-download", false, "Disable the download endpoint") + flag.BoolVar(&Flags.DisableTermination, "disable-termination", false, "Disable the termination endpoint") flag.Int64Var(&Flags.Timeout, "timeout", 6*1000, "Read timeout for connections in milliseconds. A zero value means that reads will not timeout") flag.StringVar(&Flags.S3Bucket, "s3-bucket", "", "Use AWS S3 with this bucket as storage backend (requires the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_REGION environment variables to be set)") flag.StringVar(&Flags.S3ObjectPrefix, "s3-object-prefix", "", "Prefix for S3 object names") diff --git a/cmd/tusd/cli/serve.go b/cmd/tusd/cli/serve.go index 3cbd718..30f1f73 100644 --- a/cmd/tusd/cli/serve.go +++ b/cmd/tusd/cli/serve.go @@ -27,6 +27,8 @@ func Serve() { MaxSize: Flags.MaxSize, BasePath: Flags.Basepath, RespectForwardedHeaders: Flags.BehindProxy, + DisableDownload: Flags.DisableDownload, + DisableTermination: Flags.DisableTermination, StoreComposer: Composer, NotifyCompleteUploads: true, NotifyTerminatedUploads: true, diff --git a/pkg/handler/config.go b/pkg/handler/config.go index ae9676b..dbec426 100644 --- a/pkg/handler/config.go +++ b/pkg/handler/config.go @@ -22,6 +22,12 @@ type Config struct { // absolute URL containing a scheme, e.g. "http://tus.io" BasePath string isAbs bool + // DisableDownload indicates whether the server will refuse downloads of the + // uploaded file, by not mounting the GET handler. + DisableDownload bool + // DisableTermination indicates whether the server will refuse termination + // requests of the uploaded file, by not mounting the DELETE handler. + DisableTermination bool // NotifyCompleteUploads indicates whether sending notifications about // completed uploads using the CompleteUploads channel should be enabled. NotifyCompleteUploads bool diff --git a/pkg/handler/cors_test.go b/pkg/handler/cors_test.go index 209b525..e464916 100644 --- a/pkg/handler/cors_test.go +++ b/pkg/handler/cors_test.go @@ -22,7 +22,29 @@ func TestCORS(t *testing.T) { Code: http.StatusOK, ResHeader: map[string]string{ "Access-Control-Allow-Headers": "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat", - "Access-Control-Allow-Methods": "POST, GET, HEAD, PATCH, DELETE, OPTIONS", + "Access-Control-Allow-Methods": "POST, HEAD, PATCH, OPTIONS, GET, DELETE", + "Access-Control-Max-Age": "86400", + "Access-Control-Allow-Origin": "tus.io", + }, + }).Run(handler, t) + }) + + SubTest(t, "Conditional allow methods", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + handler, _ := NewHandler(Config{ + StoreComposer: composer, + DisableTermination: true, + DisableDownload: true, + }) + + (&httpTest{ + Method: "OPTIONS", + ReqHeader: map[string]string{ + "Origin": "tus.io", + }, + Code: http.StatusOK, + ResHeader: map[string]string{ + "Access-Control-Allow-Headers": "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat", + "Access-Control-Allow-Methods": "POST, HEAD, PATCH, OPTIONS", "Access-Control-Max-Age": "86400", "Access-Control-Allow-Origin": "tus.io", }, diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index 067841f..c5030a6 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -40,10 +40,12 @@ func NewHandler(config Config) (*Handler, error) { mux.Post("", http.HandlerFunc(handler.PostFile)) mux.Head(":id", http.HandlerFunc(handler.HeadFile)) mux.Add("PATCH", ":id", http.HandlerFunc(handler.PatchFile)) - mux.Get(":id", http.HandlerFunc(handler.GetFile)) + if !config.DisableDownload { + mux.Get(":id", http.HandlerFunc(handler.GetFile)) + } // Only attach the DELETE handler if the Terminate() method is provided - if config.StoreComposer.UsesTerminater { + if config.StoreComposer.UsesTerminater && !config.DisableTermination { mux.Del(":id", http.HandlerFunc(handler.DelFile)) } diff --git a/pkg/handler/unrouted_handler.go b/pkg/handler/unrouted_handler.go index 6c5f52b..fff28e2 100644 --- a/pkg/handler/unrouted_handler.go +++ b/pkg/handler/unrouted_handler.go @@ -221,8 +221,17 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler { header.Set("Access-Control-Allow-Origin", origin) if r.Method == "OPTIONS" { + allowedMethods := "POST, HEAD, PATCH, OPTIONS" + if !handler.config.DisableDownload { + allowedMethods += ", GET" + } + + if !handler.config.DisableTermination { + allowedMethods += ", DELETE" + } + // Preflight request - header.Add("Access-Control-Allow-Methods", "POST, GET, HEAD, PATCH, DELETE, OPTIONS") + header.Add("Access-Control-Allow-Methods", allowedMethods) header.Add("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat") header.Set("Access-Control-Max-Age", "86400")