rubytus 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = space
5
+ indent_size = 2
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ tus_data
@@ -0,0 +1,6 @@
1
+ SimpleCov.start do
2
+ add_filter '/test/'
3
+ add_filter '/bin/'
4
+
5
+ add_group 'Libraries', 'lib'
6
+ end
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ script: bundle exec rake test
6
+ notifications:
7
+ email:
8
+ - subosito@gmail.com
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,57 @@
1
+ # Rubytus
2
+
3
+ Resumable upload protocol implementation in Ruby
4
+
5
+ [![Build Status](https://travis-ci.org/picocandy/rubytus.png)](https://travis-ci.org/picocandy/rubytus)
6
+ [![Coverage Status](https://coveralls.io/repos/picocandy/rubytus/badge.png?branch=master)](https://coveralls.io/r/picocandy/rubytus?branch=master)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ $ gem install rubytus
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```
17
+ $ rubytusd --help
18
+ Usage: <server> [options]
19
+
20
+ Server options:
21
+ -e, --environment NAME Set the execution environment (default: development)
22
+ -a, --address HOST Bind to HOST address (default: 0.0.0.0)
23
+ -p, --port PORT Use PORT (default: 9000)
24
+ -S, --socket FILE Bind to unix domain socket
25
+
26
+ Daemon options:
27
+ -u, --user USER Run as specified user
28
+ -c, --config FILE Config file (default: ./config/<server>.rb)
29
+ -d, --daemonize Run daemonized in the background (default: false)
30
+ -l, --log FILE Log to file (default: off)
31
+ -s, --stdout Log to stdout (default: false)
32
+ -P, --pid FILE Pid file (default: off)
33
+
34
+ SSL options:
35
+ --ssl Enables SSL (default: off)
36
+ --ssl-key FILE Path to private key
37
+ --ssl-cert FILE Path to certificate
38
+ --ssl-verify Enables SSL certificate verification
39
+
40
+ Common options:
41
+ -C, --console Start a console
42
+ -v, --verbose Enable verbose logging (default: false)
43
+ -h, --help Display help message
44
+
45
+ TUSD options:
46
+ -f, --data-dir DATA_DIR Directory to store uploaded and partial files (default: tus_data)
47
+ -b, --base-path BASE_PATH Url path used for handling uploads (default: /files/)
48
+ -m, --max-size MAX_SIZE How many bytes may be stored inside DATA_DIR (default: 1073741824)
49
+ ```
50
+
51
+ ## Contributing
52
+
53
+ 1. Fork it
54
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
55
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
56
+ 4. Push to the branch (`git push origin my-new-feature`)
57
+ 5. Create new Pull Request
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.test_files = FileList['test/**/test*']
7
+ end
8
+
9
+ desc 'Run tests'
10
+ task :default => :test
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+ bin = Pathname.new(__FILE__).realpath
5
+
6
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', bin)
7
+ ENV['RACK_ENV'] ||= ENV['TUSD_ENV'] || 'development'
8
+
9
+ require 'bundler'
10
+ Bundler.setup
11
+ Bundler.require(:default)
12
+
13
+ require 'rubytus/command'
@@ -0,0 +1,3 @@
1
+ require 'rubytus/version'
2
+
3
+ module Rubytus; end
@@ -0,0 +1,79 @@
1
+ require 'goliath'
2
+ require 'goliath/constants'
3
+ require 'rubytus/constants'
4
+ require 'rubytus/request'
5
+ require 'rubytus/helpers'
6
+ require 'rubytus/error'
7
+ require 'stringio'
8
+
9
+ module Rubytus
10
+ class API < Goliath::API
11
+ include Goliath::Constants
12
+ include Rubytus::Constants
13
+ include Rubytus::Helpers
14
+
15
+ def on_headers(env, headers)
16
+ env['api.options'] = @options
17
+ env['api.headers'] = COMMON_HEADERS.merge({ 'Date' => Time.now.httpdate })
18
+ prepare_headers(env, headers)
19
+ end
20
+
21
+ def on_body(env, data)
22
+ if env['api.action'] == :patch
23
+ env['api.buffers'] << data
24
+ else
25
+ body = StringIO.new(data)
26
+ env[RACK_INPUT] = body
27
+ end
28
+ end
29
+
30
+ def response(env)
31
+ status = STATUS_OK
32
+ headers = env['api.headers']
33
+ body = []
34
+
35
+ [status, headers, body]
36
+ end
37
+
38
+ def default_setup
39
+ @options[:max_size] = validates_max_size(@options[:max_size])
40
+ @options[:base_path] = validates_base_path(@options[:base_path])
41
+ end
42
+
43
+ def default_options
44
+ {
45
+ :base_path => ENV[ENV_BASE_PATH] || DEFAULT_BASE_PATH,
46
+ :max_size => ENV[ENV_MAX_SIZE] || DEFAULT_MAX_SIZE
47
+ }
48
+ end
49
+
50
+ def default_parser(opts, options)
51
+ opts.separator ""
52
+ opts.separator "TUSD options:"
53
+
54
+ args = [
55
+ {
56
+ :name => :base_path,
57
+ :short => '-b',
58
+ :long => '--base-path BASE_PATH',
59
+ :desc => "Url path used for handling uploads (default: #{options[:base_path]})"
60
+ },
61
+ {
62
+ :name => :max_size,
63
+ :short => '-m',
64
+ :long => '--max-size MAX_SIZE',
65
+ :desc => "Maximum bytes may be stored inside storage (default: #{options[:max_size]})"
66
+ }
67
+ ]
68
+
69
+ args.each do |arg|
70
+ opts.on(arg[:short], arg[:long], arg[:desc]) do |value|
71
+ options[arg[:name]] = value
72
+ end
73
+ end
74
+
75
+ # save into global options
76
+ @options = options
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,72 @@
1
+ require 'rubytus/api'
2
+ require 'rubytus/request'
3
+ require 'rubytus/error'
4
+ require 'rubytus/storage'
5
+ require 'rubytus/middlewares/tus_barrier'
6
+ require 'rubytus/middlewares/storage_barrier'
7
+
8
+ module Rubytus
9
+ class Command < API
10
+ include Constants
11
+ include StorageHelper
12
+
13
+ use Middlewares::TusBarrier
14
+ use Middlewares::StorageBarrier
15
+
16
+ def on_headers(env, headers)
17
+ super(env, headers)
18
+
19
+ request = Request.new(env)
20
+
21
+ begin
22
+
23
+ if env['api.action'] == :patch
24
+ uid = env['api.uid']
25
+ info = storage.read_info(uid)
26
+
27
+ validates_offset(request.offset, info.offset)
28
+ validates_length(request.content_length, info.remaining_length)
29
+ end
30
+
31
+ rescue PermissionError => e
32
+ error!(STATUS_INTERNAL_ERROR, e.message)
33
+ end
34
+ end
35
+
36
+ def on_close(env)
37
+ if env['api.action'] == :patch
38
+ storage.patch_file(env['api.uid'], env['api.buffers'], env['api.offset'])
39
+ end
40
+ end
41
+
42
+ def options_parser(opts, options)
43
+ options = init_options.merge(options)
44
+ default_parser(opts, options)
45
+ opts.on('-f', '--data-dir DATA_DIR', "Directory to store uploads, LOCAL storage only (default: #{options[:data_dir]})") do |value|
46
+ options[:data_dir] = value
47
+ end
48
+ end
49
+
50
+ def init_options
51
+ options = default_options
52
+ options[:data_dir] = ENV[ENV_DATA_DIR] || DEFAULT_DATA_DIR
53
+ options
54
+ end
55
+
56
+ def setup
57
+ begin
58
+ default_setup
59
+ @options[:data_dir] = validates_data_dir(@options[:data_dir])
60
+ @options[:storage] = Storage.new(@options)
61
+ rescue PermissionError, ConfigurationError => e
62
+ puts '[ERROR] ' + e.message
63
+ exit(1)
64
+ end
65
+ end
66
+
67
+ private
68
+ def storage
69
+ @options[:storage]
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,11 @@
1
+ module Rubytus
2
+ module Common
3
+ def generate_uid
4
+ SecureRandom.hex(16)
5
+ end
6
+
7
+ def error!(status, message)
8
+ raise Goliath::Validation::Error.new(status, message)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ module Rubytus
2
+ module Constants
3
+ RESOURCE_UID_REGEX = /^([a-z0-9]{32})$/
4
+ BASE_PATH_REGEX = /^(\/[a-zA-Z0-9\-_]+\/)$/
5
+
6
+ RESUMABLE_CONTENT_TYPE = 'application/offset+octet-stream'
7
+
8
+ ENV_STORAGE = 'TUSD_STORAGE'
9
+ ENV_DATA_DIR = 'TUSD_DATA_DIR'
10
+ ENV_BASE_PATH = 'TUSD_BASE_PATH'
11
+ ENV_MAX_SIZE = 'TUSD_MAX_SIZE'
12
+
13
+ DEFAULT_STORAGE = 'local'
14
+ DEFAULT_DATA_DIR = 'tus_data'
15
+ DEFAULT_BASE_PATH = '/files/'
16
+ DEFAULT_MAX_SIZE = 1073741824
17
+
18
+ STATUS_OK = 200
19
+ STATUS_CREATED = 201
20
+ STATUS_BAD_REQUEST = 400
21
+ STATUS_FORBIDDEN = 403
22
+ STATUS_NOT_FOUND = 404
23
+ STATUS_NOT_ALLOWED = 405
24
+ STATUS_INTERNAL_ERROR = 500
25
+
26
+ COMMON_HEADERS = {
27
+ 'Access-Control-Allow-Origin' => '*',
28
+ 'Access-Control-Allow-Methods' => 'HEAD,GET,PUT,POST,PATCH,DELETE',
29
+ 'Access-Control-Allow-Headers' => 'Origin, X-Requested-With, Content-Type, Accept, Content-Disposition, Final-Length, Offset',
30
+ 'Access-Control-Expose-Headers' => 'Location, Range, Content-Disposition, Offset'
31
+ }
32
+ end
33
+ end
@@ -0,0 +1,4 @@
1
+ module Rubytus
2
+ class ConfigurationError < StandardError; end
3
+ class PermissionError < StandardError; end
4
+ end
@@ -0,0 +1,82 @@
1
+ require 'rubytus/constants'
2
+ require 'rubytus/common'
3
+ require 'rubytus/error'
4
+
5
+ module Rubytus
6
+ module Helpers
7
+ include Rubytus::Constants
8
+ include Rubytus::Common
9
+
10
+ def prepare_headers(env, headers)
11
+ request = Rubytus::Request.new(env)
12
+
13
+ # CREATE
14
+ if request.collection? && request.post?
15
+ uid = generate_uid
16
+
17
+ env['api.action'] = :create
18
+ env['api.uid'] = uid
19
+ env['api.final_length'] = request.final_length
20
+ env['api.resource_url'] = request.resource_url(uid)
21
+ end
22
+
23
+ if request.resource?
24
+ # UID for this resource
25
+ env['api.uid'] = request.resource_uid
26
+
27
+ # HEAD
28
+ if request.head?
29
+ env['api.action'] = :head
30
+ end
31
+
32
+ # PATCH
33
+ if request.patch?
34
+ unless request.resumable_content_type?
35
+ error!(STATUS_BAD_REQUEST, "Content-Type must be '#{RESUMABLE_CONTENT_TYPE}'")
36
+ end
37
+
38
+ env['api.action'] = :patch
39
+ env['api.buffers'] = ''
40
+ env['api.offset'] = request.offset
41
+ end
42
+
43
+ # GET
44
+ if request.get?
45
+ env['api.action'] = :get
46
+ end
47
+ end
48
+ end
49
+
50
+ def validates_offset(req_offset, info_offset)
51
+ if req_offset > info_offset
52
+ error!(STATUS_FORBIDDEN, "Offset: #{req_offset} exceeds current offset: #{info_offset}")
53
+ end
54
+ end
55
+
56
+ def validates_length(req_length, remaining)
57
+ if req_length > remaining
58
+ error!(STATUS_FORBIDDEN, "Content-Length: #{req_length} exceeded remaining length: #{remaining}")
59
+ end
60
+ end
61
+
62
+ def validates_base_path(base_path)
63
+ unless base_path =~ BASE_PATH_REGEX
64
+ raise ConfigurationError, "Invalid `base_path` configuration, it should be using format /uploads/, /user-data/, etc"
65
+ end
66
+
67
+ base_path
68
+ end
69
+
70
+ def validates_max_size(max_size)
71
+ if max_size.is_a? String
72
+ max_size = max_size.to_i
73
+ end
74
+
75
+ if max_size <= 0
76
+ raise ConfigurationError, "Invalid `max_size`, it should be > 0 bytes"
77
+ end
78
+
79
+ max_size
80
+ end
81
+ end
82
+ end