* use dep for vendoring * lets encrypt * moved web to transfer.sh-web repo * single command install * added first teststags/v1.0.0
@@ -1,8 +1,5 @@ | |||
language: go | |||
go: | |||
- 1.1 | |||
- 1.2 | |||
- 1.3 | |||
- release | |||
- tip | |||
@@ -1,308 +0,0 @@ | |||
'use strict'; | |||
// # Globbing | |||
// for performance reasons we're only matching one level down: | |||
// 'test/spec/{,*/}*.js' | |||
// use this if you want to match all subfolders: | |||
// 'test/spec/**/*.js' | |||
module.exports = function (grunt) { | |||
// load all grunt tasks | |||
require('load-grunt-tasks')(grunt); | |||
// show elapsed time at the end | |||
require('time-grunt')(grunt); | |||
// configurable paths | |||
var yeomanConfig = { | |||
app: require('./bower.json').appPath || 'transfersh-web', | |||
dist: 'transfersh-server/static/' | |||
}; | |||
grunt.initConfig({ | |||
yeoman: yeomanConfig, | |||
watch: { | |||
less: { | |||
files: ['<%= yeoman.app %>/styles/{,*/}*.less'], | |||
tasks: ['less'] | |||
}, | |||
gruntfile: { | |||
files: ['Gruntfile.js'] | |||
}, | |||
includes: { | |||
files: ['<%= yeoman.app %>/*.html', '.tmp/*.html'], | |||
tasks: ['includes:server'] | |||
}, | |||
livereload: { | |||
options: { | |||
livereload: '<%= connect.options.livereload %>' | |||
}, | |||
files: [ | |||
'<%= yeoman.app %>/*.html', | |||
'{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css', | |||
'{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js', | |||
'<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' | |||
], | |||
tasks: ['includes:server'] | |||
} | |||
}, | |||
connect: { | |||
options: { | |||
port: 9000, | |||
// change this to '0.0.0.0' to access the server from outside | |||
hostname: 'localhost', | |||
livereload: 35729 | |||
}, | |||
livereload: { | |||
options: { | |||
open: true, | |||
base: [ | |||
'.tmp', | |||
'<%= yeoman.app %>' | |||
] | |||
} | |||
}, | |||
test: { | |||
options: { | |||
port: 9001, | |||
base: [ | |||
'.tmp', | |||
'test', | |||
'<%= yeoman.app %>' | |||
] | |||
} | |||
}, | |||
dist: { | |||
options: { | |||
base: '<%= yeoman.dist %>' | |||
} | |||
} | |||
}, | |||
clean: { | |||
dist: { | |||
files: [{ | |||
dot: true, | |||
src: [ | |||
'.tmp', | |||
'<%= yeoman.dist %>/*', | |||
'!<%= yeoman.dist %>/.git*' | |||
] | |||
}] | |||
}, | |||
server: '.tmp' | |||
}, | |||
jshint: { | |||
options: { | |||
jshintrc: '.jshintrc', | |||
reporter: require('jshint-stylish') | |||
}, | |||
all: [ | |||
'Gruntfile.js', | |||
'<%= yeoman.app %>/scripts/{,*/}*.js', | |||
'!<%= yeoman.app %>/scripts/vendor/*', | |||
'test/spec/{,*/}*.js' | |||
] | |||
}, | |||
less: { | |||
dist: { | |||
files: { | |||
'<%= yeoman.app %>/styles/main.css': ['<%= yeoman.app %>/styles/main.less'] | |||
}, | |||
options: { | |||
sourceMap: true, | |||
sourceMapFilename: '<%= yeoman.app %>/styles/main.css.map', | |||
sourceMapBasepath: '<%= yeoman.app %>/', | |||
sourceMapRootpath: '/' | |||
} | |||
} | |||
}, | |||
includes: { | |||
build: { | |||
cwd: '<%= yeoman.app %>', | |||
src: ['*.html', 'includes/*.html'], | |||
dest: '<%= yeoman.dist %>', | |||
options: { | |||
flatten: true, | |||
banner: '' | |||
} | |||
}, | |||
server: { | |||
cwd: '<%= yeoman.app %>', | |||
src: ['*.html', 'includes/*.html'], | |||
dest: '.tmp/', | |||
options: { | |||
flatten: true, | |||
banner: '' | |||
} | |||
} | |||
}, | |||
// not used since Uglify task does concat, | |||
// but still available if needed | |||
/*concat: { | |||
dist: {} | |||
},*/ | |||
// not enabled since usemin task does concat and uglify | |||
// check index.html to edit your build targets | |||
// enable this task if you prefer defining your build targets here | |||
/*uglify: { | |||
dist: {} | |||
},*/ | |||
rev: { | |||
dist: { | |||
files: { | |||
src: [ | |||
'<%= yeoman.dist %>/scripts/{,*/}*.js', | |||
'<%= yeoman.dist %>/styles/{,*/}*.css', | |||
'<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', | |||
'<%= yeoman.dist %>/fonts/{,*/}*.*' | |||
] | |||
} | |||
} | |||
}, | |||
useminPrepare: { | |||
html: '<%= yeoman.app %>/*.html', | |||
options: { | |||
dest: '<%= yeoman.dist %>' | |||
} | |||
}, | |||
usemin: { | |||
html: ['<%= yeoman.dist %>/{,*/}*.html'], | |||
css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], | |||
options: { | |||
dirs: ['<%= yeoman.dist %>'] | |||
} | |||
}, | |||
imagemin: { | |||
dist: { | |||
files: [{ | |||
expand: true, | |||
cwd: '<%= yeoman.app %>/images', | |||
src: '{,*/}*.{png,jpg,jpeg}', | |||
dest: '<%= yeoman.dist %>/images' | |||
}] | |||
} | |||
}, | |||
cssmin: { | |||
dist: { | |||
files: { | |||
'<%= yeoman.dist %>/styles/main.css': [ | |||
'.tmp/styles/{,*/}*.css', | |||
'<%= yeoman.app %>/styles/{,*/}*.css' | |||
] | |||
} | |||
} | |||
}, | |||
htmlmin: { | |||
dist: { | |||
options: { | |||
/*removeCommentsFromCDATA: true, | |||
// https://github.com/yeoman/grunt-usemin/issues/44 | |||
//collapseWhitespace: true, | |||
collapseBooleanAttributes: true, | |||
removeAttributeQuotes: true, | |||
removeRedundantAttributes: true, | |||
useShortDoctype: true, | |||
removeEmptyAttributes: true, | |||
removeOptionalTags: true*/ | |||
}, | |||
files: [{ | |||
expand: true, | |||
cwd: '<%= yeoman.app %>', | |||
src: '*.html', | |||
dest: '<%= yeoman.dist %>' | |||
}] | |||
} | |||
}, | |||
copy: { | |||
dist: { | |||
files: [{ | |||
expand: true, | |||
dot: true, | |||
cwd: '<%= yeoman.app %>', | |||
dest: '<%= yeoman.dist %>', | |||
src: [ | |||
'*.{ico,png,txt}', | |||
'fonts/{,*/}*.*', | |||
'.htaccess', | |||
'index.txt', | |||
'404.txt', | |||
'images/{,*/}*.{webp,gif,svg}' | |||
] | |||
}] | |||
}, | |||
server: { | |||
files: [{ | |||
expand: true, | |||
dot: true, | |||
cwd: '<%= yeoman.app %>/bower_components/font-awesome/fonts/', | |||
dest: '<%= yeoman.app %>/fonts/font-awesome', | |||
src: ['*'] | |||
}, { | |||
expand: true, | |||
dot: true, | |||
cwd: '<%= yeoman.app %>/bower_components/bootstrap/dist/fonts/', | |||
dest: '<%= yeoman.app %>/fonts/glyphicons', | |||
src: ['*'] | |||
}] | |||
} | |||
}, | |||
concurrent: { | |||
dist: [ | |||
'less', | |||
'imagemin', | |||
'htmlmin' | |||
] | |||
} | |||
}); | |||
grunt.registerTask('serve', function (target) { | |||
if (target === 'dist') { | |||
return grunt.task.run(['build', 'connect:dist:keepalive']); | |||
} | |||
grunt.task.run([ | |||
'clean:server', | |||
'less', | |||
'includes:server', | |||
'copy:server', | |||
'connect:livereload', | |||
'watch' | |||
]); | |||
}); | |||
grunt.registerTask('server', function () { | |||
grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); | |||
grunt.task.run(['serve']); | |||
}); | |||
grunt.registerTask('test', [ | |||
'clean:server', | |||
'less', | |||
'copy:server', | |||
'connect:test', | |||
]); | |||
grunt.registerTask('build', [ | |||
'clean:dist', | |||
'copy:server', | |||
'useminPrepare', | |||
'concurrent', | |||
'cssmin', | |||
'concat', | |||
'includes:build', | |||
'uglify', | |||
'copy', | |||
'usemin', | |||
]); | |||
grunt.registerTask('default', [ | |||
'jshint', | |||
'test', | |||
'build' | |||
]); | |||
}; |
@@ -1,4 +1,4 @@ | |||
# transfer.sh [](https://gitter.im/dutchcoders/transfer.sh?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://travis-ci.org/dutchcoders/transfer.sh) | |||
# transfer.sh [](https://gitter.im/dutchcoders/transfer.sh?utm_source=badge&utm_medium=badge&utm_campaign=&utm_campaign=pr-badge&utm_content=badge) [](https://goreportcard.com/report/dutchcoders/transfer.sh) [](https://hub.docker.com/r/transfer.sh/transfer.sh/) [](https://travis-ci.org/dutchcoders/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. | |||
@@ -0,0 +1,227 @@ | |||
package cmd | |||
import ( | |||
"fmt" | |||
"os" | |||
"strings" | |||
"github.com/dutchcoders/transfer.sh/server" | |||
"github.com/fatih/color" | |||
"github.com/minio/cli" | |||
) | |||
var Version = "0.1" | |||
var helpTemplate = `NAME: | |||
{{.Name}} - {{.Usage}} | |||
DESCRIPTION: | |||
{{.Description}} | |||
USAGE: | |||
{{.Name}} {{if .Flags}}[flags] {{end}}command{{if .Flags}}{{end}} [arguments...] | |||
COMMANDS: | |||
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} | |||
{{end}}{{if .Flags}} | |||
FLAGS: | |||
{{range .Flags}}{{.}} | |||
{{end}}{{end}} | |||
VERSION: | |||
` + Version + | |||
`{{ "\n"}}` | |||
var globalFlags = []cli.Flag{ | |||
cli.StringFlag{ | |||
Name: "listener", | |||
Usage: "127.0.0.1:8080", | |||
Value: "127.0.0.1:8080", | |||
}, | |||
// redirect to https? | |||
// hostnames | |||
cli.StringFlag{ | |||
Name: "profile-listener", | |||
Usage: "127.0.0.1:6060", | |||
Value: "", | |||
}, | |||
cli.BoolFlag{ | |||
Name: "force-https", | |||
Usage: "", | |||
}, | |||
cli.StringFlag{ | |||
Name: "tls-listener", | |||
Usage: "127.0.0.1:8443", | |||
Value: "", | |||
}, | |||
cli.StringFlag{ | |||
Name: "tls-cert-file", | |||
Value: "", | |||
}, | |||
cli.StringFlag{ | |||
Name: "tls-private-key", | |||
Value: "", | |||
}, | |||
cli.StringFlag{ | |||
Name: "temp-path", | |||
Usage: "path to temp files", | |||
Value: os.TempDir(), | |||
}, | |||
cli.StringFlag{ | |||
Name: "web-path", | |||
Usage: "path to static web files", | |||
Value: "", | |||
}, | |||
cli.StringFlag{ | |||
Name: "provider", | |||
Usage: "s3|local", | |||
Value: "", | |||
}, | |||
cli.StringFlag{ | |||
Name: "aws-access-key", | |||
Usage: "", | |||
Value: "", | |||
EnvVar: "AWS_ACCESS_KEY", | |||
}, | |||
cli.StringFlag{ | |||
Name: "aws-secret-key", | |||
Usage: "", | |||
Value: "", | |||
EnvVar: "AWS_SECRET_KEY", | |||
}, | |||
cli.StringFlag{ | |||
Name: "bucket", | |||
Usage: "", | |||
Value: "", | |||
EnvVar: "BUCKET", | |||
}, | |||
cli.StringFlag{ | |||
Name: "lets-encrypt-hosts", | |||
Usage: "host1, host2", | |||
Value: "", | |||
EnvVar: "HOSTS", | |||
}, | |||
cli.StringFlag{ | |||
Name: "log", | |||
Usage: "/var/log/transfersh.log", | |||
Value: "", | |||
}, | |||
cli.StringFlag{ | |||
Name: "basedir", | |||
Usage: "path to storage", | |||
Value: "", | |||
}, | |||
cli.BoolFlag{ | |||
Name: "profiler", | |||
Usage: "enable profiling", | |||
}, | |||
} | |||
type Cmd struct { | |||
*cli.App | |||
} | |||
func VersionAction(c *cli.Context) { | |||
fmt.Println(color.YellowString(fmt.Sprintf("transfer.sh: Easy file sharing from the command line"))) | |||
} | |||
func New() *Cmd { | |||
app := cli.NewApp() | |||
app.Name = "transfer.sh" | |||
app.Author = "" | |||
app.Usage = "transfer.sh" | |||
app.Description = `Easy file sharing from the command line` | |||
app.Flags = globalFlags | |||
app.CustomAppHelpTemplate = helpTemplate | |||
app.Commands = []cli.Command{ | |||
{ | |||
Name: "version", | |||
Action: VersionAction, | |||
}, | |||
} | |||
app.Before = func(c *cli.Context) error { | |||
return nil | |||
} | |||
app.Action = func(c *cli.Context) { | |||
options := []server.OptionFn{} | |||
if v := c.String("listener"); v != "" { | |||
options = append(options, server.Listener(v)) | |||
} | |||
if v := c.String("tls-listener"); v != "" { | |||
options = append(options, server.TLSListener(v)) | |||
} | |||
if v := c.String("profile-listener"); v != "" { | |||
options = append(options, server.ProfileListener(v)) | |||
} | |||
if v := c.String("web-path"); v != "" { | |||
options = append(options, server.WebPath(v)) | |||
} | |||
if v := c.String("temp-path"); v != "" { | |||
options = append(options, server.TempPath(v)) | |||
} | |||
if v := c.String("lets-encrypt-hosts"); v != "" { | |||
options = append(options, server.UseLetsEncrypt(strings.Split(v, ","))) | |||
} | |||
if cert := c.String("tls-cert-file"); cert == "" { | |||
} else if pk := c.String("tls-private-key"); pk == "" { | |||
} else { | |||
options = append(options, server.TLSConfig(cert, pk)) | |||
} | |||
if c.Bool("profiler") { | |||
options = append(options, server.EnableProfiler()) | |||
} | |||
if c.Bool("force-https") { | |||
options = append(options, server.ForceHTTPs()) | |||
} | |||
switch provider := c.String("provider"); provider { | |||
case "s3": | |||
if accessKey := c.String("aws-access-key"); accessKey == "" { | |||
panic("access-key not set.") | |||
} else if secretKey := c.String("aws-secret-key"); secretKey == "" { | |||
panic("secret-key not set.") | |||
} else if bucket := c.String("bucket"); bucket == "" { | |||
panic("bucket not set.") | |||
} else if storage, err := server.NewS3Storage(accessKey, secretKey, bucket); err != nil { | |||
panic(err) | |||
} else { | |||
options = append(options, server.UseStorage(storage)) | |||
} | |||
case "local": | |||
if v := c.String("basedir"); v == "" { | |||
panic("basedir not set.") | |||
} else if storage, err := server.NewLocalStorage(v); err != nil { | |||
panic(err) | |||
} else { | |||
options = append(options, server.UseStorage(storage)) | |||
} | |||
default: | |||
panic("Provider not set or invalid.") | |||
} | |||
srvr, err := server.New( | |||
options..., | |||
) | |||
if err != nil { | |||
fmt.Println(color.RedString("Error starting server: %s", err.Error())) | |||
return | |||
} | |||
srvr.Run() | |||
} | |||
return &Cmd{ | |||
App: app, | |||
} | |||
} |
@@ -0,0 +1,204 @@ | |||
{ | |||
"memo": "07876113f39e289dbd1d493a6ba955bad81664a6f5291a4daa554700d5d536f3", | |||
"projects": [ | |||
{ | |||
"name": "github.com/PuerkitoBio/ghost", | |||
"branch": "master", | |||
"revision": "206e6e460e14a42d1d811c970b30248db058e9b2", | |||
"packages": [ | |||
".", | |||
"handlers" | |||
] | |||
}, | |||
{ | |||
"name": "github.com/dutchcoders/go-clamd", | |||
"branch": "master", | |||
"revision": "a9a81beaffff0392094052913ec45fa140eb8511", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/dutchcoders/go-virustotal", | |||
"branch": "master", | |||
"revision": "24cc8e6fa329f020c70a3b32330b5743f1ba7971", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/dutchcoders/transfer.sh-web", | |||
"branch": "master", | |||
"revision": "648a3f436b4772ca979e5d272010fd2719a7c5e2", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/elazarl/go-bindata-assetfs", | |||
"branch": "master", | |||
"revision": "30f82fa23fd844bd5bb1e5f216db87fd77b5eb43", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/fatih/color", | |||
"version": "v1.4.1", | |||
"revision": "9131ab34cf20d2f6d83fdc67168a5430d1c7dc23", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/garyburd/redigo", | |||
"version": "v1.0.0", | |||
"revision": "8873b2f1995f59d4bcdd2b0dc9858e2cb9bf0c13", | |||
"packages": [ | |||
"internal", | |||
"redis" | |||
] | |||
}, | |||
{ | |||
"name": "github.com/goamz/goamz", | |||
"branch": "master", | |||
"revision": "c35091c30f44b7f151ec9028b895465a191d1ea7", | |||
"packages": [ | |||
"aws", | |||
"s3" | |||
] | |||
}, | |||
{ | |||
"name": "github.com/golang/gddo", | |||
"branch": "master", | |||
"revision": "72302b972abba39585150723aea3cf343e99437c", | |||
"packages": [ | |||
"httputil/header" | |||
] | |||
}, | |||
{ | |||
"name": "github.com/gorilla/context", | |||
"version": "v1.1", | |||
"revision": "1ea25387ff6f684839d82767c1733ff4d4d15d0a", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/gorilla/mux", | |||
"version": "v1.3.0", | |||
"revision": "392c28fe23e1c45ddba891b0320b3b5df220beea", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/gorilla/securecookie", | |||
"version": "v1.1", | |||
"revision": "667fe4e3466a040b780561fe9b51a83a3753eefc", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/kennygrant/sanitize", | |||
"version": "v1.2", | |||
"revision": "6a0bfdde8629a3a3a7418a7eae45c54154692514", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/mattn/go-colorable", | |||
"version": "v0.0.7", | |||
"revision": "d228849504861217f796da67fae4f6e347643f15", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/mattn/go-isatty", | |||
"version": "v0.0.1", | |||
"revision": "3a115632dcd687f9c8cd01679c83a06a0e21c1f3", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/minio/cli", | |||
"version": "v1.3.0", | |||
"revision": "8683fa7fef37cc8cb092f47bdb6b403e0049f9ee", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/nu7hatch/gouuid", | |||
"branch": "master", | |||
"revision": "179d4d0c4d8d407a32af483c2354df1d2c91e6c3", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/russross/blackfriday", | |||
"version": "v1.4", | |||
"revision": "0b647d0506a698cca42caca173e55559b12a69f2", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/shurcooL/sanitized_anchor_name", | |||
"branch": "master", | |||
"revision": "1dba4b3954bc059efc3991ec364f9f9a35f597d2", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "github.com/vaughan0/go-ini", | |||
"branch": "master", | |||
"revision": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1", | |||
"packages": [ | |||
"." | |||
] | |||
}, | |||
{ | |||
"name": "golang.org/x/crypto", | |||
"branch": "master", | |||
"revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", | |||
"packages": [ | |||
"acme", | |||
"acme/autocert" | |||
] | |||
}, | |||
{ | |||
"name": "golang.org/x/net", | |||
"branch": "master", | |||
"revision": "a6577fac2d73be281a500b310739095313165611", | |||
"packages": [ | |||
"context", | |||
"context/ctxhttp", | |||
"html", | |||
"html/atom" | |||
] | |||
}, | |||
{ | |||
"name": "golang.org/x/sys", | |||
"branch": "master", | |||
"revision": "99f16d856c9836c42d24e7ab64ea72916925fa97", | |||
"packages": [ | |||
"unix" | |||
] | |||
}, | |||
{ | |||
"name": "gopkg.in/check.v1", | |||
"branch": "v1", | |||
"revision": "20d25e2804050c1cd24a7eea1e7a6447dd0e74ec", | |||
"packages": [ | |||
"." | |||
] | |||
} | |||
] | |||
} |
@@ -0,0 +1,8 @@ | |||
package main | |||
import "github.com/dutchcoders/transfer.sh/cmd" | |||
func main() { | |||
app := cmd.New() | |||
app.RunAndExitOnError() | |||
} |
@@ -0,0 +1,7 @@ | |||
{ | |||
"dependencies": { | |||
"github.com/dutchcoders/transfer.sh-web": { | |||
"branch": "master" | |||
} | |||
} | |||
} |
@@ -0,0 +1,76 @@ | |||
/* | |||
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 server | |||
import ( | |||
// _ "transfer.sh/app/handlers" | |||
// _ "transfer.sh/app/utils" | |||
"fmt" | |||
"io" | |||
"log" | |||
"net/http" | |||
"path/filepath" | |||
"time" | |||
clamd "github.com/dutchcoders/go-clamd" | |||
"github.com/gorilla/mux" | |||
"github.com/kennygrant/sanitize" | |||
) | |||
func (s *Server) 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(s.ClamAVDaemonHost) | |||
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) | |||
} |
@@ -0,0 +1,65 @@ | |||
/* | |||
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 server | |||
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) | |||
} |
@@ -0,0 +1,586 @@ | |||
/* | |||
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 server | |||
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" | |||
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 (s *Server) 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 (s *Server) 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 (s *Server) notFoundHandler(w http.ResponseWriter, r *http.Request) { | |||
http.Error(w, http.StatusText(404), 404) | |||
} | |||
func (s *Server) 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(s.tempPath, "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 (s *Server) 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(s.tempPath, "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 (s *Server) 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 (s *Server) 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 (s *Server) 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 (s *Server) 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 (s *Server) RedirectHandler(h http.Handler) http.HandlerFunc { | |||
return func(w http.ResponseWriter, r *http.Request) { | |||
if !s.forceHTTPs { | |||
// we don't want to enforce https | |||
} else if r.URL.Path == "/health.html" { | |||
// health check url won't redirect | |||
} else if strings.HasSuffix(ipAddrFromRemoteAddr(r.Host), ".onion") { | |||
// .onion addresses cannot get a valid certificate, so don't redirect | |||
} else if r.Header.Get("X-Forwarded-Proto") == "https" { | |||
} else if r.URL.Scheme == "https" { | |||
} else { | |||
u := *r.URL | |||
u.Scheme = "https" | |||
http.Redirect(w, r, u.String(), http.StatusPermanentRedirect) | |||
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) | |||
} | |||
} |
@@ -0,0 +1,620 @@ | |||
/* | |||
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["file |