@@ -62,6 +62,16 @@ go run transfersh-server/*.go -provider=local --port 8080 --temp=/tmp/ --basedir | |||||
go build -o transfersh-server *.go | 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 | ||||
Contributions are welcome. | Contributions are welcome. | ||||
@@ -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;" |
@@ -76,9 +76,9 @@ func previewHandler(w http.ResponseWriter, r *http.Request) { | |||||
switch { | switch { | ||||
case strings.HasPrefix(contentType, "image/"): | case strings.HasPrefix(contentType, "image/"): | ||||
templatePath = "static/download.image.html" | |||||
templatePath = "download.image.html" | |||||
case strings.HasPrefix(contentType, "text/"): | case strings.HasPrefix(contentType, "text/"): | ||||
templatePath = "static/download.md.html" | |||||
templatePath = "download.md.html" | |||||
var reader io.ReadCloser | var reader io.ReadCloser | ||||
if reader, _, _, err = storage.Get(token, filename); err != nil { | if reader, _, _, err = storage.Get(token, filename); err != nil { | ||||
@@ -100,12 +100,12 @@ func previewHandler(w http.ResponseWriter, r *http.Request) { | |||||
content = html_template.HTML(data) | content = html_template.HTML(data) | ||||
} | } | ||||
templatePath = "static/download.md.html" | |||||
templatePath = "download.md.html" | |||||
default: | default: | ||||
templatePath = "static/download.html" | |||||
templatePath = "download.html" | |||||
} | } | ||||
tmpl, err := html_template.ParseFiles(templatePath) | |||||
tmpl, err := html_template.New(templatePath).Funcs(html_template.FuncMap{"format": formatNumber}).ParseFiles("static/" + templatePath) | |||||
if err != nil { | if err != nil { | ||||
http.Error(w, err.Error(), http.StatusInternalServerError) | http.Error(w, err.Error(), http.StatusInternalServerError) | ||||
@@ -126,7 +126,7 @@ func previewHandler(w http.ResponseWriter, r *http.Request) { | |||||
contentLength, | contentLength, | ||||
} | } | ||||
if err := tmpl.Execute(w, data); err != nil { | |||||
if err := tmpl.ExecuteTemplate(w, templatePath, data); err != nil { | |||||
http.Error(w, err.Error(), http.StatusInternalServerError) | http.Error(w, err.Error(), http.StatusInternalServerError) | ||||
return | return | ||||
} | } | ||||
@@ -88,27 +88,26 @@ func main() { | |||||
r.HandleFunc("/({files:.*}).tar.gz", tarGzHandler).Methods("GET") | r.HandleFunc("/({files:.*}).tar.gz", tarGzHandler).Methods("GET") | ||||
r.HandleFunc("/download/{token}/{filename}", getHandler).Methods("GET") | r.HandleFunc("/download/{token}/{filename}", getHandler).Methods("GET") | ||||
r.HandleFunc("/{token}/{filename}", previewHandler).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool { | |||||
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 | // The file will show a preview page when opening the link in browser directly or | ||||
// from external link. Otherwise it will download the file immediatly. | |||||
// from external link. If the referer url path and current path are the same it will be | |||||
// downloaded. | |||||
if !acceptsHtml(r.Header) { | if !acceptsHtml(r.Header) { | ||||
return false | return false | ||||
} | } | ||||
match := (r.Referer() == "") | |||||
match = (r.Referer() == "") | |||||
u, err := url.Parse(r.Referer()) | u, err := url.Parse(r.Referer()) | ||||
if err != nil { | if err != nil { | ||||
log.Fatal(err) | log.Fatal(err) | ||||
return match | |||||
return | |||||
} | } | ||||
match = match || (u.Host == "transfersh.elasticbeanstalk.com") | |||||
match = match || (u.Host == "jxm5d6emw5rknovg.onion") | |||||
match = match || (u.Host == "transfer.sh") | |||||
match = match || (u.Host == "127.0.0.1") | |||||
return match | |||||
match = match || (u.Path != r.URL.Path) | |||||
return | |||||
}).Methods("GET") | }).Methods("GET") | ||||
r.HandleFunc("/{token}/{filename}", getHandler).Methods("GET") | r.HandleFunc("/{token}/{filename}", getHandler).Methods("GET") | ||||
@@ -57,12 +57,9 @@ | |||||
<section id="home"> | <section id="home"> | ||||
<div class="wrapper"> | <div class="wrapper"> | ||||
<br/> | <br/> | ||||
<h2> | |||||
{{.Filename}}</h2> | |||||
<h4> | |||||
Type: <b>{{.ContentType}}</b></h4> | |||||
<h4> | |||||
Length: <b>{{.ContentLength}}</b> bytes</h4> | |||||
<h2>{{.Filename}}</h2> | |||||
<h4>type: <b>{{.ContentType}}</b></h4> | |||||
<h4>size: <b>{{.ContentLength | format "#,###."}}</b> bytes</h4> | |||||
<div> | <div> | ||||
<a href="#" id="copy-link-btn" class="btn-cta btn">copy link</a> | <a href="#" id="copy-link-btn" class="btn-cta btn">copy link</a> | ||||
<a href="{{.Url}}" class="btn-cta btn"> download</i> </a> | <a href="{{.Url}}" class="btn-cta btn"> download</i> </a> | ||||
@@ -56,10 +56,9 @@ | |||||
<section id="home"> | <section id="home"> | ||||
<div class="wrapper"> | <div class="wrapper"> | ||||
<br/> | <br/> | ||||
<h2> | |||||
{{.Filename}}</h2> | |||||
<a href="{{.Url}}"></a> | |||||
<h2> {{.Filename}}</h2> | |||||
<h4>type: <b>{{.ContentType}}</b></h4> | |||||
<h4>size: <b>{{.ContentLength | format "#,###."}}</b> bytes</h4> | |||||
<div class="row animated fadeInDown"> | <div class="row animated fadeInDown"> | ||||
<div id="from-terminal" class=" box col-md-8 col-md-offset-2 col-xs-12"> | <div id="from-terminal" class=" box col-md-8 col-md-offset-2 col-xs-12"> | ||||
<div class="terminal-top"> | <div class="terminal-top"> | ||||
@@ -55,9 +55,10 @@ | |||||
<section id="home"> | <section id="home"> | ||||
<div class="wrapper"> | <div class="wrapper"> | ||||
<br/> | |||||
<h2>{{.Filename}}</h2> | <h2>{{.Filename}}</h2> | ||||
<h4>Type: <b>{{.ContentType}}</b></h4> | |||||
<h4>Length: <b>{{.ContentLength}}</b> bytes</h4> | |||||
<h4>type: <b>{{.ContentType}}</b></h4> | |||||
<h4>size: <b>{{.ContentLength | format "#,###."}}</b> bytes</h4> | |||||
<div class="row animated fadeInDown"> | <div class="row animated fadeInDown"> | ||||
<div id="from-terminal" class=" box col-md-8 col-md-offset-2 col-xs-12"> | <div id="from-terminal" class=" box col-md-8 col-md-offset-2 col-xs-12"> | ||||
<div class="terminal-top"> | <div class="terminal-top"> | ||||
@@ -28,8 +28,10 @@ import ( | |||||
"github.com/goamz/goamz/aws" | "github.com/goamz/goamz/aws" | ||||
"github.com/goamz/goamz/s3" | "github.com/goamz/goamz/s3" | ||||
"github.com/golang/gddo/httputil/header" | "github.com/golang/gddo/httputil/header" | ||||
"math" | |||||
"net/http" | "net/http" | ||||
"net/mail" | "net/mail" | ||||
"strconv" | |||||
"strings" | "strings" | ||||
"time" | "time" | ||||
) | ) | ||||
@@ -45,6 +47,163 @@ func getBucket() (*s3.Bucket, error) { | |||||
return b, nil | 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.: | // Request.RemoteAddress contains port, which we want to remove i.e.: | ||||
// "[::1]:58292" => "[::1]" | // "[::1]:58292" => "[::1]" | ||||
func ipAddrFromRemoteAddr(s string) string { | func ipAddrFromRemoteAddr(s string) string { | ||||
@@ -31,12 +31,9 @@ include "includes/head.html" | |||||
<section id="home"> | <section id="home"> | ||||
<div class="wrapper"> | <div class="wrapper"> | ||||
<br/> | <br/> | ||||
<h2> | |||||
{{.Filename}}</h2> | |||||
<h4> | |||||
Type: <b>{{.ContentType}}</b></h4> | |||||
<h4> | |||||
Length: <b>{{.ContentLength}}</b> bytes</h4> | |||||
<h2>{{.Filename}}</h2> | |||||
<h4>type: <b>{{.ContentType}}</b></h4> | |||||
<h4>size: <b>{{.ContentLength | format "#,###."}}</b> bytes</h4> | |||||
<div> | <div> | ||||
<a href="#" id="copy-link-btn" class="btn-cta btn">copy link</a> | <a href="#" id="copy-link-btn" class="btn-cta btn">copy link</a> | ||||
<a href="{{.Url}}" class="btn-cta btn"> download</i> </a> | <a href="{{.Url}}" class="btn-cta btn"> download</i> </a> | ||||
@@ -30,10 +30,9 @@ include "includes/head.html" | |||||
<section id="home"> | <section id="home"> | ||||
<div class="wrapper"> | <div class="wrapper"> | ||||
<br/> | <br/> | ||||
<h2> | |||||
{{.Filename}}</h2> | |||||
<a href="{{.Url}}"></a> | |||||
<h2> {{.Filename}}</h2> | |||||
<h4>type: <b>{{.ContentType}}</b></h4> | |||||
<h4>size: <b>{{.ContentLength | format "#,###."}}</b> bytes</h4> | |||||
<div class="row animated fadeInDown"> | <div class="row animated fadeInDown"> | ||||
<div id="from-terminal" class=" box col-md-8 col-md-offset-2 col-xs-12"> | <div id="from-terminal" class=" box col-md-8 col-md-offset-2 col-xs-12"> | ||||
<div class="terminal-top"> | <div class="terminal-top"> | ||||
@@ -29,9 +29,10 @@ include "includes/head.html" | |||||
<section id="home"> | <section id="home"> | ||||
<div class="wrapper"> | <div class="wrapper"> | ||||
<br/> | |||||
<h2>{{.Filename}}</h2> | <h2>{{.Filename}}</h2> | ||||
<h4>Type: <b>{{.ContentType}}</b></h4> | |||||
<h4>Length: <b>{{.ContentLength}}</b> bytes</h4> | |||||
<h4>type: <b>{{.ContentType}}</b></h4> | |||||
<h4>size: <b>{{.ContentLength | format "#,###."}}</b> bytes</h4> | |||||
<div class="row animated fadeInDown"> | <div class="row animated fadeInDown"> | ||||
<div id="from-terminal" class=" box col-md-8 col-md-offset-2 col-xs-12"> | <div id="from-terminal" class=" box col-md-8 col-md-offset-2 col-xs-12"> | ||||
<div class="terminal-top"> | <div class="terminal-top"> | ||||