From 9ba5c5a8a749de16c8ca038d591319f571be922d Mon Sep 17 00:00:00 2001 From: Remco Date: Thu, 16 Oct 2014 20:01:43 +0200 Subject: [PATCH] Initial --- .gitignore | 15 + .jshintrc | 23 + .travis.yml | 10 + Gruntfile.js | 287 ++ README.md | 8 + Vagrantfile | 13 + bower.json | 21 + extras/clamd | 159 ++ extras/transfersh | 165 ++ package.json | 32 + transfersh-server/codec.go | 41 + transfersh-server/handlers.go | 578 ++++ transfersh-server/main.go | 139 + transfersh-server/run.sh.sample | 10 + transfersh-server/static/404.html | 157 ++ transfersh-server/static/favicon.ico | Bin 0 -> 4286 bytes .../static/fonts/font-awesome/FontAwesome.otf | Bin 0 -> 62856 bytes .../font-awesome/fontawesome-webfont.eot | Bin 0 -> 38205 bytes .../font-awesome/fontawesome-webfont.svg | 414 +++ .../font-awesome/fontawesome-webfont.ttf | Bin 0 -> 80652 bytes .../font-awesome/fontawesome-webfont.woff | Bin 0 -> 44432 bytes .../glyphicons-halflings-regular.eot | Bin 0 -> 20290 bytes .../glyphicons-halflings-regular.svg | 229 ++ .../glyphicons-halflings-regular.ttf | Bin 0 -> 41236 bytes .../glyphicons-halflings-regular.woff | Bin 0 -> 23292 bytes transfersh-server/static/fonts/transfersh.eot | Bin 0 -> 3824 bytes transfersh-server/static/fonts/transfersh.svg | 25 + transfersh-server/static/fonts/transfersh.ttf | Bin 0 -> 3648 bytes .../static/fonts/transfersh.woff | Bin 0 -> 3060 bytes .../static/images/Logo-orange.png | Bin 0 -> 9074 bytes transfersh-server/static/images/terminal.svg | 1 + transfersh-server/static/index.html | 242 ++ transfersh-server/static/index.txt | 29 + transfersh-server/static/robots.txt | 5 + transfersh-server/static/scripts/main.js | 3 + .../static/scripts/vendor/modernizr.js | 1 + transfersh-server/static/styles/main.css | 1 + .../templates/index.html.template | 194 ++ .../index.html.template.bak.template | 309 +++ .../templates/index.txt.template | 29 + transfersh-server/utils.go | 80 + transfersh-server/virustotal.go | 67 + transfersh-web/404.html | 157 ++ transfersh-web/favicon.ico | Bin 0 -> 4286 bytes .../fonts/font-awesome/FontAwesome.otf | Bin 0 -> 62856 bytes .../font-awesome/fontawesome-webfont.eot | Bin 0 -> 38205 bytes .../font-awesome/fontawesome-webfont.svg | 414 +++ .../font-awesome/fontawesome-webfont.ttf | Bin 0 -> 80652 bytes .../font-awesome/fontawesome-webfont.woff | Bin 0 -> 44432 bytes .../glyphicons-halflings-regular.eot | Bin 0 -> 20290 bytes .../glyphicons-halflings-regular.svg | 229 ++ .../glyphicons-halflings-regular.ttf | Bin 0 -> 41236 bytes .../glyphicons-halflings-regular.woff | Bin 0 -> 23292 bytes transfersh-web/fonts/transfersh.eot | Bin 0 -> 3824 bytes transfersh-web/fonts/transfersh.svg | 25 + transfersh-web/fonts/transfersh.ttf | Bin 0 -> 3648 bytes transfersh-web/fonts/transfersh.woff | Bin 0 -> 3060 bytes transfersh-web/images/Logo-orange.png | Bin 0 -> 15542 bytes transfersh-web/images/terminal.svg | 146 + transfersh-web/index.html | 249 ++ transfersh-web/index.txt | 29 + transfersh-web/robots.txt | 5 + transfersh-web/scripts/main.js | 112 + transfersh-web/scripts/mainbk.js | 24 + transfersh-web/styles/bootstrap.less | 49 + transfersh-web/styles/config.less | 31 + transfersh-web/styles/contact.less | 64 + transfersh-web/styles/dropzone.less | 155 ++ transfersh-web/styles/global.less | 66 + transfersh-web/styles/home.less | 59 + transfersh-web/styles/main.css | 2386 +++++++++++++++++ transfersh-web/styles/main.css.map | 1 + transfersh-web/styles/main.less | 14 + transfersh-web/styles/pages.less | 94 + transfersh-web/styles/transfersh-icons.less | 85 + 75 files changed, 7681 insertions(+) create mode 100644 .gitignore create mode 100644 .jshintrc create mode 100644 .travis.yml create mode 100644 Gruntfile.js create mode 100644 README.md create mode 100644 Vagrantfile create mode 100644 bower.json create mode 100755 extras/clamd create mode 100755 extras/transfersh create mode 100644 package.json create mode 100644 transfersh-server/codec.go create mode 100644 transfersh-server/handlers.go create mode 100644 transfersh-server/main.go create mode 100644 transfersh-server/run.sh.sample create mode 100644 transfersh-server/static/404.html create mode 100644 transfersh-server/static/favicon.ico create mode 100644 transfersh-server/static/fonts/font-awesome/FontAwesome.otf create mode 100644 transfersh-server/static/fonts/font-awesome/fontawesome-webfont.eot create mode 100644 transfersh-server/static/fonts/font-awesome/fontawesome-webfont.svg create mode 100644 transfersh-server/static/fonts/font-awesome/fontawesome-webfont.ttf create mode 100644 transfersh-server/static/fonts/font-awesome/fontawesome-webfont.woff create mode 100644 transfersh-server/static/fonts/glyphicons/glyphicons-halflings-regular.eot create mode 100644 transfersh-server/static/fonts/glyphicons/glyphicons-halflings-regular.svg create mode 100644 transfersh-server/static/fonts/glyphicons/glyphicons-halflings-regular.ttf create mode 100644 transfersh-server/static/fonts/glyphicons/glyphicons-halflings-regular.woff create mode 100644 transfersh-server/static/fonts/transfersh.eot create mode 100644 transfersh-server/static/fonts/transfersh.svg create mode 100644 transfersh-server/static/fonts/transfersh.ttf create mode 100644 transfersh-server/static/fonts/transfersh.woff create mode 100644 transfersh-server/static/images/Logo-orange.png create mode 100644 transfersh-server/static/images/terminal.svg create mode 100644 transfersh-server/static/index.html create mode 100644 transfersh-server/static/index.txt create mode 100644 transfersh-server/static/robots.txt create mode 100644 transfersh-server/static/scripts/main.js create mode 100644 transfersh-server/static/scripts/vendor/modernizr.js create mode 100644 transfersh-server/static/styles/main.css create mode 100644 transfersh-server/templates/index.html.template create mode 100644 transfersh-server/templates/index.html.template.bak.template create mode 100644 transfersh-server/templates/index.txt.template create mode 100644 transfersh-server/utils.go create mode 100644 transfersh-server/virustotal.go create mode 100644 transfersh-web/404.html create mode 100644 transfersh-web/favicon.ico create mode 100644 transfersh-web/fonts/font-awesome/FontAwesome.otf create mode 100644 transfersh-web/fonts/font-awesome/fontawesome-webfont.eot create mode 100644 transfersh-web/fonts/font-awesome/fontawesome-webfont.svg create mode 100644 transfersh-web/fonts/font-awesome/fontawesome-webfont.ttf create mode 100644 transfersh-web/fonts/font-awesome/fontawesome-webfont.woff create mode 100644 transfersh-web/fonts/glyphicons/glyphicons-halflings-regular.eot create mode 100644 transfersh-web/fonts/glyphicons/glyphicons-halflings-regular.svg create mode 100644 transfersh-web/fonts/glyphicons/glyphicons-halflings-regular.ttf create mode 100644 transfersh-web/fonts/glyphicons/glyphicons-halflings-regular.woff create mode 100644 transfersh-web/fonts/transfersh.eot create mode 100644 transfersh-web/fonts/transfersh.svg create mode 100644 transfersh-web/fonts/transfersh.ttf create mode 100644 transfersh-web/fonts/transfersh.woff create mode 100644 transfersh-web/images/Logo-orange.png create mode 100644 transfersh-web/images/terminal.svg create mode 100644 transfersh-web/index.html create mode 100644 transfersh-web/index.txt create mode 100644 transfersh-web/robots.txt create mode 100644 transfersh-web/scripts/main.js create mode 100644 transfersh-web/scripts/mainbk.js create mode 100644 transfersh-web/styles/bootstrap.less create mode 100644 transfersh-web/styles/config.less create mode 100644 transfersh-web/styles/contact.less create mode 100644 transfersh-web/styles/dropzone.less create mode 100644 transfersh-web/styles/global.less create mode 100644 transfersh-web/styles/home.less create mode 100644 transfersh-web/styles/main.css create mode 100644 transfersh-web/styles/main.css.map create mode 100644 transfersh-web/styles/main.less create mode 100644 transfersh-web/styles/pages.less create mode 100644 transfersh-web/styles/transfersh-icons.less diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15ce9b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +build/ +pkg/ +dist/ +src/ +bin/ +*.pyc +*.egg-info/ + +.tmp +.vagrant + +bower_components/ +node_modules/ + +transfersh-server/run.sh diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..7912f95 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,23 @@ +{ + "node": true, + "browser": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "indent": 2, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "smarttabs": true, + "jquery": true, + "white": true +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..212857d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: go +go: + - 1.1 + - 1.2 + - 1.3 + - release + - tip + +script: + - go test -v ./... diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..aa176c2 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,287 @@ +'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'] + }, + 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}' + ] + } + }, + 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: '/' + } + } + }, + // 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}', + '<%= yeoman.dist %>/fonts/{,*/}*.*' + ] + } + } + }, + useminPrepare: { + html: '<%= yeoman.app %>/index.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' + }] + } + }, + svgmin: { + dist: { + files: [{ + expand: true, + cwd: '<%= yeoman.app %>/images', + src: '{,*/}*.svg', + 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', + 'images/{,*/}*.{webp,gif}' + ] + }] + }, + 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', + 'svgmin', + 'htmlmin' + ] + } + }); + + grunt.registerTask('serve', function (target) { + if (target === 'dist') { + return grunt.task.run(['build', 'connect:dist:keepalive']); + } + + grunt.task.run([ + 'clean:server', + 'less', + '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', + 'uglify', + 'copy', + 'usemin' + ]); + + grunt.registerTask('default', [ + 'jshint', + 'test', + 'build' + ]); +}; diff --git a/README.md b/README.md new file mode 100644 index 0000000..78a9e06 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# transfer.sh + + +Development + + +- grunt serve +- grunt build diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..f903c45 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,13 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + # Every Vagrant virtual environment requires a box to build off of. + config.vm.box = "puphpet/ubuntu1404-x64" + config.vm.provider "vmware_fusion" do |v| + v.gui = true + end +end diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..f59ba0c --- /dev/null +++ b/bower.json @@ -0,0 +1,21 @@ +{ + "name": "transfer.sh", + "version": "0.0.0", + "moduleType": [ + "node" + ], + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "transfersh-web/bower_components", + "test", + "tests" + ], + "dependencies": { + "bootstrap": "~3.0.0", + "modernizr": "~2.6.2", + "typed.js": "https://github.com/mattboldt/typed.js.git" + } +} diff --git a/extras/clamd b/extras/clamd new file mode 100755 index 0000000..ebb45c8 --- /dev/null +++ b/extras/clamd @@ -0,0 +1,159 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: skeleton +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Example initscript +# Description: This file should be used to construct scripts to be +# placed in /etc/init.d. +### END INIT INFO + +# Author: Foo Bar +# +# Please remove the "Author" lines above and replace them +# with your own name if you copy and modify this script. + +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="Clam Daemon" +NAME=clamd +DAEMON="/usr/local/sbin/clamd" +DAEMON_ARGS="-c /usr/local/etc/clamd.conf" +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.2-14) to ensure that this file is present +# and status_of_proc is working. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --background --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --background --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 + # Add code here, if necessary, that waits for the process to be ready + # to handle requests from services started subsequently which depend + # on this one. As a last resort, sleep for some time. +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload|force-reload) + # + # If do_reload() is not implemented then leave this commented out + # and leave 'force-reload' as an alias for 'restart'. + # + #log_daemon_msg "Reloading $DESC" "$NAME" + #do_reload + #log_end_msg $? + #;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/extras/transfersh b/extras/transfersh new file mode 100755 index 0000000..e436add --- /dev/null +++ b/extras/transfersh @@ -0,0 +1,165 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: skeleton +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Example initscript +# Description: This file should be used to construct scripts to be +# placed in /etc/init.d. +### END INIT INFO + +# Author: Foo Bar +# +# Please remove the "Author" lines above and replace them +# with your own name if you copy and modify this script. + +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/go/bin +DESC="Transfersh Web server" +NAME=transfersh +DAEMON="/opt/transfer.sh/main" +DAEMON_ARGS="--port 80 --temp /tmp/" +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME + +export BUCKET={bucket} +export AWS_ACCESS_KEY={aws_access_key} +export AWS_SECRET_KEY={aws_secret_key} +export VIRUSTOTAL_KEY={virustotal_key} +export GOPATH=/opt/go/ + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.2-14) to ensure that this file is present +# and status_of_proc is working. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --background --start --chdir /opt/transfer.sh --quiet --pidfile $PIDFILE --make-pidfile --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --background --start --chdir /opt/transfer.sh --quiet --pidfile $PIDFILE --make-pidfile --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 + # Add code here, if necessary, that waits for the process to be ready + # to handle requests from services started subsequently which depend + # on this one. As a last resort, sleep for some time. +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload|force-reload) + # + # If do_reload() is not implemented then leave this commented out + # and leave 'force-reload' as an alias for 'restart'. + # + #log_daemon_msg "Reloading $DESC" "$NAME" + #do_reload + #log_end_msg $? + #;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/package.json b/package.json new file mode 100644 index 0000000..435d4d3 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "transfer.sh", + "version": "0.0.0", + "dependencies": { + "wiredep": "^1.8.6" + }, + "devDependencies": { + "grunt": "~0.4.5", + "grunt-contrib-copy": "~0.6.0", + "grunt-contrib-concat": "~0.5.0", + "grunt-contrib-uglify": "~0.6.0", + "grunt-contrib-jshint": "~0.10.0", + "grunt-contrib-cssmin": "~0.10.0", + "grunt-contrib-connect": "~0.8.0", + "grunt-contrib-clean": "~0.6.0", + "grunt-contrib-htmlmin": "~0.3.0", + "grunt-contrib-less": "~0.11.4", + "grunt-contrib-imagemin": "0.8.1", + "grunt-contrib-watch": "~0.6.1", + "grunt-rev": "~0.1.0", + "grunt-usemin": "~2.4.0", + "grunt-svgmin": "1.0.0", + "grunt-concurrent": "~1.0.0", + "load-grunt-tasks": "~0.6.0", + "matchdep": "~0.3.0", + "time-grunt": "~1.0.0", + "jshint-stylish": "~1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } +} diff --git a/transfersh-server/codec.go b/transfersh-server/codec.go new file mode 100644 index 0000000..bd394e5 --- /dev/null +++ b/transfersh-server/codec.go @@ -0,0 +1,41 @@ +package main + +import ( + "math" + "strings" +) + +const ( + // characters used for short-urls + SYMBOLS = "0123456789abcdefghijklmnopqrsuvwxyzABCDEFGHIJKLMNOPQRSTUVXYZ" + + // 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) +} diff --git a/transfersh-server/handlers.go b/transfersh-server/handlers.go new file mode 100644 index 0000000..b751b54 --- /dev/null +++ b/transfersh-server/handlers.go @@ -0,0 +1,578 @@ +/* +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" + "github.com/dutchcoders/go-clamd" + "github.com/goamz/goamz/s3" + "github.com/golang/gddo/httputil/header" + "github.com/gorilla/mux" + "github.com/kennygrant/sanitize" + "io" + "io/ioutil" + "log" + "math/rand" + "mime" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + "time" + html_template "html/template" + text_template "text/template" +) + +func healthHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Approaching Neutral Zone, all systems normal and functioning.") +} + +// 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) + + actual := header.ParseAccept(r.Header, "Accept") + + html := false + + for _, s := range actual { + if s.Value == "text/html" { + html = true + } + } + + if html { + tmpl, err := html_template.ParseFiles("static/index.html") + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err := tmpl.Execute(w, nil); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } else { + tmpl, err := text_template.ParseFiles("static/index.txt") + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err := tmpl.Execute(w, nil); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} + +func notFoundHandler(w http.ResponseWriter, r *http.Request) { +} + +func postHandler(w http.ResponseWriter, r *http.Request) { + if err := r.ParseMultipartForm(_24K); nil != err { + log.Println(err) + http.Error(w, "Error occured copying to output stream", 500) + return + } + + bucket, err := getBucket() + if err != nil { + log.Println(err) + http.Error(w, "Error occured 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.Print(err) + 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.Print(err) + 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.Print(err) + http.Error(w, err.Error(), 500) + return + } + + reader, err = os.Open(file.Name()) + } else { + reader = bytes.NewReader(b.Bytes()) + } + + contentLength := n + + key := fmt.Sprintf("%s/%s", token, filename) + + log.Printf("Uploading %s %d %s", key, contentLength, contentType) + + if err = bucket.PutReader(key, reader, contentLength, contentType, s3.PublicRead, s3.Options{}); err != nil { + log.Print(err) + http.Error(w, err.Error(), 500) + return + + } + + fmt.Fprintf(w, "https://transfer.sh/%s\n", key) + } + } +} + +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("/tmp/clamd.socket") + + response, err := c.ScanStream(reader) + if err != nil { + http.Error(w, err.Error(), 500) + } + + var b string + + for s := range response { + b += s + + if !strings.HasPrefix(s, "stream: ") { + continue + } + + w.Write([]byte(fmt.Sprintf("%v\n", s[8:]))) + } +} + +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.Print(err) + http.Error(w, err.Error(), 500) + return + } + + if n > _24K { + file, err := ioutil.TempFile(config.Temp, "transfer-") + if err != nil { + log.Print(err) + 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.Print(err) + 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"])) + } + + key := fmt.Sprintf("%s/%s", Encode(10000000+int64(rand.Intn(1000000000))), filename) + + log.Printf("Uploading %s %d %s", key, contentLength, contentType) + + var b *s3.Bucket + var err error + + b, err = getBucket() + if err != nil { + http.Error(w, errors.New("Could not open bucket").Error(), 500) + return + } + + err = b.PutReader(key, reader, contentLength, contentType, s3.PublicRead, s3.Options{}) + + if err != nil { + 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://transfer.sh/%s\n", key) +} + +func zipHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + files := vars["files"] + + b, err := getBucket() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + filename := 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\"", filename)) + w.Header().Set("Connection", "close") + + zw := zip.NewWriter(w) + + for _, key := range strings.Split(files, ",") { + rc, err := b.GetResponse(key) + if err != nil { + if err.Error() == "The specified key does not exist." { + http.Error(w, "File not found", 404) + return + } else { + log.Printf("%s", err.Error()) + http.Error(w, "Could not retrieve file.", 500) + return + } + } + + defer rc.Body.Close() + + header := &zip.FileHeader{ + Name: strings.Split(key, "/")[1], + Method: zip.Store, + ModifiedTime: uint16(time.Now().UnixNano()), + ModifiedDate: uint16(time.Now().UnixNano()), + } + + fi := rc.Body + + 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, fi); 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"] + + b, err := getBucket() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + filename := 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\"", filename)) + 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, ",") { + rc, err := b.GetResponse(key) + if err != nil { + if err.Error() == "The specified key does not exist." { + http.Error(w, "File not found", 404) + return + } else { + log.Printf("%s", err.Error()) + http.Error(w, "Could not retrieve file.", 500) + return + } + } + + defer rc.Body.Close() + + contentLength, err := strconv.Atoi(rc.Header.Get("Content-Length")) + + 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 + } + + fi := rc.Body + + if _, err = io.Copy(zw, fi); 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"] + + b, err := getBucket() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + filename := 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\"", filename)) + w.Header().Set("Connection", "close") + + zw := tar.NewWriter(w) + defer zw.Close() + + for _, key := range strings.Split(files, ",") { + rc, err := b.GetResponse(key) + if err != nil { + if err.Error() == "The specified key does not exist." { + http.Error(w, "File not found", 404) + return + } else { + log.Printf("%s", err.Error()) + http.Error(w, "Could not retrieve file.", 500) + return + } + } + + defer rc.Body.Close() + + contentLength, err := strconv.Atoi(rc.Header.Get("Content-Length")) + + 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 + } + + fi := rc.Body + + if _, err = io.Copy(zw, fi); 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"] + + key := fmt.Sprintf("%s/%s", token, filename) + + b, err := getBucket() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + rc, err := b.GetResponse(key) + if err != nil { + if err.Error() == "The specified key does not exist." { + http.Error(w, "File not found", 404) + return + } else { + log.Printf("%s", err.Error()) + http.Error(w, "Could not retrieve file.", 500) + return + } + } + + defer rc.Body.Close() + + contentType := rc.Header.Get("Content-Type") + w.Header().Set("Content-Type", contentType) + + mediaType, _, _ := mime.ParseMediaType(contentType) + + fmt.Println(mediaType) + + switch { + case mediaType == "text/html": + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) + break + case strings.HasPrefix(mediaType, "text"): + case mediaType == "": + break + default: + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) + } + + w.Header().Set("Connection", "close") + + if _, err = io.Copy(w, rc.Body); err != nil { + http.Error(w, "Error occured copying to output stream", 500) + return + } +} + +func RedirectHandler(h http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if ipAddrFromRemoteAddr(r.Host) != "transfer.sh" && ipAddrFromRemoteAddr(r.Host) != "127.0.0.1" && r.URL.Path != "/health.html" { + http.Redirect(w, r, "https://transfer.sh/", 301) + return + } + + if ipAddrFromRemoteAddr(r.Host) == "transfer.sh" && r.Header.Get("X-Forwarded-Proto") != "https" && r.Method == "GET" { + http.Redirect(w, r, "https://transfer.sh/", 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) + } +} diff --git a/transfersh-server/main.go b/transfersh-server/main.go new file mode 100644 index 0000000..9e95265 --- /dev/null +++ b/transfersh-server/main.go @@ -0,0 +1,139 @@ +/* +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" + "flag" + "fmt" + "github.com/PuerkitoBio/ghost/handlers" + "github.com/gorilla/mux" + "log" + "math/rand" + "net/http" + "os" + "time" +) + +const SERVER_INFO = "transfer.sh" + +// parse request with maximum memory of _24Kilobits +const _24K = (1 << 20) * 24 + +var config struct { + AWS_ACCESS_KEY string + AWS_SECRET_KEY string + BUCKET string + VIRUSTOTAL_KEY string + Temp string +} + +func init() { + config.AWS_ACCESS_KEY = os.Getenv("AWS_ACCESS_KEY") + config.AWS_SECRET_KEY = os.Getenv("AWS_SECRET_KEY") + config.BUCKET = os.Getenv("BUCKET") + config.VIRUSTOTAL_KEY = os.Getenv("VIRUSTOTAL_KEY") + config.Temp = "" +} + +func main() { + rand.Seed(time.Now().UTC().UnixNano()) + + r := mux.NewRouter() + + r.PathPrefix("/scripts/").Handler(http.FileServer(http.Dir("./static/"))) + r.PathPrefix("/styles/").Handler(http.FileServer(http.Dir("./static/"))) + r.PathPrefix("/images/").Handler(http.FileServer(http.Dir("./static/"))) + r.PathPrefix("/fonts/").Handler(http.FileServer(http.Dir("./static/"))) + r.PathPrefix("/ico/").Handler(http.FileServer(http.Dir("./static/"))) + r.PathPrefix("/robots.txt").Handler(http.FileServer(http.Dir("./static/"))) + + 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}", viewHandler).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool { + u, err := url.Parse(r.Referer()) + if err != nil { + log.Fatal(err) + return true + } + + if u.Host == "transfer.sh" { + return false + } + + if u.Host == "" { + return false + } + + return true + }).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", "", "") + logpath := flag.String("log", "", "") + + 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 + log.Printf("Transfer.sh server started. :%v using temp folder: %s", *port, config.Temp) + log.Printf("---------------------------") + + s := &http.Server{ + Addr: fmt.Sprintf(":%s", *port), + Handler: handlers.PanicHandler(LoveHandler(RedirectHandler(handlers.LogHandler(r, handlers.NewLogOptions(log.Printf, "_default_")))), nil), + } + + log.Panic(s.ListenAndServe()) + log.Printf("Server stopped.") +} diff --git a/transfersh-server/run.sh.sample b/transfersh-server/run.sh.sample new file mode 100644 index 0000000..e33a047 --- /dev/null +++ b/transfersh-server/run.sh.sample @@ -0,0 +1,10 @@ +#!/bin/bash + +export BUCKET={bucket} +export AWS_ACCESS_KEY={access_key} +export AWS_SECRET_KEY={secret_key} +export VIRUSTOTAL_KEY={virustotal_key} +export PATH=$PATH:/usr/local/go/bin +export GOPATH=../go/ + +exec go run *.go diff --git a/transfersh-server/static/404.html b/transfersh-server/static/404.html new file mode 100644 index 0000000..73397ed --- /dev/null +++ b/transfersh-server/static/404.html @@ -0,0 +1,157 @@ + + + + + Page Not Found :( + + + +
+

Not found :(

+

Sorry, but the page you were trying to view does not exist.

+

It looks like this was the result of either:

+
    +
  • a mistyped address
  • +
  • an out-of-date link
  • +
+ + +
+ + \ No newline at end of file diff --git a/transfersh-server/static/favicon.ico b/transfersh-server/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6527905307f19ba00762f9241f7eb535fa84a2f9 GIT binary patch literal 4286 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