mongrel_secure_download 0.1
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.
- data/COPYING +19 -0
- data/LICENSE +19 -0
- data/README +86 -0
- data/Rakefile +38 -0
- data/lib/mongrel_secure_download/init.rb +146 -0
- data/resources/defaults.yaml +2 -0
- data/tools/rakehelp.rb +105 -0
- metadata +69 -0
data/COPYING
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2006 Josh Ferguson
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the "Software"),
|
5
|
+
to deal in the Software without restriction, including without limitation
|
6
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
7
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
8
|
+
Software is furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included
|
11
|
+
in all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
14
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2006 Josh Ferguson
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the "Software"),
|
5
|
+
to deal in the Software without restriction, including without limitation
|
6
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
7
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
8
|
+
Software is furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included
|
11
|
+
in all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
14
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
== Mongrel_secure_download GemPlugin
|
2
|
+
|
3
|
+
The need to send secured files in a fast and reliable way is common.
|
4
|
+
|
5
|
+
Sending a file from inside of a web application can be slow
|
6
|
+
and also utilizes an entire application thread/process until the user
|
7
|
+
is done downloading the file. For large files this is inefficient.
|
8
|
+
The other option is to have the web server itself send the files as normal
|
9
|
+
static content. This is faster but means that the files have to be in a web
|
10
|
+
accessible public directory so that if someone guessed the URI of a file
|
11
|
+
they could gain access to it. This is not a reasonable solution for
|
12
|
+
situations where files need to be secured against unauthorized downloading.
|
13
|
+
|
14
|
+
This handler addresses the problem of having a fast and secure
|
15
|
+
download mechanism for web applications. The mechanism works by having
|
16
|
+
the application generate a special URI containing a token that is only
|
17
|
+
valid for a certain period of time. The server then recognizes this URI
|
18
|
+
and generates a token using the parameters passed in and checks for a match
|
19
|
+
before sending the file to the user. The key to the process is the secret string
|
20
|
+
that both the server and the application are aware of.
|
21
|
+
|
22
|
+
|
23
|
+
The URI generated has a path of the following format:
|
24
|
+
|
25
|
+
<uri-prefix>/?token=<token>&relative-path=<relative-path>×tamp=<timestamp>
|
26
|
+
|
27
|
+
<uri-prefix> is a directory that does not exist in the directory structure of the application but does
|
28
|
+
exist in the directory structure of the server.
|
29
|
+
example: /downloads
|
30
|
+
|
31
|
+
<relative-path> is the path to the file being requested, relative to the path
|
32
|
+
at which the web server is running. The web server must have permissions to read the file.
|
33
|
+
example: /files/your_secured_file.txt
|
34
|
+
|
35
|
+
<timestamp> is the number of seconds since epoch until the time when this download expires
|
36
|
+
example (in ruby on rails): 1.minute.from_now.to_i
|
37
|
+
|
38
|
+
<token> is the SHA1 hash of the concatenation of the following items:
|
39
|
+
1) the secret string defined in the configuration script
|
40
|
+
2) the relative path to the file
|
41
|
+
3) the timestamp
|
42
|
+
|
43
|
+
|
44
|
+
To use the plugin you need to do the following:
|
45
|
+
|
46
|
+
1) setup the handler within a configuration script and pass in the secret string.
|
47
|
+
|
48
|
+
example configuration script:
|
49
|
+
|
50
|
+
uri "/download", :handler => plugin('/handlers/securedownload',{:secret_string => "my_secret_string"})
|
51
|
+
|
52
|
+
2) In your application, form a secured URI by creating the proper parameters and
|
53
|
+
perform an SHA1 hash of the parameters to create the proper token
|
54
|
+
|
55
|
+
example code (ruby on rails):
|
56
|
+
|
57
|
+
require 'digest/sha1'
|
58
|
+
|
59
|
+
secret_string = 'my_secret_string'
|
60
|
+
uri_prefix = '/downloads'
|
61
|
+
relative_path = '/files/secret_document.pdf'
|
62
|
+
timestamp = 1.minute.from_now.to_i
|
63
|
+
token = Digest::SHA1.hexdigest(secret_string + relative_path + timestamp)
|
64
|
+
uri = "#{uri_prefix}/?token=#{token}&relative-path=#{relative_path}×tamp=#{timestamp}"
|
65
|
+
|
66
|
+
3) Start mongel by passing in the location of the configuration script from step 1 with the -S command
|
67
|
+
line switch
|
68
|
+
|
69
|
+
example:
|
70
|
+
|
71
|
+
mongrel_rails start -S config/secure_download_config.rb
|
72
|
+
|
73
|
+
|
74
|
+
Error messages
|
75
|
+
|
76
|
+
If any of the parameters in the URI or the secret_string are missing
|
77
|
+
the handler returns a 500 Application Error.
|
78
|
+
|
79
|
+
If the token passed in as a parameter does not match the token generated
|
80
|
+
by the handler (if someone tries to guess the token) the handler returns
|
81
|
+
a 403 Forbidden error.
|
82
|
+
|
83
|
+
If the timestamp is earlier than the current server time, meaning that the file is
|
84
|
+
no longer a valid download then the handler returns a 408 Request Time-out Error.
|
85
|
+
This error is not technically correct but it makes the most sense in the context of
|
86
|
+
the handler.
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'tools/rakehelp'
|
7
|
+
require 'fileutils'
|
8
|
+
include FileUtils
|
9
|
+
|
10
|
+
setup_tests
|
11
|
+
setup_clean ["pkg", "lib/*.bundle", "*.gem", ".config"]
|
12
|
+
|
13
|
+
setup_rdoc ['README', 'LICENSE', 'COPYING', 'lib/**/*.rb', 'doc/**/*.rdoc']
|
14
|
+
|
15
|
+
desc "Does a full compile, test run"
|
16
|
+
task :default => [:test, :package]
|
17
|
+
|
18
|
+
version="0.1"
|
19
|
+
name="mongrel_secure_download"
|
20
|
+
|
21
|
+
setup_gem(name, version) do |spec|
|
22
|
+
spec.summary = "Mongrel Secure Download Plugin"
|
23
|
+
spec.description = spec.summary
|
24
|
+
spec.author="Josh Ferguson"
|
25
|
+
spec.add_dependency('gem_plugin', '>= 0.2.1')
|
26
|
+
spec.add_dependency('mongrel', '>= 0.3.13')
|
27
|
+
spec.files += Dir.glob("resources/**/*")
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
task :install => [:test, :package] do
|
32
|
+
sh %{sudo gem install pkg/#{name}-#{version}.gem}
|
33
|
+
end
|
34
|
+
|
35
|
+
task :uninstall => [:clean] do
|
36
|
+
sh %{sudo gem uninstall #{name}}
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'gem_plugin'
|
2
|
+
require 'mongrel'
|
3
|
+
require 'digest/sha1'
|
4
|
+
|
5
|
+
# = Mongrel Secure Download Handler
|
6
|
+
#
|
7
|
+
# The need to send secured files in a fast and reliable way is common.
|
8
|
+
#
|
9
|
+
# Sending a file from inside of a web application can be slow
|
10
|
+
# and also utilizes an entire application thread/process until the user
|
11
|
+
# is done downloading the file. For large files this is inefficient.
|
12
|
+
# The other option is to have the web server itself send the files as normal
|
13
|
+
# static content. This is faster but means that the files have to be in a web
|
14
|
+
# accessible public directory so that if someone guessed the URI of a file
|
15
|
+
# they could gain access to it. This is not a reasonable solution for
|
16
|
+
# situations where files need to be secured against unauthorized downloading.
|
17
|
+
#
|
18
|
+
# This handler addresses the problem of having a fast and secure
|
19
|
+
# download mechanism for web applications. The mechanism works by having
|
20
|
+
# the application generate a special URI containing a token that is only
|
21
|
+
# valid for a certain period of time. The server then recognizes this URI
|
22
|
+
# and generates a token using the parameters passed in and checks for a match
|
23
|
+
# before sending the file to the user. The key to the process is the secret
|
24
|
+
# string that both the server and the application are aware of.
|
25
|
+
#
|
26
|
+
# == URI Format
|
27
|
+
#
|
28
|
+
# A properly formed URI will have a path and query of the following format
|
29
|
+
#
|
30
|
+
# <uri-prefix>/?token=<token>&relative-path=<relative-path>×tamp=<timestamp>
|
31
|
+
#
|
32
|
+
# === Where
|
33
|
+
#
|
34
|
+
# <uri-prefix> is a directory that does not exist in the directory structure of the application but does
|
35
|
+
# exist in the directory structure of the server.
|
36
|
+
#
|
37
|
+
# <b>example uri-prefix</b>
|
38
|
+
#
|
39
|
+
# /downloads
|
40
|
+
#
|
41
|
+
# <relative-path> is the path to the file being requested, relative to the path
|
42
|
+
# at which the web server is running. The web server must have permissions to read the file.
|
43
|
+
#
|
44
|
+
# <b>example relative-path</b>
|
45
|
+
#
|
46
|
+
# /files/your_secured_file.txt
|
47
|
+
#
|
48
|
+
# <timestamp> is the number of seconds since epoch until the time when this download expires
|
49
|
+
#
|
50
|
+
# <b>example timestamp (ruby on rails)</b>
|
51
|
+
#
|
52
|
+
# 1.minute.from_now.to_i
|
53
|
+
#
|
54
|
+
# <token> is the SHA1 hash of the concatenation of the following items:
|
55
|
+
# 1. the user defined secret string
|
56
|
+
# 2. the relative path to the file
|
57
|
+
# 3. the timestamp
|
58
|
+
#
|
59
|
+
# == Using the Handler
|
60
|
+
#
|
61
|
+
# To use the handler you need to do the following:
|
62
|
+
#
|
63
|
+
# Setup the handler within a configuration script and pass in the secret string.
|
64
|
+
#
|
65
|
+
# <b>example configuration script</b>
|
66
|
+
#
|
67
|
+
# uri "/downloads", :handler => plugin('/handlers/securedownload',{:secret_string => "my_secret_string"})
|
68
|
+
#
|
69
|
+
# In your application form a secured URI by creating the proper parameters and
|
70
|
+
# perform an SHA1 hash of the parameters to create the proper token
|
71
|
+
#
|
72
|
+
# <b>example code (ruby on rails)</b>
|
73
|
+
#
|
74
|
+
# require 'digest/sha1'
|
75
|
+
#
|
76
|
+
# secret_string = 'my_secret_string'
|
77
|
+
# uri_prefix = '/downloads'
|
78
|
+
# relative_path = '/files/secret_document.pdf'
|
79
|
+
# timestamp = 1.minute.from_now.to_i
|
80
|
+
# token = Digest::SHA1.hexdigest(secret_string + relative_path + timestamp)
|
81
|
+
# uri = "#{uri_prefix}/?token=#{token}&relative-path=#{relative_path}×tamp=#{timestamp}"
|
82
|
+
#
|
83
|
+
# Start mongel by passing in the location of the configuration script from step 1 with the -S command
|
84
|
+
# line switch.
|
85
|
+
#
|
86
|
+
# <b>example mongrel start command</b>
|
87
|
+
#
|
88
|
+
# mongrel_rails start -S config/secure_download_config.rb
|
89
|
+
#
|
90
|
+
# == Error messages
|
91
|
+
#
|
92
|
+
# If any of the parameters in the URI or the secret_string are missing
|
93
|
+
# the handler returns a 500 Application Error.
|
94
|
+
#
|
95
|
+
# If the token passed in as a parameter does not match the token generated
|
96
|
+
# by the handler (if someone tries to guess the token) the handler returns
|
97
|
+
# a 403 Forbidden error.
|
98
|
+
#
|
99
|
+
# If the timestamp is earlier than the current server time, meaning that the file is
|
100
|
+
# no longer a valid download then the handler returns a 408 Request Time-out Error.
|
101
|
+
# This error is not technically correct but it makes the most sense in the context of
|
102
|
+
# the handler.
|
103
|
+
class SecureDownload < GemPlugin::Plugin "/handlers"
|
104
|
+
include Mongrel::HttpHandlerPlugin
|
105
|
+
|
106
|
+
def process(request, response)
|
107
|
+
query = Mongrel::HttpRequest.query_parse(request.params['QUERY_STRING'])
|
108
|
+
|
109
|
+
if @options[:secret_string].nil? or query['token'].nil? or query['timestamp'].nil? or query['relative-path'].nil?
|
110
|
+
response.start(500){}
|
111
|
+
elsif query['timestamp'].to_i < Time.now.to_i
|
112
|
+
response.start(408){}
|
113
|
+
elsif query['token'] == Digest::SHA1.hexdigest("#{@options[:secret_string]}#{query['relative-path']}#{query['timestamp']}").to_s
|
114
|
+
send_file(File.expand_path("." + query['relative-path']), response)
|
115
|
+
else
|
116
|
+
response.start(403){}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
# Sends the contents of a file back to the user.
|
122
|
+
def send_file(path, response)
|
123
|
+
# first we setup the headers and status then we do a very fast send on the socket directly
|
124
|
+
file_status = File.stat(path)
|
125
|
+
|
126
|
+
response.status = 200
|
127
|
+
# Set the last modified times as well and etag for all files
|
128
|
+
response.header[Mongrel::Const::LAST_MODIFIED] = file_status.mtime.httpdate
|
129
|
+
# Calculated the same as apache, not sure how well the works on win32
|
130
|
+
response.header[Mongrel::Const::ETAG] = Mongrel::Const::ETAG_FORMAT % [file_status.mtime.to_i, file_status.size, file_status.ino]
|
131
|
+
#set the content type to something generic for now
|
132
|
+
response.header[Mongrel::Const::CONTENT_TYPE] = @default_content_type
|
133
|
+
#set the content disposition and filename
|
134
|
+
response.header['Content-Disposition'] = "attachment; filename=\"#{File.basename(path)}\""
|
135
|
+
|
136
|
+
# send a status with out content length
|
137
|
+
response.send_status(file_status.size)
|
138
|
+
response.send_header
|
139
|
+
|
140
|
+
if not header_only
|
141
|
+
response.send_file(path)
|
142
|
+
else
|
143
|
+
response.send_body # should send nothing
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/tools/rakehelp.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
def make(makedir)
|
3
|
+
Dir.chdir(makedir) do
|
4
|
+
sh(PLATFORM =~ /win32/ ? 'nmake' : 'make')
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
def extconf(dir)
|
10
|
+
Dir.chdir(dir) do ruby "extconf.rb" end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def setup_tests
|
15
|
+
Rake::TestTask.new do |t|
|
16
|
+
t.libs << "test"
|
17
|
+
t.test_files = FileList['test/test*.rb']
|
18
|
+
t.verbose = true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def setup_clean otherfiles
|
24
|
+
files = ['build/*', '**/*.o', '**/*.so', '**/*.a', 'lib/*-*', '**/*.log'] + otherfiles
|
25
|
+
CLEAN.include(files)
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def setup_rdoc files
|
30
|
+
Rake::RDocTask.new do |rdoc|
|
31
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
32
|
+
rdoc.options << '--line-numbers'
|
33
|
+
rdoc.rdoc_files.add(files)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def setup_extension(dir, extension)
|
39
|
+
ext = "ext/#{dir}"
|
40
|
+
ext_so = "#{ext}/#{extension}.#{Config::CONFIG['DLEXT']}"
|
41
|
+
ext_files = FileList[
|
42
|
+
"#{ext}/*.c",
|
43
|
+
"#{ext}/*.h",
|
44
|
+
"#{ext}/extconf.rb",
|
45
|
+
"#{ext}/Makefile",
|
46
|
+
"lib"
|
47
|
+
]
|
48
|
+
|
49
|
+
task "lib" do
|
50
|
+
directory "lib"
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Builds just the #{extension} extension"
|
54
|
+
task extension.to_sym => ["#{ext}/Makefile", ext_so ]
|
55
|
+
|
56
|
+
file "#{ext}/Makefile" => ["#{ext}/extconf.rb"] do
|
57
|
+
extconf "#{ext}"
|
58
|
+
end
|
59
|
+
|
60
|
+
file ext_so => ext_files do
|
61
|
+
make "#{ext}"
|
62
|
+
cp ext_so, "lib"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def base_gem_spec(pkg_name, pkg_version)
|
68
|
+
pkg_version = pkg_version
|
69
|
+
pkg_name = pkg_name
|
70
|
+
pkg_file_name = "#{pkg_name}-#{pkg_version}"
|
71
|
+
Gem::Specification.new do |s|
|
72
|
+
s.name = pkg_name
|
73
|
+
s.version = pkg_version
|
74
|
+
s.platform = Gem::Platform::RUBY
|
75
|
+
s.has_rdoc = true
|
76
|
+
s.extra_rdoc_files = [ "README" ]
|
77
|
+
|
78
|
+
s.files = %w(COPYING LICENSE README Rakefile) +
|
79
|
+
Dir.glob("{bin,doc/rdoc,test,lib}/**/*") +
|
80
|
+
Dir.glob("ext/**/*.{h,c,rb}") +
|
81
|
+
Dir.glob("examples/**/*.rb") +
|
82
|
+
Dir.glob("tools/*.rb")
|
83
|
+
|
84
|
+
s.require_path = "lib"
|
85
|
+
s.extensions = FileList["ext/**/extconf.rb"].to_a
|
86
|
+
s.bindir = "bin"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def setup_gem(pkg_name, pkg_version)
|
91
|
+
spec = base_gem_spec(pkg_name, pkg_version)
|
92
|
+
yield spec if block_given?
|
93
|
+
|
94
|
+
Rake::GemPackageTask.new(spec) do |p|
|
95
|
+
p.gem_spec = spec
|
96
|
+
p.need_tar = true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def setup_win32_gem(pkg_name, pkg_version)
|
101
|
+
spec = base_gem_spec(pkg_name, pkg_version)
|
102
|
+
yield spec if block_given?
|
103
|
+
|
104
|
+
Gem::Builder.new(spec).build
|
105
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: mongrel_secure_download
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "0.1"
|
7
|
+
date: 2006-05-30 00:00:00 -04:00
|
8
|
+
summary: Mongrel Secure Download Plugin
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email:
|
12
|
+
homepage:
|
13
|
+
rubyforge_project:
|
14
|
+
description: Mongrel Secure Download Plugin
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- Josh Ferguson
|
30
|
+
files:
|
31
|
+
- COPYING
|
32
|
+
- LICENSE
|
33
|
+
- README
|
34
|
+
- Rakefile
|
35
|
+
- lib/mongrel_secure_download
|
36
|
+
- lib/mongrel_secure_download/init.rb
|
37
|
+
- tools/rakehelp.rb
|
38
|
+
- resources/defaults.yaml
|
39
|
+
test_files: []
|
40
|
+
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
extra_rdoc_files:
|
44
|
+
- README
|
45
|
+
executables: []
|
46
|
+
|
47
|
+
extensions: []
|
48
|
+
|
49
|
+
requirements: []
|
50
|
+
|
51
|
+
dependencies:
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: gem_plugin
|
54
|
+
version_requirement:
|
55
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 0.2.1
|
60
|
+
version:
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: mongrel
|
63
|
+
version_requirement:
|
64
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.3.13
|
69
|
+
version:
|