@@ -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))) | |||
} |