diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..30ea494 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +build +pkg +dist +src +bin +*.pyc +*.egg-info +.vagrant +.git +.tmp +bower_components +node_modules +extras +build +transfersh-server/run.sh +.elasticbeanstalk diff --git a/.ebextensions/01config_nginx.config b/.ebextensions/01config_nginx.config new file mode 100644 index 0000000..5b2b9cf --- /dev/null +++ b/.ebextensions/01config_nginx.config @@ -0,0 +1,6 @@ +files: + "/etc/nginx/conf.d/client_max_body_size.conf": + mode: "000644" + owner: root + group: root + content: "client_max_body_size 0;" diff --git a/.gitignore b/.gitignore index 15ce9b5..f8201fb 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ bower_components/ node_modules/ transfersh-server/run.sh +.elasticbeanstalk/ diff --git a/transfersh-server/Dockerfile b/Dockerfile similarity index 89% rename from transfersh-server/Dockerfile rename to Dockerfile index cb6bab1..01da0cc 100644 --- a/transfersh-server/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ RUN mkdir -p /go/src/app WORKDIR /go/src/app # Copy the local package files to the container's workspace. -ADD . /go/src/app +ADD ./transfersh-server /go/src/app # install dependencies RUN go get ./ diff --git a/Gruntfile.js b/Gruntfile.js index 944237e..bc3854c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -28,6 +28,10 @@ module.exports = function (grunt) { gruntfile: { files: ['Gruntfile.js'] }, + includes: { + files: ['<%= yeoman.app %>/*.html', '.tmp/*.html'], + tasks: ['includes:server'] + }, livereload: { options: { livereload: '<%= connect.options.livereload %>' @@ -37,7 +41,8 @@ module.exports = function (grunt) { '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css', '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js', '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' - ] + ], + tasks: ['includes:server'] } }, connect: { @@ -112,6 +117,27 @@ module.exports = function (grunt) { } } }, + + 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: { @@ -240,6 +266,7 @@ module.exports = function (grunt) { grunt.task.run([ 'clean:server', 'less', + 'includes:server', 'copy:server', 'connect:livereload', 'watch' @@ -260,14 +287,17 @@ module.exports = function (grunt) { grunt.registerTask('build', [ 'clean:dist', + 'copy:server', 'useminPrepare', 'concurrent', 'cssmin', 'concat', + 'includes:build', 'uglify', 'copy', - 'usemin' + 'usemin', + ]); grunt.registerTask('default', [ @@ -275,4 +305,4 @@ module.exports = function (grunt) { 'test', 'build' ]); -}; +}; \ No newline at end of file diff --git a/README.md b/README.md index c1ce53a..b2518e2 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,16 @@ go run transfersh-server/*.go -provider=local --port 8080 --temp=/tmp/ --basedir go build -o transfersh-server *.go ``` +## Docker + +For easy deployment we've enabled Docker deployment. + +``` +cd ./transfer-server/ +docker build -t transfersh . +docker run --publish 8080:8080 --rm transfersh --provider local --basedir /tmp/ +``` + ## Contributions Contributions are welcome. diff --git a/package.json b/package.json index ea7bf74..860d013 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "grunt-contrib-less": "~0.11.4", "grunt-contrib-uglify": "~0.6.0", "grunt-contrib-watch": "~0.6.1", + "grunt-include-replace": "^2.0.0", + "grunt-includes": "^0.4.5", "grunt-rev": "~0.1.0", "grunt-svgmin": "1.0.0", "grunt-usemin": "~2.4.0", diff --git a/transfersh-server/handlers.go b/transfersh-server/handlers.go index a4fd93f..1e19c47 100644 --- a/transfersh-server/handlers.go +++ b/transfersh-server/handlers.go @@ -35,9 +35,9 @@ import ( "errors" "fmt" "github.com/dutchcoders/go-clamd" - "github.com/golang/gddo/httputil/header" "github.com/gorilla/mux" "github.com/kennygrant/sanitize" + "github.com/russross/blackfriday" html_template "html/template" "io" "io/ioutil" @@ -57,23 +57,94 @@ 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", data)) + } else { + content = html_template.HTML(data) + } + + templatePath = "download.markdown.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) - actual := header.ParseAccept(r.Header, "Accept") - - html := false - - for _, s := range actual { - if s.Value == "text/html" { - html = true - } - } - - if html { + if acceptsHtml(r.Header) { tmpl, err := html_template.ParseFiles("static/index.html") if err != nil { @@ -106,7 +177,7 @@ func notFoundHandler(w http.ResponseWriter, r *http.Request) { func postHandler(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(_24K); nil != err { - log.Println(err) + log.Printf("%s", err.Error()) http.Error(w, "Error occured copying to output stream", 500) return } @@ -128,7 +199,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) { var err error if f, err = fheader.Open(); err != nil { - log.Print(err) + log.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) return } @@ -137,7 +208,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) { n, err := io.CopyN(&b, f, _24K+1) if err != nil && err != io.EOF { - log.Print(err) + log.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) return } @@ -155,7 +226,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) { if err != nil { os.Remove(file.Name()) - log.Print(err) + log.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) return } @@ -170,7 +241,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) { log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) if err = storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil { - log.Print(err) + log.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) return @@ -199,7 +270,9 @@ func scanHandler(w http.ResponseWriter, r *http.Request) { response, err := c.ScanStream(reader) if err != nil { + log.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) + return } var b string @@ -237,7 +310,7 @@ func putHandler(w http.ResponseWriter, r *http.Request) { n, err := io.CopyN(&b, f, _24K+1) if err != nil && err != io.EOF { - log.Print(err) + log.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) return } @@ -245,7 +318,7 @@ func putHandler(w http.ResponseWriter, r *http.Request) { if n > _24K { file, err := ioutil.TempFile(config.Temp, "transfer-") if err != nil { - log.Print(err) + log.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) return } @@ -255,8 +328,7 @@ func putHandler(w http.ResponseWriter, r *http.Request) { n, err = io.Copy(file, io.MultiReader(&b, f)) if err != nil { os.Remove(file.Name()) - - log.Print(err) + log.Printf("%s", err.Error()) http.Error(w, err.Error(), 500) return } @@ -282,6 +354,7 @@ func putHandler(w http.ResponseWriter, r *http.Request) { 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 } @@ -307,10 +380,17 @@ func zipHandler(w http.ResponseWriter, r *http.Request) { zw := zip.NewWriter(w) for _, key := range strings.Split(files, ",") { - token := sanitize.Path(strings.Split(key, "/")[0]) + 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 err.Error() == "The specified key does not exist." { http.Error(w, "File not found", 404) @@ -371,8 +451,14 @@ func tarGzHandler(w http.ResponseWriter, r *http.Request) { 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 := strings.Split(key, "/")[1] + filename := sanitize.Path(strings.Split(key, "/")[1]) reader, _, contentLength, err := storage.Get(token, filename) if err != nil { @@ -482,23 +568,11 @@ func getHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Length", strconv.FormatUint(contentLength, 10)) - - mediaType, _, _ := mime.ParseMediaType(contentType) - - switch { - case mediaType == "text/html": - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) - break - case strings.HasPrefix(mediaType, "text"): - case mediaType == "": - break - default: - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) - } - + 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 occured copying to output stream", 500) return } diff --git a/transfersh-server/main.go b/transfersh-server/main.go index 1f4a997..6369705 100644 --- a/transfersh-server/main.go +++ b/transfersh-server/main.go @@ -33,7 +33,9 @@ import ( "github.com/gorilla/mux" "log" "math/rand" + "mime" "net/http" + "net/url" "os" "time" ) @@ -86,23 +88,27 @@ func main() { r.HandleFunc("/({files:.*}).tar.gz", tarGzHandler).Methods("GET") r.HandleFunc("/download/{token}/{filename}", getHandler).Methods("GET") - /*r.HandleFunc("/{token}/{filename}", viewHandler).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool { - u, err := url.Parse(r.Referer()) - if err != nil { - log.Fatal(err) - return true - } + r.HandleFunc("/{token}/{filename}", previewHandler).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) (match bool) { + match = false - if u.Host == "transfer.sh" { - return 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 + } - if u.Host == "" { - return false - } + match = (r.Referer() == "") - return true - }).Methods("GET")*/ + 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") @@ -156,7 +162,9 @@ func main() { log.Panic("Error while creating storage.") } - log.Printf("Transfer.sh server started. :%v using temp folder: %s", *port, config.Temp) + 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{ diff --git a/transfersh-server/static/404.html b/transfersh-server/static/404.html index 73397ed..0446544 100644 --- a/transfersh-server/static/404.html +++ b/transfersh-server/static/404.html @@ -154,4 +154,4 @@