diff --git a/.travis.yml b/.travis.yml index 212857d..f863add 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,5 @@ language: go go: - - 1.1 - - 1.2 - - 1.3 - release - tip diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index bc3854c..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,308 +0,0 @@ -'use strict'; - -// # Globbing -// for performance reasons we're only matching one level down: -// 'test/spec/{,*/}*.js' -// use this if you want to match all subfolders: -// 'test/spec/**/*.js' - -module.exports = function (grunt) { - // load all grunt tasks - require('load-grunt-tasks')(grunt); - // show elapsed time at the end - require('time-grunt')(grunt); - - // configurable paths - var yeomanConfig = { - app: require('./bower.json').appPath || 'transfersh-web', - dist: 'transfersh-server/static/' - }; - - grunt.initConfig({ - yeoman: yeomanConfig, - watch: { - less: { - files: ['<%= yeoman.app %>/styles/{,*/}*.less'], - tasks: ['less'] - }, - gruntfile: { - files: ['Gruntfile.js'] - }, - includes: { - files: ['<%= yeoman.app %>/*.html', '.tmp/*.html'], - tasks: ['includes:server'] - }, - livereload: { - options: { - livereload: '<%= connect.options.livereload %>' - }, - files: [ - '<%= yeoman.app %>/*.html', - '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css', - '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js', - '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' - ], - tasks: ['includes:server'] - } - }, - connect: { - options: { - port: 9000, - // change this to '0.0.0.0' to access the server from outside - hostname: 'localhost', - livereload: 35729 - }, - livereload: { - options: { - open: true, - base: [ - '.tmp', - '<%= yeoman.app %>' - ] - } - }, - test: { - options: { - port: 9001, - base: [ - '.tmp', - 'test', - '<%= yeoman.app %>' - ] - } - }, - dist: { - options: { - base: '<%= yeoman.dist %>' - } - } - }, - clean: { - dist: { - files: [{ - dot: true, - src: [ - '.tmp', - '<%= yeoman.dist %>/*', - '!<%= yeoman.dist %>/.git*' - ] - }] - }, - server: '.tmp' - }, - jshint: { - options: { - jshintrc: '.jshintrc', - reporter: require('jshint-stylish') - }, - all: [ - 'Gruntfile.js', - '<%= yeoman.app %>/scripts/{,*/}*.js', - '!<%= yeoman.app %>/scripts/vendor/*', - 'test/spec/{,*/}*.js' - ] - }, - - - less: { - dist: { - files: { - '<%= yeoman.app %>/styles/main.css': ['<%= yeoman.app %>/styles/main.less'] - }, - options: { - sourceMap: true, - sourceMapFilename: '<%= yeoman.app %>/styles/main.css.map', - sourceMapBasepath: '<%= yeoman.app %>/', - sourceMapRootpath: '/' - } - } - }, - - includes: { - build: { - cwd: '<%= yeoman.app %>', - src: ['*.html', 'includes/*.html'], - dest: '<%= yeoman.dist %>', - options: { - flatten: true, - banner: '' - } - }, - server: { - cwd: '<%= yeoman.app %>', - src: ['*.html', 'includes/*.html'], - dest: '.tmp/', - options: { - flatten: true, - banner: '' - } - } - }, - // not used since Uglify task does concat, - // but still available if needed - /*concat: { - dist: {} - },*/ - // not enabled since usemin task does concat and uglify - // check index.html to edit your build targets - // enable this task if you prefer defining your build targets here - /*uglify: { - dist: {} - },*/ - rev: { - dist: { - files: { - src: [ - '<%= yeoman.dist %>/scripts/{,*/}*.js', - '<%= yeoman.dist %>/styles/{,*/}*.css', - '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', - '<%= yeoman.dist %>/fonts/{,*/}*.*' - ] - } - } - }, - useminPrepare: { - html: '<%= yeoman.app %>/*.html', - options: { - dest: '<%= yeoman.dist %>' - } - }, - usemin: { - html: ['<%= yeoman.dist %>/{,*/}*.html'], - css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], - options: { - dirs: ['<%= yeoman.dist %>'] - } - }, - imagemin: { - dist: { - files: [{ - expand: true, - cwd: '<%= yeoman.app %>/images', - src: '{,*/}*.{png,jpg,jpeg}', - dest: '<%= yeoman.dist %>/images' - }] - } - }, - - cssmin: { - dist: { - files: { - '<%= yeoman.dist %>/styles/main.css': [ - '.tmp/styles/{,*/}*.css', - '<%= yeoman.app %>/styles/{,*/}*.css' - ] - } - } - }, - htmlmin: { - dist: { - options: { - /*removeCommentsFromCDATA: true, - // https://github.com/yeoman/grunt-usemin/issues/44 - //collapseWhitespace: true, - collapseBooleanAttributes: true, - removeAttributeQuotes: true, - removeRedundantAttributes: true, - useShortDoctype: true, - removeEmptyAttributes: true, - removeOptionalTags: true*/ - }, - files: [{ - expand: true, - cwd: '<%= yeoman.app %>', - src: '*.html', - dest: '<%= yeoman.dist %>' - }] - } - }, - copy: { - dist: { - files: [{ - expand: true, - dot: true, - cwd: '<%= yeoman.app %>', - dest: '<%= yeoman.dist %>', - src: [ - '*.{ico,png,txt}', - 'fonts/{,*/}*.*', - '.htaccess', - 'index.txt', - '404.txt', - 'images/{,*/}*.{webp,gif,svg}' - ] - }] - }, - server: { - files: [{ - expand: true, - dot: true, - cwd: '<%= yeoman.app %>/bower_components/font-awesome/fonts/', - dest: '<%= yeoman.app %>/fonts/font-awesome', - src: ['*'] - }, { - expand: true, - dot: true, - cwd: '<%= yeoman.app %>/bower_components/bootstrap/dist/fonts/', - dest: '<%= yeoman.app %>/fonts/glyphicons', - src: ['*'] - }] - } - }, - concurrent: { - dist: [ - 'less', - 'imagemin', - 'htmlmin' - ] - } - }); - - grunt.registerTask('serve', function (target) { - if (target === 'dist') { - return grunt.task.run(['build', 'connect:dist:keepalive']); - } - - grunt.task.run([ - 'clean:server', - 'less', - 'includes:server', - 'copy:server', - 'connect:livereload', - 'watch' - ]); - }); - - grunt.registerTask('server', function () { - grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); - grunt.task.run(['serve']); - }); - - grunt.registerTask('test', [ - 'clean:server', - 'less', - 'copy:server', - 'connect:test', - ]); - - grunt.registerTask('build', [ - 'clean:dist', - - 'copy:server', - 'useminPrepare', - 'concurrent', - 'cssmin', - 'concat', - 'includes:build', - 'uglify', - 'copy', - 'usemin', - - ]); - - grunt.registerTask('default', [ - 'jshint', - 'test', - 'build' - ]); -}; \ No newline at end of file diff --git a/README.md b/README.md index e5c5406..f00fc8e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# transfer.sh [](https://gitter.im/dutchcoders/transfer.sh?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://travis-ci.org/dutchcoders/transfer.sh) +# transfer.sh [](https://gitter.im/dutchcoders/transfer.sh?utm_source=badge&utm_medium=badge&utm_campaign=&utm_campaign=pr-badge&utm_content=badge) [](https://goreportcard.com/report/dutchcoders/transfer.sh) [](https://hub.docker.com/r/transfer.sh/transfer.sh/) [](https://travis-ci.org/dutchcoders/transfer.sh) Easy and fast file sharing from the command-line. This code contains the server with everything you need to create your own instance. diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 0000000..ac8fcf0 --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,227 @@ +package cmd + +import ( + "fmt" + + "os" + + "strings" + + "github.com/dutchcoders/transfer.sh/server" + "github.com/fatih/color" + "github.com/minio/cli" +) + +var Version = "0.1" +var helpTemplate = `NAME: +{{.Name}} - {{.Usage}} + +DESCRIPTION: +{{.Description}} + +USAGE: +{{.Name}} {{if .Flags}}[flags] {{end}}command{{if .Flags}}{{end}} [arguments...] + +COMMANDS: +{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} +{{end}}{{if .Flags}} +FLAGS: +{{range .Flags}}{{.}} +{{end}}{{end}} +VERSION: +` + Version + + `{{ "\n"}}` + +var globalFlags = []cli.Flag{ + cli.StringFlag{ + Name: "listener", + Usage: "127.0.0.1:8080", + Value: "127.0.0.1:8080", + }, + // redirect to https? + // hostnames + cli.StringFlag{ + Name: "profile-listener", + Usage: "127.0.0.1:6060", + Value: "", + }, + cli.BoolFlag{ + Name: "force-https", + Usage: "", + }, + cli.StringFlag{ + Name: "tls-listener", + Usage: "127.0.0.1:8443", + Value: "", + }, + cli.StringFlag{ + Name: "tls-cert-file", + Value: "", + }, + cli.StringFlag{ + Name: "tls-private-key", + Value: "", + }, + cli.StringFlag{ + Name: "temp-path", + Usage: "path to temp files", + Value: os.TempDir(), + }, + cli.StringFlag{ + Name: "web-path", + Usage: "path to static web files", + Value: "", + }, + cli.StringFlag{ + Name: "provider", + Usage: "s3|local", + Value: "", + }, + cli.StringFlag{ + Name: "aws-access-key", + Usage: "", + Value: "", + EnvVar: "AWS_ACCESS_KEY", + }, + cli.StringFlag{ + Name: "aws-secret-key", + Usage: "", + Value: "", + EnvVar: "AWS_SECRET_KEY", + }, + cli.StringFlag{ + Name: "bucket", + Usage: "", + Value: "", + EnvVar: "BUCKET", + }, + cli.StringFlag{ + Name: "lets-encrypt-hosts", + Usage: "host1, host2", + Value: "", + EnvVar: "HOSTS", + }, + cli.StringFlag{ + Name: "log", + Usage: "/var/log/transfersh.log", + Value: "", + }, + cli.StringFlag{ + Name: "basedir", + Usage: "path to storage", + Value: "", + }, + cli.BoolFlag{ + Name: "profiler", + Usage: "enable profiling", + }, +} + +type Cmd struct { + *cli.App +} + +func VersionAction(c *cli.Context) { + fmt.Println(color.YellowString(fmt.Sprintf("transfer.sh: Easy file sharing from the command line"))) +} + +func New() *Cmd { + app := cli.NewApp() + app.Name = "transfer.sh" + app.Author = "" + app.Usage = "transfer.sh" + app.Description = `Easy file sharing from the command line` + app.Flags = globalFlags + app.CustomAppHelpTemplate = helpTemplate + app.Commands = []cli.Command{ + { + Name: "version", + Action: VersionAction, + }, + } + + app.Before = func(c *cli.Context) error { + return nil + } + + app.Action = func(c *cli.Context) { + options := []server.OptionFn{} + if v := c.String("listener"); v != "" { + options = append(options, server.Listener(v)) + } + + if v := c.String("tls-listener"); v != "" { + options = append(options, server.TLSListener(v)) + } + + if v := c.String("profile-listener"); v != "" { + options = append(options, server.ProfileListener(v)) + } + + if v := c.String("web-path"); v != "" { + options = append(options, server.WebPath(v)) + } + + if v := c.String("temp-path"); v != "" { + options = append(options, server.TempPath(v)) + } + + if v := c.String("lets-encrypt-hosts"); v != "" { + options = append(options, server.UseLetsEncrypt(strings.Split(v, ","))) + } + + if cert := c.String("tls-cert-file"); cert == "" { + } else if pk := c.String("tls-private-key"); pk == "" { + } else { + options = append(options, server.TLSConfig(cert, pk)) + } + + if c.Bool("profiler") { + options = append(options, server.EnableProfiler()) + } + + if c.Bool("force-https") { + options = append(options, server.ForceHTTPs()) + } + + switch provider := c.String("provider"); provider { + case "s3": + if accessKey := c.String("aws-access-key"); accessKey == "" { + panic("access-key not set.") + } else if secretKey := c.String("aws-secret-key"); secretKey == "" { + panic("secret-key not set.") + } else if bucket := c.String("bucket"); bucket == "" { + panic("bucket not set.") + } else if storage, err := server.NewS3Storage(accessKey, secretKey, bucket); err != nil { + panic(err) + } else { + options = append(options, server.UseStorage(storage)) + } + case "local": + if v := c.String("basedir"); v == "" { + panic("basedir not set.") + } else if storage, err := server.NewLocalStorage(v); err != nil { + panic(err) + } else { + options = append(options, server.UseStorage(storage)) + } + default: + panic("Provider not set or invalid.") + } + + srvr, err := server.New( + options..., + ) + + if err != nil { + fmt.Println(color.RedString("Error starting server: %s", err.Error())) + return + } + + srvr.Run() + } + + return &Cmd{ + App: app, + } +} diff --git a/lock.json b/lock.json new file mode 100644 index 0000000..bd9186f --- /dev/null +++ b/lock.json @@ -0,0 +1,204 @@ +{ + "memo": "07876113f39e289dbd1d493a6ba955bad81664a6f5291a4daa554700d5d536f3", + "projects": [ + { + "name": "github.com/PuerkitoBio/ghost", + "branch": "master", + "revision": "206e6e460e14a42d1d811c970b30248db058e9b2", + "packages": [ + ".", + "handlers" + ] + }, + { + "name": "github.com/dutchcoders/go-clamd", + "branch": "master", + "revision": "a9a81beaffff0392094052913ec45fa140eb8511", + "packages": [ + "." + ] + }, + { + "name": "github.com/dutchcoders/go-virustotal", + "branch": "master", + "revision": "24cc8e6fa329f020c70a3b32330b5743f1ba7971", + "packages": [ + "." + ] + }, + { + "name": "github.com/dutchcoders/transfer.sh-web", + "branch": "master", + "revision": "648a3f436b4772ca979e5d272010fd2719a7c5e2", + "packages": [ + "." + ] + }, + { + "name": "github.com/elazarl/go-bindata-assetfs", + "branch": "master", + "revision": "30f82fa23fd844bd5bb1e5f216db87fd77b5eb43", + "packages": [ + "." + ] + }, + { + "name": "github.com/fatih/color", + "version": "v1.4.1", + "revision": "9131ab34cf20d2f6d83fdc67168a5430d1c7dc23", + "packages": [ + "." + ] + }, + { + "name": "github.com/garyburd/redigo", + "version": "v1.0.0", + "revision": "8873b2f1995f59d4bcdd2b0dc9858e2cb9bf0c13", + "packages": [ + "internal", + "redis" + ] + }, + { + "name": "github.com/goamz/goamz", + "branch": "master", + "revision": "c35091c30f44b7f151ec9028b895465a191d1ea7", + "packages": [ + "aws", + "s3" + ] + }, + { + "name": "github.com/golang/gddo", + "branch": "master", + "revision": "72302b972abba39585150723aea3cf343e99437c", + "packages": [ + "httputil/header" + ] + }, + { + "name": "github.com/gorilla/context", + "version": "v1.1", + "revision": "1ea25387ff6f684839d82767c1733ff4d4d15d0a", + "packages": [ + "." + ] + }, + { + "name": "github.com/gorilla/mux", + "version": "v1.3.0", + "revision": "392c28fe23e1c45ddba891b0320b3b5df220beea", + "packages": [ + "." + ] + }, + { + "name": "github.com/gorilla/securecookie", + "version": "v1.1", + "revision": "667fe4e3466a040b780561fe9b51a83a3753eefc", + "packages": [ + "." + ] + }, + { + "name": "github.com/kennygrant/sanitize", + "version": "v1.2", + "revision": "6a0bfdde8629a3a3a7418a7eae45c54154692514", + "packages": [ + "." + ] + }, + { + "name": "github.com/mattn/go-colorable", + "version": "v0.0.7", + "revision": "d228849504861217f796da67fae4f6e347643f15", + "packages": [ + "." + ] + }, + { + "name": "github.com/mattn/go-isatty", + "version": "v0.0.1", + "revision": "3a115632dcd687f9c8cd01679c83a06a0e21c1f3", + "packages": [ + "." + ] + }, + { + "name": "github.com/minio/cli", + "version": "v1.3.0", + "revision": "8683fa7fef37cc8cb092f47bdb6b403e0049f9ee", + "packages": [ + "." + ] + }, + { + "name": "github.com/nu7hatch/gouuid", + "branch": "master", + "revision": "179d4d0c4d8d407a32af483c2354df1d2c91e6c3", + "packages": [ + "." + ] + }, + { + "name": "github.com/russross/blackfriday", + "version": "v1.4", + "revision": "0b647d0506a698cca42caca173e55559b12a69f2", + "packages": [ + "." + ] + }, + { + "name": "github.com/shurcooL/sanitized_anchor_name", + "branch": "master", + "revision": "1dba4b3954bc059efc3991ec364f9f9a35f597d2", + "packages": [ + "." + ] + }, + { + "name": "github.com/vaughan0/go-ini", + "branch": "master", + "revision": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1", + "packages": [ + "." + ] + }, + { + "name": "golang.org/x/crypto", + "branch": "master", + "revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", + "packages": [ + "acme", + "acme/autocert" + ] + }, + { + "name": "golang.org/x/net", + "branch": "master", + "revision": "a6577fac2d73be281a500b310739095313165611", + "packages": [ + "context", + "context/ctxhttp", + "html", + "html/atom" + ] + }, + { + "name": "golang.org/x/sys", + "branch": "master", + "revision": "99f16d856c9836c42d24e7ab64ea72916925fa97", + "packages": [ + "unix" + ] + }, + { + "name": "gopkg.in/check.v1", + "branch": "v1", + "revision": "20d25e2804050c1cd24a7eea1e7a6447dd0e74ec", + "packages": [ + "." + ] + } + ] +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..54d7259 --- /dev/null +++ b/main.go @@ -0,0 +1,8 @@ +package main + +import "github.com/dutchcoders/transfer.sh/cmd" + +func main() { + app := cmd.New() + app.RunAndExitOnError() +} diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..abc9935 --- /dev/null +++ b/manifest.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "github.com/dutchcoders/transfer.sh-web": { + "branch": "master" + } + } +} diff --git a/server/clamav.go b/server/clamav.go new file mode 100644 index 0000000..d358f75 --- /dev/null +++ b/server/clamav.go @@ -0,0 +1,76 @@ +/* +The MIT License (MIT) + +Copyright (c) 2014 DutchCoders [https://github.com/dutchcoders/] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package server + +import ( + // _ "transfer.sh/app/handlers" + // _ "transfer.sh/app/utils" + + "fmt" + "io" + "log" + "net/http" + "path/filepath" + "time" + + clamd "github.com/dutchcoders/go-clamd" + + "github.com/gorilla/mux" + "github.com/kennygrant/sanitize" +) + +func (s *Server) scanHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + filename := sanitize.Path(filepath.Base(vars["filename"])) + + contentLength := r.ContentLength + contentType := r.Header.Get("Content-Type") + + log.Printf("Scanning %s %d %s", filename, contentLength, contentType) + + var reader io.Reader + + reader = r.Body + + c := clamd.NewClamd(s.ClamAVDaemonHost) + + abort := make(chan bool) + response, err := c.ScanStream(reader, abort) + if err != nil { + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + select { + case s := <-response: + w.Write([]byte(fmt.Sprintf("%v\n", s.Status))) + case <-time.After(time.Second * 60): + abort <- true + } + + close(abort) +} diff --git a/server/codec.go b/server/codec.go new file mode 100644 index 0000000..fda3682 --- /dev/null +++ b/server/codec.go @@ -0,0 +1,65 @@ +/* +https://github.com/fs111/kurz.go/blob/master/src/codec.go + +Originally written and Copyright (c) 2011 André Kelpe +Modifications Copyright (c) 2015 John Ko + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package server + +import ( + "math" + "strings" +) + +const ( + // characters used for short-urls + SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + + // someone set us up the bomb !! + BASE = int64(len(SYMBOLS)) +) + +// encodes a number into our *base* representation +// TODO can this be made better with some bitshifting? +func Encode(number int64) string { + rest := number % BASE + // strings are a bit weird in go... + result := string(SYMBOLS[rest]) + if number-rest != 0 { + newnumber := (number - rest) / BASE + result = Encode(newnumber) + result + } + return result +} + +// Decodes a string given in our encoding and returns the decimal +// integer. +func Decode(input string) int64 { + const floatbase = float64(BASE) + l := len(input) + var sum int = 0 + for index := l - 1; index > -1; index -= 1 { + current := string(input[index]) + pos := strings.Index(SYMBOLS, current) + sum = sum + (pos * int(math.Pow(floatbase, float64((l-index-1))))) + } + return int64(sum) +} diff --git a/transfersh-server/codec.go b/server/codec.go.bak similarity index 100% rename from transfersh-server/codec.go rename to server/codec.go.bak diff --git a/server/handlers.go b/server/handlers.go new file mode 100644 index 0000000..346d516 --- /dev/null +++ b/server/handlers.go @@ -0,0 +1,586 @@ +/* +The MIT License (MIT) + +Copyright (c) 2014 DutchCoders [https://github.com/dutchcoders/] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package server + +import ( + // _ "transfer.sh/app/handlers" + // _ "transfer.sh/app/utils" + + "archive/tar" + "archive/zip" + "bytes" + "compress/gzip" + "errors" + "fmt" + "html" + html_template "html/template" + "io" + "io/ioutil" + "log" + "math/rand" + "mime" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + text_template "text/template" + "time" + + web "github.com/dutchcoders/transfer.sh-web" + "github.com/gorilla/mux" + "github.com/kennygrant/sanitize" + "github.com/russross/blackfriday" +) + +var ( + html_templates = initHTMLTemplates() + text_templates = initTextTemplates() +) + +func stripPrefix(path string) string { + return strings.Replace(path, web.Prefix+"/", "", -1) +} + +func initTextTemplates() *text_template.Template { + templateMap := text_template.FuncMap{"format": formatNumber} + + // Templates with functions available to them + var templates = text_template.New("").Funcs(templateMap) + return templates +} + +func initHTMLTemplates() *html_template.Template { + templateMap := html_template.FuncMap{"format": formatNumber} + + // Templates with functions available to them + var templates = html_template.New("").Funcs(templateMap) + + return templates +} + +func healthHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Approaching Neutral Zone, all systems normal and functioning.") +} + +/* The preview handler will show a preview of the content for browsers (accept type text/html), and referer is not transfer.sh */ +func (s *Server) previewHandler(w http.ResponseWriter, r *http.Request) { + + vars := mux.Vars(r) + + token := vars["token"] + filename := vars["filename"] + + contentType, contentLength, err := storage.Head(token, filename) + if err != nil { + http.Error(w, http.StatusText(404), 404) + return + } + + var templatePath string + var content html_template.HTML + + switch { + case strings.HasPrefix(contentType, "image/"): + templatePath = "download.image.html" + case strings.HasPrefix(contentType, "video/"): + templatePath = "download.video.html" + case strings.HasPrefix(contentType, "audio/"): + templatePath = "download.audio.html" + case strings.HasPrefix(contentType, "text/"): + templatePath = "download.markdown.html" + + var reader io.ReadCloser + if reader, _, _, err = storage.Get(token, filename); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var data []byte + if data, err = ioutil.ReadAll(reader); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if strings.HasPrefix(contentType, "text/x-markdown") || strings.HasPrefix(contentType, "text/markdown") { + output := blackfriday.MarkdownCommon(data) + content = html_template.HTML(output) + } else if strings.HasPrefix(contentType, "text/plain") { + content = html_template.HTML(fmt.Sprintf("
%s", html.EscapeString(string(data)))) + } else { + templatePath = "download.sandbox.html" + } + + default: + templatePath = "download.html" + } + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + data := struct { + ContentType string + Content html_template.HTML + Filename string + Url string + ContentLength uint64 + }{ + contentType, + content, + filename, + r.URL.String(), + contentLength, + } + + if err := html_templates.ExecuteTemplate(w, templatePath, data); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + +} + +// this handler will output html or text, depending on the +// support of the client (Accept header). + +func (s *Server) viewHandler(w http.ResponseWriter, r *http.Request) { + // vars := mux.Vars(r) + + if acceptsHtml(r.Header) { + if err := html_templates.ExecuteTemplate(w, "index.html", nil); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } else { + if err := text_templates.ExecuteTemplate(w, "index.txt", nil); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} + +func (s *Server) notFoundHandler(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(404), 404) +} + +func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) { + if err := r.ParseMultipartForm(_24K); nil != err { + log.Printf("%s", err.Error()) + http.Error(w, "Error occurred copying to output stream", 500) + return + } + + token := Encode(10000000 + int64(rand.Intn(1000000000))) + + w.Header().Set("Content-Type", "text/plain") + + for _, fheaders := range r.MultipartForm.File { + for _, fheader := range fheaders { + filename := sanitize.Path(filepath.Base(fheader.Filename)) + contentType := fheader.Header.Get("Content-Type") + + if contentType == "" { + contentType = mime.TypeByExtension(filepath.Ext(fheader.Filename)) + } + + var f io.Reader + var err error + + if f, err = fheader.Open(); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + var b bytes.Buffer + + n, err := io.CopyN(&b, f, _24K+1) + if err != nil && err != io.EOF { + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + var reader io.Reader + + if n > _24K { + file, err := ioutil.TempFile(s.tempPath, "transfer-") + if err != nil { + log.Fatal(err) + } + defer file.Close() + + n, err = io.Copy(file, io.MultiReader(&b, f)) + if err != nil { + os.Remove(file.Name()) + + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + reader, err = os.Open(file.Name()) + } else { + reader = bytes.NewReader(b.Bytes()) + } + + contentLength := n + + log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) + + if err = storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + + } + + fmt.Fprintf(w, "https://%s/%s/%s\n", ipAddrFromRemoteAddr(r.Host), token, filename) + } + } +} + +func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + filename := sanitize.Path(filepath.Base(vars["filename"])) + + contentLength := r.ContentLength + + var reader io.Reader + + reader = r.Body + + if contentLength == -1 { + // queue file to disk, because s3 needs content length + var err error + var f io.Reader + + f = reader + + var b bytes.Buffer + + n, err := io.CopyN(&b, f, _24K+1) + if err != nil && err != io.EOF { + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + if n > _24K { + file, err := ioutil.TempFile(s.tempPath, "transfer-") + if err != nil { + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + defer file.Close() + + n, err = io.Copy(file, io.MultiReader(&b, f)) + if err != nil { + os.Remove(file.Name()) + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + reader, err = os.Open(file.Name()) + } else { + reader = bytes.NewReader(b.Bytes()) + } + + contentLength = n + } + + contentType := r.Header.Get("Content-Type") + + if contentType == "" { + contentType = mime.TypeByExtension(filepath.Ext(vars["filename"])) + } + + token := Encode(10000000 + int64(rand.Intn(1000000000))) + + log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) + + var err error + + if err = storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, errors.New("Could not save file").Error(), 500) + return + } + + // w.Statuscode = 200 + + w.Header().Set("Content-Type", "text/plain") + + fmt.Fprintf(w, "https://%s/%s/%s\n", ipAddrFromRemoteAddr(r.Host), token, filename) +} + +func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + files := vars["files"] + + zipfilename := fmt.Sprintf("transfersh-%d.zip", uint16(time.Now().UnixNano())) + + w.Header().Set("Content-Type", "application/zip") + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", zipfilename)) + w.Header().Set("Connection", "close") + + zw := zip.NewWriter(w) + + for _, key := range strings.Split(files, ",") { + if strings.HasPrefix(key, "/") { + key = key[1:] + } + + key = strings.Replace(key, "\\", "/", -1) + + token := strings.Split(key, "/")[0] + filename := sanitize.Path(strings.Split(key, "/")[1]) + + reader, _, _, err := storage.Get(token, filename) + + if err != nil { + if storage.IsNotExist(err) { + http.Error(w, "File not found", 404) + return + } else { + log.Printf("%s", err.Error()) + http.Error(w, "Could not retrieve file.", 500) + return + } + } + + defer reader.Close() + + header := &zip.FileHeader{ + Name: strings.Split(key, "/")[1], + Method: zip.Store, + ModifiedTime: uint16(time.Now().UnixNano()), + ModifiedDate: uint16(time.Now().UnixNano()), + } + + fw, err := zw.CreateHeader(header) + + if err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } + + if _, err = io.Copy(fw, reader); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } + } + + if err := zw.Close(); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } +} + +func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + files := vars["files"] + + tarfilename := fmt.Sprintf("transfersh-%d.tar.gz", uint16(time.Now().UnixNano())) + + w.Header().Set("Content-Type", "application/x-gzip") + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", tarfilename)) + w.Header().Set("Connection", "close") + + os := gzip.NewWriter(w) + defer os.Close() + + zw := tar.NewWriter(os) + defer zw.Close() + + for _, key := range strings.Split(files, ",") { + if strings.HasPrefix(key, "/") { + key = key[1:] + } + + key = strings.Replace(key, "\\", "/", -1) + + token := strings.Split(key, "/")[0] + filename := sanitize.Path(strings.Split(key, "/")[1]) + + reader, _, contentLength, err := storage.Get(token, filename) + if err != nil { + if storage.IsNotExist(err) { + http.Error(w, "File not found", 404) + return + } else { + log.Printf("%s", err.Error()) + http.Error(w, "Could not retrieve file.", 500) + return + } + } + + defer reader.Close() + + header := &tar.Header{ + Name: strings.Split(key, "/")[1], + Size: int64(contentLength), + } + + err = zw.WriteHeader(header) + if err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } + + if _, err = io.Copy(zw, reader); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } + } +} + +func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + files := vars["files"] + + tarfilename := fmt.Sprintf("transfersh-%d.tar", uint16(time.Now().UnixNano())) + + w.Header().Set("Content-Type", "application/x-tar") + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", tarfilename)) + w.Header().Set("Connection", "close") + + zw := tar.NewWriter(w) + defer zw.Close() + + for _, key := range strings.Split(files, ",") { + token := strings.Split(key, "/")[0] + filename := strings.Split(key, "/")[1] + + reader, _, contentLength, err := storage.Get(token, filename) + if err != nil { + if storage.IsNotExist(err) { + http.Error(w, "File not found", 404) + return + } else { + log.Printf("%s", err.Error()) + http.Error(w, "Could not retrieve file.", 500) + return + } + } + + defer reader.Close() + + header := &tar.Header{ + Name: strings.Split(key, "/")[1], + Size: int64(contentLength), + } + + err = zw.WriteHeader(header) + if err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } + + if _, err = io.Copy(zw, reader); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } + } +} + +func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + token := vars["token"] + filename := vars["filename"] + + reader, contentType, contentLength, err := storage.Get(token, filename) + if err != nil { + if storage.IsNotExist(err) { + http.Error(w, "File not found", 404) + return + } else { + log.Printf("%s", err.Error()) + http.Error(w, "Could not retrieve file.", 500) + return + } + } + + defer reader.Close() + + w.Header().Set("Content-Type", contentType) + w.Header().Set("Content-Length", strconv.FormatUint(contentLength, 10)) + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) + w.Header().Set("Connection", "close") + + if _, err = io.Copy(w, reader); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Error occurred copying to output stream", 500) + return + } +} + +func (s *Server) RedirectHandler(h http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !s.forceHTTPs { + // we don't want to enforce https + } else if r.URL.Path == "/health.html" { + // health check url won't redirect + } else if strings.HasSuffix(ipAddrFromRemoteAddr(r.Host), ".onion") { + // .onion addresses cannot get a valid certificate, so don't redirect + } else if r.Header.Get("X-Forwarded-Proto") == "https" { + } else if r.URL.Scheme == "https" { + } else { + u := *r.URL + u.Scheme = "https" + + http.Redirect(w, r, u.String(), http.StatusPermanentRedirect) + return + } + + h.ServeHTTP(w, r) + } +} + +// Create a log handler for every request it receives. +func LoveHandler(h http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("x-made-with", "<3 by DutchCoders") + w.Header().Set("x-served-by", "Proudly served by DutchCoders") + w.Header().Set("Server", "Transfer.sh HTTP Server 1.0") + h.ServeHTTP(w, r) + } +} diff --git a/server/handlers.go.bak b/server/handlers.go.bak new file mode 100644 index 0000000..6299484 --- /dev/null +++ b/server/handlers.go.bak @@ -0,0 +1,620 @@ +/* +The MIT License (MIT) + +Copyright (c) 2014 DutchCoders [https://github.com/dutchcoders/] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package main + +import ( + // _ "transfer.sh/app/handlers" + // _ "transfer.sh/app/utils" + + "archive/tar" + "archive/zip" + "bytes" + "compress/gzip" + "errors" + "fmt" + "html" + html_template "html/template" + "io" + "io/ioutil" + "log" + "math/rand" + "mime" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + text_template "text/template" + "time" + + clamd "github.com/dutchcoders/go-clamd" + + web "github.com/dutchcoders/transfer.sh-web" + "github.com/gorilla/mux" + "github.com/kennygrant/sanitize" + "github.com/russross/blackfriday" +) + +var ( + html_templates = initHTMLTemplates() + text_templates = initTextTemplates() +) + +func stripPrefix(path string) string { + return strings.Replace(path, web.Prefix+"/", "", -1) +} + +func initTextTemplates() *text_template.Template { + templateMap := text_template.FuncMap{"format": formatNumber} + + // Templates with functions available to them + var templates = text_template.New("").Funcs(templateMap) + return templates +} + +func initHTMLTemplates() *html_template.Template { + templateMap := html_template.FuncMap{"format": formatNumber} + + // Templates with functions available to them + var templates = html_template.New("").Funcs(templateMap) + + return templates +} + +func healthHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Approaching Neutral Zone, all systems normal and functioning.") +} + +/* The preview handler will show a preview of the content for browsers (accept type text/html), and referer is not transfer.sh */ +func previewHandler(w http.ResponseWriter, r *http.Request) { + + vars := mux.Vars(r) + + token := vars["token"] + filename := vars["filename"] + + contentType, contentLength, err := storage.Head(token, filename) + if err != nil { + http.Error(w, http.StatusText(404), 404) + return + } + + var templatePath string + var content html_template.HTML + + switch { + case strings.HasPrefix(contentType, "image/"): + templatePath = "download.image.html" + case strings.HasPrefix(contentType, "video/"): + templatePath = "download.video.html" + case strings.HasPrefix(contentType, "audio/"): + templatePath = "download.audio.html" + case strings.HasPrefix(contentType, "text/"): + templatePath = "download.markdown.html" + + var reader io.ReadCloser + if reader, _, _, err = storage.Get(token, filename); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var data []byte + if data, err = ioutil.ReadAll(reader); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if strings.HasPrefix(contentType, "text/x-markdown") || strings.HasPrefix(contentType, "text/markdown") { + output := blackfriday.MarkdownCommon(data) + content = html_template.HTML(output) + } else if strings.HasPrefix(contentType, "text/plain") { + content = html_template.HTML(fmt.Sprintf("
%s", html.EscapeString(string(data)))) + } else { + templatePath = "download.sandbox.html" + } + + default: + templatePath = "download.html" + } + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + data := struct { + ContentType string + Content html_template.HTML + Filename string + Url string + ContentLength uint64 + }{ + contentType, + content, + filename, + r.URL.String(), + contentLength, + } + + if err := html_templates.ExecuteTemplate(w, templatePath, data); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + +} + +// this handler will output html or text, depending on the +// support of the client (Accept header). + +func viewHandler(w http.ResponseWriter, r *http.Request) { + // vars := mux.Vars(r) + + if acceptsHtml(r.Header) { + if err := html_templates.ExecuteTemplate(w, "index.html", nil); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } else { + if err := text_templates.ExecuteTemplate(w, "index.txt", nil); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} + +func notFoundHandler(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(404), 404) +} + +func postHandler(w http.ResponseWriter, r *http.Request) { + if err := r.ParseMultipartForm(_24K); nil != err { + log.Printf("%s", err.Error()) + http.Error(w, "Error occurred copying to output stream", 500) + return + } + + token := Encode(10000000 + int64(rand.Intn(1000000000))) + + w.Header().Set("Content-Type", "text/plain") + + for _, fheaders := range r.MultipartForm.File { + for _, fheader := range fheaders { + filename := sanitize.Path(filepath.Base(fheader.Filename)) + contentType := fheader.Header.Get("Content-Type") + + if contentType == "" { + contentType = mime.TypeByExtension(filepath.Ext(fheader.Filename)) + } + + var f io.Reader + var err error + + if f, err = fheader.Open(); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + var b bytes.Buffer + + n, err := io.CopyN(&b, f, _24K+1) + if err != nil && err != io.EOF { + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + var reader io.Reader + + if n > _24K { + file, err := ioutil.TempFile(config.Temp, "transfer-") + if err != nil { + log.Fatal(err) + } + defer file.Close() + + n, err = io.Copy(file, io.MultiReader(&b, f)) + if err != nil { + os.Remove(file.Name()) + + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + reader, err = os.Open(file.Name()) + } else { + reader = bytes.NewReader(b.Bytes()) + } + + contentLength := n + + log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) + + if err = storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + + } + + fmt.Fprintf(w, "https://%s/%s/%s\n", ipAddrFromRemoteAddr(r.Host), token, filename) + } + } +} + +func scanHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + filename := sanitize.Path(filepath.Base(vars["filename"])) + + contentLength := r.ContentLength + contentType := r.Header.Get("Content-Type") + + log.Printf("Scanning %s %d %s", filename, contentLength, contentType) + + var reader io.Reader + + reader = r.Body + + c := clamd.NewClamd(config.CLAMAV_DAEMON_HOST) + + abort := make(chan bool) + response, err := c.ScanStream(reader, abort) + if err != nil { + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + select { + case s := <-response: + w.Write([]byte(fmt.Sprintf("%v\n", s.Status))) + case <-time.After(time.Second * 60): + abort <- true + } + + close(abort) +} + +func putHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + filename := sanitize.Path(filepath.Base(vars["filename"])) + + contentLength := r.ContentLength + + var reader io.Reader + + reader = r.Body + + if contentLength == -1 { + // queue file to disk, because s3 needs content length + var err error + var f io.Reader + + f = reader + + var b bytes.Buffer + + n, err := io.CopyN(&b, f, _24K+1) + if err != nil && err != io.EOF { + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + if n > _24K { + file, err := ioutil.TempFile(config.Temp, "transfer-") + if err != nil { + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + defer file.Close() + + n, err = io.Copy(file, io.MultiReader(&b, f)) + if err != nil { + os.Remove(file.Name()) + log.Printf("%s", err.Error()) + http.Error(w, err.Error(), 500) + return + } + + reader, err = os.Open(file.Name()) + } else { + reader = bytes.NewReader(b.Bytes()) + } + + contentLength = n + } + + contentType := r.Header.Get("Content-Type") + + if contentType == "" { + contentType = mime.TypeByExtension(filepath.Ext(vars["filename"])) + } + + token := Encode(10000000 + int64(rand.Intn(1000000000))) + + log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) + + var err error + + if err = storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, errors.New("Could not save file").Error(), 500) + return + } + + // w.Statuscode = 200 + + w.Header().Set("Content-Type", "text/plain") + + fmt.Fprintf(w, "https://%s/%s/%s\n", ipAddrFromRemoteAddr(r.Host), token, filename) +} + +func zipHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + files := vars["files"] + + zipfilename := fmt.Sprintf("transfersh-%d.zip", uint16(time.Now().UnixNano())) + + w.Header().Set("Content-Type", "application/zip") + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", zipfilename)) + w.Header().Set("Connection", "close") + + zw := zip.NewWriter(w) + + for _, key := range strings.Split(files, ",") { + if strings.HasPrefix(key, "/") { + key = key[1:] + } + + key = strings.Replace(key, "\\", "/", -1) + + token := strings.Split(key, "/")[0] + filename := sanitize.Path(strings.Split(key, "/")[1]) + + reader, _, _, err := storage.Get(token, filename) + + if err != nil { + if storage.IsNotExist(err) { + http.Error(w, "File not found", 404) + return + } else { + log.Printf("%s", err.Error()) + http.Error(w, "Could not retrieve file.", 500) + return + } + } + + defer reader.Close() + + header := &zip.FileHeader{ + Name: strings.Split(key, "/")[1], + Method: zip.Store, + ModifiedTime: uint16(time.Now().UnixNano()), + ModifiedDate: uint16(time.Now().UnixNano()), + } + + fw, err := zw.CreateHeader(header) + + if err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } + + if _, err = io.Copy(fw, reader); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } + } + + if err := zw.Close(); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } +} + +func tarGzHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + files := vars["files"] + + tarfilename := fmt.Sprintf("transfersh-%d.tar.gz", uint16(time.Now().UnixNano())) + + w.Header().Set("Content-Type", "application/x-gzip") + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", tarfilename)) + w.Header().Set("Connection", "close") + + os := gzip.NewWriter(w) + defer os.Close() + + zw := tar.NewWriter(os) + defer zw.Close() + + for _, key := range strings.Split(files, ",") { + if strings.HasPrefix(key, "/") { + key = key[1:] + } + + key = strings.Replace(key, "\\", "/", -1) + + token := strings.Split(key, "/")[0] + filename := sanitize.Path(strings.Split(key, "/")[1]) + + reader, _, contentLength, err := storage.Get(token, filename) + if err != nil { + if storage.IsNotExist(err) { + http.Error(w, "File not found", 404) + return + } else { + log.Printf("%s", err.Error()) + http.Error(w, "Could not retrieve file.", 500) + return + } + } + + defer reader.Close() + + header := &tar.Header{ + Name: strings.Split(key, "/")[1], + Size: int64(contentLength), + } + + err = zw.WriteHeader(header) + if err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } + + if _, err = io.Copy(zw, reader); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } + } +} + +func tarHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + files := vars["files"] + + tarfilename := fmt.Sprintf("transfersh-%d.tar", uint16(time.Now().UnixNano())) + + w.Header().Set("Content-Type", "application/x-tar") + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", tarfilename)) + w.Header().Set("Connection", "close") + + zw := tar.NewWriter(w) + defer zw.Close() + + for _, key := range strings.Split(files, ",") { + token := strings.Split(key, "/")[0] + filename := strings.Split(key, "/")[1] + + reader, _, contentLength, err := storage.Get(token, filename) + if err != nil { + if storage.IsNotExist(err) { + http.Error(w, "File not found", 404) + return + } else { + log.Printf("%s", err.Error()) + http.Error(w, "Could not retrieve file.", 500) + return + } + } + + defer reader.Close() + + header := &tar.Header{ + Name: strings.Split(key, "/")[1], + Size: int64(contentLength), + } + + err = zw.WriteHeader(header) + if err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } + + if _, err = io.Copy(zw, reader); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Internal server error.", 500) + return + } + } +} + +func getHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + token := vars["token"] + filename := vars["filename"] + + reader, contentType, contentLength, err := storage.Get(token, filename) + if err != nil { + if storage.IsNotExist(err) { + http.Error(w, "File not found", 404) + return + } else { + log.Printf("%s", err.Error()) + http.Error(w, "Could not retrieve file.", 500) + return + } + } + + defer reader.Close() + + w.Header().Set("Content-Type", contentType) + w.Header().Set("Content-Length", strconv.FormatUint(contentLength, 10)) + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) + w.Header().Set("Connection", "close") + + if _, err = io.Copy(w, reader); err != nil { + log.Printf("%s", err.Error()) + http.Error(w, "Error occurred copying to output stream", 500) + return + } +} + +func RedirectHandler(h http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/health.html" { + } else if ipAddrFromRemoteAddr(r.Host) == "127.0.0.1" { + } else if strings.HasSuffix(ipAddrFromRemoteAddr(r.Host), ".elasticbeanstalk.com") { + } else if ipAddrFromRemoteAddr(r.Host) == "jxm5d6emw5rknovg.onion" { + } else if ipAddrFromRemoteAddr(r.Host) == "transfer.sh" { + if r.Header.Get("X-Forwarded-Proto") != "https" && r.Method == "GET" { + http.Redirect(w, r, "https://transfer.sh"+r.RequestURI, 301) + return + } + } else if ipAddrFromRemoteAddr(r.Host) != "transfer.sh" { + http.Redirect(w, r, "https://transfer.sh"+r.RequestURI, 301) + return + } + + h.ServeHTTP(w, r) + } +} + +// Create a log handler for every request it receives. +func LoveHandler(h http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("x-made-with", "<3 by DutchCoders") + w.Header().Set("x-served-by", "Proudly served by DutchCoders") + w.Header().Set("Server", "Transfer.sh HTTP Server 1.0") + h.ServeHTTP(w, r) + } +} diff --git a/server/handlers_test.go b/server/handlers_test.go new file mode 100644 index 0000000..ae0113a --- /dev/null +++ b/server/handlers_test.go @@ -0,0 +1,110 @@ +package server + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + . "gopkg.in/check.v1" +) + +// Hook up gocheck into the "go test" runner. +func Test(t *testing.T) { TestingT(t) } + +var ( + _ = Suite(&SuiteRedirectWithForceHTTPs{}) + _ = Suite(&SuiteRedirectWithoutForceHTTPs{}) +) + +type SuiteRedirectWithForceHTTPs struct { + handler http.HandlerFunc +} + +func (s *SuiteRedirectWithForceHTTPs) SetUpTest(c *C) { + srvr, err := New(ForceHTTPs()) + c.Assert(err, IsNil) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hello, client") + }) + + s.handler = srvr.RedirectHandler(handler) +} + +func (s *SuiteRedirectWithForceHTTPs) TestHTTPs(c *C) { + req := httptest.NewRequest("GET", "https://test/test", nil) + + w := httptest.NewRecorder() + s.handler(w, req) + + resp := w.Result() + c.Assert(resp.StatusCode, Equals, http.StatusOK) +} + +func (s *SuiteRedirectWithForceHTTPs) TestOnion(c *C) { + req := httptest.NewRequest("GET", "http://test.onion/test", nil) + + w := httptest.NewRecorder() + s.handler(w, req) + + resp := w.Result() + c.Assert(resp.StatusCode, Equals, http.StatusOK) +} + +func (s *SuiteRedirectWithForceHTTPs) TestXForwardedFor(c *C) { + req := httptest.NewRequest("GET", "http://127.0.0.1/test", nil) + req.Header.Set("X-Forwarded-Proto", "https") + + w := httptest.NewRecorder() + s.handler(w, req) + + resp := w.Result() + c.Assert(resp.StatusCode, Equals, http.StatusOK) +} + +func (s *SuiteRedirectWithForceHTTPs) TestHTTP(c *C) { + req := httptest.NewRequest("GET", "http://127.0.0.1/test", nil) + + w := httptest.NewRecorder() + s.handler(w, req) + + resp := w.Result() + c.Assert(resp.StatusCode, Equals, http.StatusPermanentRedirect) + c.Assert(resp.Header.Get("Location"), Equals, "https://127.0.0.1/test") +} + +type SuiteRedirectWithoutForceHTTPs struct { + handler http.HandlerFunc +} + +func (s *SuiteRedirectWithoutForceHTTPs) SetUpTest(c *C) { + srvr, err := New() + c.Assert(err, IsNil) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hello, client") + }) + + s.handler = srvr.RedirectHandler(handler) +} + +func (s *SuiteRedirectWithoutForceHTTPs) TestHTTP(c *C) { + req := httptest.NewRequest("GET", "http://127.0.0.1/test", nil) + + w := httptest.NewRecorder() + s.handler(w, req) + + resp := w.Result() + c.Assert(resp.StatusCode, Equals, http.StatusOK) +} + +func (s *SuiteRedirectWithoutForceHTTPs) TestHTTPs(c *C) { + req := httptest.NewRequest("GET", "https://127.0.0.1/test", nil) + + w := httptest.NewRecorder() + s.handler(w, req) + + resp := w.Result() + c.Assert(resp.StatusCode, Equals, http.StatusOK) +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..683e757 --- /dev/null +++ b/server/server.go @@ -0,0 +1,371 @@ +/* +The MIT License (MIT) + +Copyright (c) 2014 DutchCoders [https://github.com/dutchcoders/] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package server + +import ( + "errors" + "fmt" + "log" + "math/rand" + "mime" + "net/http" + "net/url" + "os" + "os/signal" + "strings" + "syscall" + "time" + + context "golang.org/x/net/context" + + "github.com/PuerkitoBio/ghost/handlers" + "github.com/gorilla/mux" + + _ "net/http/pprof" + + "crypto/tls" + + web "github.com/dutchcoders/transfer.sh-web" + assetfs "github.com/elazarl/go-bindata-assetfs" + + autocert "golang.org/x/crypto/acme/autocert" +) + +const SERVER_INFO = "transfer.sh" + +// parse request with maximum memory of _24Kilobits +const _24K = (1 << 20) * 24 + +var storage Storage + +type OptionFn func(*Server) + +func Listener(s string) OptionFn { + return func(srvr *Server) { + srvr.ListenerString = s + } + +} + +func TLSListener(s string) OptionFn { + return func(srvr *Server) { + srvr.TLSListenerString = s + } + +} + +func ProfileListener(s string) OptionFn { + return func(srvr *Server) { + srvr.ProfileListenerString = s + } +} + +func WebPath(s string) OptionFn { + return func(srvr *Server) { + srvr.webPath = s + } +} + +func TempPath(s string) OptionFn { + return func(srvr *Server) { + srvr.tempPath = s + } +} + +func LogFile(s string) OptionFn { + return func(srvr *Server) { + f, err := os.OpenFile(s, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Fatalf("error opening file: %v", err) + } + + log.SetOutput(f) + } +} + +func ForceHTTPs() OptionFn { + return func(srvr *Server) { + srvr.forceHTTPs = true + } +} + +func EnableProfiler() OptionFn { + return func(srvr *Server) { + srvr.profilerEnabled = true + } +} + +func UseStorage(s Storage) OptionFn { + return func(srvr *Server) { + srvr.storage = s + } +} + +func UseLetsEncrypt(hosts []string) OptionFn { + return func(srvr *Server) { + cacheDir := "./cache/" + + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + Cache: autocert.DirCache(cacheDir), + HostPolicy: func(_ context.Context, host string) error { + found := false + + for _, h := range hosts { + fmt.Println(h) + found = found || strings.HasSuffix(host, h) + } + + if !found { + return errors.New("acme/autocert: host not configured") + } + + return nil + }, + } + + srvr.tlsConfig = &tls.Config{ + GetCertificate: m.GetCertificate, + } + } +} + +func TLSConfig(cert, pk string) OptionFn { + certificate, err := tls.LoadX509KeyPair(cert, pk) + return func(srvr *Server) { + srvr.tlsConfig = &tls.Config{ + GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { + return &certificate, err + }, + } + } +} + +type Server struct { + tlsConfig *tls.Config + + profilerEnabled bool + + storage Storage + + forceHTTPs bool + + VirusTotalKey string + ClamAVDaemonHost string + + tempPath string + + webPath string + + ListenerString string + TLSListenerString string + ProfileListenerString string + + Certificate string + + LetsEncryptCache string +} + +func New(options ...OptionFn) (*Server, error) { + s := &Server{} + + for _, optionFn := range options { + optionFn(s) + } + + return s, nil +} + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} + +func (s *Server) Run() { + if s.profilerEnabled { + go func() { + fmt.Println("Profiled listening at: :6060") + + http.ListenAndServe(":6060", nil) + }() + } + + r := mux.NewRouter() + + var fs http.FileSystem + + if s.webPath != "" { + log.Println("Using static file path: ", s.webPath) + + fs = http.Dir(s.webPath) + + html_templates, _ = html_templates.ParseGlob(s.webPath + "*.html") + text_templates, _ = text_templates.ParseGlob(s.webPath + "*.txt") + } else { + fs = &assetfs.AssetFS{ + Asset: web.Asset, + AssetDir: web.AssetDir, + AssetInfo: func(path string) (os.FileInfo, error) { + return os.Stat(path) + }, + Prefix: web.Prefix, + } + + for _, path := range web.AssetNames() { + bytes, err := web.Asset(path) + if err != nil { + log.Panicf("Unable to parse: path=%s, err=%s", path, err) + } + + html_templates.New(stripPrefix(path)).Parse(string(bytes)) + text_templates.New(stripPrefix(path)).Parse(string(bytes)) + } + } + + staticHandler := http.FileServer(fs) + + r.PathPrefix("/images/").Handler(staticHandler) + r.PathPrefix("/styles/").Handler(staticHandler) + r.PathPrefix("/scripts/").Handler(staticHandler) + r.PathPrefix("/fonts/").Handler(staticHandler) + r.PathPrefix("/ico/").Handler(staticHandler) + r.PathPrefix("/favicon.ico").Handler(staticHandler) + r.PathPrefix("/robots.txt").Handler(staticHandler) + + r.HandleFunc("/({files:.*}).zip", s.zipHandler).Methods("GET") + r.HandleFunc("/({files:.*}).tar", s.tarHandler).Methods("GET") + r.HandleFunc("/({files:.*}).tar.gz", s.tarGzHandler).Methods("GET") + r.HandleFunc("/download/{token}/{filename}", s.getHandler).Methods("GET") + + r.HandleFunc("/{token}/{filename}", s.previewHandler).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) (match bool) { + match = false + + // The file will show a preview page when opening the link in browser directly or + // from external link. If the referer url path and current path are the same it will be + // downloaded. + if !acceptsHtml(r.Header) { + return false + } + + match = (r.Referer() == "") + + u, err := url.Parse(r.Referer()) + if err != nil { + log.Fatal(err) + return + } + + match = match || (u.Path != r.URL.Path) + return + }).Methods("GET") + + r.HandleFunc("/{token}/{filename}", s.getHandler).Methods("GET") + r.HandleFunc("/get/{token}/{filename}", s.getHandler).Methods("GET") + r.HandleFunc("/{filename}/virustotal", s.virusTotalHandler).Methods("PUT") + r.HandleFunc("/{filename}/scan", s.scanHandler).Methods("PUT") + r.HandleFunc("/put/{filename}", s.putHandler).Methods("PUT") + r.HandleFunc("/upload/{filename}", s.putHandler).Methods("PUT") + r.HandleFunc("/{filename}", s.putHandler).Methods("PUT") + r.HandleFunc("/health.html", healthHandler).Methods("GET") + r.HandleFunc("/", s.postHandler).Methods("POST") + // r.HandleFunc("/{page}", viewHandler).Methods("GET") + r.HandleFunc("/", s.viewHandler).Methods("GET") + + r.NotFoundHandler = http.HandlerFunc(s.notFoundHandler) + + mime.AddExtensionType(".md", "text/x-markdown") + + log.Printf("Transfer.sh server started. :\nlistening on port: %v\nusing temp folder: %s\nusing storage provider: %s", s.ListenerString, s.tempPath, s.storage.Type()) + log.Printf("---------------------------") + + h := handlers.PanicHandler(handlers.LogHandler(LoveHandler(s.RedirectHandler(r)), handlers.NewLogOptions(log.Printf, "_default_")), nil) + + srvr := &http.Server{ + Addr: s.ListenerString, + Handler: h, + } + + go func() { + srvr.ListenAndServe() + }() + + if s.TLSListenerString != "" { + go func() { + s := &http.Server{ + Addr: s.TLSListenerString, + Handler: h, + TLSConfig: s.tlsConfig, + } + + if err := s.ListenAndServeTLS("", ""); err != nil { + panic(err) + } + }() + } + + /* + cacheDir := "/var/cache/autocert" + + if s.LetsEncryptCache != "" { + cacheDir = s.LetsEncryptCache + } + + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + Cache: autocert.DirCache(cacheDir), + HostPolicy: func(_ context.Context, host string) error { + if !strings.HasSuffix(host, "transfer.sh") { + return errors.New("acme/autocert: host not configured") + } + return nil + }, + } + + if s.TLSListenerString != "" { + go func() { + s := &http.Server{ + Addr: ":https", + Handler: lh, + TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, + } + + if err := s.ListenAndServeTLS("", ""); err != nil { + panic(err) + } + }() + + if err := http.ListenAndServe(c.ListenerString, RedirectHandler()); err != nil { + panic(err) + } + } + */ + + term := make(chan os.Signal, 1) + signal.Notify(term, os.Interrupt) + signal.Notify(term, syscall.SIGTERM) + + <-term + + log.Printf("Server stopped.") +} diff --git a/server/server.go.bak b/server/server.go.bak new file mode 100644 index 0000000..eb7c087 --- /dev/null +++ b/server/server.go.bak @@ -0,0 +1,239 @@ +/* +The MIT License (MIT) + +Copyright (c) 2014 DutchCoders [https://github.com/dutchcoders/] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package main + +import ( + "flag" + "fmt" + "log" + "math/rand" + "mime" + "net/http" + "net/url" + "os" + "os/signal" + "runtime" + "syscall" + "time" + + "github.com/PuerkitoBio/ghost/handlers" + "github.com/gorilla/mux" + + _ "net/http/pprof" + + web "github.com/dutchcoders/transfer.sh-web" + assetfs "github.com/elazarl/go-bindata-assetfs" +) + +const SERVER_INFO = "transfer.sh" + +// parse request with maximum memory of _24Kilobits +const _24K = (1 << 20) * 24 + +var storage Storage + +type Server struct { + AWS_ACCESS_KEY string + AWS_SECRET_KEY string + BUCKET string + VIRUSTOTAL_KEY string + CLAMAV_DAEMON_HOST string "/tmp/clamd.socket" + Temp string + Path string +} + +func New() *Server { + s := &Server{} + s.AWS_ACCESS_KEY = os.Getenv("AWS_ACCESS_KEY_ID") + s.AWS_SECRET_KEY = os.Getenv("AWS_SECRET_KEY") + s.BUCKET = os.Getenv("BUCKET") + + s.VIRUSTOTAL_KEY = os.Getenv("VIRUSTOTAL_KEY") + + if os.Getenv("CLAMAV_DAEMON_HOST") != "" { + s.CLAMAV_DAEMON_HOST = os.Getenv("CLAMAV_DAEMON_HOST") + } + + s.Temp = os.TempDir() + + s.Path = "" // "../transfer.sh-web/dist/" + + return s +} + +func (s *Server) Run() { + rand.Seed(time.Now().UTC().UnixNano()) + + nCPU := runtime.NumCPU() + runtime.GOMAXPROCS(nCPU) + fmt.Println("Number of CPUs: ", nCPU) + + go func() { + fmt.Println("Profiled listening at: :6060") + http.ListenAndServe(":6060", nil) + }() + + r := mux.NewRouter() + + var fs http.FileSystem + + if config.Path != "" { + log.Println("Using static file path: ", config.Path) + + fs = http.Dir(config.Path) + html_templates, _ = html_templates.ParseGlob(config.Path + "*.html") + text_templates, _ = text_templates.ParseGlob(config.Path + "*.txt") + } else { + fs = &assetfs.AssetFS{ + Asset: web.Asset, + AssetDir: web.AssetDir, + AssetInfo: func(path string) (os.FileInfo, error) { + return os.Stat(path) + }, + Prefix: web.Prefix, + } + + for _, path := range web.AssetNames() { + bytes, err := web.Asset(path) + if err != nil { + log.Panicf("Unable to parse: path=%s, err=%s", path, err) + } + + html_templates.New(stripPrefix(path)).Parse(string(bytes)) + text_templates.New(stripPrefix(path)).Parse(string(bytes)) + } + } + + staticHandler := http.FileServer(fs) + + r.PathPrefix("/images/").Handler(staticHandler) + r.PathPrefix("/styles/").Handler(staticHandler) + r.PathPrefix("/scripts/").Handler(staticHandler) + r.PathPrefix("/fonts/").Handler(staticHandler) + r.PathPrefix("/ico/").Handler(staticHandler) + r.PathPrefix("/favicon.ico").Handler(staticHandler) + r.PathPrefix("/robots.txt").Handler(staticHandler) + + r.HandleFunc("/({files:.*}).zip", zipHandler).Methods("GET") + r.HandleFunc("/({files:.*}).tar", tarHandler).Methods("GET") + r.HandleFunc("/({files:.*}).tar.gz", tarGzHandler).Methods("GET") + r.HandleFunc("/download/{token}/{filename}", getHandler).Methods("GET") + + r.HandleFunc("/{token}/{filename}", previewHandler).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) (match bool) { + match = false + + // The file will show a preview page when opening the link in browser directly or + // from external link. If the referer url path and current path are the same it will be + // downloaded. + if !acceptsHtml(r.Header) { + return false + } + + match = (r.Referer() == "") + + u, err := url.Parse(r.Referer()) + if err != nil { + log.Fatal(err) + return + } + + match = match || (u.Path != r.URL.Path) + return + }).Methods("GET") + + r.HandleFunc("/{token}/{filename}", getHandler).Methods("GET") + r.HandleFunc("/get/{token}/{filename}", getHandler).Methods("GET") + r.HandleFunc("/{filename}/virustotal", virusTotalHandler).Methods("PUT") + r.HandleFunc("/{filename}/scan", scanHandler).Methods("PUT") + r.HandleFunc("/put/{filename}", putHandler).Methods("PUT") + r.HandleFunc("/upload/{filename}", putHandler).Methods("PUT") + r.HandleFunc("/{filename}", putHandler).Methods("PUT") + r.HandleFunc("/health.html", healthHandler).Methods("GET") + r.HandleFunc("/", postHandler).Methods("POST") + // r.HandleFunc("/{page}", viewHandler).Methods("GET") + r.HandleFunc("/", viewHandler).Methods("GET") + + r.NotFoundHandler = http.HandlerFunc(notFoundHandler) + + port := flag.String("port", "8080", "port number, default: 8080") + temp := flag.String("temp", config.Temp, "") + basedir := flag.String("basedir", "", "") + logpath := flag.String("log", "", "") + provider := flag.String("provider", "s3", "") + + flag.Parse() + + if *logpath != "" { + f, err := os.OpenFile(*logpath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Fatalf("error opening file: %v", err) + } + + defer f.Close() + + log.SetOutput(f) + } + + config.Temp = *temp + + var err error + + switch *provider { + case "s3": + storage, err = NewS3Storage() + case "local": + if *basedir == "" { + log.Panic("basedir not set") + } + + storage, err = NewLocalStorage(*basedir) + } + + if err != nil { + log.Panic("Error while creating storage.", err) + } + + mime.AddExtensionType(".md", "text/x-markdown") + + log.Printf("Transfer.sh server started. :\nlistening on port: %v\nusing temp folder: %s\nusing storage provider: %s", *port, config.Temp, *provider) + log.Printf("---------------------------") + + s := &http.Server{ + Addr: fmt.Sprintf(":%s", *port), + Handler: handlers.PanicHandler(LoveHandler(RedirectHandler(handlers.LogHandler(r, handlers.NewLogOptions(log.Printf, "_default_")))), nil), + } + + go func() { + s.ListenAndServe() + }() + + term := make(chan os.Signal, 1) + signal.Notify(term, os.Interrupt) + signal.Notify(term, syscall.SIGTERM) + + <-term + + log.Printf("Server stopped.") +} diff --git a/server/storage.go b/server/storage.go new file mode 100644 index 0000000..c895cbb --- /dev/null +++ b/server/storage.go @@ -0,0 +1,278 @@ +package server + +import ( + "bytes" + "fmt" + "io" + "log" + "mime" + "os" + "path/filepath" + "strconv" + "sync" + + "github.com/goamz/goamz/s3" +) + +type Storage interface { + Get(token string, filename string) (reader io.ReadCloser, contentType string, contentLength uint64, err error) + Head(token string, filename string) (contentType string, contentLength uint64, err error) + Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error + IsNotExist(err error) bool + + Type() string +} + +type LocalStorage struct { + Storage + basedir string +} + +func NewLocalStorage(basedir string) (*LocalStorage, error) { + return &LocalStorage{basedir: basedir}, nil +} + +func (s *LocalStorage) Type() string { + return "local" +} + +func (s *LocalStorage) Head(token string, filename string) (contentType string, contentLength uint64, err error) { + path := filepath.Join(s.basedir, token, filename) + + var fi os.FileInfo + if fi, err = os.Lstat(path); err != nil { + return + } + + contentLength = uint64(fi.Size()) + + contentType = mime.TypeByExtension(filepath.Ext(filename)) + + return +} + +func (s *LocalStorage) Get(token string, filename string) (reader io.ReadCloser, contentType string, contentLength uint64, err error) { + path := filepath.Join(s.basedir, token, filename) + + // content type , content length + if reader, err = os.Open(path); err != nil { + return + } + + var fi os.FileInfo + if fi, err = os.Lstat(path); err != nil { + return + } + + contentLength = uint64(fi.Size()) + + contentType = mime.TypeByExtension(filepath.Ext(filename)) + + return +} + +func (s *LocalStorage) IsNotExist(err error) bool { + return os.IsNotExist(err) +} + +func (s *LocalStorage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error { + var f io.WriteCloser + var err error + + path := filepath.Join(s.basedir, token) + + if err = os.Mkdir(path, 0700); err != nil && !os.IsExist(err) { + return err + } + + if f, err = os.OpenFile(filepath.Join(path, filename), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600); err != nil { + fmt.Printf("%s", err) + return err + } + + defer f.Close() + + if _, err = io.Copy(f, reader); err != nil { + return err + } + + return nil +} + +type S3Storage struct { + Storage + bucket *s3.Bucket +} + +func NewS3Storage(accessKey, secretKey, bucketName string) (*S3Storage, error) { + bucket, err := getBucket(accessKey, secretKey, bucketName) + if err != nil { + return nil, err + } + + return &S3Storage{bucket: bucket}, nil +} + +func (s *S3Storage) Type() string { + return "s3" +} + +func (s *S3Storage) Head(token string, filename string) (contentType string, contentLength uint64, err error) { + key := fmt.Sprintf("%s/%s", token, filename) + + // content type , content length + response, err := s.bucket.Head(key, map[string][]string{}) + if err != nil { + return + } + + contentType = response.Header.Get("Content-Type") + + contentLength, err = strconv.ParseUint(response.Header.Get("Content-Length"), 10, 0) + if err != nil { + return + } + + return +} + +func (s *S3Storage) IsNotExist(err error) bool { + log.Printf("IsNotExist: %s, %#v", err.Error(), err) + + b := (err.Error() == "The specified key does not exist.") + b = b || (err.Error() == "Access Denied") + return b +} + +func (s *S3Storage) Get(token string, filename string) (reader io.ReadCloser, contentType string, contentLength uint64, err error) { + key := fmt.Sprintf("%s/%s", token, filename) + + // content type , content length + response, err := s.bucket.GetResponse(key) + if err != nil { + return + } + + contentType = response.Header.Get("Content-Type") + contentLength, err = strconv.ParseUint(response.Header.Get("Content-Length"), 10, 0) + if err != nil { + return + } + + reader = response.Body + return +} + +func (s *S3Storage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) (err error) { + key := fmt.Sprintf("%s/%s", token, filename) + + var ( + multi *s3.Multi + parts []s3.Part + ) + + if multi, err = s.bucket.InitMulti(key, contentType, s3.Private); err != nil { + log.Printf(err.Error()) + return + } + + // 20 mb parts + partsChan := make(chan interface{}) + // partsChan := make(chan s3.Part) + + go func() { + // maximize to 20 threads + sem := make(chan int, 20) + index := 1 + var wg sync.WaitGroup + + for { + // buffered in memory because goamz s3 multi needs seekable reader + var ( + buffer []byte = make([]byte, (1<<20)*10) + count int + err error + ) + + // Amazon expects parts of at least 5MB, except for the last one + if count, err = io.ReadAtLeast(reader, buffer, (1<<20)*5); err != nil && err != io.ErrUnexpectedEOF && err != io.EOF { + log.Printf(err.Error()) + return + } + + // always send minimal 1 part + if err == io.EOF && index > 1 { + log.Printf("Waiting for all parts to finish uploading.") + + // wait for all parts to be finished uploading + wg.Wait() + + // and close the channel + close(partsChan) + + return + } + + wg.Add(1) + + sem <- 1 + + // using goroutines because of retries when upload fails + go func(multi *s3.Multi, buffer []byte, index int) { + log.Printf("Uploading part %d %d", index, len(buffer)) + + defer func() { + log.Printf("Finished part %d %d", index, len(buffer)) + + wg.Done() + + <-sem + }() + + partReader := bytes.NewReader(buffer) + + var part s3.Part + + if part, err = multi.PutPart(index, partReader); err != nil { + log.Printf("Error while uploading part %d %d %s", index, len(buffer), err.Error()) + partsChan <- err + return + } + + log.Printf("Finished uploading part %d %d", index, len(buffer)) + + partsChan <- part + + }(multi, buffer[:count], index) + + index++ + } + }() + + // wait for all parts to be uploaded + for part := range partsChan { + switch part.(type) { + case s3.Part: + parts = append(parts, part.(s3.Part)) + case error: + // abort multi upload + log.Printf("Error during upload, aborting %s.", part.(error).Error()) + err = part.(error) + + multi.Abort() + return + } + + } + + log.Printf("Completing upload %d parts", len(parts)) + + if err = multi.Complete(parts); err != nil { + log.Printf("Error during completing upload %d parts %s", len(parts), err.Error()) + return + } + + log.Printf("Completed uploading %d", len(parts)) + + return +} diff --git a/transfersh-server/storage.go b/server/storage.go.bak similarity index 100% rename from transfersh-server/storage.go rename to server/storage.go.bak diff --git a/server/utils.go b/server/utils.go new file mode 100644 index 0000000..aa309a4 --- /dev/null +++ b/server/utils.go @@ -0,0 +1,276 @@ +/* +The MIT License (MIT) + +Copyright (c) 2014 DutchCoders [https://github.com/dutchcoders/] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package server + +import ( + "math" + "net/http" + "net/mail" + "strconv" + "strings" + "time" + + "github.com/goamz/goamz/aws" + "github.com/goamz/goamz/s3" + "github.com/golang/gddo/httputil/header" +) + +func getBucket(accessKey, secretKey, bucket string) (*s3.Bucket, error) { + auth, err := aws.GetAuth(accessKey, secretKey, "", time.Time{}) + if err != nil { + return nil, err + } + + var EUWestWithoutHTTPS = aws.Region{ + "eu-west-1", + "https://ec2.eu-west-1.amazonaws.com", + "http://s3-eu-west-1.amazonaws.com", + "", + true, + true, + "https://sdb.eu-west-1.amazonaws.com", + "https://email.eu-west-1.amazonaws.com", + "https://sns.eu-west-1.amazonaws.com", + "https://sqs.eu-west-1.amazonaws.com", + "https://iam.amazonaws.com", + "https://elasticloadbalancing.eu-west-1.amazonaws.com", + "https://dynamodb.eu-west-1.amazonaws.com", + aws.ServiceInfo{"https://monitoring.eu-west-1.amazonaws.com", aws.V2Signature}, + "https://autoscaling.eu-west-1.amazonaws.com", + aws.ServiceInfo{"https://rds.eu-west-1.amazonaws.com", aws.V2Signature}, + "https://sts.amazonaws.com", + "https://cloudformation.eu-west-1.amazonaws.com", + "https://ecs.eu-west-1.amazonaws.com", + "https://streams.dynamodb.eu-west-1.amazonaws.com", + } + + conn := s3.New(auth, EUWestWithoutHTTPS) + b := conn.Bucket(bucket) + return b, nil +} + +func formatNumber(format string, s uint64) string { + + return RenderFloat(format, float64(s)) +} + +var renderFloatPrecisionMultipliers = [10]float64{ + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, +} + +var renderFloatPrecisionRounders = [10]float64{ + 0.5, + 0.05, + 0.005, + 0.0005, + 0.00005, + 0.000005, + 0.0000005, + 0.00000005, + 0.000000005, + 0.0000000005, +} + +func RenderFloat(format string, n float64) string { + // Special cases: + // NaN = "NaN" + // +Inf = "+Infinity" + // -Inf = "-Infinity" + if math.IsNaN(n) { + return "NaN" + } + if n > math.MaxFloat64 { + return "Infinity" + } + if n < -math.MaxFloat64 { + return "-Infinity" + } + + // default format + precision := 2 + decimalStr := "." + thousandStr := "," + positiveStr := "" + negativeStr := "-" + + if len(format) > 0 { + // If there is an explicit format directive, + // then default values are these: + precision = 9 + thousandStr = "" + + // collect indices of meaningful formatting directives + formatDirectiveChars := []rune(format) + formatDirectiveIndices := make([]int, 0) + for i, char := range formatDirectiveChars { + if char != '#' && char != '0' { + formatDirectiveIndices = append(formatDirectiveIndices, i) + } + } + + if len(formatDirectiveIndices) > 0 { + // Directive at index 0: + // Must be a '+' + // Raise an error if not the case + // index: 0123456789 + // +0.000,000 + // +000,000.0 + // +0000.00 + // +0000 + if formatDirectiveIndices[0] == 0 { + if formatDirectiveChars[formatDirectiveIndices[0]] != '+' { + panic("RenderFloat(): invalid positive sign directive") + } + positiveStr = "+" + formatDirectiveIndices = formatDirectiveIndices[1:] + } + + // Two directives: + // First is thousands separator + // Raise an error if not followed by 3-digit + // 0123456789 + // 0.000,000 + // 000,000.00 + if len(formatDirectiveIndices) == 2 { + if (formatDirectiveIndices[1] - formatDirectiveIndices[0]) != 4 { + panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers") + } + thousandStr = string(formatDirectiveChars[formatDirectiveIndices[0]]) + formatDirectiveIndices = formatDirectiveIndices[1:] + } + + // One directive: + // Directive is decimal separator + // The number of digit-specifier following the separator indicates wanted precision + // 0123456789 + // 0.00 + // 000,0000 + if len(formatDirectiveIndices) == 1 { + decimalStr = string(formatDirectiveChars[formatDirectiveIndices[0]]) + precision = len(formatDirectiveChars) - formatDirectiveIndices[0] - 1 + } + } + } + + // generate sign part + var signStr string + if n >= 0.000000001 { + signStr = positiveStr + } else if n <= -0.000000001 { + signStr = negativeStr + n = -n + } else { + signStr = "" + n = 0.0 + } + + // split number into integer and fractional parts + intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision]) + + // generate integer part string + intStr := strconv.Itoa(int(intf)) + + // add thousand separator if required + if len(thousandStr) > 0 { + for i := len(intStr); i > 3; { + i -= 3 + intStr = intStr[:i] + thousandStr + intStr[i:] + } + } + + // no fractional part, we can leave now + if precision == 0 { + return signStr + intStr + } + + // generate fractional part + fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision])) + // may need padding + if len(fracStr) < precision { + fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr + } + + return signStr + intStr + decimalStr + fracStr +} + +func RenderInteger(format string, n int) string { + return RenderFloat(format, float64(n)) +} + +// Request.RemoteAddress contains port, which we want to remove i.e.: +// "[::1]:58292" => "[::1]" +func ipAddrFromRemoteAddr(s string) string { + idx := strings.LastIndex(s, ":") + if idx == -1 { + return s + } + return s[:idx] +} + +func getIpAddress(r *http.Request) string { + hdr := r.Header + hdrRealIp := hdr.Get("X-Real-Ip") + hdrForwardedFor := hdr.Get("X-Forwarded-For") + if hdrRealIp == "" && hdrForwardedFor == "" { + return ipAddrFromRemoteAddr(r.RemoteAddr) + } + if hdrForwardedFor != "" { + // X-Forwarded-For is potentially a list of addresses separated with "," + parts := strings.Split(hdrForwardedFor, ",") + for i, p := range parts { + parts[i] = strings.TrimSpace(p) + } + // TODO: should return first non-local address + return parts[0] + } + return hdrRealIp +} + +func encodeRFC2047(String string) string { + // use mail's rfc2047 to encode any string + addr := mail.Address{String, ""} + return strings.Trim(addr.String(), " <>") +} + +func acceptsHtml(hdr http.Header) bool { + actual := header.ParseAccept(hdr, "Accept") + + for _, s := range actual { + if s.Value == "text/html" { + return (true) + } + } + + return (false) +} diff --git a/transfersh-server/utils.go b/server/utils.go.bak similarity index 100% rename from transfersh-server/utils.go rename to server/utils.go.bak diff --git a/server/virustotal.go b/server/virustotal.go new file mode 100644 index 0000000..03b709f --- /dev/null +++ b/server/virustotal.go @@ -0,0 +1,66 @@ +/* +The MIT License (MIT) + +Copyright (c) 2014 DutchCoders [https://github.com/dutchcoders/] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package server + +import ( + "fmt" + "io" + "log" + "net/http" + "path/filepath" + + _ "github.com/PuerkitoBio/ghost/handlers" + "github.com/dutchcoders/go-virustotal" + "github.com/gorilla/mux" + "github.com/kennygrant/sanitize" +) + +func (s *Server) virusTotalHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + filename := sanitize.Path(filepath.Base(vars["filename"])) + + contentLength := r.ContentLength + contentType := r.Header.Get("Content-Type") + + log.Printf("Submitting to VirusTotal: %s %d %s", filename, contentLength, contentType) + + vt, err := virustotal.NewVirusTotal(s.VirusTotalKey) + if err != nil { + http.Error(w, err.Error(), 500) + } + + var reader io.Reader + + reader = r.Body + + result, err := vt.Scan(filename, reader) + if err != nil { + http.Error(w, err.Error(), 500) + } + + log.Println(result) + w.Write([]byte(fmt.Sprintf("%v\n", result.Permalink))) +} diff --git a/transfersh-server/virustotal.go b/server/virustotal.go.bak similarity index 100% rename from transfersh-server/virustotal.go rename to server/virustotal.go.bak diff --git a/transfersh-server/handlers.go b/transfersh-server/handlers.go deleted file mode 100644 index a45a42d..0000000 --- a/transfersh-server/handlers.go +++ /dev/null @@ -1,609 +0,0 @@ -/* -The MIT License (MIT) - -Copyright (c) 2014 DutchCoders [https://github.com/dutchcoders/] - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package main - -import ( - // _ "transfer.sh/app/handlers" - // _ "transfer.sh/app/utils" - - "archive/tar" - "archive/zip" - "bytes" - "compress/gzip" - "errors" - "fmt" - "html" - html_template "html/template" - "io" - "io/ioutil" - "log" - "math/rand" - "mime" - "net/http" - "os" - "path/filepath" - "strconv" - "strings" - text_template "text/template" - "time" - - clamd "github.com/dutchcoders/go-clamd" - - "github.com/gorilla/mux" - "github.com/kennygrant/sanitize" - "github.com/russross/blackfriday" -) - -func healthHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Approaching Neutral Zone, all systems normal and functioning.") -} - -/* The preview handler will show a preview of the content for browsers (accept type text/html), and referer is not transfer.sh */ -func previewHandler(w http.ResponseWriter, r *http.Request) { - - vars := mux.Vars(r) - - token := vars["token"] - filename := vars["filename"] - - contentType, contentLength, err := storage.Head(token, filename) - if err != nil { - http.Error(w, http.StatusText(404), 404) - return - } - - var templatePath string - var content html_template.HTML - - switch { - case strings.HasPrefix(contentType, "image/"): - templatePath = "download.image.html" - case strings.HasPrefix(contentType, "video/"): - templatePath = "download.video.html" - case strings.HasPrefix(contentType, "audio/"): - templatePath = "download.audio.html" - case strings.HasPrefix(contentType, "text/"): - templatePath = "download.markdown.html" - - var reader io.ReadCloser - if reader, _, _, err = storage.Get(token, filename); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - var data []byte - if data, err = ioutil.ReadAll(reader); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if strings.HasPrefix(contentType, "text/x-markdown") || strings.HasPrefix(contentType, "text/markdown") { - output := blackfriday.MarkdownCommon(data) - content = html_template.HTML(output) - } else if strings.HasPrefix(contentType, "text/plain") { - content = html_template.HTML(fmt.Sprintf("
%s", html.EscapeString(string(data)))) - } else { - templatePath = "download.sandbox.html" - } - - default: - templatePath = "download.html" - } - - tmpl, err := html_template.New(templatePath).Funcs(html_template.FuncMap{"format": formatNumber}).ParseFiles("static/" + templatePath) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - data := struct { - ContentType string - Content html_template.HTML - Filename string - Url string - ContentLength uint64 - }{ - contentType, - content, - filename, - r.URL.String(), - contentLength, - } - - if err := tmpl.ExecuteTemplate(w, templatePath, data); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - -} - -// this handler will output html or text, depending on the -// support of the client (Accept header). - -func viewHandler(w http.ResponseWriter, r *http.Request) { - // vars := mux.Vars(r) - - if acceptsHtml(r.Header) { - tmpl, err := html_template.ParseFiles("static/index.html") - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if err := tmpl.Execute(w, nil); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } else { - tmpl, err := text_template.ParseFiles("static/index.txt") - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if err := tmpl.Execute(w, nil); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } -} - -func notFoundHandler(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(404), 404) -} - -func postHandler(w http.ResponseWriter, r *http.Request) { - if err := r.ParseMultipartForm(_24K); nil != err { - log.Printf("%s", err.Error()) - http.Error(w, "Error occurred copying to output stream", 500) - return - } - - token := Encode(10000000 + int64(rand.Intn(1000000000))) - - w.Header().Set("Content-Type", "text/plain") - - for _, fheaders := range r.MultipartForm.File { - for _, fheader := range fheaders { - filename := sanitize.Path(filepath.Base(fheader.Filename)) - contentType := fheader.Header.Get("Content-Type") - - if contentType == "" { - contentType = mime.TypeByExtension(filepath.Ext(fheader.Filename)) - } - - var f io.Reader - var err error - - if f, err = fheader.Open(); err != nil { - log.Printf("%s", err.Error()) - http.Error(w, err.Error(), 500) - return - } - - var b bytes.Buffer - - n, err := io.CopyN(&b, f, _24K+1) - if err != nil && err != io.EOF { - log.Printf("%s", err.Error()) - http.Error(w, err.Error(), 500) - return - } - - var reader io.Reader - - if n > _24K { - file, err := ioutil.TempFile(config.Temp, "transfer-") - if err != nil { - log.Fatal(err) - } - defer file.Close() - - n, err = io.Copy(file, io.MultiReader(&b, f)) - if err != nil { - os.Remove(file.Name()) - - log.Printf("%s", err.Error()) - http.Error(w, err.Error(), 500) - return - } - - reader, err = os.Open(file.Name()) - } else { - reader = bytes.NewReader(b.Bytes()) - } - - contentLength := n - - log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) - - if err = storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil { - log.Printf("%s", err.Error()) - http.Error(w, err.Error(), 500) - return - - } - - fmt.Fprintf(w, "https://%s/%s/%s\n", ipAddrFromRemoteAddr(r.Host), token, filename) - } - } -} - -func scanHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - filename := sanitize.Path(filepath.Base(vars["filename"])) - - contentLength := r.ContentLength - contentType := r.Header.Get("Content-Type") - - log.Printf("Scanning %s %d %s", filename, contentLength, contentType) - - var reader io.Reader - - reader = r.Body - - c := clamd.NewClamd(config.CLAMAV_DAEMON_HOST) - - abort := make(chan bool) - response, err := c.ScanStream(reader, abort) - if err != nil { - log.Printf("%s", err.Error()) - http.Error(w, err.Error(), 500) - return - } - - select { - case s := <-response: - w.Write([]byte(fmt.Sprintf("%v\n", s.Status))) - case <-time.After(time.Second * 60): - abort <- true - } - - close(abort) -} - -func putHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - filename := sanitize.Path(filepath.Base(vars["filename"])) - - contentLength := r.ContentLength - - var reader io.Reader - - reader = r.Body - - if contentLength == -1 { - // queue file to disk, because s3 needs content length - var err error - var f io.Reader - - f = reader - - var b bytes.Buffer - - n, err := io.CopyN(&b, f, _24K+1) - if err != nil && err != io.EOF { - log.Printf("%s", err.Error()) - http.Error(w, err.Error(), 500) - return - } - - if n > _24K { - file, err := ioutil.TempFile(config.Temp, "transfer-") - if err != nil { - log.Printf("%s", err.Error()) - http.Error(w, err.Error(), 500) - return - } - - defer file.Close() - - n, err = io.Copy(file, io.MultiReader(&b, f)) - if err != nil { - os.Remove(file.Name()) - log.Printf("%s", err.Error()) - http.Error(w, err.Error(), 500) - return - } - - reader, err = os.Open(file.Name()) - } else { - reader = bytes.NewReader(b.Bytes()) - } - - contentLength = n - } - - contentType := r.Header.Get("Content-Type") - - if contentType == "" { - contentType = mime.TypeByExtension(filepath.Ext(vars["filename"])) - } - - token := Encode(10000000 + int64(rand.Intn(1000000000))) - - log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) - - var err error - - if err = storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil { - log.Printf("%s", err.Error()) - http.Error(w, errors.New("Could not save file").Error(), 500) - return - } - - // w.Statuscode = 200 - - w.Header().Set("Content-Type", "text/plain") - - fmt.Fprintf(w, "https://%s/%s/%s\n", ipAddrFromRemoteAddr(r.Host), token, filename) -} - -func zipHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - files := vars["files"] - - zipfilename := fmt.Sprintf("transfersh-%d.zip", uint16(time.Now().UnixNano())) - - w.Header().Set("Content-Type", "application/zip") - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", zipfilename)) - w.Header().Set("Connection", "close") - - zw := zip.NewWriter(w) - - for _, key := range strings.Split(files, ",") { - if strings.HasPrefix(key, "/") { - key = key[1:] - } - - key = strings.Replace(key, "\\", "/", -1) - - token := strings.Split(key, "/")[0] - filename := sanitize.Path(strings.Split(key, "/")[1]) - - reader, _, _, err := storage.Get(token, filename) - - if err != nil { - if storage.IsNotExist(err) { - http.Error(w, "File not found", 404) - return - } else { - log.Printf("%s", err.Error()) - http.Error(w, "Could not retrieve file.", 500) - return - } - } - - defer reader.Close() - - header := &zip.FileHeader{ - Name: strings.Split(key, "/")[1], - Method: zip.Store, - ModifiedTime: uint16(time.Now().UnixNano()), - ModifiedDate: uint16(time.Now().UnixNano()), - } - - fw, err := zw.CreateHeader(header) - - if err != nil { - log.Printf("%s", err.Error()) - http.Error(w, "Internal server error.", 500) - return - } - - if _, err = io.Copy(fw, reader); err != nil { - log.Printf("%s", err.Error()) - http.Error(w, "Internal server error.", 500) - return - } - } - - if err := zw.Close(); err != nil { - log.Printf("%s", err.Error()) - http.Error(w, "Internal server error.", 500) - return - } -} - -func tarGzHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - files := vars["files"] - - tarfilename := fmt.Sprintf("transfersh-%d.tar.gz", uint16(time.Now().UnixNano())) - - w.Header().Set("Content-Type", "application/x-gzip") - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", tarfilename)) - w.Header().Set("Connection", "close") - - os := gzip.NewWriter(w) - defer os.Close() - - zw := tar.NewWriter(os) - defer zw.Close() - - for _, key := range strings.Split(files, ",") { - if strings.HasPrefix(key, "/") { - key = key[1:] - } - - key = strings.Replace(key, "\\", "/", -1) - - token := strings.Split(key, "/")[0] - filename := sanitize.Path(strings.Split(key, "/")[1]) - - reader, _, contentLength, err := storage.Get(token, filename) - if err != nil { - if storage.IsNotExist(err) { - http.Error(w, "File not found", 404) - return - } else { - log.Printf("%s", err.Error()) - http.Error(w, "Could not retrieve file.", 500) - return - } - } - - defer reader.Close() - - header := &tar.Header{ - Name: strings.Split(key, "/")[1], - Size: int64(contentLength), - } - - err = zw.WriteHeader(header) - if err != nil { - log.Printf("%s", err.Error()) - http.Error(w, "Internal server error.", 500) - return - } - - if _, err = io.Copy(zw, reader); err != nil { - log.Printf("%s", err.Error()) - http.Error(w, "Internal server error.", 500) - return - } - } -} - -func tarHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - files := vars["files"] - - tarfilename := fmt.Sprintf("transfersh-%d.tar", uint16(time.Now().UnixNano())) - - w.Header().Set("Content-Type", "application/x-tar") - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", tarfilename)) - w.Header().Set("Connection", "close") - - zw := tar.NewWriter(w) - defer zw.Close() - - for _, key := range strings.Split(files, ",") { - token := strings.Split(key, "/")[0] - filename := strings.Split(key, "/")[1] - - reader, _, contentLength, err := storage.Get(token, filename) - if err != nil { - if storage.IsNotExist(err) { - http.Error(w, "File not found", 404) - return - } else { - log.Printf("%s", err.Error()) - http.Error(w, "Could not retrieve file.", 500) - return - } - } - - defer reader.Close() - - header := &tar.Header{ - Name: strings.Split(key, "/")[1], - Size: int64(contentLength), - } - - err = zw.WriteHeader(header) - if err != nil { - log.Printf("%s", err.Error()) - http.Error(w, "Internal server error.", 500) - return - } - - if _, err = io.Copy(zw, reader); err != nil { - log.Printf("%s", err.Error()) - http.Error(w, "Internal server error.", 500) - return - } - } -} - -func getHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - token := vars["token"] - filename := vars["filename"] - - reader, contentType, contentLength, err := storage.Get(token, filename) - if err != nil { - if storage.IsNotExist(err) { - http.Error(w, "File not found", 404) - return - } else { - log.Printf("%s", err.Error()) - http.Error(w, "Could not retrieve file.", 500) - return - } - } - - defer reader.Close() - - w.Header().Set("Content-Type", contentType) - w.Header().Set("Content-Length", strconv.FormatUint(contentLength, 10)) - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) - w.Header().Set("Connection", "close") - - if _, err = io.Copy(w, reader); err != nil { - log.Printf("%s", err.Error()) - http.Error(w, "Error occurred copying to output stream", 500) - return - } -} - -func RedirectHandler(h http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/health.html" { - } else if ipAddrFromRemoteAddr(r.Host) == "127.0.0.1" { - } else if strings.HasSuffix(ipAddrFromRemoteAddr(r.Host), ".elasticbeanstalk.com") { - } else if ipAddrFromRemoteAddr(r.Host) == "jxm5d6emw5rknovg.onion" { - } else if ipAddrFromRemoteAddr(r.Host) == "transfer.sh" { - if r.Header.Get("X-Forwarded-Proto") != "https" && r.Method == "GET" { - http.Redirect(w, r, "https://transfer.sh"+r.RequestURI, 301) - return - } - } else if ipAddrFromRemoteAddr(r.Host) != "transfer.sh" { - http.Redirect(w, r, "https://transfer.sh"+r.RequestURI, 301) - return - } - - h.ServeHTTP(w, r) - } -} - -// Create a log handler for every request it receives. -func LoveHandler(h http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("x-made-with", "<3 by DutchCoders") - w.Header().Set("x-served-by", "Proudly served by DutchCoders") - w.Header().Set("Server", "Transfer.sh HTTP Server 1.0") - h.ServeHTTP(w, r) - } -} diff --git a/transfersh-server/main.go b/transfersh-server/main.go deleted file mode 100644 index 2ef722a..0000000 --- a/transfersh-server/main.go +++ /dev/null @@ -1,201 +0,0 @@ -/* -The MIT License (MIT) - -Copyright (c) 2014 DutchCoders [https://github.com/dutchcoders/] - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package main - -import ( - // _ "transfer.sh/app/handlers" - // _ "transfer.sh/app/utils" - "flag" - "fmt" - "log" - "math/rand" - "mime" - "net/http" - "net/url" - "os" - "os/signal" - "runtime" - "syscall" - "time" - - "github.com/PuerkitoBio/ghost/handlers" - "github.com/gorilla/mux" - - _ "net/http/pprof" -) - -const SERVER_INFO = "transfer.sh" - -// parse request with maximum memory of _24Kilobits -const _24K = (1 << 20) * 24 - -var config struct { - AWS_ACCESS_KEY string - AWS_SECRET_KEY string - BUCKET string - VIRUSTOTAL_KEY string - CLAMAV_DAEMON_HOST string "/tmp/clamd.socket" - Temp string -} - -var storage Storage - -func init() { - config.AWS_ACCESS_KEY = os.Getenv("AWS_ACCESS_KEY_ID") - config.AWS_SECRET_KEY = os.Getenv("AWS_SECRET_KEY") - config.BUCKET = os.Getenv("BUCKET") - - config.VIRUSTOTAL_KEY = os.Getenv("VIRUSTOTAL_KEY") - - if os.Getenv("CLAMAV_DAEMON_HOST") != "" { - config.CLAMAV_DAEMON_HOST = os.Getenv("CLAMAV_DAEMON_HOST") - } - - config.Temp = os.TempDir() -} - -func main() { - rand.Seed(time.Now().UTC().UnixNano()) - - nCPU := runtime.NumCPU() - runtime.GOMAXPROCS(nCPU) - fmt.Println("Number of CPUs: ", nCPU) - - go func() { - fmt.Println("Profiled listening at: :6060") - http.ListenAndServe(":6060", nil) - }() - - r := mux.NewRouter() - - r.PathPrefix("/scripts/").Methods("GET").Handler(http.FileServer(http.Dir("./static/"))) - r.PathPrefix("/styles/").Methods("GET").Handler(http.FileServer(http.Dir("./static/"))) - r.PathPrefix("/images/").Methods("GET").Handler(http.FileServer(http.Dir("./static/"))) - r.PathPrefix("/fonts/").Methods("GET").Handler(http.FileServer(http.Dir("./static/"))) - r.PathPrefix("/ico/").Methods("GET").Handler(http.FileServer(http.Dir("./static/"))) - r.PathPrefix("/favicon.ico").Methods("GET").Handler(http.FileServer(http.Dir("./static/"))) - r.PathPrefix("/robots.txt").Methods("GET").Handler(http.FileServer(http.Dir("./static/"))) - - r.HandleFunc("/({files:.*}).zip", zipHandler).Methods("GET") - r.HandleFunc("/({files:.*}).tar", tarHandler).Methods("GET") - r.HandleFunc("/({files:.*}).tar.gz", tarGzHandler).Methods("GET") - r.HandleFunc("/download/{token}/{filename}", getHandler).Methods("GET") - - r.HandleFunc("/{token}/{filename}", previewHandler).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) (match bool) { - match = false - - // The file will show a preview page when opening the link in browser directly or - // from external link. If the referer url path and current path are the same it will be - // downloaded. - if !acceptsHtml(r.Header) { - return false - } - - match = (r.Referer() == "") - - u, err := url.Parse(r.Referer()) - if err != nil { - log.Fatal(err) - return - } - - match = match || (u.Path != r.URL.Path) - return - }).Methods("GET") - - r.HandleFunc("/{token}/{filename}", getHandler).Methods("GET") - r.HandleFunc("/get/{token}/{filename}", getHandler).Methods("GET") - r.HandleFunc("/{filename}/virustotal", virusTotalHandler).Methods("PUT") - r.HandleFunc("/{filename}/scan", scanHandler).Methods("PUT") - r.HandleFunc("/put/{filename}", putHandler).Methods("PUT") - r.HandleFunc("/upload/{filename}", putHandler).Methods("PUT") - r.HandleFunc("/{filename}", putHandler).Methods("PUT") - r.HandleFunc("/health.html", healthHandler).Methods("GET") - r.HandleFunc("/", postHandler).Methods("POST") - // r.HandleFunc("/{page}", viewHandler).Methods("GET") - r.HandleFunc("/", viewHandler).Methods("GET") - - r.NotFoundHandler = http.HandlerFunc(notFoundHandler) - - port := flag.String("port", "8080", "port number, default: 8080") - temp := flag.String("temp", config.Temp, "") - basedir := flag.String("basedir", "", "") - logpath := flag.String("log", "", "") - provider := flag.String("provider", "s3", "") - - flag.Parse() - - if *logpath != "" { - f, err := os.OpenFile(*logpath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - if err != nil { - log.Fatalf("error opening file: %v", err) - } - - defer f.Close() - - log.SetOutput(f) - } - - config.Temp = *temp - - var err error - - switch *provider { - case "s3": - storage, err = NewS3Storage() - case "local": - if *basedir == "" { - log.Panic("basedir not set") - } - - storage, err = NewLocalStorage(*basedir) - } - - if err != nil { - log.Panic("Error while creating storage.", err) - } - - mime.AddExtensionType(".md", "text/x-markdown") - - log.Printf("Transfer.sh server started. :\nlistening on port: %v\nusing temp folder: %s\nusing storage provider: %s", *port, config.Temp, *provider) - log.Printf("---------------------------") - - s := &http.Server{ - Addr: fmt.Sprintf(":%s", *port), - Handler: handlers.PanicHandler(LoveHandler(RedirectHandler(handlers.LogHandler(r, handlers.NewLogOptions(log.Printf, "_default_")))), nil), - } - - go func() { - s.ListenAndServe() - }() - - term := make(chan os.Signal, 1) - signal.Notify(term, os.Interrupt) - signal.Notify(term, syscall.SIGTERM) - - <-term - - log.Printf("Server stopped.") -} diff --git a/transfersh-server/run.sh.sample b/transfersh-server/run.sh.sample deleted file mode 100644 index e33a047..0000000 --- a/transfersh-server/run.sh.sample +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -export BUCKET={bucket} -export AWS_ACCESS_KEY={access_key} -export AWS_SECRET_KEY={secret_key} -export VIRUSTOTAL_KEY={virustotal_key} -export PATH=$PATH:/usr/local/go/bin -export GOPATH=../go/ - -exec go run *.go diff --git a/transfersh-server/static/404.html b/transfersh-server/static/404.html deleted file mode 100644 index 0446544..0000000 --- a/transfersh-server/static/404.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - -
Sorry, but the page you were trying to view does not exist.
-It looks like this was the result of either:
-
- # Upload using cURL
-
$ curl --upload-file ./hello.txt https://transfer.sh/hello.txt https://transfer.sh/66nb8/hello.txt
-
-
- # Using the alias
-
$ transfer hello.txt
-
###################################s################## 100.0% https://transfer.sh/eibhM/hello.txt
-
-
-
- # Upload from web
-
Drag your files here, or click to browse.
-
-
-
-
- # Uploading is easy using curl
-
$ curl --upload-file ./hello.txt https://transfer.sh/hello.txt
-
https://transfer.sh/66nb8/hello.txt
-
- # Download the file
-
$ curl https://transfer.sh/66nb8/hello.txt -o hello.txt
-
-
- # Add this to .bashrc or its equivalent
-
transfer() { if [ $# -eq 0 ]; then echo "No arguments specified. Usage:\necho transfer /tmp/test.md\ncat /tmp/test.md | transfer test.md"; return 1; fi
tmpfile=$( mktemp -t transferXXX ); if tty -s; then basefile=$(basename "$1" | sed -e 's/[^a-zA-Z0-9._-]/-/g'); curl --progress-bar --upload-file "$1" "https://transfer.sh/$basefile" >> $tmpfile; else curl --progress-bar --upload-file "-" "https://transfer.sh/$1" >> $tmpfile ; fi; cat $tmpfile; rm -f $tmpfile; }
-
-
- # Now you can use transfer command
-
$ transfer hello.txt
-
-
-
$ curl -i -F filedata=@/tmp/hello.txt -F filedata=@/tmp/hello2.txt https://transfer.sh/
-
-
- # Combining downloads as zip or tar archive
-
$ curl https://transfer.sh/(15HKz/hello.txt,15HKz/hello.txt).tar.gz
-
$ curl https://transfer.sh/(15HKz/hello.txt,15HKz/hello.txt).zip
-
-
- # Encrypt files with password using gpg
-
$ cat /tmp/hello.txt|gpg -ac -o-|curl -X PUT --upload-file "-" https://transfer.sh/test.txt
-
-
- # Download and decrypt
-
$ curl https://transfer.sh/1lDau/test.txt|gpg -o- > /tmp/hello.txt
-
-
- # Scan for malware or viruses using Clamav
-
$ wget http://www.eicar.org/download/eicar.com
-
$ curl -X PUT --upload-file ./eicar.com https://transfer.sh/eicar.com/scan
-
-
- # Upload malware to VirusTotal, get a permalink in return
-
$ curl -X PUT --upload-file nhgbhhj https://transfer.sh/test.txt/virustotal
-
-
-
- # Backup, encrypt and transfer
-
$ mysqldump --all-databases|gzip|gpg -ac -o-|curl -X PUT --upload-file "-" https://transfer.sh/test.txt
-
- # Transfer and send email with link (uses alias)
-
$ transfer /tmp/hello.txt | mail -s "Hello World" user@yourmaildomain.com
-
-
- # Import keys from keybase
-
$ keybase track [them]
- # Encrypt for recipient(s)
-
$ cat somebackupfile.tar.gz | keybase encrypt [them] | curl --upload-file '-' https://transfer.sh/test.txt
- # Decrypt
-
$ curl https://transfer.sh/sqUFi/test.md |keybase decrypt
-
-
- # wget
-
$ wget --method PUT --body-file=/tmp/file.tar https://transfer.sh/file.tar -O - -nv
-
-
- # grep syslog for pound and transfer
-
$ cat /var/log/syslog|grep pound|curl --upload-file - https://transfer.sh/pound.log
-
-
- # Your awesome sample will be put here
-
-
-
-
-
- @dutchcoders Thanks for transfer.sh. Just used it for a production purpose for a customer. So great, so easy, so https. :)
-
- — Dave Sims (@FloifyDave)
-
-
-
-
-
- @dutchcoders love transfer.sh! any change we can *pay* for a self-hosted version?
— Kareem Kouddous (@kareemk)
-
-
-
-
-
- http://t.co/JomAmqWYEB by @dutchcoders is pure awesomeness! any chance of source on github? :-)
— PJ Spagnolatti (@drakpz)
-
-
-
-
-
- Love transfer.sh! Will be using it from now on! Thanks for the amazing service we can use from the CLI @dutchcoders
-
— Jacob Lindgren (@jacoblindgren11)
-
-
-
-
-
- transfer.sh is my latest fav service! Try simple command-line and web file sharing! https://t.co/FSrsb1JKJd
Thanks @dutchcoders !
— Lars Arvestad (@arvestad)
-
-