diff --git a/go.mod b/go.mod index 2d88d57..fee2bba 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( github.com/Luzifer/rconfig/v2 v2.3.0 github.com/aws/aws-sdk-go v1.40.30 github.com/cheggaaa/pb v1.0.29 + github.com/gofrs/uuid v4.1.0+incompatible + github.com/gosimple/slug v1.11.0 github.com/pkg/errors v0.9.1 github.com/rivo/uniseg v0.2.0 // indirect github.com/sirupsen/logrus v1.8.1 @@ -13,6 +15,7 @@ require ( require ( github.com/Luzifer/rconfig v1.2.0 // indirect + github.com/gosimple/unidecode v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/onsi/ginkgo v1.16.4 // indirect diff --git a/go.sum b/go.sum index 7efa8bf..1a1a724 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gofrs/uuid v4.1.0+incompatible h1:sIa2eCvUTwgjbqXrPLfNwUf9S3i3mpH1O1atV+iL/Wk= +github.com/gofrs/uuid v4.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -28,6 +30,10 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gosimple/slug v1.11.0 h1:QkFeOkXIEDvvtIt++P7cUuO4G9PZVQEgLuYbYZzawMA= +github.com/gosimple/slug v1.11.0/go.mod h1:MICb3w495l9KNdZm+Xn5b6T2Hn831f9DMxiJ1r+bAjw= +github.com/gosimple/unidecode v1.0.0 h1:kPdvM+qy0tnk4/BrnkrbdJ82xe88xn7c9hcaipDz4dQ= +github.com/gosimple/unidecode v1.0.0/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= diff --git a/main.go b/main.go index 470d4b8..51fc3e6 100644 --- a/main.go +++ b/main.go @@ -17,12 +17,13 @@ import ( var ( cfg = struct { - BaseURL string `flag:"base-url" env:"BASE_URL" default:"" description:"URL to prepend before filename"` - BasePath string `flag:"base-path" env:"BASE_PATH" default:"file/{{ printf \"%.6s\" .Hash }}" description:"Path to upload the file to"` + BaseURL string `flag:"base-url" default:"" description:"URL to prepend before filename"` + BasePath string `flag:"base-path" default:"" description:"DEPRECATED: Path to upload the file to"` Bootstrap bool `flag:"bootstrap" default:"false" description:"Upload frontend files into bucket"` - Bucket string `flag:"bucket" env:"BUCKET" default:"" description:"S3 bucket to upload files to" validate:"nonzero"` - ContentType string `flag:"content-type,c" default:"" description:"Force content-type to be set to this value"` - Listen string `flag:"listen" env:"LISTEN" default:"" description:"Enable HTTP server if set to IP/Port (e.g. ':3000')"` + Bucket string `flag:"bucket" default:"" description:"S3 bucket to upload files to" validate:"nonzero"` + ContentType string `flag:"content-type,c" vardefault:"file_template" description:"Force content-type to be set to this value"` + FileTemplate string `flag:"file-template" default:"" description:"Full name template of the uploaded file"` + Listen string `flag:"listen" default:"" description:"Enable HTTP server if set to IP/Port (e.g. ':3000')"` Progress bool `flag:"progress" default:"false" description:"Show progress bar while uploading"` VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"` }{} @@ -33,8 +34,12 @@ var ( version = "dev" ) -func init() { +func initApp() { rconfig.AutoEnv(true) + rconfig.SetVariableDefaults(map[string]string{ + "file_template": `file/{{ printf "%.6s" .Hash }}/{{ .SafeFileName }}`, + }) + if err := rconfig.ParseAndValidate(&cfg); err != nil { log.Fatalf("Unable to parse commandline options: %s", err) } @@ -43,9 +48,16 @@ func init() { fmt.Printf("share %s\n", version) os.Exit(0) } + + if cfg.BasePath != "" { + cfg.FileTemplate = strings.Join([]string{strings.TrimRight(cfg.BasePath, "/"), `{{ .SafeFileName }}`}, "/") + log.WithField("file-template", cfg.FileTemplate).Warn("Using deprecated base-path parameter! Using update file-template...") + } } func main() { + initApp() + switch { case cfg.Bootstrap: @@ -81,10 +93,12 @@ func doCLIUpload() error { inFileName := rconfig.Args()[1] if inFileName == "-" { - inFileName = "stdin" if cfg.ContentType == "" { // If we don't have an explicitly set content-type assume stdin contains text + inFileName = "stdin" cfg.ContentType = "text/plain" + } else if ext, err := mimeResolver.ExtensionsByType(cfg.ContentType); err == nil { + inFileName = strings.Join([]string{"stdin", ext}, "") } // Stdin is not seekable, so we need to buffer it diff --git a/mime.go b/mime.go new file mode 100644 index 0000000..ce9dc0e --- /dev/null +++ b/mime.go @@ -0,0 +1,37 @@ +package main + +import ( + "mime" + "strings" + + "github.com/pkg/errors" +) + +type mimeDB map[string]string + +// mimeResolver contains some well-known mime-types and falls back +// to mime package to resolve the extension if no internal override +// is known for the given mime-type +var mimeResolver = mimeDB{ + "text/plain": ".txt", // Detected as .asc when using mime package +} + +func (m mimeDB) ExtensionsByType(t string) (string, error) { + if v, ok := m[t]; ok { + return v, nil + } + + exts, err := mime.ExtensionsByType(t) + if err != nil { + return "", err + } + + for _, ext := range exts { + if !strings.HasPrefix(ext, ".") { + continue + } + return ext, nil + } + + return "", errors.New("no extension found") +} diff --git a/upload.go b/upload.go index 7a2353a..3728044 100644 --- a/upload.go +++ b/upload.go @@ -17,6 +17,8 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/cheggaaa/pb" + "github.com/gofrs/uuid" + "github.com/gosimple/slug" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) @@ -101,18 +103,22 @@ func executeUpload(inFileName string, inFileHandle io.ReadSeeker, useCalculatedF } func calculateUploadFilename(inFile string, inFileHandle io.ReadSeeker) (string, error) { - upFile := path.Join( - cfg.BasePath, - strings.Replace(path.Base(inFile), " ", "_", -1), - ) - fileHash, err := hashFile(inFileHandle) if err != nil { return "", err } - return executeTemplate(upFile, map[string]interface{}{ - "Hash": fileHash, + safeFileName := strings.Join([]string{ + slug.Make(strings.TrimSuffix(path.Base(inFile), path.Ext(inFile))), + path.Ext(inFile), + }, "") + + return executeTemplate(cfg.FileTemplate, map[string]interface{}{ + "Ext": path.Ext(inFile), + "FileName": path.Base(inFile), + "Hash": fileHash, + "SafeFileName": safeFileName, + "UUID": uuid.Must(uuid.NewV4()).String(), }) } @@ -130,7 +136,7 @@ func hashFile(inFileHandle io.ReadSeeker) (string, error) { } func executeTemplate(tplStr string, vars map[string]interface{}) (string, error) { - tpl, err := template.New("basepath").Parse(tplStr) + tpl, err := template.New("filename").Parse(tplStr) if err != nil { return "", err }