Permalink
Browse files

init

  • Loading branch information...
0 parents commit 6a0cd4954881f391ca9b0b33ea5a1f8ebf014226 @technomancy committed Nov 8, 2012
Showing with 312 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +3 −0 Gemfile
  3. +1 −0 Procfile
  4. +8 −0 bin/receiver
  5. +2 −0 lib/sokoban.rb
  6. +297 −0 lib/sokoban/receiver.rb
1 .gitignore
@@ -0,0 +1 @@
+repos
3 Gemfile
@@ -0,0 +1,3 @@
+source :rubygems
+
+gem "puma"
1 Procfile
@@ -0,0 +1 @@
+receiver: bin/receiver
8 bin/receiver
@@ -0,0 +1,8 @@
+#!/usr/bin/ruby
+
+require "puma"
+require "sokoban/receiver"
+
+s = Puma::Server.new(Sokoban::Receiver.new)
+s.add_tcp_listener("localhost", (ENV["PORT"] || 5000))
+s.run.join
2 lib/sokoban.rb
@@ -0,0 +1,2 @@
+module Sokoban
+end
297 lib/sokoban/receiver.rb
@@ -0,0 +1,297 @@
+require 'zlib'
+require 'rack/request'
+require 'rack/response'
+require 'rack/utils'
+require 'time'
+
+module Sokoban
+ class Receiver
+
+ SERVICES =
+ [["POST", 'service_rpc', /(.*?)\/git-upload-pack$/, 'upload-pack'],
+ ["POST", 'service_rpc', /(.*?)\/git-receive-pack$/, 'receive-pack'],
+
+ # TODO: need a sensible route for this?
+ ["GET", 'get_text_file', /(.*?)\.git$/],
+ ["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$/],
+ ]
+
+ DEFAULTS = { :project_root = `pwd` }
+
+ def initialize(config = false)
+ @config = DEFAULTS.merge(config || {})
+ end
+
+ def call(env)
+ @env = env
+ @req = Rack::Request.new(env)
+
+ cmd, path, @reqfile, @rpc = route(@req.request_method, @req.path_info)
+
+ return render_method_not_allowed if cmd == :not_allowed
+ return render_not_found if !cmd
+
+ @dir = get_git_dir or return render_not_found
+
+ Dir.chdir(@dir) do
+ self.send(cmd.to_sym)
+ 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(16) # 16B 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)
+ repo_path = File.join(@config[:project_root], path)
+ if File.exists?(repo_path) # TODO: check is a valid git directory
+ repo_path
+ end
+ 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 route(req_method, req_path)
+ SERVICES.each do |method, handler, matcher, rpc|
+ if m = matcher.match(req_path)
+ if method == req_method
+ path = m[1]
+ file = req_path.sub(path + '/', '')
+ return [handler, path, file, rpc]
+ else
+ return [:not_allowed]
+ end
+ end
+ end
+ 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

0 comments on commit 6a0cd49

Please sign in to comment.