blaggard 1.0.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.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +15 -0
  5. data/Gemfile.lock +66 -0
  6. data/README.md +84 -0
  7. data/Rakefile +20 -0
  8. data/bin/blaggard +55 -0
  9. data/blaggard.gemspec +18 -0
  10. data/config.yml.example +9 -0
  11. data/install.txt +60 -0
  12. data/lib/blaggard.rb +33 -0
  13. data/lib/blaggard/advertisement.rb +92 -0
  14. data/lib/blaggard/auth.rb +37 -0
  15. data/lib/blaggard/bundle.rb +20 -0
  16. data/lib/blaggard/console.rb +3 -0
  17. data/lib/blaggard/git.rb +82 -0
  18. data/lib/blaggard/group_config.rb +123 -0
  19. data/lib/blaggard/group_finder.rb +28 -0
  20. data/lib/blaggard/server.rb +311 -0
  21. data/script/console +7 -0
  22. data/spec/advertisement_spec.rb +81 -0
  23. data/spec/fixtures/spec_repo.git/FETCH_HEAD +2 -0
  24. data/spec/fixtures/spec_repo.git/HEAD +1 -0
  25. data/spec/fixtures/spec_repo.git/HEAD_TRACKER +1 -0
  26. data/spec/fixtures/spec_repo.git/config +53 -0
  27. data/spec/fixtures/spec_repo.git/index +0 -0
  28. data/spec/fixtures/spec_repo.git/logs/HEAD +7 -0
  29. data/spec/fixtures/spec_repo.git/logs/refs/heads/br2 +2 -0
  30. data/spec/fixtures/spec_repo.git/logs/refs/heads/master +2 -0
  31. data/spec/fixtures/spec_repo.git/logs/refs/heads/not-good +1 -0
  32. data/spec/fixtures/spec_repo.git/logs/refs/remotes/origin/HEAD +1 -0
  33. data/spec/fixtures/spec_repo.git/logs/refs/remotes/test/master +2 -0
  34. data/spec/fixtures/spec_repo.git/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 +0 -0
  35. data/spec/fixtures/spec_repo.git/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 +0 -0
  36. data/spec/fixtures/spec_repo.git/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 +0 -0
  37. data/spec/fixtures/spec_repo.git/objects/18/10dff58d8a660512d4832e740f692884338ccd +0 -0
  38. data/spec/fixtures/spec_repo.git/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd +0 -0
  39. data/spec/fixtures/spec_repo.git/objects/1b/8cbad43e867676df601306689fe7c3def5e689 +0 -0
  40. data/spec/fixtures/spec_repo.git/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b +0 -0
  41. data/spec/fixtures/spec_repo.git/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 +0 -0
  42. data/spec/fixtures/spec_repo.git/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d +0 -0
  43. data/spec/fixtures/spec_repo.git/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 +0 -0
  44. data/spec/fixtures/spec_repo.git/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 +0 -0
  45. data/spec/fixtures/spec_repo.git/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc +0 -0
  46. data/spec/fixtures/spec_repo.git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 +0 -0
  47. data/spec/fixtures/spec_repo.git/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 +2 -0
  48. data/spec/fixtures/spec_repo.git/objects/4a/23e2e65ad4e31c4c9db7dc746650bfad082679 +0 -0
  49. data/spec/fixtures/spec_repo.git/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea +0 -0
  50. data/spec/fixtures/spec_repo.git/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 +0 -0
  51. data/spec/fixtures/spec_repo.git/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 +2 -0
  52. data/spec/fixtures/spec_repo.git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a +0 -0
  53. data/spec/fixtures/spec_repo.git/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af +1 -0
  54. data/spec/fixtures/spec_repo.git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 +0 -0
  55. data/spec/fixtures/spec_repo.git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d +0 -0
  56. data/spec/fixtures/spec_repo.git/objects/84/96071c1b46c854b31185ea97743be6a8774479 +0 -0
  57. data/spec/fixtures/spec_repo.git/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe +2 -0
  58. data/spec/fixtures/spec_repo.git/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 +1 -0
  59. data/spec/fixtures/spec_repo.git/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 +0 -0
  60. data/spec/fixtures/spec_repo.git/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 +2 -0
  61. data/spec/fixtures/spec_repo.git/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a +3 -0
  62. data/spec/fixtures/spec_repo.git/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f +2 -0
  63. data/spec/fixtures/spec_repo.git/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 +3 -0
  64. data/spec/fixtures/spec_repo.git/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd +0 -0
  65. data/spec/fixtures/spec_repo.git/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 +0 -0
  66. data/spec/fixtures/spec_repo.git/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 +0 -0
  67. data/spec/fixtures/spec_repo.git/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 +2 -0
  68. data/spec/fixtures/spec_repo.git/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 +0 -0
  69. data/spec/fixtures/spec_repo.git/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 +3 -0
  70. data/spec/fixtures/spec_repo.git/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd +3 -0
  71. data/spec/fixtures/spec_repo.git/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659 +0 -0
  72. data/spec/fixtures/spec_repo.git/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f +0 -0
  73. data/spec/fixtures/spec_repo.git/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759 +0 -0
  74. data/spec/fixtures/spec_repo.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 +0 -0
  75. data/spec/fixtures/spec_repo.git/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 +0 -0
  76. data/spec/fixtures/spec_repo.git/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 +0 -0
  77. data/spec/fixtures/spec_repo.git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 +0 -0
  78. data/spec/fixtures/spec_repo.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 +0 -0
  79. data/spec/fixtures/spec_repo.git/objects/fd/093bff70906175335656e6ce6ae05783708765 +0 -0
  80. data/spec/fixtures/spec_repo.git/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09 +0 -0
  81. data/spec/fixtures/spec_repo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx +0 -0
  82. data/spec/fixtures/spec_repo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack +0 -0
  83. data/spec/fixtures/spec_repo.git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx +0 -0
  84. data/spec/fixtures/spec_repo.git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack +0 -0
  85. data/spec/fixtures/spec_repo.git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx +0 -0
  86. data/spec/fixtures/spec_repo.git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack +0 -0
  87. data/spec/fixtures/spec_repo.git/packed-refs +3 -0
  88. data/spec/fixtures/spec_repo.git/refs/heads/br2 +1 -0
  89. data/spec/fixtures/spec_repo.git/refs/heads/cannot-fetch +1 -0
  90. data/spec/fixtures/spec_repo.git/refs/heads/chomped +1 -0
  91. data/spec/fixtures/spec_repo.git/refs/heads/haacked +1 -0
  92. data/spec/fixtures/spec_repo.git/refs/heads/master +1 -0
  93. data/spec/fixtures/spec_repo.git/refs/heads/not-good +1 -0
  94. data/spec/fixtures/spec_repo.git/refs/heads/packed-test +1 -0
  95. data/spec/fixtures/spec_repo.git/refs/heads/subtrees +1 -0
  96. data/spec/fixtures/spec_repo.git/refs/heads/test +1 -0
  97. data/spec/fixtures/spec_repo.git/refs/heads/track-local +1 -0
  98. data/spec/fixtures/spec_repo.git/refs/heads/trailing +1 -0
  99. data/spec/fixtures/spec_repo.git/refs/notes/fanout +1 -0
  100. data/spec/fixtures/spec_repo.git/refs/remotes/test/master +1 -0
  101. data/spec/fixtures/spec_repo.git/refs/tags/master-r1 +1 -0
  102. data/spec/fixtures/spec_repo.git/refs/tags/master-r2 +1 -0
  103. data/spec/fixtures/spec_repo.git/refs/tags/test-r1 +1 -0
  104. data/spec/fixtures/spec_repo.git/refs/tags/test-r2 +1 -0
  105. data/spec/git_spec.rb +23 -0
  106. data/spec/group_config_spec.rb +64 -0
  107. data/spec/spec_helper.rb +42 -0
  108. data/spec/support/test_env.rb +17 -0
  109. metadata +197 -0
