pax_global_header00006660000000000000000000000064125156631130014515gustar00rootroot0000000000000052 comment=949be9116a76b50a6f86baf507dec4c4d7fb34c6 grack-2.0.2/000077500000000000000000000000001251566311300126055ustar00rootroot00000000000000grack-2.0.2/.gitignore000066400000000000000000000000441251566311300145730ustar00rootroot00000000000000coverage/ examples/*.git pkg/ *.gem grack-2.0.2/.gitmodules000066400000000000000000000001401251566311300147550ustar00rootroot00000000000000[submodule "tests/example"] path = tests/example url = git://github.com/schacon/simplegit.git grack-2.0.2/.travis.yml000066400000000000000000000003261251566311300147170ustar00rootroot00000000000000language: ruby env: - TRAVIS=true branches: only: - 'master' rvm: - 1.9.3-p327 - 2.0.0 before_script: - "bundle install" - "git submodule init" - "git submodule update" script: "bundle exec rake" grack-2.0.2/CHANGELOG000066400000000000000000000007471251566311300140270ustar00rootroot000000000000002.0.2 - Revert MR that broke smart HTTP clients. 2.0.1 - Make sure child processes get reaped after popen, again. 2.0.0 - Use safer shell commands and avoid Dir.chdir - Restrict the environment for shell commands - Make Grack::Server thread-safe (zimbatm) - Make sure child processes get reaped after popen - Verify requested path is actually a Git directory (Ryan Canty) 1.1.0 - Modifies service_rpc to use chunked transfer (https://github.com/gitlabhq/grack/pull/1) grack-2.0.2/Gemfile000066400000000000000000000001551251566311300141010ustar00rootroot00000000000000source "https://rubygems.org" gemspec group :development do gem 'rake' gem 'pry' gem 'rack-test' end grack-2.0.2/Gemfile.lock000066400000000000000000000007771251566311300150420ustar00rootroot00000000000000PATH remote: . specs: gitlab-grack (2.0.2) rack (~> 1.5.1) GEM remote: https://rubygems.org/ specs: coderay (1.1.0) metaclass (0.0.1) method_source (0.8.2) mocha (0.14.0) metaclass (~> 0.0.1) pry (0.10.1) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) rack (1.5.2) rack-test (0.6.2) rack (>= 1.0) rake (10.1.0) slop (3.6.0) PLATFORMS ruby DEPENDENCIES gitlab-grack! mocha (~> 0.11) pry rack-test rake grack-2.0.2/README.md000066400000000000000000000073451251566311300140750ustar00rootroot00000000000000Grack - Ruby/Rack Git Smart-HTTP Server Handler =============================================== [![Build Status](https://travis-ci.org/gitlabhq/grack.png)](https://travis-ci.org/gitlabhq/grack) [![Code Climate](https://codeclimate.com/github/gitlabhq/grack.png)](https://codeclimate.com/github/gitlabhq/grack) 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 * Puma 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. grack-2.0.2/Rakefile000066400000000000000000000007731251566311300142610ustar00rootroot00000000000000#!/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('./bin/testserver') end end desc "Start everything." multitask :start => [ 'grack:start' ] grack-2.0.2/bin/000077500000000000000000000000001251566311300133555ustar00rootroot00000000000000grack-2.0.2/bin/console000077500000000000000000000001351251566311300147440ustar00rootroot00000000000000#! /bin/bash set -e cd $(dirname "$0")/.. exec /usr/bin/env bundle exec pry -Ilib -r grack grack-2.0.2/bin/testserver000077500000000000000000000006221251566311300155110ustar00rootroot00000000000000#! /usr/bin/env ruby libdir = File.absolute_path( File.join( File.dirname(__FILE__), '../lib' ) ) $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) require 'grack' require 'rack' root = File.absolute_path( File.join( File.dirname(__FILE__), '../examples' ) ) app = Grack::Server.new({ project_root: root, upload_pack: true, receive_pack:true }) Rack::Server.start app: app, Port: 3001 grack-2.0.2/examples/000077500000000000000000000000001251566311300144235ustar00rootroot00000000000000grack-2.0.2/examples/dispatch.fcgi000066400000000000000000000003771251566311300170630ustar00rootroot00000000000000#! /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))grack-2.0.2/grack.gemspec000066400000000000000000000014261251566311300152440ustar00rootroot00000000000000# -*- 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/gitlabhq/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.5.1") gem.add_development_dependency("mocha", "~> 0.11") end grack-2.0.2/install.txt000066400000000000000000000027061251566311300150210ustar00rootroot00000000000000Installation ======================== ** 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 grack-2.0.2/lib/000077500000000000000000000000001251566311300133535ustar00rootroot00000000000000grack-2.0.2/lib/grack.rb000066400000000000000000000000521251566311300147640ustar00rootroot00000000000000require "grack/bundle" module Grack end grack-2.0.2/lib/grack/000077500000000000000000000000001251566311300144425ustar00rootroot00000000000000grack-2.0.2/lib/grack/auth.rb000066400000000000000000000014511251566311300157310ustar00rootroot00000000000000require '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 grack-2.0.2/lib/grack/bundle.rb000066400000000000000000000004611251566311300162410ustar00rootroot00000000000000require '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 grack-2.0.2/lib/grack/git.rb000066400000000000000000000032201251566311300155470ustar00rootroot00000000000000module Grack class Git attr_reader :repo def initialize(git_path, repo_path) @git_path = git_path @repo = repo_path end def update_server_info execute(%W(update-server-info)) end def command(cmd) [@git_path || 'git'] + cmd end def capture(cmd) # _Not_ the same as `IO.popen(...).read` # By using a block we tell IO.popen to close (wait for) the child process # after we are done reading its output. IO.popen(popen_env, cmd, popen_options) { |p| p.read } end def execute(cmd) cmd = command(cmd) if block_given? IO.popen(popen_env, cmd, File::RDWR, popen_options) do |pipe| yield(pipe) end else capture(cmd).chomp end end def popen_options { chdir: repo, unsetenv_others: true } end def popen_env { 'PATH' => ENV['PATH'], 'GL_ID' => ENV['GL_ID'] } end def config_setting(service_name) service_name = service_name.gsub('-', '') setting = config("http.#{service_name}") if service_name == 'uploadpack' setting != 'false' else setting == 'true' end end def config(config_name) execute(%W(config #{config_name})) end def valid_repo? return false unless File.exists?(repo) && File.realpath(repo) == repo match = execute(%W(rev-parse --git-dir)).match(/\.$|\.git$/) if match.to_s == '.git' # Since the parent could be a git repo, we want to make sure the actual repo contains a git dir. return false unless Dir.entries(repo).include?('.git') end match end end end grack-2.0.2/lib/grack/server.rb000066400000000000000000000173071251566311300163050ustar00rootroot00000000000000require 'zlib' require 'rack/request' require 'rack/response' require 'rack/utils' require 'time' require 'grack/git' module Grack class Server attr_reader :git 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) dup._call(env) 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 unless cmd @git = get_git(path) return render_not_found unless git.valid_repo? self.method(cmd).call end # --------------------------------- # actual command handling functions # --------------------------------- # Uses chunked (streaming) transfer, otherwise response # blocks to calculate Content-Length header # http://en.wikipedia.org/wiki/Chunked_transfer_encoding CRLF = "\r\n" def service_rpc return render_no_access unless has_access?(@rpc, true) input = read_body @res = Rack::Response.new @res.status = 200 @res["Content-Type"] = "application/x-git-%s-result" % @rpc @res["Transfer-Encoding"] = "chunked" @res["Cache-Control"] = "no-cache" @res.finish do git.execute([@rpc, '--stateless-rpc', git.repo]) do |pipe| pipe.write(input) pipe.close_write while block = pipe.read(8192) # 8KB at a time @res.write encode_chunk(block) # stream it to the client end @res.write terminating_chunk end end end def encode_chunk(chunk) size_in_hex = chunk.size.to_s(16) [size_in_hex, CRLF, chunk, CRLF].join end def terminating_chunk [0, CRLF, CRLF].join end def get_info_refs service_name = get_service_type return dumb_info_refs unless has_access?(service_name) refs = git.execute([service_name, '--stateless-rpc', '--advertise-refs', git.repo]) @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 end def dumb_info_refs git.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 # ------------------------ # some of this borrowed from the Rack::File implementation def send_file(reqfile, content_type) reqfile = File.join(git.repo, reqfile) return render_not_found unless File.exists?(reqfile) return render_not_found unless reqfile == File.realpath(reqfile) # reqfile looks legit: no path traversal, no leading '|' @res = Rack::Response.new @res.status = 200 @res["Content-Type"] = content_type @res["Last-Modified"] = File.mtime(reqfile).httpdate yield if size = File.size?(reqfile) @res["Content-Length"] = size.to_s @res.finish do File.open(reqfile, "rb") do |file| while part = file.read(8192) @res.write part end end end else body = [File.read(reqfile)] size = Rack::Utils.bytesize(body.first) @res["Content-Length"] = size @res.write body @res.finish end end def get_git(path) root = @config[:project_root] || Dir.pwd path = File.join(root, path) Grack::Git.new(@config[:git_path], path) end def get_service_type service_type = @req.params['service'] return false unless 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| next unless m = Regexp.new(match).match(@req.path_info) return ['not_allowed'] unless method == @req.request_method cmd = handler path = m[1] file = @req.path_info.sub(path + '/', '') return [cmd, path, file, rpc] end nil end def has_access?(rpc, check_content_type = false) if check_content_type conten_type = "application/x-git-%s-request" % rpc return false unless @req.content_type == conten_type end return false unless ['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 git.config_setting(rpc) end def read_body if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/ Zlib::GzipReader.new(@req.body).read else @req.body.read end 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(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 grack-2.0.2/lib/grack/version.rb000066400000000000000000000000451251566311300164530ustar00rootroot00000000000000module Grack VERSION = "2.0.2" end grack-2.0.2/tests/000077500000000000000000000000001251566311300137475ustar00rootroot00000000000000grack-2.0.2/tests/example/000077500000000000000000000000001251566311300154025ustar00rootroot00000000000000grack-2.0.2/tests/main_test.rb000066400000000000000000000174661251566311300162750ustar00rootroot00000000000000require 'rack' require 'rack/test' require 'test/unit' require 'mocha' require 'digest/sha1' require_relative '../lib/grack/server.rb' require_relative '../lib/grack/git.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 Grack::Git.any_instance.stubs(:valid_repo?).returns(false) post "/example-wrong/git-upload-pack" assert_equal 404, r.status end def test_upload_pack_rpc Grack::Git.any_instance.stubs(:valid_repo?).returns(true) 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 Grack::Git.any_instance.stubs(:valid_repo?).returns(true) 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}) app1.instance_variable_set(:@git, Grack::Git.new('git', example )) session = Rack::Test::Session.new(app1) git = Grack::Git git.any_instance.stubs(:config).with('http.receivepack').returns('') session.get "/example/info/refs?service=git-receive-pack" assert_equal 404, session.last_response.status git.any_instance.stubs(:config).with('http.receivepack').returns('true') session.get "/example/info/refs?service=git-receive-pack" assert_equal 200, session.last_response.status git.any_instance.stubs(: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}) # app1.instance_variable_set(:@git, Grack::Git.new('git', example )) session = Rack::Test::Session.new(app1) git = Grack::Git git.any_instance.stubs(:config).with('http.uploadpack').returns('') session.get "/example/info/refs?service=git-upload-pack" assert_equal 200, session.last_response.status git.any_instance.stubs(:config).with('http.uploadpack').returns('true') session.get "/example/info/refs?service=git-upload-pack" assert_equal 200, session.last_response.status git.any_instance.stubs(:config).with('http.uploadpack').returns('false') session.get "/example/info/refs?service=git-upload-pack" assert_equal 404, session.last_response.status end def test_send_file app1 = app app1.instance_variable_set(:@git, Grack::Git.new('git', Dir.pwd)) # Reject path traversal assert_equal 404, app1.send_file('tests/../tests', 'text/plain').first # Reject paths starting with '|', avoid File.read('|touch /tmp/pawned; ls /tmp') assert_equal 404, app1.send_file('|tests', 'text/plain').first end def test_get_git # Guard against non-existent directories git1 = Grack::Git.new('git', 'foobar') assert_equal false, git1.valid_repo? # Guard against path traversal git2 = Grack::Git.new('git', '/../tests') assert_equal false, git2.valid_repo? 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 = nil) '' end def eof? @counter += 1 @counter > 1 ? true : false end def close_write true end end