@@ -1,6 +1,8 @@ | |||||
# transfer.sh | # transfer.sh | ||||
Easy and fast file sharing from the command-line. This code contains the server with everything you need to create your own instance. Transfer.sh currently runs on top of Amazon S3. Other storage types will be added shortly. | |||||
Easy and fast file sharing from the command-line. This code contains the server with everything you need to create your own instance. | |||||
Transfer.sh support currently the s3 (Amazon S3) provider and local file system (local). | |||||
[![Build Status](https://travis-ci.org/dutchcoders/transfer.sh.svg?branch=master)](https://travis-ci.org/dutchcoders/transfer.sh) | [![Build Status](https://travis-ci.org/dutchcoders/transfer.sh.svg?branch=master)](https://travis-ci.org/dutchcoders/transfer.sh) | ||||
@@ -38,19 +38,19 @@ import ( | |||||
"github.com/golang/gddo/httputil/header" | "github.com/golang/gddo/httputil/header" | ||||
"github.com/gorilla/mux" | "github.com/gorilla/mux" | ||||
"github.com/kennygrant/sanitize" | "github.com/kennygrant/sanitize" | ||||
html_template "html/template" | |||||
"io" | "io" | ||||
"io/ioutil" | "io/ioutil" | ||||
"strconv" | |||||
"log" | "log" | ||||
"math/rand" | "math/rand" | ||||
"mime" | "mime" | ||||
"net/http" | "net/http" | ||||
"os" | "os" | ||||
"path/filepath" | "path/filepath" | ||||
"strconv" | |||||
"strings" | "strings" | ||||
"time" | |||||
html_template "html/template" | |||||
text_template "text/template" | text_template "text/template" | ||||
"time" | |||||
) | ) | ||||
func healthHandler(w http.ResponseWriter, r *http.Request) { | func healthHandler(w http.ResponseWriter, r *http.Request) { | ||||
@@ -101,7 +101,7 @@ func viewHandler(w http.ResponseWriter, r *http.Request) { | |||||
} | } | ||||
func notFoundHandler(w http.ResponseWriter, r *http.Request) { | func notFoundHandler(w http.ResponseWriter, r *http.Request) { | ||||
http.Error(w, http.StatusText(404), 404) | |||||
http.Error(w, http.StatusText(404), 404) | |||||
} | } | ||||
func postHandler(w http.ResponseWriter, r *http.Request) { | func postHandler(w http.ResponseWriter, r *http.Request) { | ||||
@@ -169,7 +169,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) { | |||||
log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) | log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) | ||||
if err = storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil { | |||||
if err = storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil { | |||||
log.Print(err) | log.Print(err) | ||||
http.Error(w, err.Error(), 500) | http.Error(w, err.Error(), 500) | ||||
return | return | ||||
@@ -275,7 +275,7 @@ func putHandler(w http.ResponseWriter, r *http.Request) { | |||||
contentType = mime.TypeByExtension(filepath.Ext(vars["filename"])) | contentType = mime.TypeByExtension(filepath.Ext(vars["filename"])) | ||||
} | } | ||||
token := Encode(10000000+int64(rand.Intn(1000000000))) | |||||
token := Encode(10000000 + int64(rand.Intn(1000000000))) | |||||
log.Printf("Uploading %s %d %s", token, filename, contentLength, contentType) | log.Printf("Uploading %s %d %s", token, filename, contentLength, contentType) | ||||
@@ -307,8 +307,8 @@ func zipHandler(w http.ResponseWriter, r *http.Request) { | |||||
zw := zip.NewWriter(w) | zw := zip.NewWriter(w) | ||||
for _, key := range strings.Split(files, ",") { | for _, key := range strings.Split(files, ",") { | ||||
token := sanitize.Path(strings.Split(key, "/")[0]) | |||||
filename := sanitize.Path(strings.Split(key, "/")[1]) | |||||
token := sanitize.Path(strings.Split(key, "/")[0]) | |||||
filename := sanitize.Path(strings.Split(key, "/")[1]) | |||||
reader, _, _, err := storage.Get(token, filename) | reader, _, _, err := storage.Get(token, filename) | ||||
if err != nil { | if err != nil { | ||||
@@ -322,7 +322,7 @@ func zipHandler(w http.ResponseWriter, r *http.Request) { | |||||
} | } | ||||
} | } | ||||
defer reader.Close() | |||||
defer reader.Close() | |||||
header := &zip.FileHeader{ | header := &zip.FileHeader{ | ||||
Name: strings.Split(key, "/")[1], | Name: strings.Split(key, "/")[1], | ||||
@@ -371,8 +371,8 @@ func tarGzHandler(w http.ResponseWriter, r *http.Request) { | |||||
defer zw.Close() | defer zw.Close() | ||||
for _, key := range strings.Split(files, ",") { | for _, key := range strings.Split(files, ",") { | ||||
token := strings.Split(key, "/")[0] | |||||
filename := strings.Split(key, "/")[1] | |||||
token := strings.Split(key, "/")[0] | |||||
filename := strings.Split(key, "/")[1] | |||||
reader, _, contentLength, err := storage.Get(token, filename) | reader, _, contentLength, err := storage.Get(token, filename) | ||||
if err != nil { | if err != nil { | ||||
@@ -386,7 +386,7 @@ func tarGzHandler(w http.ResponseWriter, r *http.Request) { | |||||
} | } | ||||
} | } | ||||
defer reader.Close() | |||||
defer reader.Close() | |||||
header := &tar.Header{ | header := &tar.Header{ | ||||
Name: strings.Split(key, "/")[1], | Name: strings.Split(key, "/")[1], | ||||
@@ -423,8 +423,8 @@ func tarHandler(w http.ResponseWriter, r *http.Request) { | |||||
defer zw.Close() | defer zw.Close() | ||||
for _, key := range strings.Split(files, ",") { | for _, key := range strings.Split(files, ",") { | ||||
token := strings.Split(key, "/")[0] | |||||
filename := strings.Split(key, "/")[1] | |||||
token := strings.Split(key, "/")[0] | |||||
filename := strings.Split(key, "/")[1] | |||||
reader, _, contentLength, err := storage.Get(token, filename) | reader, _, contentLength, err := storage.Get(token, filename) | ||||
if err != nil { | if err != nil { | ||||
@@ -438,7 +438,7 @@ func tarHandler(w http.ResponseWriter, r *http.Request) { | |||||
} | } | ||||
} | } | ||||
defer reader.Close() | |||||
defer reader.Close() | |||||
header := &tar.Header{ | header := &tar.Header{ | ||||
Name: strings.Split(key, "/")[1], | Name: strings.Split(key, "/")[1], | ||||
@@ -466,7 +466,7 @@ func getHandler(w http.ResponseWriter, r *http.Request) { | |||||
token := vars["token"] | token := vars["token"] | ||||
filename := vars["filename"] | filename := vars["filename"] | ||||
reader, contentType, contentLength, err := storage.Get(token, filename) | |||||
reader, contentType, contentLength, err := storage.Get(token, filename) | |||||
if err != nil { | if err != nil { | ||||
if err.Error() == "The specified key does not exist." { | if err.Error() == "The specified key does not exist." { | ||||
http.Error(w, "File not found", 404) | http.Error(w, "File not found", 404) | ||||
@@ -478,10 +478,10 @@ func getHandler(w http.ResponseWriter, r *http.Request) { | |||||
} | } | ||||
} | } | ||||
defer reader.Close() | |||||
defer reader.Close() | |||||
w.Header().Set("Content-Type", contentType) | w.Header().Set("Content-Type", contentType) | ||||
w.Header().Set("Content-Length", strconv.FormatUint(contentLength, 10)) | |||||
w.Header().Set("Content-Length", strconv.FormatUint(contentLength, 10)) | |||||
mediaType, _, _ := mime.ParseMediaType(contentType) | mediaType, _, _ := mime.ParseMediaType(contentType) | ||||
@@ -131,23 +131,23 @@ func main() { | |||||
} | } | ||||
config.Temp = *temp | 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.") | |||||
} | |||||
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.") | |||||
} | |||||
log.Printf("Transfer.sh server started. :%v using temp folder: %s", *port, config.Temp) | log.Printf("Transfer.sh server started. :%v using temp folder: %s", *port, config.Temp) | ||||
log.Printf("---------------------------") | log.Printf("---------------------------") | ||||
@@ -1,100 +1,99 @@ | |||||
package main | package main | ||||
import ( | import ( | ||||
"io" | |||||
"github.com/goamz/goamz/s3" | |||||
"strconv" | |||||
"fmt" | |||||
"os" | |||||
"path/filepath" | |||||
"fmt" | |||||
"github.com/goamz/goamz/s3" | |||||
"io" | |||||
"os" | |||||
"path/filepath" | |||||
"strconv" | |||||
) | ) | ||||
type Storage interface { | type Storage interface { | ||||
Get(token string, filename string) (reader io.ReadCloser, contentType string, contentLength uint64, err error) | |||||
Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error | |||||
Get(token string, filename string) (reader io.ReadCloser, contentType string, contentLength uint64, err error) | |||||
Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error | |||||
} | } | ||||
type LocalStorage struct { | type LocalStorage struct { | ||||
Storage | |||||
basedir string | |||||
Storage | |||||
basedir string | |||||
} | } | ||||
func NewLocalStorage(basedir string) (*LocalStorage, error) { | func NewLocalStorage(basedir string) (*LocalStorage, error) { | ||||
return &LocalStorage {basedir: basedir}, nil | |||||
return &LocalStorage{basedir: basedir}, nil | |||||
} | } | ||||
func (s *LocalStorage) Get(token string, filename string) (reader io.ReadCloser, contentType string, contentLength uint64, err error) { | |||||
path := filepath.Join(s.basedir, token, filename) | |||||
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 | |||||
} | |||||
// 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 { | |||||
} | |||||
var fi os.FileInfo | |||||
if fi, err = os.Lstat(path); err != nil { | |||||
} | |||||
contentLength = uint64(fi.Size()) | |||||
contentLength = uint64(fi.Size()) | |||||
contentType = "" | |||||
contentType = "" | |||||
return | |||||
return | |||||
} | } | ||||
func (s *LocalStorage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error { | func (s *LocalStorage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error { | ||||
var f io.WriteCloser | |||||
var err error | |||||
var f io.WriteCloser | |||||
var err error | |||||
path := filepath.Join(s.basedir, token) | |||||
path := filepath.Join(s.basedir, token) | |||||
if err = os.Mkdir(path, 0700); err != nil && !os.IsExist(err) { | |||||
return err | |||||
} | |||||
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 | |||||
} | |||||
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() | |||||
defer f.Close() | |||||
if _, err = io.Copy(f, reader); err != nil { | |||||
return err | |||||
} | |||||
if _, err = io.Copy(f, reader); err != nil { | |||||
return err | |||||
} | |||||
return nil | |||||
return nil | |||||
} | } | ||||
type S3Storage struct { | type S3Storage struct { | ||||
Storage | |||||
bucket *s3.Bucket | |||||
Storage | |||||
bucket *s3.Bucket | |||||
} | } | ||||
func NewS3Storage() (*S3Storage, error) { | func NewS3Storage() (*S3Storage, error) { | ||||
bucket, err := getBucket() | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
bucket, err := getBucket() | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
return &S3Storage {bucket: bucket}, nil | |||||
return &S3Storage{bucket: bucket}, nil | |||||
} | } | ||||
func (s *S3Storage) Get(token string, filename string) (reader io.ReadCloser, contentType string, contentLength uint64, err error) { | |||||
key := fmt.Sprintf("%s/%s", token, filename) | |||||
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) | |||||
contentType = "" | |||||
contentLength, err = strconv.ParseUint(response.Header.Get("Content-Length"), 10, 0) | |||||
// content type , content length | |||||
response, err := s.bucket.GetResponse(key) | |||||
contentType = "" | |||||
contentLength, err = strconv.ParseUint(response.Header.Get("Content-Length"), 10, 0) | |||||
reader = response.Body | |||||
return | |||||
reader = response.Body | |||||
return | |||||
} | } | ||||
func (s *S3Storage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error { | func (s *S3Storage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error { | ||||
key := fmt.Sprintf("%s/%s", token, filename) | |||||
err := s.bucket.PutReader(key, reader, int64(contentLength), contentType, s3.Private, s3.Options{}) | |||||
return err | |||||
key := fmt.Sprintf("%s/%s", token, filename) | |||||
err := s.bucket.PutReader(key, reader, int64(contentLength), contentType, s3.Private, s3.Options{}) | |||||
return err | |||||
} | } |