gitlab-grack-1.0.0/0000755000175000017500000000000012133010713013352 5ustar ondrejondrejgitlab-grack-1.0.0/examples/0000755000175000017500000000000012133010713015170 5ustar ondrejondrejgitlab-grack-1.0.0/examples/dispatch.fcgi0000644000175000017500000000037712133010713017630 0ustar ondrejondrej#! /usr/bin/env ruby $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/lib') require 'lib/git_http' config = { :project_root => "/opt", :upload_pack => true, :receive_pack => false, } Rack::Handler::FastCGI.run(GitHttp::App.new(config))gitlab-grack-1.0.0/grack.gemspec0000644000175000017500000000142512133010713016010 0ustar ondrejondrej# -*- encoding: utf-8 -*- require File.expand_path('../lib/grack/version', __FILE__) Gem::Specification.new do |gem| gem.authors = ["Scott Chacon"] gem.email = ["schacon@gmail.com"] gem.description = %q{Ruby/Rack Git Smart-HTTP Server Handler} gem.summary = %q{Ruby/Rack Git Smart-HTTP Server Handler} gem.homepage = "https://github.com/schacon/grack" gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } gem.files = `git ls-files`.split("\n") gem.test_files = `git ls-files -- tests/*`.split("\n") gem.name = "gitlab-grack" gem.require_paths = ["lib"] gem.version = Grack::VERSION gem.add_dependency("rack", "~> 1.4.1") gem.add_development_dependency("mocha", "~> 0.11") end gitlab-grack-1.0.0/tests/0000755000175000017500000000000012133010713014514 5ustar ondrejondrejgitlab-grack-1.0.0/tests/main_test.rb0000644000175000017500000001532212133010713017027 0ustar ondrejondrejrequire 'rack' require 'rack/test' require 'test/unit' require 'mocha' require 'digest/sha1' require_relative '../lib/grack/server.rb' require 'pp' class GitHttpTest < Test::Unit::TestCase include Rack::Test::Methods def example File.expand_path(File.dirname(__FILE__)) end def app config = { :project_root => example, :upload_pack => true, :receive_pack => true, } Grack::Server.new(config) end def test_upload_pack_advertisement get "/example/info/refs?service=git-upload-pack" assert_equal 200, r.status assert_equal "application/x-git-upload-pack-advertisement", r.headers["Content-Type"] assert_equal "001e# service=git-upload-pack", r.body.split("\n").first assert_match 'multi_ack_detailed', r.body end def test_no_access_wrong_content_type_up post "/example/git-upload-pack" assert_equal 403, r.status end def test_no_access_wrong_content_type_rp post "/example/git-receive-pack" assert_equal 403, r.status end def test_no_access_wrong_method_rcp get "/example/git-upload-pack" assert_equal 400, r.status end def test_no_access_wrong_command_rcp post "/example/git-upload-packfile" assert_equal 404, r.status end def test_no_access_wrong_path_rcp post "/example-wrong/git-upload-pack" assert_equal 404, r.status end def test_upload_pack_rpc IO.stubs(:popen).returns(MockProcess.new) post "/example/git-upload-pack", {}, {"CONTENT_TYPE" => "application/x-git-upload-pack-request"} assert_equal 200, r.status assert_equal "application/x-git-upload-pack-result", r.headers["Content-Type"] end def test_receive_pack_advertisement get "/example/info/refs?service=git-receive-pack" assert_equal 200, r.status assert_equal "application/x-git-receive-pack-advertisement", r.headers["Content-Type"] assert_equal "001f# service=git-receive-pack", r.body.split("\n").first assert_match 'report-status', r.body assert_match 'delete-refs', r.body assert_match 'ofs-delta', r.body end def test_recieve_pack_rpc IO.stubs(:popen).yields(MockProcess.new) post "/example/git-receive-pack", {}, {"CONTENT_TYPE" => "application/x-git-receive-pack-request"} assert_equal 200, r.status assert_equal "application/x-git-receive-pack-result", r.headers["Content-Type"] end def test_info_refs_dumb get "/example/.git/info/refs" assert_equal 200, r.status end def test_info_packs get "/example/.git/objects/info/packs" assert_equal 200, r.status assert_match /P pack-(.*?).pack/, r.body end def test_loose_objects path, content = write_test_objects get "/example/.git/objects/#{path}" assert_equal 200, r.status assert_equal content, r.body remove_test_objects end def test_pack_file path, content = write_test_objects get "/example/.git/objects/pack/pack-#{content}.pack" assert_equal 200, r.status assert_equal content, r.body remove_test_objects end def test_index_file path, content = write_test_objects get "/example/.git/objects/pack/pack-#{content}.idx" assert_equal 200, r.status assert_equal content, r.body remove_test_objects end def test_text_file get "/example/.git/HEAD" assert_equal 200, r.status assert_equal 41, r.body.size # submodules have detached head end def test_no_size_avail File.stubs('size?').returns(false) get "/example/.git/HEAD" assert_equal 200, r.status assert_equal 46, r.body.size # submodules have detached head end def test_config_upload_pack_off a1 = app a1.set_config_setting(:upload_pack, false) session = Rack::Test::Session.new(a1) session.get "/example/info/refs?service=git-upload-pack" assert_equal 404, session.last_response.status end def test_config_receive_pack_off a1 = app a1.set_config_setting(:receive_pack, false) session = Rack::Test::Session.new(a1) session.get "/example/info/refs?service=git-receive-pack" assert_equal 404, session.last_response.status end def test_config_bad_service get "/example/info/refs?service=git-receive-packfile" assert_equal 404, r.status end def test_git_config_receive_pack app1 = Grack::Server.new({:project_root => example}) session = Rack::Test::Session.new(app1) app1.stubs(:get_git_config).with('http.receivepack').returns('') session.get "/example/info/refs?service=git-receive-pack" assert_equal 404, session.last_response.status app1.stubs(:get_git_config).with('http.receivepack').returns('true') session.get "/example/info/refs?service=git-receive-pack" assert_equal 200, session.last_response.status app1.stubs(:get_git_config).with('http.receivepack').returns('false') session.get "/example/info/refs?service=git-receive-pack" assert_equal 404, session.last_response.status end def test_git_config_upload_pack app1 = Grack::Server.new({:project_root => example}) session = Rack::Test::Session.new(app1) app1.stubs(:get_git_config).with('http.uploadpack').returns('') session.get "/example/info/refs?service=git-upload-pack" assert_equal 200, session.last_response.status app1.stubs(:get_git_config).with('http.uploadpack').returns('true') session.get "/example/info/refs?service=git-upload-pack" assert_equal 200, session.last_response.status app1.stubs(:get_git_config).with('http.uploadpack').returns('false') session.get "/example/info/refs?service=git-upload-pack" assert_equal 404, session.last_response.status end private def r last_response end def write_test_objects content = Digest::SHA1.hexdigest('gitrocks') base = File.join(File.expand_path(File.dirname(__FILE__)), 'example', '.git', 'objects') obj = File.join(base, '20') Dir.mkdir(obj) rescue nil file = File.join(obj, content[0, 38]) File.open(file, 'w') { |f| f.write(content) } pack = File.join(base, 'pack', "pack-#{content}.pack") File.open(pack, 'w') { |f| f.write(content) } idx = File.join(base, 'pack', "pack-#{content}.idx") File.open(idx, 'w') { |f| f.write(content) } ["20/#{content[0,38]}", content] end def remove_test_objects content = Digest::SHA1.hexdigest('gitrocks') base = File.join(File.expand_path(File.dirname(__FILE__)), 'example', '.git', 'objects') obj = File.join(base, '20') file = File.join(obj, content[0, 38]) pack = File.join(base, 'pack', "pack-#{content}.pack") idx = File.join(base, 'pack', "pack-#{content}.idx") File.unlink(file) File.unlink(pack) File.unlink(idx) end end class MockProcess def initialize @counter = 0 end def write(data) end def read(data) end def eof? @counter += 1 @counter > 1 ? true : false end end gitlab-grack-1.0.0/Rakefile0000644000175000017500000000100212133010713015010 0ustar ondrejondrej#!/usr/bin/env rake require "bundler/gem_tasks" task :default => :test desc "Run the tests." task :test do Dir.glob("tests/*_test.rb").each do |f| system "ruby #{f}" end end desc "Run test coverage." task :rcov do system "rcov tests/*_test.rb -i lib/git_http.rb -x rack -x Library -x tests" system "open coverage/index.html" end namespace :grack do desc "Start Grack" task :start do system "rackup config.ru -p 8080" end end desc "Start everything." multitask :start => [ 'grack:start' ] gitlab-grack-1.0.0/.gitignore0000644000175000017500000000001712133010713015340 0ustar ondrejondrejcoverage/ pkg/ gitlab-grack-1.0.0/.gitmodules0000644000175000017500000000014012133010713015522 0ustar ondrejondrej[submodule "tests/example"] path = tests/example url = git://github.com/schacon/simplegit.git gitlab-grack-1.0.0/install.txt0000644000175000017500000000270612133010713015566 0ustar ondrejondrejInstallation ======================== ** This documentation is not finished yet. I haven't tested all of these and it's obviously incomplete - these are currently just notes. FastCGI --------------------------------------- Here is an example config from lighttpd server: ---- # main fastcgi entry $HTTP["url"] =~ "^/myapp/.+$" { fastcgi.server = ( "/myapp" => ( "localhost" => ( "bin-path" => "/var/www/localhost/cgi-bin/dispatch.fcgi", "docroot" => "/var/www/localhost/htdocs/myapp", "host" => "127.0.0.1", "port" => 1026, "check-local" => "disable" ) ) ) } # HTTP[url] ---- You can use the examples/dispatch.fcgi file as your dispatcher. (Example Apache setup?) Installing in a Java application server --------------------------------------- # install Warbler $ sudo gem install warbler $ cd gitsmart $ (edit config.ru) $ warble $ cp gitsmart.war /path/to/java/autodeploy/dir Unicorn --------------------------------------- With Unicorn (http://unicorn.bogomips.org/) you can just run 'unicorn' in the directory with the config.ru file. Thin --------------------------------------- thin.yml --- pid: /home/deploy/myapp/server/thin.pid log: /home/deploy/myapp/logs/thin.log timeout: 30 port: 7654 max_conns: 1024 chdir: /home/deploy/myapp/site_files rackup: /home/deploy/myapp/server/config.ru max_persistent_conns: 512 environment: production address: 127.0.0.1 servers: 1 daemonize: true gitlab-grack-1.0.0/lib/0000755000175000017500000000000012133010713014120 5ustar ondrejondrejgitlab-grack-1.0.0/lib/grack/0000755000175000017500000000000012133010713015207 5ustar ondrejondrejgitlab-grack-1.0.0/lib/grack/bundle.rb0000644000175000017500000000046112133010713017006 0ustar ondrejondrejrequire 'rack/builder' require 'grack/auth' require 'grack/server' module Grack module Bundle extend self def new(config) Rack::Builder.new do use Grack::Auth do |username, password| false end run Grack::Server.new(config) end end end end gitlab-grack-1.0.0/lib/grack/auth.rb0000644000175000017500000000145112133010713016476 0ustar ondrejondrejrequire 'rack/auth/basic' require 'rack/auth/abstract/handler' require 'rack/auth/abstract/request' module Grack class Auth < Rack::Auth::Basic def call(env) @env = env @request = Rack::Request.new(env) @auth = Request.new(env) if not @auth.provided? unauthorized elsif not @auth.basic? bad_request else result = if (access = valid? and access == true) @env['REMOTE_USER'] = @auth.username @app.call(env) else if access == '404' render_not_found elsif access == '403' render_no_access else unauthorized end end result end end# method call def valid? false end end# class Auth end# module Grack gitlab-grack-1.0.0/lib/grack/server.rb0000644000175000017500000001734512133010713017054 0ustar ondrejondrejrequire 'zlib' require 'rack/request' require 'rack/response' require 'rack/utils' require 'time' module Grack class Server SERVICES = [ ["POST", 'service_rpc', "(.*?)/git-upload-pack$", 'upload-pack'], ["POST", 'service_rpc', "(.*?)/git-receive-pack$", 'receive-pack'], ["GET", 'get_info_refs', "(.*?)/info/refs$"], ["GET", 'get_text_file', "(.*?)/HEAD$"], ["GET", 'get_text_file', "(.*?)/objects/info/alternates$"], ["GET", 'get_text_file', "(.*?)/objects/info/http-alternates$"], ["GET", 'get_info_packs', "(.*?)/objects/info/packs$"], ["GET", 'get_text_file', "(.*?)/objects/info/[^/]*$"], ["GET", 'get_loose_object', "(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"], ["GET", 'get_pack_file', "(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"], ["GET", 'get_idx_file', "(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"], ] def initialize(config = false) set_config(config) end def set_config(config) @config = config || {} end def set_config_setting(key, value) @config[key] = value end def call(env) @env = env @req = Rack::Request.new(env) cmd, path, @reqfile, @rpc = match_routing return render_method_not_allowed if cmd == 'not_allowed' return render_not_found if !cmd @dir = get_git_dir(path) return render_not_found if !@dir Dir.chdir(@dir) do self.method(cmd).call() end end # --------------------------------- # actual command handling functions # --------------------------------- def service_rpc return render_no_access if !has_access(@rpc, true) input = read_body @res = Rack::Response.new @res.status = 200 @res["Content-Type"] = "application/x-git-%s-result" % @rpc @res.finish do command = git_command("#{@rpc} --stateless-rpc #{@dir}") IO.popen(command, File::RDWR) do |pipe| pipe.write(input) while !pipe.eof? block = pipe.read(8192) # 8M at a time @res.write block # steam it to the client end end end end def get_info_refs service_name = get_service_type if has_access(service_name) cmd = git_command("#{service_name} --stateless-rpc --advertise-refs .") refs = `#{cmd}` @res = Rack::Response.new @res.status = 200 @res["Content-Type"] = "application/x-git-%s-advertisement" % service_name hdr_nocache @res.write(pkt_write("# service=git-#{service_name}\n")) @res.write(pkt_flush) @res.write(refs) @res.finish else dumb_info_refs end end def dumb_info_refs update_server_info send_file(@reqfile, "text/plain; charset=utf-8") do hdr_nocache end end def get_info_packs # objects/info/packs send_file(@reqfile, "text/plain; charset=utf-8") do hdr_nocache end end def get_loose_object send_file(@reqfile, "application/x-git-loose-object") do hdr_cache_forever end end def get_pack_file send_file(@reqfile, "application/x-git-packed-objects") do hdr_cache_forever end end def get_idx_file send_file(@reqfile, "application/x-git-packed-objects-toc") do hdr_cache_forever end end def get_text_file send_file(@reqfile, "text/plain") do hdr_nocache end end # ------------------------ # logic helping functions # ------------------------ F = ::File # some of this borrowed from the Rack::File implementation def send_file(reqfile, content_type) reqfile = File.join(@dir, reqfile) return render_not_found if !F.exists?(reqfile) @res = Rack::Response.new @res.status = 200 @res["Content-Type"] = content_type @res["Last-Modified"] = F.mtime(reqfile).httpdate yield if size = F.size?(reqfile) @res["Content-Length"] = size.to_s @res.finish do F.open(reqfile, "rb") do |file| while part = file.read(8192) @res.write part end end end else body = [F.read(reqfile)] size = Rack::Utils.bytesize(body.first) @res["Content-Length"] = size @res.write body @res.finish end end def get_git_dir(path) root = @config[:project_root] || `pwd` path = File.join(root, path) if File.exists?(path) # TODO: check is a valid git directory return path end false end def get_service_type service_type = @req.params['service'] return false if !service_type return false if service_type[0, 4] != 'git-' service_type.gsub('git-', '') end def match_routing cmd = nil path = nil SERVICES.each do |method, handler, match, rpc| if m = Regexp.new(match).match(@req.path_info) return ['not_allowed'] if method != @req.request_method cmd = handler path = m[1] file = @req.path_info.sub(path + '/', '') return [cmd, path, file, rpc] end end return nil end def has_access(rpc, check_content_type = false) if check_content_type return false if @req.content_type != "application/x-git-%s-request" % rpc end return false if !['upload-pack', 'receive-pack'].include? rpc if rpc == 'receive-pack' return @config[:receive_pack] if @config.include? :receive_pack end if rpc == 'upload-pack' return @config[:upload_pack] if @config.include? :upload_pack end return get_config_setting(rpc) end def get_config_setting(service_name) service_name = service_name.gsub('-', '') setting = get_git_config("http.#{service_name}") if service_name == 'uploadpack' return setting != 'false' else return setting == 'true' end end def get_git_config(config_name) cmd = git_command("config #{config_name}") `#{cmd}`.chomp end def read_body if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/ input = Zlib::GzipReader.new(@req.body).read else input = @req.body.read end end def update_server_info cmd = git_command("update-server-info") `#{cmd}` end def git_command(command) git_bin = @config[:git_path] || 'git' command = "#{git_bin} #{command}" command end # -------------------------------------- # HTTP error response handling functions # -------------------------------------- PLAIN_TYPE = {"Content-Type" => "text/plain"} def render_method_not_allowed if @env['SERVER_PROTOCOL'] == "HTTP/1.1" [405, PLAIN_TYPE, ["Method Not Allowed"]] else [400, PLAIN_TYPE, ["Bad Request"]] end end def render_not_found [404, PLAIN_TYPE, ["Not Found"]] end def render_no_access [403, PLAIN_TYPE, ["Forbidden"]] end # ------------------------------ # packet-line handling functions # ------------------------------ def pkt_flush '0000' end def pkt_write(str) (str.size + 4).to_s(base=16).rjust(4, '0') + str end # ------------------------ # header writing functions # ------------------------ def hdr_nocache @res["Expires"] = "Fri, 01 Jan 1980 00:00:00 GMT" @res["Pragma"] = "no-cache" @res["Cache-Control"] = "no-cache, max-age=0, must-revalidate" end def hdr_cache_forever now = Time.now().to_i @res["Date"] = now.to_s @res["Expires"] = (now + 31536000).to_s; @res["Cache-Control"] = "public, max-age=31536000"; end end end gitlab-grack-1.0.0/lib/grack/version.rb0000644000175000017500000000004512133010713017220 0ustar ondrejondrejmodule Grack VERSION = "1.0.0" end gitlab-grack-1.0.0/lib/grack.rb0000644000175000017500000000005212133010713015531 0ustar ondrejondrejrequire "grack/bundle" module Grack end gitlab-grack-1.0.0/README.md0000644000175000017500000000700712133010713014635 0ustar ondrejondrejGrack - Ruby/Rack Git Smart-HTTP Server Handler =============================================== This project aims to replace the builtin git-http-backend CGI handler distributed with C Git with a Rack application. This reason for doing this is to allow far more webservers to be able to handle Git smart http requests. The default git-http-backend only runs as a CGI script, and specifically is only targeted for Apache 2.x usage (it requires PATH_INFO to be set and specifically formatted). So, instead of trying to get it to work with other CGI capable webservers (Lighttpd, etc), we can get it running on nearly every major and minor webserver out there by making it Rack capable. Rack applications can run with the following handlers: * CGI * FCGI * Mongrel (and EventedMongrel and SwiftipliedMongrel) * WEBrick * SCGI * LiteSpeed * Thin These web servers include Rack handlers in their distributions: * Ebb * Fuzed * Phusion Passenger (which is mod_rack for Apache and for nginx) * Unicorn With [Warbler](http://caldersphere.rubyforge.org/warbler/classes/Warbler.html), and JRuby, we can also generate a WAR file that can be deployed in any Java web application server (Tomcat, Glassfish, Websphere, JBoss, etc). Since the git-http-backend is really just a simple wrapper for the upload-pack and receive-pack processes with the '--stateless-rpc' option, it does not actually re-implement very much. Dependencies ======================== * Ruby - http://www.ruby-lang.org * Rack - http://rack.rubyforge.org * A Rack-compatible web server * Git >= 1.7 (currently the 'pu' branch) * Mocha (only for running the tests) Quick Start ======================== $ gem install rack $ (edit config.ru to set git project path) $ rackup --host 127.0.0.1 -p 8080 config.ru $ git clone http://127.0.0.1:8080/schacon/grit.git Contributing ======================== If you would like to contribute to the Grack project, I prefer to get pull-requests via GitHub. You should include tests for whatever functionality you add. Just fork this project, push your changes to your fork and click the 'pull request' button. To run the tests, you first need to install the 'mocha' mocking library and initialize the submodule. $ sudo gem install mocha $ git submodule init $ git submodule update Then you should be able to run the tests with a 'rake' command. You can also run coverage tests with 'rake rcov' if you have rcov installed. License ======================== (The MIT License) Copyright (c) 2009 Scott Chacon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gitlab-grack-1.0.0/metadata.yml0000644000175000017500000000371212133010713015660 0ustar ondrejondrej--- !ruby/object:Gem::Specification name: gitlab-grack version: !ruby/object:Gem::Version version: 1.0.0 prerelease: platform: ruby authors: - Scott Chacon autorequire: bindir: bin cert_chain: [] date: 2013-03-03 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rack requirement: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: 1.4.1 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: 1.4.1 - !ruby/object:Gem::Dependency name: mocha requirement: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '0.11' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: '0.11' description: Ruby/Rack Git Smart-HTTP Server Handler email: - schacon@gmail.com executables: [] extensions: [] extra_rdoc_files: [] files: - .gitignore - .gitmodules - README.md - Rakefile - examples/dispatch.fcgi - grack.gemspec - install.txt - lib/grack.rb - lib/grack/auth.rb - lib/grack/bundle.rb - lib/grack/server.rb - lib/grack/version.rb - tests/main_test.rb homepage: https://github.com/schacon/grack licenses: [] post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 1.8.24 signing_key: specification_version: 3 summary: Ruby/Rack Git Smart-HTTP Server Handler test_files: - tests/main_test.rb