=begin

fcgi.rb 0.9.2 - fcgi.so compatible pure-ruby FastCGI library

fastcgi.rb Copyright (C) 2001 Eli Green
fcgi.rb    Copyright (C) 2002-2003 MoonWolf <moonwolf@moonwolf.com>
fcgi.rb    Copyright (C) 2004 Minero Aoki
fcgi.rb    Copyright (C) 2011 saks and Julik Tarkhanov
fcgi.rb    Copyright (C) 2012-2013 mva

=end
trap('SIGTERM') { exit }
trap('SIGPIPE','IGNORE')

begin
  raise LoadError if ENV["USE_PURE_RUBY_FCGI"]
  require "fcgi.so"
rescue LoadError # Load the pure ruby version instead
  # At this point we do have STDERR so put it to some good use
  $stderr.puts "Your FCGI gem does not contain the FCGI shared library, running pure ruby instead"

  require 'socket'
  require 'stringio'

  class FCGI

    def self.is_cgi?
      begin
        s = Socket.for_fd($stdin.fileno)
        s.getpeername
        false
      rescue Errno::ENOTCONN
        false
      rescue Errno::ENOTSOCK, Errno::EINVAL
        true
      end
    end

    def self.each(&block)
      f = default_connection()
      Server.new(f).each_request(&block)
    ensure
      f.close if f
    end

    def self.each_request(&block)
      f = default_connection()
      Server.new(f).each_request(&block)
    ensure
      f.close if f
    end

    def self.default_connection
      ::Socket.for_fd($stdin.fileno)
    end



    ProtocolVersion = 1

    # Record types
    FCGI_BEGIN_REQUEST = 1
    FCGI_ABORT_REQUEST = 2
    FCGI_END_REQUEST = 3
    FCGI_PARAMS = 4
    FCGI_STDIN = 5
    FCGI_STDOUT = 6
    FCGI_STDERR = 7
    FCGI_DATA = 8
    FCGI_GET_VALUES = 9
    FCGI_GET_VALUES_RESULT = 10
    FCGI_UNKNOWN_TYPE = 11
    FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE

    FCGI_NULL_REQUEST_ID = 0

    # FCGI_BEGIN_REQUSET.role
    FCGI_RESPONDER = 1
    FCGI_AUTHORIZER = 2
    FCGI_FILTER = 3

    # FCGI_BEGIN_REQUEST.flags
    FCGI_KEEP_CONN = 1

    # FCGI_END_REQUEST.protocolStatus
    FCGI_REQUEST_COMPLETE = 0
    FCGI_CANT_MPX_CONN = 1
    FCGI_OVERLOADED = 2
    FCGI_UNKNOWN_ROLE = 3


    class Server

      def initialize(server)
        @server = server
        @buffers = {}
        @default_parameters = {
          "FCGI_MAX_CONNS" => 1,
          "FCGI_MAX_REQS"  => 1,
          "FCGI_MPX_CONNS" => true
        }
      end

      def each_request(&block)
        graceful = false
        trap("SIGUSR1") { graceful = true }
        while true
          begin
            session(&block)
          rescue Errno::EPIPE, EOFError
            # HTTP request is canceled by the remote user
          end
          exit 0 if graceful
        end
      end

      def session
        sock, _ = *@server.accept
        return unless sock
        fsock = FastCGISocket.new(sock)
        req = next_request(fsock)
        yield req
        respond_to req, fsock, FCGI_REQUEST_COMPLETE
      ensure
        sock.close if sock and not sock.closed?
      end

      private

      def next_request(sock)
        while rec = sock.read_record
          if rec.management_record?
            case rec.type
            when FCGI_GET_VALUES
              sock.send_record handle_GET_VALUES(rec)
            else
              sock.send_record UnknownTypeRecord.new(rec.request_id, rec.type)
            end
          else
            case rec.type
            when FCGI_BEGIN_REQUEST
              @buffers[rec.request_id] = RecordBuffer.new(rec)
            when FCGI_ABORT_REQUEST
              raise "got ABORT_REQUEST"   # FIXME
            else
              buf = @buffers[rec.request_id]   or next # inactive request
              buf.push rec
              if buf.ready?
                @buffers.delete rec.request_id
                return buf.new_request
              end
            end
          end
        end
        raise "must not happen: FCGI socket unexpected EOF"
      end

      def handle_GET_VALUES(rec)
        h = {}
        rec.values.each_key do |name|
          h[name] = @default_parameters[name]
        end
        ValuesRecord.new(FCGI_GET_VALUES_RESULT, rec.request_id, h)
      end

      def respond_to(req, sock, status)
        split_data(FCGI_STDOUT, req.id, req.out) do |rec|
          sock.send_record rec
        end
        split_data(FCGI_STDERR, req.id, req.err) do |rec|
          sock.send_record rec
        end if req.err.length > 0
        sock.send_record EndRequestRecord.new(req.id, 0, status)
      end

      DATA_UNIT = 16384

      def split_data(type, id, f)
        unless f.length == 0
          f.rewind
          while s = f.read(DATA_UNIT)
            yield GenericDataRecord.new(type, id, s)
          end
        end
        yield GenericDataRecord.new(type, id, '')
      end

    end


    class FastCGISocket
      def initialize(sock)
        @socket = sock
      end

      def read_record
        header = @socket.read(Record::HEADER_LENGTH) or return nil
        return nil unless header.size == Record::HEADER_LENGTH
        _, type, reqid, clen, padlen, _ = *Record.parse_header(header)
        Record.class_for(type).parse(reqid, read_record_body(clen, padlen))
      end

      def read_record_body(clen, padlen)
        buf = ''
        while buf.length < clen
          buf << @socket.read([1024, clen - buf.length].min)
        end
        @socket.read padlen if padlen
        buf
      end
      private :read_record_body

      def send_record(rec)
        @socket.write rec.serialize
        @socket.flush
      end
    end


    class RecordBuffer
      def initialize(rec)
        @begin_request = rec
        @envs = []
        @stdins = []
        @datas = []
      end

      def push(rec)
        case rec
        when ParamsRecord
          @envs.push rec
        when StdinDataRecord
          @stdins.push rec
        when DataRecord
          @datas.push rec
        else
          raise "got unknown record: #{rec.class}"
        end
      end

      def ready?
        case @begin_request.role
        when FCGI_RESPONDER
          completed?(@envs) and
          completed?(@stdins)
        when FCGI_AUTHORIZER
          completed?(@envs)
        when FCGI_FILTER
          completed?(@envs) and
          completed?(@stdins) and
          completed?(@datas)
        else
          raise "unknown role: #{@begin_request.role}"
        end
      end

      def completed?(records)
        records.last and records.last.empty?
      end
      private :completed?

      def new_request
        Request.new(@begin_request.request_id, env(), stdin(), nil, nil, data())
      end

      def env
        h = {}
        @envs.each {|rec| h.update rec.values }
        h
      end

      def stdin
        StringIO.new(@stdins.inject('') {|buf, rec| buf << rec.flagment })
      end

      def data
        StringIO.new(@datas.inject('') {|buf, rec| buf << rec.flagment })
      end
    end


    class Request
      def initialize(id, env, stdin, stdout = nil, stderr = nil, data = nil)
        @id = id
        @env = env
        @in = stdin
        @out = stdout || StringIO.new
        @err = stderr || StringIO.new
        @data = data || StringIO.new
      end

      attr_reader :id
      attr_reader :env
      attr_reader :in
      attr_reader :out
      attr_reader :err
      attr_reader :data

      def finish   # for backword compatibility
      end
    end


    class Record
      # uint8_t  protocol_version;
      # uint8_t  record_type;
      # uint16_t request_id;     (big endian)
      # uint16_t content_length; (big endian)
      # uint8_t  padding_length;
      # uint8_t  reserved;
      HEADER_FORMAT = 'CCnnCC'
      HEADER_LENGTH = 8

      def self.parse_header(buf)
        return *buf.unpack(HEADER_FORMAT)
      end

      def self.class_for(type)
        RECORD_CLASS[type]
      end

      def initialize(type, reqid)
        @type = type
        @request_id = reqid
      end

      def version
        ::FCGI::ProtocolVersion
      end

      attr_reader :type
      attr_reader :request_id

      def management_record?
        @request_id == FCGI_NULL_REQUEST_ID
      end

      def serialize
        body = make_body()
        padlen = body.length % 8
        header = make_header(body.length, padlen)
        header + body + "\000" * padlen
      end

      private

      def make_header(clen, padlen)
        [version(), @type, @request_id, clen, padlen, 0].pack(HEADER_FORMAT)
      end
    end

    class BeginRequestRecord < Record
      # uint16_t role; (big endian)
      # uint8_t  flags;
      # uint8_t  reserved[5];
      BODY_FORMAT = 'nCC5'

      def BeginRequestRecord.parse(id, body)
        role, flags, *_ = *body.unpack(BODY_FORMAT)
        new(id, role, flags)
      end

      def initialize(id, role, flags)
        super FCGI_BEGIN_REQUEST, id
        @role = role
        @flags = flags
      end

      attr_reader :role
      attr_reader :flags

      def make_body
        [@role, @flags, 0, 0, 0, 0, 0].pack(BODY_FORMAT)
      end
    end

    class AbortRequestRecord < Record
      def AbortRequestRecord.parse(id, body)
        new(id)
      end

      def initialize(id)
        super FCGI_ABORT_REQUEST, id
      end
    end

    class EndRequestRecord < Record
      # uint32_t appStatus; (big endian)
      # uint8_t  protocolStatus;
      # uint8_t  reserved[3];
      BODY_FORMAT = 'NCC3'

      def self.parse(id, body)
        appstatus, protostatus, *reserved = *body.unpack(BODY_FORMAT)
        new(id, appstatus, protostatus)
      end

      def initialize(id, appstatus, protostatus)
        super FCGI_END_REQUEST, id
        @application_status = appstatus
        @protocol_status = protostatus
      end

      attr_reader :application_status
      attr_reader :protocol_status

      private

      def make_body
        [@application_status, @protocol_status, 0, 0, 0].pack(BODY_FORMAT)
      end
    end

    class UnknownTypeRecord < Record
      # uint8_t type;
      # uint8_t reserved[7];
      BODY_FORMAT = 'CC7'

      def self.parse(id, body)
        type, *reserved = *body.unpack(BODY_FORMAT)
        new(id, type)
      end

      def initialize(id, t)
        super FCGI_UNKNOWN_TYPE, id
        @unknown_type = t
      end

      attr_reader :unknown_type

      private

      def make_body
        [@unknown_type, 0, 0, 0, 0, 0, 0, 0].pack(BODY_FORMAT)
      end
    end

    class ValuesRecord < Record
      def self.parse(id, body)
        new(id, parse_values(body))
      end

      def self.parse_values(buf)
        result = {}
        until buf.empty?
          name, value = *read_pair(buf)
          result[name] = value
        end
        result
      end

      def self.read_pair(buf)
        nlen = read_length(buf)
        vlen = read_length(buf)
        [buf.slice!(0, nlen), buf.slice!(0, vlen)]
      end


      if "".respond_to?(:bytes) # Ruby 1.9 string semantics
        def self.read_length(buf)
          if buf[0].bytes.first >> 7 == 0
            buf.slice!(0,1)[0].bytes.first
          else
            buf.slice!(0,4).unpack('N')[0] & ((1<<31) - 1)
          end
        end
      else # Ruby 1.8 string
        def self.read_length(buf)
          if buf[0] >> 7 == 0
            buf.slice!(0,1)[0].bytes.first
          else
            buf.slice!(0,4).unpack('N')[0] & ((1<<31) - 1)
          end
        end
      end

      def initialize(type, id, values)
        super type, id
        @values = values
      end

      attr_reader :values

      private

      def make_body
        buf = ''
        @values.each do |name, value|
          buf << serialize_length(name.length)
          buf << serialize_length(value.length)
          buf << name
          buf << value
        end
        buf
      end

      def serialize_length(len)
        if len < 0x80
        then len.chr
        else [len | (1<<31)].pack('N')
        end
      end
    end

    class GetValuesRecord < ValuesRecord
      def initialize(id, values)
        super FCGI_GET_VALUES, id, values
      end
    end

    class ParamsRecord < ValuesRecord
      def initialize(id, values)
        super FCGI_PARAMS, id, values
      end

      def empty?
        @values.empty?
      end
    end

    class GenericDataRecord < Record
      def self.parse(id, body)
        new(id, body)
      end

      def initialize(type, id, flagment)
        super type, id
        @flagment = flagment
      end

      attr_reader :flagment

      def empty?
        @flagment.empty?
      end

      private

      def make_body
        if @flagment.respond_to? 'force_encoding' then
          return @flagment.dup.force_encoding('BINARY')
        else
          return @flagment
        end
      end
    end

    class StdinDataRecord < GenericDataRecord
      def initialize(id, flagment)
        super FCGI_STDIN, id, flagment
      end
    end

    class StdoutDataRecord < GenericDataRecord
      def initialize(id, flagment)
        super FCGI_STDOUT, id, flagment
      end
    end

    class DataRecord < GenericDataRecord
      def initialize(id, flagment)
        super FCGI_DATA, id, flagment
      end
    end

    class Record   # redefine
      RECORD_CLASS = {
        FCGI_GET_VALUES    => GetValuesRecord,

        FCGI_BEGIN_REQUEST => BeginRequestRecord,
        FCGI_ABORT_REQUEST => AbortRequestRecord,
        FCGI_PARAMS        => ParamsRecord,
        FCGI_STDIN         => StdinDataRecord,
        FCGI_DATA          => DataRecord,
        FCGI_STDOUT        => StdoutDataRecord,
        FCGI_END_REQUEST   => EndRequestRecord
      }
    end

  end # FCGI class
end # begin

# There is no C version of 'each_cgi'
# Note: for ruby-1.6.8 at least, the constants CGI_PARAMS/CGI_COOKIES
# are defined within module 'CGI', even if you have subclassed it

class FCGI
  def self.each_cgi(*args)
    require 'cgi'

    eval(<<-EOS,TOPLEVEL_BINDING)
    class CGI
      public :env_table
      def self.remove_params
        if (const_defined?(:CGI_PARAMS))
          remove_const(:CGI_PARAMS)
          remove_const(:CGI_COOKIES)
        end
      end
    end # ::CGI class

    class FCGI
      class CGI < ::CGI
        def initialize(request, *args)
          ::CGI.remove_params
          @request = request
          super(*args)
          @args = *args
        end
        def args
          @args
        end
        def env_table
          @request.env
        end
        def stdinput
          @request.in
        end
        def stdoutput
          @request.out
        end
      end # FCGI::CGI class
    end # FCGI class
    EOS

    if FCGI::is_cgi?
      yield ::CGI.new(*args)
    else
      FCGI::each do |request|

        $stdout, $stderr = request.out, request.err

        yield CGI.new(request, *args)

        request.finish
      end
    end
  end
end
