From 19a1141af3f1aa52c706e402af734797d5d929ff Mon Sep 17 00:00:00 2001 From: Heinrich 'Henrik' Langos Date: Mon, 16 Jan 2023 11:33:12 +0000 Subject: [PATCH] preserve-video-filename (#49) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should fix issue #40 I have not made the `allowed_characters` configurable yet nor the `replacement_character`. Mostly because I couldn't decide if I should define those "globally" on a server basis, or on the library nodes. Feel free to modify, extend, rip apart. 😁 Reviewed-on: https://git.mills.io/prologic/tube/pulls/49 Co-authored-by: Heinrich 'Henrik' Langos Co-committed-by: Heinrich 'Henrik' Langos --- README.md | 35 ++++++++++++++++++++++++++++--- app/app.go | 58 +++++++++++++++++++++++++++++++++++++++++++++------ app/config.go | 32 +++++++++++++++------------- config.json | 4 +++- go.mod | 1 + go.sum | 2 ++ media/path.go | 5 +++-- 7 files changed, 111 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 8124ae4..5aeca5f 100644 --- a/README.md +++ b/README.md @@ -96,14 +96,37 @@ Here are some documentation on key configuration items: "library": [ { "path": "videos", - "prefix": "" + "prefix": "", + "preserve_upload_filename": false } ], } ``` -Set `path` to the value of the path where you want to store videos and where -`tube` will look for new videos. +- Set `path` to the value of the path where you want to store videos +and where `tube` will watch for new video files to show up. +- Set `prefix` to add a directory component in the video URL. +- Set the (optional) `preserve_upload_filename` parameter to `true`, +to to preserve the name of files that are uploaded to this location. + +You can add more than one location for video files. +```#!json +{ + "library": [ + { + "path": "/path/to/cat/videos", + "prefix": "cats", + "preserve_upload_filename": true + }, + { + "path": "relative/dog/directory/", + "prefix": "dogs" + }, + ], +} +``` +The path will be visible on the upload page and clients can select a +destination for their uploads. Both `prefix` and `path` need to be unique. ### Server Options / Upload Path and Max Upload Size @@ -114,6 +137,7 @@ Set `path` to the value of the path where you want to store videos and where "port": 8000, "store_path": "tube.db", "upload_path": "uploads", + "preserve_upload_filename": false, "max_upload_size": 104857600 } } @@ -129,6 +153,11 @@ Set `path` to the value of the path where you want to store videos and where - Set `upload_path` to a directory that you wish to use as a temporary working space for `tube` to store uploaded videos and process them. This can be a tmpfs file system for example for faster I/O. +- Set `preserve_upload_filename` parameter to `true` and tube will try to + preserve the filename that was transmitted by the client. The default is + to give random filenames to uploaded files. + If you set it to `true` in the "server" node, it will be active for all + library locations. - Set `max_upload_size` to the maximum number of bytes you wish to impose on uploaded and imported videos. Upload(s)/Import(s) that exceed this size will by denied by the server. This is a saftey measure so as to not DoS the diff --git a/app/app.go b/app/app.go index f2d729a..9d0828b 100644 --- a/app/app.go +++ b/app/app.go @@ -21,6 +21,7 @@ import ( "git.mills.io/prologic/tube/templates" "git.mills.io/prologic/tube/utils" + "github.com/cyphar/filepath-securejoin" "github.com/dustin/go-humanize" "github.com/fsnotify/fsnotify" "github.com/gorilla/handlers" @@ -148,8 +149,9 @@ func (a *App) Run() error { for _, pc := range a.Config.Library { pc.Path = filepath.Clean(pc.Path) p := &media.Path{ - Path: pc.Path, - Prefix: pc.Prefix, + Path: pc.Path, + Prefix: pc.Prefix, + PreserveUploadFilename: pc.PreserveUploadFilename, } err := a.Library.AddPath(p) if err != nil { @@ -215,6 +217,11 @@ func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) { } } +func filenameWithoutExtension(path string) (stem string) { + var basename string = filepath.Base(path) + return basename[0:len(basename)-len(filepath.Ext(basename))] +} + // HTTP handler for /upload func (a *App) uploadHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { @@ -278,10 +285,49 @@ func (a *App) uploadHandler(w http.ResponseWriter, r *http.Request) { return } - vf := filepath.Join( - a.Library.Paths[targetLibraryPath].Path, - fmt.Sprintf("%s.mp4", shortuuid.New()), - ) + // Here we set the final filename for the video file after transcoding. + var vf string + if a.Config.Server.PreserveUploadFilename || + a.Library.Paths[targetLibraryPath].PreserveUploadFilename { + vf, err = securejoin.SecureJoin( + a.Library.Paths[targetLibraryPath].Path, + fmt.Sprintf("%s.mp4", filenameWithoutExtension(handler.Filename)), + ) + } else { + vf, err = securejoin.SecureJoin( + a.Library.Paths[targetLibraryPath].Path, + fmt.Sprintf("%s.mp4", shortuuid.New()), + ) + } + if err != nil { + err := fmt.Errorf("error creating file name in target library: %w", err) + log.Error(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + // If the (sanitized) original filename collides with an existing file, + // we try to add a shortuuid() to it until we find one that doesn't exist. + for _, err := os.Stat(vf) ; ! os.IsNotExist(err) ; _, err = os.Stat(vf) { + if err != nil { + log.Error(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + log.Warn("File '"+ vf + "' already exists."); + vf, err = securejoin.SecureJoin( + a.Library.Paths[targetLibraryPath].Path, + fmt.Sprintf("%s_%s.mp4", filenameWithoutExtension(vf), shortuuid.New()), + ) + if err != nil { + err := fmt.Errorf("error creating file name in target library: %w", err) + log.Error(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + log.Warn("Using filename '" + vf + "' instead."); + } + + thumbFn1 := fmt.Sprintf("%s.jpg", strings.TrimSuffix(tf.Name(), filepath.Ext(tf.Name()))) thumbFn2 := fmt.Sprintf("%s.jpg", strings.TrimSuffix(vf, filepath.Ext(vf))) diff --git a/app/config.go b/app/config.go index 2b11688..7d886c0 100644 --- a/app/config.go +++ b/app/config.go @@ -17,17 +17,19 @@ type Config struct { // PathConfig settings for media library path. type PathConfig struct { - Path string `json:"path"` - Prefix string `json:"prefix"` + Path string `json:"path"` + Prefix string `json:"prefix"` + PreserveUploadFilename bool `json:"preserve_upload_filename,omitempty"` } // ServerConfig settings for App Server. type ServerConfig struct { - Host string `json:"host"` - Port int `json:"port"` - StorePath string `json:"store_path"` - UploadPath string `json:"upload_path"` - MaxUploadSize int64 `json:"max_upload_size"` + Host string `json:"host"` + Port int `json:"port"` + StorePath string `json:"store_path"` + UploadPath string `json:"upload_path"` + PreserveUploadFilename bool `json:"preserve_upload_filename,omitempty"` + MaxUploadSize int64 `json:"max_upload_size"` } // ThumbnailerConfig settings for Transcoder @@ -68,16 +70,18 @@ func DefaultConfig() *Config { return &Config{ Library: []*PathConfig{ &PathConfig{ - Path: "videos", - Prefix: "", + Path: "videos", + Prefix: "", + PreserveUploadFilename: false, }, }, Server: &ServerConfig{ - Host: "0.0.0.0", - Port: 8000, - StorePath: "tube.db", - UploadPath: "uploads", - MaxUploadSize: 104857600, + Host: "0.0.0.0", + Port: 8000, + StorePath: "tube.db", + UploadPath: "uploads", + PreserveUploadFilename: false, + MaxUploadSize: 104857600, }, Thumbnailer: &ThumbnailerConfig{ Timeout: 60, diff --git a/config.json b/config.json index eb43aac..bb764f4 100644 --- a/config.json +++ b/config.json @@ -2,7 +2,8 @@ "library": [ { "path": "videos", - "prefix": "" + "prefix": "", + "preserve_upload_filename": false } ], "server": { @@ -10,6 +11,7 @@ "port": 8000, "store_path": "tube.db", "upload_path": "uploads", + "preserve_upload_filename": false, "max_upload_size": 104857600 }, "thumbnailer": { diff --git a/go.mod b/go.mod index 1d9352f..879b5b6 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81 // indirect github.com/antchfx/jsonquery v1.3.0 // indirect github.com/antchfx/xpath v1.2.1 // indirect + github.com/cyphar/filepath-securejoin v0.2.3 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect diff --git a/go.sum b/go.sum index 4a3c321..29bd1ac 100644 --- a/go.sum +++ b/go.sum @@ -85,6 +85,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/media/path.go b/media/path.go index 1248ed5..bc40b37 100644 --- a/media/path.go +++ b/media/path.go @@ -2,6 +2,7 @@ package media // Path represents a media library path. type Path struct { - Path string - Prefix string + Path string + Prefix string + PreserveUploadFilename bool }