@@ -0,0 +1,37 @@
1
+ require 'rack/auth/basic'
2
+ require 'rack/auth/abstract/handler'
3
+ require 'rack/auth/abstract/request'
4
+
5
+ module Blaggard
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
@@ -0,0 +1,20 @@
1
+ require 'rack/builder'
2
+ require 'blaggard/auth'
3
+ require 'blaggard/server'
4
+
5
+ module Blaggard
6
+ module Bundle
7
+ extend self
8
+
9
+ def new(config)
10
+ Rack::Builder.new do
11
+ use Blaggard::Auth do |username, password|
12
+ false
13
+ end
14
+
15
+ run Blaggard::Server.new(config)
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ Pry.config.prompt = lambda do |context, nesting, pry|
2
+ "[blaggard] #{context}> "
3
+ end
@@ -0,0 +1,82 @@
1
+ module Blaggard
2
+
3
+ class Git
4
+ attr_accessor :git_path, :repo
5
+
6
+ def initialize(repo, path = nil)
7
+ @repo = repo
8
+ @git_path = path ? path : 'git'
9
+ end
10
+
11
+ def execute(cmd)
12
+ cmd = command(cmd)
13
+ if block_given?
14
+ IO.popen(popen_env, cmd, File::RDWR, popen_options) do |pipe|
15
+ yield(pipe)
16
+ end
17
+ else
18
+ capture(cmd).chomp
19
+ end
20
+ end
21
+
22
+ def time_ordered_refs
23
+ opts = %W(--sort=-committerdate refs/heads/ --format=%(refname))
24
+ return execute(['for-each-ref', opts]).split("\n")
25
+ end
26
+
27
+ def tags_on_branch(commitish)
28
+ opts = %W(--simplify-by-decoration --decorate --pretty=oneline)
29
+ return execute(['log', opts, commitish]).scan(/tag: (.*?)(\,|\))/).map{ |match| match.first}
30
+ rescue => e
31
+ []
32
+ end
33
+
34
+ def command(cmd)
35
+ [git_path || 'git'] + cmd.flatten
36
+ end
37
+
38
+ def capture(command)
39
+ IO.popen(popen_env, command, popen_options).read
40
+ end
41
+
42
+ def popen_options
43
+ {chdir: repo, unsetenv_others: true}
44
+ end
45
+
46
+ def popen_env
47
+ {'PATH' => ENV['PATH'], 'GL_ID' => ENV['GL_ID']}
48
+ end
49
+
50
+ def get_config_setting(service_name)
51
+ service_name = service_name.gsub('-', '')
52
+ setting = get_git_config("http.#{service_name}")
53
+ if service_name == 'uploadpack'
54
+ return setting != 'false'
55
+ else
56
+ return setting == 'true'
57
+ end
58
+ end
59
+
60
+ def get_git_config(config_name)
61
+ execute(%W(config #{config_name}))
62
+ end
63
+
64
+ def valid_repo?
65
+ return false unless File.exists?(repo) &&
66
+ File.realpath(repo) == repo
67
+ match = execute(%W(rev-parse --git-dir)).match(/\.$|\.git$/)
68
+
69
+ if match.to_s == '.git'
70
+ # Since the parent could be a git repo, we want to make sure the actual repo contains a git dir.
71
+ return false unless Dir.entries(repo).include?('.git')
72
+ end
73
+
74
+ !!match
75
+ end
76
+
77
+ def update_server_info
78
+ # TODO: Update this for use with ACL
79
+ execute(%W(update-server-info))
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,123 @@
1
+ module Blaggard
2
+ class GroupConfigError < StandardError; end
3
+ class GroupConfig
4
+ # groups is an object like this:
5
+ # {
6
+ # # group: { 'read': [refs]
7
+ # # 'write': [refs]}
8
+ # "admin_users" => {
9
+ # 'read' => ['refs/heads/*'],
10
+ # 'write' => ['refs/heads/haacked', 'refs/heads/master']
11
+ # },
12
+ # "normal_users" => {
13
+ # 'read' => ['refs/heads/*'],
14
+ # 'write' => []
15
+ # }
16
+ # }
17
+ attr_accessor :groups, :git
18
+ def initialize( repo_path )
19
+ @repo_path = repo_path
20
+ @git = Blaggard::Git.new(repo_path)
21
+ @config_path = File.join(@repo_path, 'refs/meta')
22
+ unless File.directory?(@config_path)
23
+ FileUtils.mkdir_p(File.join(@repo_path, 'refs/meta'))
24
+ end
25
+ @config_file = File.join(@config_path, 'config')
26
+ read_from_git
27
+ end
28
+
29
+ def write_to_git
30
+ tmp_file = File.absolute_path File.join(@config_path, 'tmp')
31
+ if File.exist? tmp_file
32
+ # tmp acts like a lock file to prevent multiple writes at the same time.
33
+ raise Blaggard::GroupConfigError, "Unable to write to meta config, someone else is currently using it. Try again in a few minutes."
34
+ end
35
+ File.open( tmp_file , 'w' ) do |f|
36
+ f.write(YAML.dump @groups)
37
+ end
38
+
39
+ ref = git.execute(['hash-object', '-w', tmp_file])
40
+ File.open( @config_file , 'w') do |f|
41
+ f.write(ref)
42
+ end
43
+ FileUtils.rm(tmp_file)
44
+ return true
45
+ end
46
+
47
+ def read_from_git
48
+ ref = File.open(@config_file, &:readline).chomp
49
+ yml = git.execute(['cat-file', '-p', ref])
50
+ @groups = YAML.load yml
51
+ return @groups
52
+ rescue
53
+ @groups = {}
54
+ end
55
+
56
+ def add_branch(group, priv, branch)
57
+ @groups = read_from_git
58
+ @groups[group] = {:read =>[], :write => []} unless @groups[group]
59
+ validate_privilege priv
60
+ unless @groups[group][priv].include? branch
61
+ if valid_branch_name? branch
62
+ @groups[group][priv] << branch
63
+ write_to_git
64
+ else
65
+ raise Blaggard::GroupConfigError, "Branch name #{branch} invalid. Must be of format 'refs/heads/<branch_name>"
66
+ end
67
+ end
68
+ end
69
+
70
+ def delete_branch(group, priv, branch)
71
+ @groups = read_from_git
72
+ validate_group group
73
+ validate_privilege priv
74
+ if @groups[group][priv].delete(branch)
75
+ write_to_git
76
+ else
77
+ return false
78
+ end
79
+ end
80
+
81
+ def delete_group(group)
82
+ @groups = read_from_git
83
+ validate_group group
84
+ if @groups.delete(group)
85
+ write_to_git
86
+ else
87
+ false
88
+ end
89
+ end
90
+
91
+ def can_access_branch?(group, priv, branch)
92
+ validate_group group rescue (return false)
93
+ validate_privilege priv
94
+ @groups[group][priv].include? branch
95
+ end
96
+
97
+ def branches(user_groups, priv)
98
+ validate_privilege priv
99
+ user_groups.map{ |group|
100
+ validate_group group rescue next
101
+ @groups[group][priv]
102
+ }.uniq.flatten.compact
103
+ end
104
+
105
+ def valid_branch_name?(branch)
106
+ valid_branches = Dir[File.join(@repo_path, 'refs/heads/*')].map{|b| b.split('/')[-3..-1].join('/')}
107
+ valid_branches << 'refs/heads/*'
108
+ return valid_branches.include?(branch)
109
+ end
110
+
111
+ # Validation Methods
112
+
113
+ def validate_group(group)
114
+ unless @groups[group]
115
+ raise Blaggard::GroupConfigError, "Group #{group} does not exist. Use add_branch to create it."
116
+ end
117
+ end
118
+
119
+ def validate_privilege(priv)
120
+ raise Blaggard::GroupConfigError, "Privilege must be either :read or :write" unless [:read, :write].include?(priv)
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,28 @@
1
+ require 'net/http'
2
+ require 'json'
3
+ module Blaggard
4
+ class GroupFinder
5
+ def initialize(config)
6
+ # Make your base url and resource something like:
7
+ #
8
+ # https://example.com/api/v1/users_groups/user1
9
+ #
10
+ # Ideally this should bring down a list of strings that
11
+ # will correspond to the group keys in the repo config.
12
+ # These will be the groups that user is a part of. The
13
+ # User is identified by their username over http auth
14
+ # ie. the REMOTE_USER header.
15
+ @url = "#{config[:base_url]}/#{config[:group_resource]}/:id"
16
+ end
17
+
18
+ def find(identifier)
19
+ uri = URI(@url.gsub(':id', identifier))
20
+ res = Net::HTTP.get_response(uri)
21
+ if res.code == "200"
22
+ return JSON.load(res.body)
23
+ else
24
+ []
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,311 @@
1
+ module Blaggard
2
+ class Server
3
+ attr_reader :git, :config
4
+ SERVICES = [
5
+ ["POST", 'service_rpc', "(.*?)/git-upload-pack$", 'upload-pack'],
6
+ ["POST", 'service_rpc', "(.*?)/git-receive-pack$", 'receive-pack'],
7
+
8
+ ["GET", 'get_info_refs', "(.*?)/info/refs$"],
9
+ ["GET", 'get_text_file', "(.*?)/HEAD$"],
10
+ ["GET", 'get_text_file', "(.*?)/objects/info/alternates$"],
11
+ ["GET", 'get_text_file', "(.*?)/objects/info/http-alternates$"],
12
+ ["GET", 'get_info_packs', "(.*?)/objects/info/packs$"],
13
+ ["GET", 'get_text_file', "(.*?)/objects/info/[^/]*$"],
14
+ ["GET", 'get_loose_object', "(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"],
15
+ ["GET", 'get_pack_file', "(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"],
16
+ ["GET", 'get_idx_file', "(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"],
17
+ ]
18
+
19
+ def initialize(conf = false)
20
+ if conf.instance_of? Hash
21
+ @config = conf
22
+ elsif (File.exist?(conf) rescue false)
23
+ @config = YAML.load( File.read conf )
24
+ else
25
+ @config = {}
26
+ end
27
+ end
28
+
29
+ def set_config_setting(key, value)
30
+ @config[key] = value
31
+ end
32
+
33
+ def call(env)
34
+ dup._call(env)
35
+ end
36
+
37
+ def _call(env)
38
+ @env = env
39
+ # puts env
40
+ @req = Rack::Request.new(env)
41
+
42
+ cmd, path, @reqfile, @rpc = match_routing
43
+ return render_method_not_allowed if cmd == 'not_allowed'
44
+ return render_not_found if !cmd
45
+
46
+ @git = get_git(path)
47
+ return render_not_found unless git.valid_repo?
48
+
49
+
50
+ identifier = env['REMOTE_USER']
51
+ @groups = Blaggard::GroupFinder.new(@config).find(identifier) if @config[:use_acl]
52
+
53
+ self.method(cmd).call()
54
+ end
55
+
56
+ # ---------------------------------
57
+ # actual command handling functions
58
+ # ---------------------------------
59
+
60
+ # Uses chunked (streaming) transfer, otherwise response
61
+ # blocks to calculate Content-Length header
62
+ # http://en.wikipedia.org/wiki/Chunked_transfer_encoding
63
+
64
+ CRLF = "\r\n"
65
+
66
+ def service_rpc
67
+ return render_no_access unless has_access?(@rpc, true)
68
+
69
+ input = read_body
70
+
71
+ @res = Rack::Response.new
72
+ @res.status = 200
73
+ @res["Content-Type"] = "application/x-git-%s-result" % @rpc
74
+ @res["Transfer-Encoding"] = "chunked"
75
+ @res["Cache-Control"] = "no-cache"
76
+
77
+ @res.finish do
78
+ git.execute([@rpc, '--stateless-rpc', git.repo]) do |pipe|
79
+ pipe.write(input)
80
+ pipe.close_write
81
+
82
+ while !pipe.eof?
83
+ block = pipe.read(8192) # 8KB at a time
84
+ @res.write encode_chunk(block) # stream it to the client
85
+ end
86
+
87
+ @res.write terminating_chunk
88
+ end
89
+ end
90
+ end
91
+
92
+ def encode_chunk(chunk)
93
+ size_in_hex = chunk.size.to_s(16)
94
+ [ size_in_hex, CRLF, chunk, CRLF ].join
95
+ end
96
+
97
+ def terminating_chunk
98
+ [ 0, CRLF, CRLF ].join
99
+ end
100
+
101
+ def get_info_refs
102
+ service_name = get_service_type
103
+ return render_no_access unless has_access?(service_name)
104
+
105
+ refs = begin
106
+ if @config[:use_acl]
107
+ Blaggard::Advertisement.new(git.repo, @groups, service_name).advertise
108
+ else
109
+ git.execute(%W(#{service_name} --stateless-rpc --advertise-refs #{git.repo}))
110
+ end
111
+ end
112
+
113
+ @res = Rack::Response.new
114
+ @res.status = 200
115
+ @res["Content-Type"] = "application/x-git-%s-advertisement" % service_name
116
+ hdr_nocache
117
+ @res.write(pkt_write("# service=git-#{service_name}\n"))
118
+ @res.write(pkt_flush)
119
+ @res.write(refs)
120
+ @res.finish
121
+ end
122
+
123
+ def dumb_info_refs
124
+ git.update_server_info
125
+ send_file(@reqfile, "text/plain; charset=utf-8") do
126
+ hdr_nocache
127
+ end
128
+ end
129
+
130
+ def get_info_packs
131
+ # objects/info/packs
132
+ send_file(@reqfile, "text/plain; charset=utf-8") do
133
+ hdr_nocache
134
+ end
135
+ end
136
+
137
+ def get_loose_object
138
+ send_file(@reqfile, "application/x-git-loose-object") do
139
+ hdr_cache_forever
140
+ end
141
+ end
142
+
143
+ def get_pack_file
144
+ send_file(@reqfile, "application/x-git-packed-objects") do
145
+ hdr_cache_forever
146
+ end
147
+ end
148
+
149
+ def get_idx_file
150
+ send_file(@reqfile, "application/x-git-packed-objects-toc") do
151
+ hdr_cache_forever
152
+ end
153
+ end
154
+
155
+ def get_text_file
156
+ send_file(@reqfile, "text/plain") do
157
+ hdr_nocache
158
+ end
159
+ end
160
+
161
+ # ------------------------
162
+ # logic helping functions
163
+ # ------------------------
164
+
165
+ # some of this borrowed from the Rack::File implementation
166
+ def send_file(reqfile, content_type)
167
+ reqfile = File.join(git.repo, reqfile)
168
+ return render_not_found unless File.exists?(reqfile)
169
+
170
+ return render_not_found unless reqfile == File.realpath(reqfile)
171
+
172
+ # reqfile looks legit: no path traversal, no leading '|'
173
+
174
+ @res = Rack::Response.new
175
+ @res.status = 200
176
+ @res["Content-Type"] = content_type
177
+ @res["Last-Modified"] = File.mtime(reqfile).httpdate
178
+
179
+ yield
180
+
181
+ if size = File.size?(reqfile)
182
+ @res["Content-Length"] = size.to_s
183
+ @res.finish do
184
+ File.open(reqfile, "rb") do |file|
185
+ while part = file.read(8192)
186
+ @res.write part
187
+ end
188
+ end
189
+ end
190
+ else
191
+ body = [File.read(reqfile)]
192
+ size = Rack::Utils.bytesize(body.first)
193
+ @res["Content-Length"] = size
194
+ @res.write body
195
+ @res.finish
196
+ end
197
+ end
198
+
199
+ def get_git(path)
200
+ root = @config[:project_root] || Dir.pwd
201
+ path = File.join(root, path)
202
+ Blaggard::Git.new( path, @config[:git_path] )
203
+ end
204
+
205
+ def get_service_type
206
+ service_type = @req.params['service']
207
+ return false if !service_type
208
+ return false if service_type[0, 4] != 'git-'
209
+ service_type.gsub('git-', '')
210
+ end
211
+
212
+ def match_routing
213
+ SERVICES.each do |method, handler, match, rpc|
214
+ if m = Regexp.new(match).match(@req.path_info)
215
+ return ['not_allowed'] if method != @req.request_method
216
+ cmd = handler
217
+ path = m[1]
218
+ file = @req.path_info.sub(path + '/', '')
219
+ return [cmd, path, file, rpc]
220
+ end
221
+ end
222
+ return nil
223
+ end
224
+
225
+ def smart_http?(rpc = @rpc)
226
+ @req.content_type == "application/x-git-#{rpc}-request"
227
+ end
228
+
229
+ def has_access?(rpc, check_content_type = false)
230
+ if check_content_type
231
+ return false unless smart_http?(rpc)
232
+ end
233
+
234
+ return false unless ['upload-pack', 'receive-pack'].include?(rpc)
235
+
236
+ if rpc == 'receive-pack'
237
+ return @config[:receive_pack] if @config.include?(:receive_pack)
238
+ end
239
+
240
+ if rpc == 'upload-pack'
241
+ return @config[:upload_pack] if @config.include?(:upload_pack)
242
+ end
243
+
244
+ git.config_setting(rpc)
245
+ end
246
+
247
+
248
+
249
+ def read_body
250
+ if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
251
+ input = Zlib::GzipReader.new(@req.body).read
252
+ else
253
+ input = @req.body.read
254
+ end
255
+ end
256
+
257
+ # --------------------------------------
258
+ # HTTP error response handling functions
259
+ # --------------------------------------
260
+
261
+ PLAIN_TYPE = {"Content-Type" => "text/plain"}
262
+
263
+ def render_method_not_allowed
264
+ if @env['SERVER_PROTOCOL'] == "HTTP/1.1"
265
+ [405, PLAIN_TYPE, ["Method Not Allowed"]]
266
+ else
267
+ [400, PLAIN_TYPE, ["Bad Request"]]
268
+ end
269
+ end
270
+
271
+ def render_not_found
272
+ [404, PLAIN_TYPE, ["Not Found"]]
273
+ end
274
+
275
+ def render_no_access
276
+ [403, PLAIN_TYPE, ["Forbidden"]]
277
+ end
278
+
279
+
280
+ # ------------------------------
281
+ # packet-line handling functions
282
+ # ------------------------------
283
+
284
+ def pkt_flush
285
+ '0000'
286
+ end
287
+
288
+ def pkt_write(str)
289
+ (str.size + 4).to_s(base=16).rjust(4, '0') + str
290
+ end
291
+
292
+
293
+ # ------------------------
294
+ # header writing functions
295
+ # ------------------------
296
+
297
+ def hdr_nocache
298
+ @res["Expires"] = "Fri, 01 Jan 1980 00:00:00 GMT"
299
+ @res["Pragma"] = "no-cache"
300
+ @res["Cache-Control"] = "no-cache, max-age=0, must-revalidate"
301
+ end
302
+
303
+ def hdr_cache_forever
304
+ now = Time.now().to_i
305
+ @res["Date"] = now.to_s
306
+ @res["Expires"] = (now + 31536000).to_s;
307
+ @res["Cache-Control"] = "public, max-age=31536000";
308
+ end
309
+
310
+ end
311
+ end