gitlab-grack 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.gitmodules +3 -0
- data/README.md +91 -0
- data/Rakefile +27 -0
- data/examples/dispatch.fcgi +9 -0
- data/grack.gemspec +20 -0
- data/install.txt +60 -0
- data/lib/grack.rb +5 -0
- data/lib/grack/auth.rb +37 -0
- data/lib/grack/bundle.rb +20 -0
- data/lib/grack/server.rb +304 -0
- data/lib/grack/version.rb +3 -0
- data/tests/main_test.rb +237 -0
- metadata +91 -0
data/.gitignore
ADDED
data/.gitmodules
ADDED
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
Grack - Ruby/Rack Git Smart-HTTP Server Handler
|
2
|
+
===============================================
|
3
|
+
|
4
|
+
This project aims to replace the builtin git-http-backend CGI handler
|
5
|
+
distributed with C Git with a Rack application. This reason for doing this
|
6
|
+
is to allow far more webservers to be able to handle Git smart http requests.
|
7
|
+
|
8
|
+
The default git-http-backend only runs as a CGI script, and specifically is
|
9
|
+
only targeted for Apache 2.x usage (it requires PATH_INFO to be set and
|
10
|
+
specifically formatted). So, instead of trying to get it to work with
|
11
|
+
other CGI capable webservers (Lighttpd, etc), we can get it running on nearly
|
12
|
+
every major and minor webserver out there by making it Rack capable. Rack
|
13
|
+
applications can run with the following handlers:
|
14
|
+
|
15
|
+
* CGI
|
16
|
+
* FCGI
|
17
|
+
* Mongrel (and EventedMongrel and SwiftipliedMongrel)
|
18
|
+
* WEBrick
|
19
|
+
* SCGI
|
20
|
+
* LiteSpeed
|
21
|
+
* Thin
|
22
|
+
|
23
|
+
These web servers include Rack handlers in their distributions:
|
24
|
+
|
25
|
+
* Ebb
|
26
|
+
* Fuzed
|
27
|
+
* Phusion Passenger (which is mod_rack for Apache and for nginx)
|
28
|
+
* Unicorn
|
29
|
+
|
30
|
+
With [Warbler](http://caldersphere.rubyforge.org/warbler/classes/Warbler.html),
|
31
|
+
and JRuby, we can also generate a WAR file that can be deployed in any Java
|
32
|
+
web application server (Tomcat, Glassfish, Websphere, JBoss, etc).
|
33
|
+
|
34
|
+
Since the git-http-backend is really just a simple wrapper for the upload-pack
|
35
|
+
and receive-pack processes with the '--stateless-rpc' option, it does not
|
36
|
+
actually re-implement very much.
|
37
|
+
|
38
|
+
Dependencies
|
39
|
+
========================
|
40
|
+
* Ruby - http://www.ruby-lang.org
|
41
|
+
* Rack - http://rack.rubyforge.org
|
42
|
+
* A Rack-compatible web server
|
43
|
+
* Git >= 1.7 (currently the 'pu' branch)
|
44
|
+
* Mocha (only for running the tests)
|
45
|
+
|
46
|
+
Quick Start
|
47
|
+
========================
|
48
|
+
$ gem install rack
|
49
|
+
$ (edit config.ru to set git project path)
|
50
|
+
$ rackup --host 127.0.0.1 -p 8080 config.ru
|
51
|
+
$ git clone http://127.0.0.1:8080/schacon/grit.git
|
52
|
+
|
53
|
+
Contributing
|
54
|
+
========================
|
55
|
+
If you would like to contribute to the Grack project, I prefer to get
|
56
|
+
pull-requests via GitHub. You should include tests for whatever functionality
|
57
|
+
you add. Just fork this project, push your changes to your fork and click
|
58
|
+
the 'pull request' button. To run the tests, you first need to install the
|
59
|
+
'mocha' mocking library and initialize the submodule.
|
60
|
+
|
61
|
+
$ sudo gem install mocha
|
62
|
+
$ git submodule init
|
63
|
+
$ git submodule update
|
64
|
+
|
65
|
+
Then you should be able to run the tests with a 'rake' command. You can also
|
66
|
+
run coverage tests with 'rake rcov' if you have rcov installed.
|
67
|
+
|
68
|
+
License
|
69
|
+
========================
|
70
|
+
(The MIT License)
|
71
|
+
|
72
|
+
Copyright (c) 2009 Scott Chacon <schacon@gmail.com>
|
73
|
+
|
74
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
75
|
+
a copy of this software and associated documentation files (the
|
76
|
+
'Software'), to deal in the Software without restriction, including
|
77
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
78
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
79
|
+
permit persons to whom the Software is furnished to do so, subject to
|
80
|
+
the following conditions:
|
81
|
+
|
82
|
+
The above copyright notice and this permission notice shall be
|
83
|
+
included in all copies or substantial portions of the Software.
|
84
|
+
|
85
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
88
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
89
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
90
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
91
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
task :default => :test
|
5
|
+
|
6
|
+
desc "Run the tests."
|
7
|
+
task :test do
|
8
|
+
Dir.glob("tests/*_test.rb").each do |f|
|
9
|
+
system "ruby #{f}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Run test coverage."
|
14
|
+
task :rcov do
|
15
|
+
system "rcov tests/*_test.rb -i lib/git_http.rb -x rack -x Library -x tests"
|
16
|
+
system "open coverage/index.html"
|
17
|
+
end
|
18
|
+
|
19
|
+
namespace :grack do
|
20
|
+
desc "Start Grack"
|
21
|
+
task :start do
|
22
|
+
system "rackup config.ru -p 8080"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Start everything."
|
27
|
+
multitask :start => [ 'grack:start' ]
|
@@ -0,0 +1,9 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/lib')
|
3
|
+
require 'lib/git_http'
|
4
|
+
config = {
|
5
|
+
:project_root => "/opt",
|
6
|
+
:upload_pack => true,
|
7
|
+
:receive_pack => false,
|
8
|
+
}
|
9
|
+
Rack::Handler::FastCGI.run(GitHttp::App.new(config))
|
data/grack.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/grack/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Scott Chacon"]
|
6
|
+
gem.email = ["schacon@gmail.com"]
|
7
|
+
gem.description = %q{Ruby/Rack Git Smart-HTTP Server Handler}
|
8
|
+
gem.summary = %q{Ruby/Rack Git Smart-HTTP Server Handler}
|
9
|
+
gem.homepage = "https://github.com/schacon/grack"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- tests/*`.split("\n")
|
14
|
+
gem.name = "gitlab-grack"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Grack::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency("rack", "~> 1.4.1")
|
19
|
+
gem.add_development_dependency("mocha", "~> 0.11")
|
20
|
+
end
|
data/install.txt
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
Installation
|
2
|
+
========================
|
3
|
+
|
4
|
+
** This documentation is not finished yet. I haven't tested all of
|
5
|
+
these and it's obviously incomplete - these are currently just notes.
|
6
|
+
|
7
|
+
FastCGI
|
8
|
+
---------------------------------------
|
9
|
+
Here is an example config from lighttpd server:
|
10
|
+
----
|
11
|
+
# main fastcgi entry
|
12
|
+
$HTTP["url"] =~ "^/myapp/.+$" {
|
13
|
+
fastcgi.server = ( "/myapp" =>
|
14
|
+
( "localhost" =>
|
15
|
+
( "bin-path" => "/var/www/localhost/cgi-bin/dispatch.fcgi",
|
16
|
+
"docroot" => "/var/www/localhost/htdocs/myapp",
|
17
|
+
"host" => "127.0.0.1",
|
18
|
+
"port" => 1026,
|
19
|
+
"check-local" => "disable"
|
20
|
+
)
|
21
|
+
)
|
22
|
+
)
|
23
|
+
} # HTTP[url]
|
24
|
+
----
|
25
|
+
You can use the examples/dispatch.fcgi file as your dispatcher.
|
26
|
+
|
27
|
+
(Example Apache setup?)
|
28
|
+
|
29
|
+
Installing in a Java application server
|
30
|
+
---------------------------------------
|
31
|
+
# install Warbler
|
32
|
+
$ sudo gem install warbler
|
33
|
+
$ cd gitsmart
|
34
|
+
$ (edit config.ru)
|
35
|
+
$ warble
|
36
|
+
$ cp gitsmart.war /path/to/java/autodeploy/dir
|
37
|
+
|
38
|
+
Unicorn
|
39
|
+
---------------------------------------
|
40
|
+
With Unicorn (http://unicorn.bogomips.org/) you can just run 'unicorn'
|
41
|
+
in the directory with the config.ru file.
|
42
|
+
|
43
|
+
Thin
|
44
|
+
---------------------------------------
|
45
|
+
thin.yml
|
46
|
+
---
|
47
|
+
pid: /home/deploy/myapp/server/thin.pid
|
48
|
+
log: /home/deploy/myapp/logs/thin.log
|
49
|
+
timeout: 30
|
50
|
+
port: 7654
|
51
|
+
max_conns: 1024
|
52
|
+
chdir: /home/deploy/myapp/site_files
|
53
|
+
rackup: /home/deploy/myapp/server/config.ru
|
54
|
+
max_persistent_conns: 512
|
55
|
+
environment: production
|
56
|
+
address: 127.0.0.1
|
57
|
+
servers: 1
|
58
|
+
daemonize: true
|
59
|
+
|
60
|
+
|
data/lib/grack/auth.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rack/auth/basic'
|
2
|
+
require 'rack/auth/abstract/handler'
|
3
|
+
require 'rack/auth/abstract/request'
|
4
|
+
|
5
|
+
module Grack
|
6
|
+
class Auth < Rack::Auth::Basic
|
7
|
+
def call(env)
|
8
|
+
@env = env
|
9
|
+
@request = Rack::Request.new(env)
|
10
|
+
@auth = Request.new(env)
|
11
|
+
|
12
|
+
if not @auth.provided?
|
13
|
+
unauthorized
|
14
|
+
elsif not @auth.basic?
|
15
|
+
bad_request
|
16
|
+
else
|
17
|
+
result = if (access = valid? and access == true)
|
18
|
+
@env['REMOTE_USER'] = @auth.username
|
19
|
+
@app.call(env)
|
20
|
+
else
|
21
|
+
if access == '404'
|
22
|
+
render_not_found
|
23
|
+
elsif access == '403'
|
24
|
+
render_no_access
|
25
|
+
else
|
26
|
+
unauthorized
|
27
|
+
end
|
28
|
+
end
|
29
|
+
result
|
30
|
+
end
|
31
|
+
end# method call
|
32
|
+
|
33
|
+
def valid?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end# class Auth
|
37
|
+
end# module Grack
|
data/lib/grack/bundle.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rack/builder'
|
2
|
+
require 'grack/auth'
|
3
|
+
require 'grack/server'
|
4
|
+
|
5
|
+
module Grack
|
6
|
+
module Bundle
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def new(config)
|
10
|
+
Rack::Builder.new do
|
11
|
+
use Grack::Auth do |username, password|
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
run Grack::Server.new(config)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
data/lib/grack/server.rb
ADDED
@@ -0,0 +1,304 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'rack/request'
|
3
|
+
require 'rack/response'
|
4
|
+
require 'rack/utils'
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
module Grack
|
8
|
+
class Server
|
9
|
+
|
10
|
+
SERVICES = [
|
11
|
+
["POST", 'service_rpc', "(.*?)/git-upload-pack$", 'upload-pack'],
|
12
|
+
["POST", 'service_rpc', "(.*?)/git-receive-pack$", 'receive-pack'],
|
13
|
+
|
14
|
+
["GET", 'get_info_refs', "(.*?)/info/refs$"],
|
15
|
+
["GET", 'get_text_file', "(.*?)/HEAD$"],
|
16
|
+
["GET", 'get_text_file', "(.*?)/objects/info/alternates$"],
|
17
|
+
["GET", 'get_text_file', "(.*?)/objects/info/http-alternates$"],
|
18
|
+
["GET", 'get_info_packs', "(.*?)/objects/info/packs$"],
|
19
|
+
["GET", 'get_text_file', "(.*?)/objects/info/[^/]*$"],
|
20
|
+
["GET", 'get_loose_object', "(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"],
|
21
|
+
["GET", 'get_pack_file', "(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"],
|
22
|
+
["GET", 'get_idx_file', "(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"],
|
23
|
+
]
|
24
|
+
|
25
|
+
def initialize(config = false)
|
26
|
+
set_config(config)
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_config(config)
|
30
|
+
@config = config || {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_config_setting(key, value)
|
34
|
+
@config[key] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(env)
|
38
|
+
@env = env
|
39
|
+
@req = Rack::Request.new(env)
|
40
|
+
|
41
|
+
cmd, path, @reqfile, @rpc = match_routing
|
42
|
+
|
43
|
+
return render_method_not_allowed if cmd == 'not_allowed'
|
44
|
+
return render_not_found if !cmd
|
45
|
+
|
46
|
+
@dir = get_git_dir(path)
|
47
|
+
return render_not_found if !@dir
|
48
|
+
|
49
|
+
Dir.chdir(@dir) do
|
50
|
+
self.method(cmd).call()
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# ---------------------------------
|
55
|
+
# actual command handling functions
|
56
|
+
# ---------------------------------
|
57
|
+
|
58
|
+
def service_rpc
|
59
|
+
return render_no_access if !has_access(@rpc, true)
|
60
|
+
input = read_body
|
61
|
+
|
62
|
+
@res = Rack::Response.new
|
63
|
+
@res.status = 200
|
64
|
+
@res["Content-Type"] = "application/x-git-%s-result" % @rpc
|
65
|
+
@res.finish do
|
66
|
+
command = git_command("#{@rpc} --stateless-rpc #{@dir}")
|
67
|
+
IO.popen(command, File::RDWR) do |pipe|
|
68
|
+
pipe.write(input)
|
69
|
+
while !pipe.eof?
|
70
|
+
block = pipe.read(8192) # 8M at a time
|
71
|
+
@res.write block # steam it to the client
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_info_refs
|
78
|
+
service_name = get_service_type
|
79
|
+
|
80
|
+
if has_access(service_name)
|
81
|
+
cmd = git_command("#{service_name} --stateless-rpc --advertise-refs .")
|
82
|
+
refs = `#{cmd}`
|
83
|
+
|
84
|
+
@res = Rack::Response.new
|
85
|
+
@res.status = 200
|
86
|
+
@res["Content-Type"] = "application/x-git-%s-advertisement" % service_name
|
87
|
+
hdr_nocache
|
88
|
+
@res.write(pkt_write("# service=git-#{service_name}\n"))
|
89
|
+
@res.write(pkt_flush)
|
90
|
+
@res.write(refs)
|
91
|
+
@res.finish
|
92
|
+
else
|
93
|
+
dumb_info_refs
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def dumb_info_refs
|
98
|
+
update_server_info
|
99
|
+
send_file(@reqfile, "text/plain; charset=utf-8") do
|
100
|
+
hdr_nocache
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_info_packs
|
105
|
+
# objects/info/packs
|
106
|
+
send_file(@reqfile, "text/plain; charset=utf-8") do
|
107
|
+
hdr_nocache
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_loose_object
|
112
|
+
send_file(@reqfile, "application/x-git-loose-object") do
|
113
|
+
hdr_cache_forever
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_pack_file
|
118
|
+
send_file(@reqfile, "application/x-git-packed-objects") do
|
119
|
+
hdr_cache_forever
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_idx_file
|
124
|
+
send_file(@reqfile, "application/x-git-packed-objects-toc") do
|
125
|
+
hdr_cache_forever
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def get_text_file
|
130
|
+
send_file(@reqfile, "text/plain") do
|
131
|
+
hdr_nocache
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# ------------------------
|
136
|
+
# logic helping functions
|
137
|
+
# ------------------------
|
138
|
+
|
139
|
+
F = ::File
|
140
|
+
|
141
|
+
# some of this borrowed from the Rack::File implementation
|
142
|
+
def send_file(reqfile, content_type)
|
143
|
+
reqfile = File.join(@dir, reqfile)
|
144
|
+
return render_not_found if !F.exists?(reqfile)
|
145
|
+
|
146
|
+
@res = Rack::Response.new
|
147
|
+
@res.status = 200
|
148
|
+
@res["Content-Type"] = content_type
|
149
|
+
@res["Last-Modified"] = F.mtime(reqfile).httpdate
|
150
|
+
|
151
|
+
yield
|
152
|
+
|
153
|
+
if size = F.size?(reqfile)
|
154
|
+
@res["Content-Length"] = size.to_s
|
155
|
+
@res.finish do
|
156
|
+
F.open(reqfile, "rb") do |file|
|
157
|
+
while part = file.read(8192)
|
158
|
+
@res.write part
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
else
|
163
|
+
body = [F.read(reqfile)]
|
164
|
+
size = Rack::Utils.bytesize(body.first)
|
165
|
+
@res["Content-Length"] = size
|
166
|
+
@res.write body
|
167
|
+
@res.finish
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def get_git_dir(path)
|
172
|
+
root = @config[:project_root] || `pwd`
|
173
|
+
path = File.join(root, path)
|
174
|
+
if File.exists?(path) # TODO: check is a valid git directory
|
175
|
+
return path
|
176
|
+
end
|
177
|
+
false
|
178
|
+
end
|
179
|
+
|
180
|
+
def get_service_type
|
181
|
+
service_type = @req.params['service']
|
182
|
+
return false if !service_type
|
183
|
+
return false if service_type[0, 4] != 'git-'
|
184
|
+
service_type.gsub('git-', '')
|
185
|
+
end
|
186
|
+
|
187
|
+
def match_routing
|
188
|
+
cmd = nil
|
189
|
+
path = nil
|
190
|
+
SERVICES.each do |method, handler, match, rpc|
|
191
|
+
if m = Regexp.new(match).match(@req.path_info)
|
192
|
+
return ['not_allowed'] if method != @req.request_method
|
193
|
+
cmd = handler
|
194
|
+
path = m[1]
|
195
|
+
file = @req.path_info.sub(path + '/', '')
|
196
|
+
return [cmd, path, file, rpc]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
return nil
|
200
|
+
end
|
201
|
+
|
202
|
+
def has_access(rpc, check_content_type = false)
|
203
|
+
if check_content_type
|
204
|
+
return false if @req.content_type != "application/x-git-%s-request" % rpc
|
205
|
+
end
|
206
|
+
return false if !['upload-pack', 'receive-pack'].include? rpc
|
207
|
+
if rpc == 'receive-pack'
|
208
|
+
return @config[:receive_pack] if @config.include? :receive_pack
|
209
|
+
end
|
210
|
+
if rpc == 'upload-pack'
|
211
|
+
return @config[:upload_pack] if @config.include? :upload_pack
|
212
|
+
end
|
213
|
+
return get_config_setting(rpc)
|
214
|
+
end
|
215
|
+
|
216
|
+
def get_config_setting(service_name)
|
217
|
+
service_name = service_name.gsub('-', '')
|
218
|
+
setting = get_git_config("http.#{service_name}")
|
219
|
+
if service_name == 'uploadpack'
|
220
|
+
return setting != 'false'
|
221
|
+
else
|
222
|
+
return setting == 'true'
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def get_git_config(config_name)
|
227
|
+
cmd = git_command("config #{config_name}")
|
228
|
+
`#{cmd}`.chomp
|
229
|
+
end
|
230
|
+
|
231
|
+
def read_body
|
232
|
+
if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
|
233
|
+
input = Zlib::GzipReader.new(@req.body).read
|
234
|
+
else
|
235
|
+
input = @req.body.read
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def update_server_info
|
240
|
+
cmd = git_command("update-server-info")
|
241
|
+
`#{cmd}`
|
242
|
+
end
|
243
|
+
|
244
|
+
def git_command(command)
|
245
|
+
git_bin = @config[:git_path] || 'git'
|
246
|
+
command = "#{git_bin} #{command}"
|
247
|
+
command
|
248
|
+
end
|
249
|
+
|
250
|
+
# --------------------------------------
|
251
|
+
# HTTP error response handling functions
|
252
|
+
# --------------------------------------
|
253
|
+
|
254
|
+
PLAIN_TYPE = {"Content-Type" => "text/plain"}
|
255
|
+
|
256
|
+
def render_method_not_allowed
|
257
|
+
if @env['SERVER_PROTOCOL'] == "HTTP/1.1"
|
258
|
+
[405, PLAIN_TYPE, ["Method Not Allowed"]]
|
259
|
+
else
|
260
|
+
[400, PLAIN_TYPE, ["Bad Request"]]
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def render_not_found
|
265
|
+
[404, PLAIN_TYPE, ["Not Found"]]
|
266
|
+
end
|
267
|
+
|
268
|
+
def render_no_access
|
269
|
+
[403, PLAIN_TYPE, ["Forbidden"]]
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
# ------------------------------
|
274
|
+
# packet-line handling functions
|
275
|
+
# ------------------------------
|
276
|
+
|
277
|
+
def pkt_flush
|
278
|
+
'0000'
|
279
|
+
end
|
280
|
+
|
281
|
+
def pkt_write(str)
|
282
|
+
(str.size + 4).to_s(base=16).rjust(4, '0') + str
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
# ------------------------
|
287
|
+
# header writing functions
|
288
|
+
# ------------------------
|
289
|
+
|
290
|
+
def hdr_nocache
|
291
|
+
@res["Expires"] = "Fri, 01 Jan 1980 00:00:00 GMT"
|
292
|
+
@res["Pragma"] = "no-cache"
|
293
|
+
@res["Cache-Control"] = "no-cache, max-age=0, must-revalidate"
|
294
|
+
end
|
295
|
+
|
296
|
+
def hdr_cache_forever
|
297
|
+
now = Time.now().to_i
|
298
|
+
@res["Date"] = now.to_s
|
299
|
+
@res["Expires"] = (now + 31536000).to_s;
|
300
|
+
@res["Cache-Control"] = "public, max-age=31536000";
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|
304
|
+
end
|
data/tests/main_test.rb
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/test'
|
3
|
+
require 'test/unit'
|
4
|
+
require 'mocha'
|
5
|
+
require 'digest/sha1'
|
6
|
+
|
7
|
+
require_relative '../lib/grack/server.rb'
|
8
|
+
require 'pp'
|
9
|
+
|
10
|
+
class GitHttpTest < Test::Unit::TestCase
|
11
|
+
include Rack::Test::Methods
|
12
|
+
|
13
|
+
def example
|
14
|
+
File.expand_path(File.dirname(__FILE__))
|
15
|
+
end
|
16
|
+
|
17
|
+
def app
|
18
|
+
config = {
|
19
|
+
:project_root => example,
|
20
|
+
:upload_pack => true,
|
21
|
+
:receive_pack => true,
|
22
|
+
}
|
23
|
+
Grack::Server.new(config)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_upload_pack_advertisement
|
27
|
+
get "/example/info/refs?service=git-upload-pack"
|
28
|
+
assert_equal 200, r.status
|
29
|
+
assert_equal "application/x-git-upload-pack-advertisement", r.headers["Content-Type"]
|
30
|
+
assert_equal "001e# service=git-upload-pack", r.body.split("\n").first
|
31
|
+
assert_match 'multi_ack_detailed', r.body
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_no_access_wrong_content_type_up
|
35
|
+
post "/example/git-upload-pack"
|
36
|
+
assert_equal 403, r.status
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_no_access_wrong_content_type_rp
|
40
|
+
post "/example/git-receive-pack"
|
41
|
+
assert_equal 403, r.status
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_no_access_wrong_method_rcp
|
45
|
+
get "/example/git-upload-pack"
|
46
|
+
assert_equal 400, r.status
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_no_access_wrong_command_rcp
|
50
|
+
post "/example/git-upload-packfile"
|
51
|
+
assert_equal 404, r.status
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_no_access_wrong_path_rcp
|
55
|
+
post "/example-wrong/git-upload-pack"
|
56
|
+
assert_equal 404, r.status
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_upload_pack_rpc
|
60
|
+
IO.stubs(:popen).returns(MockProcess.new)
|
61
|
+
post "/example/git-upload-pack", {}, {"CONTENT_TYPE" => "application/x-git-upload-pack-request"}
|
62
|
+
assert_equal 200, r.status
|
63
|
+
assert_equal "application/x-git-upload-pack-result", r.headers["Content-Type"]
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_receive_pack_advertisement
|
67
|
+
get "/example/info/refs?service=git-receive-pack"
|
68
|
+
assert_equal 200, r.status
|
69
|
+
assert_equal "application/x-git-receive-pack-advertisement", r.headers["Content-Type"]
|
70
|
+
assert_equal "001f# service=git-receive-pack", r.body.split("\n").first
|
71
|
+
assert_match 'report-status', r.body
|
72
|
+
assert_match 'delete-refs', r.body
|
73
|
+
assert_match 'ofs-delta', r.body
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_recieve_pack_rpc
|
77
|
+
IO.stubs(:popen).yields(MockProcess.new)
|
78
|
+
post "/example/git-receive-pack", {}, {"CONTENT_TYPE" => "application/x-git-receive-pack-request"}
|
79
|
+
assert_equal 200, r.status
|
80
|
+
assert_equal "application/x-git-receive-pack-result", r.headers["Content-Type"]
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_info_refs_dumb
|
84
|
+
get "/example/.git/info/refs"
|
85
|
+
assert_equal 200, r.status
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_info_packs
|
89
|
+
get "/example/.git/objects/info/packs"
|
90
|
+
assert_equal 200, r.status
|
91
|
+
assert_match /P pack-(.*?).pack/, r.body
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_loose_objects
|
95
|
+
path, content = write_test_objects
|
96
|
+
get "/example/.git/objects/#{path}"
|
97
|
+
assert_equal 200, r.status
|
98
|
+
assert_equal content, r.body
|
99
|
+
remove_test_objects
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_pack_file
|
103
|
+
path, content = write_test_objects
|
104
|
+
get "/example/.git/objects/pack/pack-#{content}.pack"
|
105
|
+
assert_equal 200, r.status
|
106
|
+
assert_equal content, r.body
|
107
|
+
remove_test_objects
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_index_file
|
111
|
+
path, content = write_test_objects
|
112
|
+
get "/example/.git/objects/pack/pack-#{content}.idx"
|
113
|
+
assert_equal 200, r.status
|
114
|
+
assert_equal content, r.body
|
115
|
+
remove_test_objects
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_text_file
|
119
|
+
get "/example/.git/HEAD"
|
120
|
+
assert_equal 200, r.status
|
121
|
+
assert_equal 41, r.body.size # submodules have detached head
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_no_size_avail
|
125
|
+
File.stubs('size?').returns(false)
|
126
|
+
get "/example/.git/HEAD"
|
127
|
+
assert_equal 200, r.status
|
128
|
+
assert_equal 46, r.body.size # submodules have detached head
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_config_upload_pack_off
|
132
|
+
a1 = app
|
133
|
+
a1.set_config_setting(:upload_pack, false)
|
134
|
+
session = Rack::Test::Session.new(a1)
|
135
|
+
session.get "/example/info/refs?service=git-upload-pack"
|
136
|
+
assert_equal 404, session.last_response.status
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_config_receive_pack_off
|
140
|
+
a1 = app
|
141
|
+
a1.set_config_setting(:receive_pack, false)
|
142
|
+
session = Rack::Test::Session.new(a1)
|
143
|
+
session.get "/example/info/refs?service=git-receive-pack"
|
144
|
+
assert_equal 404, session.last_response.status
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_config_bad_service
|
148
|
+
get "/example/info/refs?service=git-receive-packfile"
|
149
|
+
assert_equal 404, r.status
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_git_config_receive_pack
|
153
|
+
app1 = Grack::Server.new({:project_root => example})
|
154
|
+
session = Rack::Test::Session.new(app1)
|
155
|
+
|
156
|
+
app1.stubs(:get_git_config).with('http.receivepack').returns('')
|
157
|
+
session.get "/example/info/refs?service=git-receive-pack"
|
158
|
+
assert_equal 404, session.last_response.status
|
159
|
+
|
160
|
+
app1.stubs(:get_git_config).with('http.receivepack').returns('true')
|
161
|
+
session.get "/example/info/refs?service=git-receive-pack"
|
162
|
+
assert_equal 200, session.last_response.status
|
163
|
+
|
164
|
+
app1.stubs(:get_git_config).with('http.receivepack').returns('false')
|
165
|
+
session.get "/example/info/refs?service=git-receive-pack"
|
166
|
+
assert_equal 404, session.last_response.status
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_git_config_upload_pack
|
170
|
+
app1 = Grack::Server.new({:project_root => example})
|
171
|
+
session = Rack::Test::Session.new(app1)
|
172
|
+
|
173
|
+
app1.stubs(:get_git_config).with('http.uploadpack').returns('')
|
174
|
+
session.get "/example/info/refs?service=git-upload-pack"
|
175
|
+
assert_equal 200, session.last_response.status
|
176
|
+
|
177
|
+
app1.stubs(:get_git_config).with('http.uploadpack').returns('true')
|
178
|
+
session.get "/example/info/refs?service=git-upload-pack"
|
179
|
+
assert_equal 200, session.last_response.status
|
180
|
+
|
181
|
+
app1.stubs(:get_git_config).with('http.uploadpack').returns('false')
|
182
|
+
session.get "/example/info/refs?service=git-upload-pack"
|
183
|
+
assert_equal 404, session.last_response.status
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
def r
|
189
|
+
last_response
|
190
|
+
end
|
191
|
+
|
192
|
+
def write_test_objects
|
193
|
+
content = Digest::SHA1.hexdigest('gitrocks')
|
194
|
+
base = File.join(File.expand_path(File.dirname(__FILE__)), 'example', '.git', 'objects')
|
195
|
+
obj = File.join(base, '20')
|
196
|
+
Dir.mkdir(obj) rescue nil
|
197
|
+
file = File.join(obj, content[0, 38])
|
198
|
+
File.open(file, 'w') { |f| f.write(content) }
|
199
|
+
pack = File.join(base, 'pack', "pack-#{content}.pack")
|
200
|
+
File.open(pack, 'w') { |f| f.write(content) }
|
201
|
+
idx = File.join(base, 'pack', "pack-#{content}.idx")
|
202
|
+
File.open(idx, 'w') { |f| f.write(content) }
|
203
|
+
["20/#{content[0,38]}", content]
|
204
|
+
end
|
205
|
+
|
206
|
+
def remove_test_objects
|
207
|
+
content = Digest::SHA1.hexdigest('gitrocks')
|
208
|
+
base = File.join(File.expand_path(File.dirname(__FILE__)), 'example', '.git', 'objects')
|
209
|
+
obj = File.join(base, '20')
|
210
|
+
file = File.join(obj, content[0, 38])
|
211
|
+
pack = File.join(base, 'pack', "pack-#{content}.pack")
|
212
|
+
idx = File.join(base, 'pack', "pack-#{content}.idx")
|
213
|
+
File.unlink(file)
|
214
|
+
File.unlink(pack)
|
215
|
+
File.unlink(idx)
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
class MockProcess
|
221
|
+
|
222
|
+
def initialize
|
223
|
+
@counter = 0
|
224
|
+
end
|
225
|
+
|
226
|
+
def write(data)
|
227
|
+
end
|
228
|
+
|
229
|
+
def read(data)
|
230
|
+
end
|
231
|
+
|
232
|
+
def eof?
|
233
|
+
@counter += 1
|
234
|
+
@counter > 1 ? true : false
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gitlab-grack
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Scott Chacon
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.4.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.4.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: mocha
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0.11'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0.11'
|
46
|
+
description: Ruby/Rack Git Smart-HTTP Server Handler
|
47
|
+
email:
|
48
|
+
- schacon@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- .gitmodules
|
55
|
+
- README.md
|
56
|
+
- Rakefile
|
57
|
+
- examples/dispatch.fcgi
|
58
|
+
- grack.gemspec
|
59
|
+
- install.txt
|
60
|
+
- lib/grack.rb
|
61
|
+
- lib/grack/auth.rb
|
62
|
+
- lib/grack/bundle.rb
|
63
|
+
- lib/grack/server.rb
|
64
|
+
- lib/grack/version.rb
|
65
|
+
- tests/main_test.rb
|
66
|
+
homepage: https://github.com/schacon/grack
|
67
|
+
licenses: []
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.8.24
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: Ruby/Rack Git Smart-HTTP Server Handler
|
90
|
+
test_files:
|
91
|
+
- tests/main_test.rb
|