geminabox 0.11.1 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of geminabox might be problematic. Click here for more details.

@@ -1,65 +1,70 @@
1
- class Geminabox::IncomingGem
2
- def initialize(gem_data, root_path = Geminabox.settings.data)
3
- unless gem_data.respond_to? :read
4
- raise ArgumentError, "Expected an instance of IO"
5
- end
1
+ module Geminabox
6
2
 
7
- digest = Digest::SHA1.new
8
- if RbConfig::CONFIG["MAJOR"].to_i <= 1 and RbConfig::CONFIG["MINOR"].to_i <= 8
9
- @tempfile = Tempfile.new("gem")
10
- else
11
- @tempfile = Tempfile.new("gem", :encoding => 'binary')
12
- end
3
+ class IncomingGem
4
+ def initialize(gem_data, root_path = Geminabox.settings.data)
5
+ unless gem_data.respond_to? :read
6
+ raise ArgumentError, "Expected an instance of IO"
7
+ end
13
8
 
14
- while data = gem_data.read(1024**2)
15
- @tempfile.write data
16
- digest << data
17
- end
9
+ digest = Digest::SHA1.new
10
+ if RbConfig::CONFIG["MAJOR"].to_i <= 1 and RbConfig::CONFIG["MINOR"].to_i <= 8
11
+ @tempfile = Tempfile.new("gem")
12
+ else
13
+ @tempfile = Tempfile.new("gem", :encoding => 'binary')
14
+ end
18
15
 
19
- @tempfile.close
20
- @sha1 = digest.hexdigest
16
+ while data = gem_data.read(1024**2)
17
+ @tempfile.write data
18
+ digest << data
19
+ end
21
20
 
22
- @root_path = root_path
23
- end
21
+ @tempfile.close
22
+ @sha1 = digest.hexdigest
24
23
 
25
- def gem_data
26
- File.open(@tempfile.path, "rb")
27
- end
24
+ @root_path = root_path
25
+ end
28
26
 
29
- def valid?
30
- spec && spec.name && spec.version
31
- rescue Gem::Package::Error
32
- false
33
- end
27
+ def gem_data
28
+ File.open(@tempfile.path, "rb")
29
+ end
34
30
 
35
- def spec
36
- @spec ||= extract_spec
37
- end
31
+ def valid?
32
+ spec && spec.name && spec.version
33
+ rescue Gem::Package::Error
34
+ false
35
+ end
36
+
37
+ def spec
38
+ @spec ||= extract_spec
39
+ end
38
40
 
39
- def extract_spec
40
- if Gem::Package.respond_to? :open
41
- Gem::Package.open(gem_data, "r", nil) do |pkg|
42
- return pkg.metadata
41
+ def extract_spec
42
+ if Gem::Package.respond_to? :open
43
+ Gem::Package.open(gem_data, "r", nil) do |pkg|
44
+ return pkg.metadata
45
+ end
46
+ else
47
+ Gem::Package.new(@tempfile.path).spec
43
48
  end
44
- else
45
- Gem::Package.new(@tempfile.path).spec
46
49
  end
47
- end
48
50
 
