@@ -1,65 +0,0 @@ | |||||
/* | |||||
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 main | |||||
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) | |||||
} |
@@ -1,620 +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" | |||||
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("<pre>%s</pre>", 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) | |||||
} | |||||
} |
@@ -1,239 +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 ( | |||||
"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.") | |||||
} |
@@ -1,268 +0,0 @@ | |||||
package main | |||||
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 LocalStorage struct { | |||||
Storage | |||||
basedir string | |||||
} | |||||
func NewLocalStorage(basedir string) (*LocalStorage, error) { | |||||
return &LocalStorage{basedir: basedir}, nil | |||||
} | |||||
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() (*S3Storage, error) { | |||||
bucket, err := getBucket() | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
return &S3Storage{bucket: bucket}, nil | |||||
} | |||||
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 | |||||
} |
@@ -1,276 +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 ( | |||||
"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() (*s3.Bucket, error) { | |||||
auth, err := aws.GetAuth(config.AWS_ACCESS_KEY, config.AWS_SECRET_KEY, "", 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(config.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) | |||||
} |
@@ -1,67 +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 ( | |||||
"fmt" | |||||
_ "github.com/PuerkitoBio/ghost/handlers" | |||||
"github.com/dutchcoders/go-virustotal" | |||||
"github.com/gorilla/mux" | |||||
"github.com/kennygrant/sanitize" | |||||
"io" | |||||
"log" | |||||
"net/http" | |||||
"path/filepath" | |||||
) | |||||
func 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) | |||||
apikey := config.VIRUSTOTAL_KEY | |||||
vt, err := virustotal.NewVirusTotal(apikey) | |||||
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))) | |||||
} |