49
- def name
50
- unless @name
51
+ def name
52
+ @name ||= get_name
53
+ end
54
+
55
+ def get_name
51
56
  filename = %W[#{spec.name} #{spec.version}]
52
57
  filename.push(spec.platform) if spec.platform && spec.platform != "ruby"
53
- @name = filename.join("-") + ".gem"
58
+ filename.join("-") + ".gem"
54
59
  end
55
- @name
56
- end
57
60
 
58
- def dest_filename
59
- File.join(@root_path, "gems", name)
60
- end
61
+ def dest_filename
62
+ File.join(@root_path, "gems", name)
63
+ end
61
64
 
62
- def hexdigest
63
- @sha1
65
+ def hexdigest
66
+ @sha1
67
+ end
64
68
  end
69
+
65
70
  end
@@ -0,0 +1,13 @@
1
+
2
+ module Geminabox
3
+ module Proxy
4
+ def self.proxy_path(file)
5
+ File.join File.dirname(__FILE__), 'proxy', file
6
+ end
7
+
8
+ autoload :Hostess, proxy_path('hostess')
9
+ autoload :FileHandler, proxy_path('file_handler')
10
+ autoload :Splicer, proxy_path('splicer')
11
+ autoload :Copier, proxy_path('copier')
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+
2
+
3
+ module Geminabox
4
+ module Proxy
5
+ class Copier < FileHandler
6
+
7
+ def self.copy(file_name)
8
+ copier = new(file_name)
9
+ copier.get_file
10
+ copier
11
+ end
12
+
13
+ def get_file
14
+ return true if proxy_file_exists?
15
+ return copy_local if local_file_exists?
16
+ get_remote
17
+ end
18
+
19
+ def copy_local
20
+ FileUtils.cp local_path, proxy_path
21
+ end
22
+
23
+ def get_remote
24
+ File.open(proxy_path, 'w'){|f| f.write(remote_content)}
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,93 @@
1
+ require 'httpclient'
2
+ module Geminabox
3
+ module Proxy
4
+ class FileHandler
5
+
6
+ attr_reader :file_name
7
+
8
+ def initialize(file_name)
9
+ @file_name = file_name
10
+ ensure_destination_exists
11
+ end
12
+
13
+ def local_path
14
+ File.expand_path(file_name, root_path)
15
+ end
16
+
17
+ def root_path
18
+ Geminabox.data
19
+ end
20
+
21
+ def local_file_exists?
22
+ file_exists? local_path
23
+ end
24
+
25
+ def proxy_file_exists?
26
+ file_exists? proxy_path
27
+ end
28
+
29
+ def proxy_path
30
+ File.expand_path(file_name, proxy_folder_path)
31
+ end
32
+
33
+ def file_exists?(path)
34
+ File.exists? path
35
+ end
36
+
37
+ def proxy_folder_path
38
+ File.join(root_path, proxy_folder_name)
39
+ end
40
+
41
+ def proxy_folder_name
42
+ 'proxy'
43
+ end
44
+
45
+ def remote_content
46
+ HTTPClient.get_content(remote_url).force_encoding(encoding)
47
+ end
48
+
49
+ def remote_url
50
+ "http://rubygems.org/#{file_name}"
51
+ end
52
+
53
+ def local_content
54
+ File.read(local_path).force_encoding(encoding)
55
+ end
56
+
57
+ private
58
+ def encoding
59
+ "UTF-8"
60
+ end
61
+
62
+ def ensure_destination_exists
63
+ create_local_folder unless local_folder_exists?
64
+ create_proxy_folder unless proxy_folder_exists?
65
+ end
66
+
67
+ def proxy_file_folder
68
+ File.dirname proxy_path
69
+ end
70
+
71
+ def proxy_folder_exists?
72
+ Dir.exists?(proxy_file_folder)
73
+ end
74
+
75
+ def create_proxy_folder
76
+ FileUtils.mkdir_p(proxy_file_folder)
77
+ end
78
+
79
+ def local_file_folder
80
+ File.dirname local_path
81
+ end
82
+
83
+ def local_folder_exists?
84
+ Dir.exists?(local_file_folder)
85
+ end
86
+
87
+ def create_local_folder
88
+ FileUtils.mkdir_p(local_file_folder)
89
+ end
90
+
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,81 @@
1
+ require 'sinatra/base'
2
+
3
+ module Geminabox
4
+ module Proxy
5
+ class Hostess < Sinatra::Base
6
+ attr_accessor :file_handler
7
+
8
+ def serve
9
+ if file_handler
10
+ send_file file_handler.proxy_path
11
+ else
12
+ send_file(File.expand_path(File.join(Server.data, *request.path_info)), :type => response['Content-Type'])
13
+ end
14
+ end
15
+
16
+ %w[specs.4.8.gz
17
+ latest_specs.4.8.gz
18
+ prerelease_specs.4.8.gz
19
+ ].each do |index|
20
+ get "/#{index}" do
21
+ splice_file index
22
+ content_type 'application/x-gzip'
23
+ serve
24
+ end
25
+ end
26
+
27
+ %w[quick/Marshal.4.8/*.gemspec.rz
28
+ yaml.Z
29
+ Marshal.4.8.Z
30
+ ].each do |deflated_index|
31
+ get "/#{deflated_index}" do
32
+ copy_file request.path_info[1..-1]
33
+ content_type('application/x-deflate')
34
+ serve
35
+ end
36
+ end
37
+
38
+ %w[yaml
39
+ Marshal.4.8
40
+ specs.4.8
41
+ latest_specs.4.8
42
+ prerelease_specs.4.8
43
+ ].each do |old_index|
44
+ get "/#{old_index}" do
45
+ splice_file old_index
46
+ serve
47
+ end
48
+ end
49
+
50
+ get "/gems/*.gem" do
51
+ get_from_rubygems_if_not_local
52
+ serve
53
+ end
54
+
55
+ private
56
+ def get_from_rubygems_if_not_local
57
+
58
+ file = File.expand_path(File.join(Server.data, *request.path_info))
59
+
60
+ unless File.exists?(file)
61
+ Net::HTTP.start("production.cf.rubygems.org") do |http|
62
+ path = File.join(*request.path_info)
63
+ response = http.get(path)
64
+ GemStore.create(IncomingGem.new(StringIO.new(response.body)))
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ def splice_file(file_name)
71
+ self.file_handler = Splicer.make(file_name)
72
+ end
73
+
74
+ def copy_file(file_name)
75
+ self.file_handler = Copier.copy(file_name)
76
+ end
77
+
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,67 @@
1
+
2
+ module Geminabox
3
+ module Proxy
4
+ class Splicer < FileHandler
5
+
6
+ def self.make(file_name)
7
+ splicer = new(file_name)
8
+ splicer.create
9
+ splicer
10
+ end
11
+
12
+ def create
13
+ File.open(splice_path, 'w'){|f| f.write(new_content)}
14
+ end
15
+
16
+ def new_content
17
+ if local_file_exists?
18
+ merge_content
19
+ else
20
+ remote_content
21
+ end
22
+ end
23
+
24
+ def splice_path
25
+ proxy_path
26
+ end
27
+
28
+ def splice_folder_path
29
+ proxy_folder_path
30
+ end
31
+
32
+ def splice_file_exists?
33
+ file_exists? splice_path
34
+ end
35
+
36
+ def merge_content
37
+ if gzip?
38
+ merge_gziped_content
39
+ else
40
+ merge_text_content
41
+ end
42
+ end
43
+
44
+ def gzip?
45
+ /\.gz$/ =~ file_name
46
+ end
47
+
48
+ private
49
+ def merge_gziped_content
50
+ package(unpackage(local_content) | unpackage(remote_content))
51
+ end
52
+
53
+ def unpackage(content)
54
+ Marshal.load(Gem.gunzip(content))
55
+ end
56
+
57
+ def package(content)
58
+ Gem.gzip(Marshal.dump(content))
59
+ end
60
+
61
+ def merge_text_content
62
+ local_content.to_s + remote_content.to_s
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,27 @@
1
+ require 'httpclient'
2
+ require 'json'
3
+
4
+ module Geminabox
5
+ module RubygemsDependency
6
+
7
+ class << self
8
+
9
+ def for(*gems)
10
+
11
+ url = [
12
+ rubygems_uri,
13
+ '?gems=',
14
+ gems.map(&:to_s).join(',')
15
+ ].join
16
+ body = HTTPClient.get_content(url)
17
+ JSON.parse(body)
18
+ end
19
+
20
+ def rubygems_uri
21
+ "https://bundler.rubygems.org/api/v1/dependencies.json"
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,279 @@
1
+ module Geminabox
2
+
3
+ class Server < Sinatra::Base
4
+ enable :static, :methodoverride
5
+
6
+ def self.delegate_to_geminabox(*delegate_methods)
7
+ delegate_methods.each{|m| set m, Geminabox.send(m)}
8
+ end
9
+
10
+ delegate_to_geminabox(
11
+ :public_folder,
12
+ :data,
13
+ :build_legacy,
14
+ :incremental_updates,
15
+ :views,
16
+ :allow_replace,
17
+ :gem_permissions,
18
+ :allow_delete,
19
+ :rubygems_proxy
20
+ )
21
+
22
+ if Server.rubygems_proxy
23
+ use Proxy::Hostess
24
+ else
25
+ use Hostess
26
+ end
27
+
28
+ class << self
29
+ def disallow_replace?
30
+ ! allow_replace
31
+ end
32
+
33
+ def allow_delete?
34
+ allow_delete
35
+ end
36
+
37
+ def fixup_bundler_rubygems!
38
+ return if @post_reset_hook_applied
39
+ Gem.post_reset{ Gem::Specification.all = nil } if defined? Bundler and Gem.respond_to? :post_reset
40
+ @post_reset_hook_applied = true
41
+ end
42
+
43
+ def reindex(force_rebuild = false)
44
+ fixup_bundler_rubygems!
45
+ force_rebuild = true unless incremental_updates
46
+ if force_rebuild
47
+ indexer.generate_index
48
+ dependency_cache.flush
49
+ else
50
+ begin
51
+ require 'geminabox/indexer'
52
+ updated_gemspecs = Geminabox::Indexer.updated_gemspecs(indexer)
53
+ Geminabox::Indexer.patch_rubygems_update_index_pre_1_8_25(indexer)
54
+ indexer.update_index
55
+ updated_gemspecs.each { |gem| dependency_cache.flush_key(gem.name) }
56
+ rescue => e
57
+ puts "#{e.class}:#{e.message}"
58
+ puts e.backtrace.join("\n")
59
+ reindex(:force_rebuild)
60
+ end
61
+ end
62
+ end
63
+
64
+ def indexer
65
+ Gem::Indexer.new(data, :build_legacy => build_legacy)
66
+ end
67
+
68
+ def dependency_cache
69
+ @dependency_cache ||= Geminabox::DiskCache.new(File.join(data, "_cache"))
70
+ end
71
+ end
72
+
73
+
74
+
75
+ before do
76
+ headers 'X-Powered-By' => "geminabox #{Geminabox::VERSION}"
77
+ end
78
+
79
+ get '/' do
80
+ @gems = load_gems
81
+ @index_gems = index_gems(@gems)
82
+ erb :index
83
+ end
84
+
85
+ get '/atom.xml' do
86
+ @gems = load_gems
87
+ erb :atom, :layout => false
88
+ end
89
+
90
+ get '/api/v1/dependencies' do
91
+ Marshal.dump(gem_list)
92
+ end
93
+
94
+ get '/api/v1/dependencies.json' do
95
+ gem_list.to_json
96
+ end
97
+
98
+ get '/upload' do
99
+ erb :upload
100
+ end
101
+
102
+ get '/reindex' do
103
+ self.class.reindex(:force_rebuild)
104
+ redirect url("/")
105
+ end
106
+
107
+ get '/gems/:gemname' do
108
+ gems = Hash[load_gems.by_name]
109
+ @gem = gems[params[:gemname]]
110
+ halt 404 unless @gem
111
+ erb :gem
112
+ end
113
+
114
+ delete '/gems/*.gem' do
115
+ unless self.class.allow_delete?
116
+ error_response(403, 'Gem deletion is disabled - see https://github.com/cwninja/geminabox/issues/115')
117
+ end
118
+ File.delete file_path if File.exists? file_path
119
+ self.class.reindex(:force_rebuild)
120
+ redirect url("/")
121
+ end
122
+
123
+ post '/upload' do
124
+ unless params[:file] && params[:file][:filename] && (tmpfile = params[:file][:tempfile])
125
+ @error = "No file selected"
126
+ halt [400, erb(:upload)]
127
+ end
128
+ handle_incoming_gem(Geminabox::IncomingGem.new(tmpfile))
129
+ end
130
+
131
+ post '/api/v1/gems' do
132
+ begin
133
+ handle_incoming_gem(Geminabox::IncomingGem.new(request.body))
134
+ rescue Object => o
135
+ File.open "/tmp/debug.txt", "a" do |io|
136
+ io.puts o, o.backtrace
137
+ end
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ def handle_incoming_gem(gem)
144
+ begin
145
+ GemStore.create(gem, params[:overwrite])
146
+ rescue GemStoreError => error
147
+ error_response error.code, error.reason
148
+ end
149
+
150
+ if api_request?
151
+ "Gem #{gem.name} received and indexed."
152
+ else
153
+ redirect url("/")
154
+ end
155
+ end
156
+
157
+ def api_request?
158
+ request.accept.first != "text/html"
159
+ end
160
+
161
+ def error_response(code, message)
162
+ halt [code, message] if api_request?
163
+ html = <<HTML
164
+ <html>
165
+ <head><title>Error - #{code}</title></head>
166
+ <body>
167
+ <h1>Error - #{code}</h1>
168
+ <p>#{message}</p>
169
+ </body>
170
+ </html>
171
+ HTML
172
+ halt [code, html]
173
+ end
174
+
175
+ def file_path
176
+ File.expand_path(File.join(settings.data, *request.path_info))
177
+ end
178
+
179
+ def dependency_cache
180
+ self.class.dependency_cache
181
+ end
182
+
183
+ def all_gems
184
+ all_gems_with_duplicates.inject(:|)
185
+ end
186
+
187
+ def all_gems_with_duplicates
188
+ specs_files_paths.map do |specs_file_path|
189
+ if File.exists?(specs_file_path)
190
+ Marshal.load(Gem.gunzip(Gem.read_binary(specs_file_path)))
191
+ else
192
+ []
193
+ end
194
+ end
195
+ end
196
+
197
+ def specs_file_types
198
+ [:specs, :prerelease_specs]
199
+ end
200
+
201
+ def specs_files_paths
202
+ specs_file_types.map do |specs_file_type|
203
+ File.join(settings.data, spec_file_name(specs_file_type))
204
+ end
205
+ end
206
+
207
+ def spec_file_name(specs_file_type)
208
+ [specs_file_type, Gem.marshal_version, 'gz'].join('.')
209
+ end
210
+
211
+ def load_gems
212
+ @loaded_gems ||= Geminabox::GemVersionCollection.new(all_gems)
213
+ end
214
+
215
+ def index_gems(gems)
216
+ Set.new(gems.map{|gem| gem.name[0..0].downcase})
217
+ end
218
+
219
+ def gem_list
220
+ settings.rubygems_proxy ? combined_gem_list : local_gem_list
221
+ end
222
+
223
+ def query_gems
224
+ params[:gems].to_s.split(',')
225
+ end
226
+
227
+ def local_gem_list
228
+ query_gems.map{|query_gem| gem_dependencies(query_gem) }.flatten(1)
229
+ end
230
+
231
+ def remote_gem_list
232
+ RubygemsDependency.for(*query_gems)
233
+ end
234
+
235
+ def combined_gem_list
236
+ GemListMerge.from(local_gem_list, remote_gem_list)
237
+ end
238
+
239
+ helpers do
240
+ def spec_for(gem_name, version)
241
+ spec_file = File.join(settings.data, "quick", "Marshal.#{Gem.marshal_version}", "#{gem_name}-#{version}.gemspec.rz")
242
+ Marshal.load(Gem.inflate(File.read(spec_file))) if File.exists? spec_file
243
+ end
244
+
245
+ # Return a list of versions of gem 'gem_name' with the dependencies of each version.
246
+ def gem_dependencies(gem_name)
247
+ dependency_cache.marshal_cache(gem_name) do
248
+ load_gems.
249
+ select { |gem| gem_name == gem.name }.
250
+ map { |gem| [gem, spec_for(gem.name, gem.number)] }.
251
+ reject { |(_, spec)| spec.nil? }.
252
+ map do |(gem, spec)|
253
+ {
254
+ :name => gem.name,
255
+ :number => gem.number.version,
256
+ :platform => gem.platform,
257
+ :dependencies => runtime_dependencies(spec)
258
+ }
259
+ end
260
+ end
261
+ end
262
+
263
+
264
+
265
+ def runtime_dependencies(spec)
266
+ spec.
267
+ dependencies.
268
+ select { |dep| dep.type == :runtime }.
269
+ map { |dep| name_and_requirements_for(dep) }
270
+ end
271
+
272
+ def name_and_requirements_for(dep)
273
+ name = dep.name.kind_of?(Array) ? dep.name.first : dep.name
274
+ [name, dep.requirement.to_s]
275
+ end
276
+ end
277
+ end
278
+
279
+ end