fcgi-0.9.2.2/0000755000004100000410000000000014333123356012625 5ustar www-datawww-datafcgi-0.9.2.2/test/0000755000004100000410000000000014333123356013604 5ustar www-datawww-datafcgi-0.9.2.2/test/helper.rb0000644000004100000410000000030514333123356015406 0ustar www-datawww-datarequire 'rubygems' require 'test/unit' $LOAD_PATH.unshift(File.dirname(__FILE__)) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require 'fcgi' class Test::Unit::TestCase end fcgi-0.9.2.2/test/test_fcgi.rb0000644000004100000410000000026714333123356016105 0ustar www-datawww-datarequire 'helper' class TestFcgi < Test::Unit::TestCase def test_something_for_real flunk "hey buddy, you should probably rename this file and start testing for real" end end fcgi-0.9.2.2/fcgi.gemspec0000644000004100000410000000202414333123356015100 0ustar www-datawww-dataGem::Specification.new do |s| s.name = %q{fcgi} s.version = "0.9.2.2" s.license = "MIT" s.authors = [%q{mva}] s.date = %q{2022-10-17} s.description = %q{FastCGI is a language independent, scalable, open extension to CGI that provides high performance without the limitations of server specific APIs. This version aims to be compatible with both 1.8.x and 1.9.x versions of Ruby, and also will be ported to 2.0.x. It has been hacked to work with Ruby 3.0.x.} s.email = %q{mva@mva.name} s.extensions = [%q{ext/fcgi/extconf.rb}] s.extra_rdoc_files = [ "LICENSE", "README.rdoc", "README.signals" ] s.rdoc_options = ["--charset=UTF-8"] s.files = [ "VERSION", "ext/fcgi/MANIFEST", "ext/fcgi/Makefile", "ext/fcgi/extconf.rb", "ext/fcgi/fcgi.c", "lib/fcgi.rb", "fcgi.gemspec" ] s.test_files = [ "test/helper.rb", "test/test_fcgi.rb" ] s.homepage = %q{http://github.com/alphallc/ruby-fcgi-ng} s.require_paths = [%q{lib}] s.summary = %q{FastCGI library for Ruby.} end fcgi-0.9.2.2/LICENSE0000644000004100000410000000205714333123356013636 0ustar www-datawww-dataCopyright (c) 2009 saks Copyright (c) 2013 mva 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. fcgi-0.9.2.2/README.signals0000644000004100000410000000567514333123356015161 0ustar www-datawww-data= Handling of SIGUSR1 When you request a 'graceful' restart of Apache, mod_fastcgi sends a SIGUSR1 to each of the fastcgi worker processes. The intention is that each one should finish the current request, and then exit, at which point Apache will restart it. Of course, if the worker isn't doing anything, it should die immediately. This is implemented in the fcgi C library as follows: - a signal handler is installed for SIGUSR1, which just sets a flag (shutdownPending) and returns - fcgi sits inside an accept() call waiting for a new request - if this accept() call terminates with EINTR, and this flag is set, then it returns with no request Unfortunately, Ruby defeats this mechanism in at least two ways: 1. Ruby installs its own signal handlers for a host of common signals, including USR1. The fcgi library will not install its own handler if it detects that a handler has already been set (i.e. the current handler is not SIG_DFL) 2. When Ruby installs its own signal handlers, it does so with SA_RESTART set. This means that the accept() call does not terminate with EINTR; it is restarted automatically by the OS. When a signal comes in during the accept(), Ruby's own handler does nothing except store it in a queue to be processed later. It is only when the accept() call completes, i.e. when a genuine new request comes in, that Ruby takes action. Unfortunately it's too late by then, and if that already-accepted request is not honoured, a 500 Internal Error will be returned to the client. The simplest solution to this would be to remove Ruby's SIGUSR1 handler before initialising the FastCGI library. However, a cleaner solution is to call rb_thread_select before going into FastCGI's accept loop. If a signal happens during the select, it can be handled using Ruby's normal mechanisms. This also gives a very useful side-benefit, which is that FCGI::accept no longer blocks out other Ruby threads. The program below demonstrates this problem; its background logging thread is supposed to write a message every 10 seconds, but under older versions of ruby-fcgi it does not do so if it is waiting for a new request. #!/usr/local/bin/ruby require "fcgi" Thread.new do f = File.new("/tmp/fcgi.log","a") f.sync=true while true f.puts "#{Time.now.to_s} pid #{$$}" sleep 10 end end FCGI.each_cgi {|cgi| name = cgi['name'][0] puts cgi.header puts "Hey! You are #{name} " if name puts "Connecting from #{cgi.remote_addr}" } Having protected the accept() with a ruby select(), you can then handle signals as follows: - call FCGI::accept (it will raise an exception if USR1 is called) - install a USR1 handler - process the request - remove the USR1 handler The USR1 handler should set a flag to note if a USR1 signal came in while the request was being processed; you terminate the loop if it was set. The overall effect is that USR1 will cause the process to terminate, but without causing a half-completed request. fcgi-0.9.2.2/lib/0000755000004100000410000000000014333123356013373 5ustar www-datawww-datafcgi-0.9.2.2/lib/fcgi.rb0000644000004100000410000003451714333123356014642 0ustar www-datawww-data=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 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 fcgi-0.9.2.2/README.rdoc0000644000004100000410000000537214333123356014442 0ustar www-datawww-data= fcgi - The New generation of FastCGI library for Ruby. Version 0.9.2.1 == Depends === C version * (())(FastCGI Developer's Kit) === Pure Ruby Version * StringIO == Install $ gem install fcgi == Usage === Class Method --- FCGI.accept Returns FCGI instance --- FCGI.each --- FCGI.each_request --- FCGI.is_cgi? --- FCGI.each_cgi Automatically detects whether this program is running under the FastCGI environment, and generates a 'CGI' type object for each request. Also installs signal handlers for graceful handling of SIGPIPE (which may occur if a client gives up on a request before it is complete) and SIGUSR1 (generated by Apache for a 'graceful' exit) If you are using the HTML output methods you can also pass the HTML type e.g. FCGI.each_cgi('html3') do ... end However, you should beware that the CGI library is quite slow when used in this way, as it dynamically adds a large number of methods to itself each time a new instance is created. === Instance Method --- FCGI#finish Finish --- FCGI#in Returns Stream or StringIO --- FCGI#out Returns Stream or StringIO --- FCGI#err Returns Stream or StringIO --- FCGI#env Returns Environment(Hash) == Sample Using the FastCGI native interface: #!/usr/bin/ruby require "fcgi" FCGI.each {|request| out = request.out out.print "Content-Type: text/plain\r\n" out.print "\r\n" out.print Time.now.to_s request.finish } Using the CGI-compatible interface, which works both as a standalone CGI and under FastCGI with no modifications: #!/usr/bin/ruby require "fcgi" FCGI.each_cgi {|cgi| name = cgi['name'][0] puts cgi.header puts "You are #{name} " if name puts "Connecting from #{cgi.remote_addr}" } Note: you can't reference CGI environment variables using ENV when under FastCGI. It is recommended that you use the CGI-generated methods, e.g. cgi.remote_addr as above. If you need to access environment variables directly, perhaps extra ones set in your Apache config, then use cgi.env_table['REMOTE_ADDR'] instead. This isn't quite as portable because env_table is a private method in the standard CGI library. == License * (()) (Japanese) * (()) (English) == Copyright fcgi.c 0.1 Copyright (C) 1998-1999 Network Applied Communication Laboratory, Inc. 0.8 Copyright (C) 2002 MoonWolf 0.9 Copyright (C) 2013 mva Alpha, LLC fastcgi.rb 0.7 Copyright (C) 2001 Eli Green fcgi.rb 0.8 Copyright (C) 2002 MoonWolf fcgi.rb 0.8.5 Copyright (C) 2004 Minero Aoki fcgi-0.9.2.2/ext/0000755000004100000410000000000014333123356013425 5ustar www-datawww-datafcgi-0.9.2.2/ext/fcgi/0000755000004100000410000000000014333123356014335 5ustar www-datawww-datafcgi-0.9.2.2/ext/fcgi/Makefile0000644000004100000410000000005214333123356015772 0ustar www-datawww-data# Use ruby extconf.rb to generate makefilefcgi-0.9.2.2/ext/fcgi/MANIFEST0000644000004100000410000000003314333123356015462 0ustar www-datawww-dataMANIFEST extconf.rb fcgi.c fcgi-0.9.2.2/ext/fcgi/fcgi.c0000755000004100000410000003707514333123356015430 0ustar www-datawww-data/* * fcgi.c * Copyright (C) 1998-1999 Network Applied Communication Laboratory, Inc. * Copyright (C) 2002-2006 MoonWolf * Copyright (C) 2012-2014 mva */ #include #include #include #include #include #include "ruby.h" #ifdef HAVE_RUBY_VERSION_H #include "ruby/version.h" #endif #ifndef RSTRING_PTR #define RSTRING_PTR(str) (RSTRING(str)->ptr) #endif #ifndef RSTRING_LEN #define RSTRING_LEN(str) (RSTRING(str)->len) #endif #ifdef HAVE_FASTCGI_FCGIAPP_H #include #else #include "fcgiapp.h" #endif static VALUE cFCGI; static VALUE eFCGIError; static VALUE cFCGIStream; static VALUE eFCGIStreamError; static VALUE eFCGIStreamUnsupportedVersionError; static VALUE eFCGIStreamProtocolError; static VALUE eFCGIStreamParamsError; static VALUE eFCGIStreamCallSeqError; typedef struct fcgi_stream_data { VALUE req; FCGX_Stream *stream; } fcgi_stream_data; typedef struct fcgi_data { FCGX_Request *req; VALUE in; VALUE out; VALUE err; VALUE env; } fcgi_data; static void fcgi_stream_mark(fcgi_stream_data *stream_data) { rb_gc_mark(stream_data->req); } static void fcgi_stream_free(fcgi_stream_data *stream_data) { free(stream_data); } static void fcgi_mark(fcgi_data *data) { rb_gc_mark(data->in); rb_gc_mark(data->out); rb_gc_mark(data->err); rb_gc_mark(data->env); } static void fcgi_free_req(fcgi_data *data) { FCGX_Free(data->req, 1); free(data->req); free(data); } static VALUE fcgi_s_accept(VALUE self) { int status; FCGX_Request *req; rb_fdset_t readfds; req = ALLOC(FCGX_Request); status = FCGX_InitRequest(req,0,0); if (status != 0) { rb_raise(eFCGIError, "FCGX_Init() failed"); return Qnil; } rb_fd_init(&readfds); rb_fd_set(req->listen_sock, &readfds); if (rb_thread_fd_select(readfds.maxfd, &readfds, NULL, NULL, NULL) < 1) { return Qnil; } status = FCGX_Accept_r(req); if (status >= 0) { fcgi_data *data; fcgi_stream_data *stream_data; char **env; VALUE obj,key, value; char *pkey,*pvalue; int flags, fd; /* Unset NONBLOCKING */ fd = ((FCGX_Request*) req)->ipcFd; flags = fcntl(fd, F_GETFL); if (flags & O_NONBLOCK) { fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); } obj = Data_Make_Struct(self, fcgi_data, fcgi_mark, fcgi_free_req, data); data->req = req; data->in = Data_Make_Struct(cFCGIStream, fcgi_stream_data, fcgi_stream_mark, fcgi_stream_free, stream_data); stream_data->stream = req->in; stream_data->req = obj; data->out = Data_Make_Struct(cFCGIStream, fcgi_stream_data, fcgi_stream_mark, fcgi_stream_free, stream_data); stream_data->stream = req->out; stream_data->req = obj; data->err = Data_Make_Struct(cFCGIStream, fcgi_stream_data, fcgi_stream_mark, fcgi_stream_free, stream_data); stream_data->stream = req->err; stream_data->req = obj; data->env = rb_hash_new(); env = req->envp; for (; *env; env++) { int size = 0; pkey = *env; pvalue = pkey; while( *(pvalue++) != '=') size++; key = rb_str_new(pkey, size); value = rb_str_new2(pvalue); OBJ_TAINT(key); OBJ_TAINT(value); rb_hash_aset(data->env, key, value); } return obj; } else { FCGX_Free(req, 1); free(req); return Qnil; } } static VALUE fcgi_s_each(VALUE self) { VALUE fcgi; while ((fcgi = fcgi_s_accept(self)) != Qnil) { rb_yield(fcgi); } return Qnil; } static VALUE fcgi_s_iscgi(VALUE self) { if (FCGX_IsCGI()) { return Qtrue; } else { return Qfalse; } } static VALUE fcgi_in(VALUE self) { fcgi_data *data; Data_Get_Struct(self, fcgi_data, data); return data->in; } static VALUE fcgi_out(VALUE self) { fcgi_data *data; Data_Get_Struct(self, fcgi_data, data); return data->out; } static VALUE fcgi_err(VALUE self) { fcgi_data *data; Data_Get_Struct(self, fcgi_data, data); return data->err; } static VALUE fcgi_env(VALUE self) { fcgi_data *data; Data_Get_Struct(self, fcgi_data, data); return data->env; } static VALUE fcgi_finish(VALUE self) { fcgi_data *data; fcgi_stream_data *stream_data; Data_Get_Struct(self, fcgi_data, data); if (Qnil != data->in) { Data_Get_Struct(data->in, fcgi_stream_data, stream_data); stream_data->req = Qnil; stream_data->stream = NULL; } if (Qnil != data->out) { Data_Get_Struct(data->out, fcgi_stream_data, stream_data); stream_data->req = Qnil; stream_data->stream = NULL; } if (Qnil != data->err) { Data_Get_Struct(data->err, fcgi_stream_data, stream_data); stream_data->req = Qnil; stream_data->stream = NULL; } data->in = data->out = data->err = Qnil; FCGX_Finish_r(data->req); return Qtrue; } #define CHECK_STREAM_ERROR(stream) {\ int err = FCGX_GetError(stream);\ extern int errno; \ if (err) {\ if (err > 0) {\ rb_raise(eFCGIStreamError, "unknown error (syscall error)");\ }\ else {\ switch (err) {\ case FCGX_UNSUPPORTED_VERSION:\ rb_raise(eFCGIStreamUnsupportedVersionError, "unsupported version");\ break;\ case FCGX_PROTOCOL_ERROR:\ rb_raise(eFCGIStreamProtocolError, "protocol error");\ break;\ case FCGX_PARAMS_ERROR:\ rb_raise(eFCGIStreamProtocolError, "parameter error");\ break;\ case FCGX_CALL_SEQ_ERROR:\ rb_raise(eFCGIStreamCallSeqError, "preconditions are not met");\ break;\ default:\ rb_raise(eFCGIStreamError, "unknown error");\ break;\ }\ }\ }\ } #define Data_Get_Stream(value, stream) do {\ fcgi_stream_data* _fsd;\ Data_Get_Struct(value, fcgi_stream_data, _fsd);\ if (NULL == (stream = _fsd->stream))\ rb_raise(eFCGIStreamError, "stream invalid as fastcgi request is already finished");\ } while (0) static VALUE fcgi_stream_putc(VALUE self, VALUE ch) { FCGX_Stream *stream; int c; #if !defined(RUBY_API_VERSION_MAJOR) || (RUBY_API_VERSION_MAJOR < 3) rb_secure(4); #endif Data_Get_Stream(self, stream); if ((c = FCGX_PutChar(NUM2INT(ch), stream)) == EOF) CHECK_STREAM_ERROR(stream); return INT2NUM(c); } static VALUE fcgi_stream_write(VALUE self, VALUE str) { FCGX_Stream *stream; int len; #if !defined(RUBY_API_VERSION_MAJOR) || (RUBY_API_VERSION_MAJOR < 3) rb_secure(4); #endif Data_Get_Stream(self, stream); str = rb_obj_as_string(str); len = FCGX_PutStr(RSTRING_PTR(str), RSTRING_LEN(str), stream); if (len == EOF) CHECK_STREAM_ERROR(stream); return INT2NUM(len); } static VALUE fcgi_stream_print(int argc, VALUE *argv, VALUE out) { int i; VALUE line; /* if no argument given, print `$_' */ if (argc == 0) { argc = 1; line = rb_lastline_get(); argv = &line; } for (i=0; i0) { fcgi_stream_write(out, rb_output_fs); } switch (TYPE(argv[i])) { case T_NIL: fcgi_stream_write(out, rb_str_new2("nil")); break; default: fcgi_stream_write(out, argv[i]); break; } } if (!NIL_P(rb_output_rs)) { fcgi_stream_write(out, rb_output_rs); } return Qnil; } static VALUE fcgi_stream_printf(int argc, VALUE *argv, VALUE out) { fcgi_stream_write(out, rb_f_sprintf(argc, argv)); return Qnil; } static VALUE fcgi_stream_puts _((int, VALUE*, VALUE)); static VALUE fcgi_stream_puts_ary(VALUE ary, VALUE out, int recur) { VALUE tmp; int i; if (recur) { tmp = rb_str_new2("[...]"); fcgi_stream_puts(1, &tmp, out); return Qnil; } for (i=0; i= 4 && !OBJ_TAINTED(self)) { rb_raise(rb_eSecurityError, "Insecure: operation on untainted IO"); } #endif Data_Get_Stream(self, stream); c = FCGX_UnGetChar(NUM2INT(ch), stream); CHECK_STREAM_ERROR(stream); return INT2NUM(c); } static VALUE fcgi_stream_gets(VALUE self) { FCGX_Stream *stream; char buff[BUFSIZ]; VALUE str = rb_str_new(0,0); OBJ_TAINT(str); #if !defined(RUBY_API_VERSION_MAJOR) || (RUBY_API_VERSION_MAJOR < 3) if (rb_safe_level() >= 4 && !OBJ_TAINTED(self)) { rb_raise(rb_eSecurityError, "Insecure: operation on untainted IO"); } #endif Data_Get_Stream(self, stream); for (;;) { if (FCGX_GetLine(buff, BUFSIZ, stream) == NULL) { CHECK_STREAM_ERROR(stream); break; } rb_str_cat(str, buff, strlen(buff)); if (strchr(buff, '\n')) break; } if (RSTRING_LEN(str) > 0) return str; else return Qnil; } static VALUE fcgi_stream_read(int argc, VALUE *argv, VALUE self) { VALUE num,str; FCGX_Stream *stream; char *buff; int n; #if !defined(RUBY_API_VERSION_MAJOR) || (RUBY_API_VERSION_MAJOR < 3) if (rb_safe_level() >= 4 && !OBJ_TAINTED(self)) { rb_raise(rb_eSecurityError, "Insecure: operation on untainted IO"); } #endif Data_Get_Stream(self, stream); if (argc==0) { buff = ALLOC_N(char, 16384); n = FCGX_GetStr(buff, 16384, stream); CHECK_STREAM_ERROR(stream); if (n == 0) { free(buff); return Qnil; } str = rb_str_new(buff, n); OBJ_TAINT(str); while(!FCGX_HasSeenEOF(stream)) { n = FCGX_GetStr(buff, 16384, stream); CHECK_STREAM_ERROR(stream); if (n > 0) { rb_str_cat(str, buff, n); } else { free(buff); return Qnil; } } free(buff); return str; } num = argv[0]; n = NUM2INT(num); buff = ALLOC_N(char, n); n = FCGX_GetStr(buff, n, stream); CHECK_STREAM_ERROR(stream); if (n > 0) { str = rb_str_new(buff, n); OBJ_TAINT(str); free(buff); return str; } else { free(buff); return Qnil; } } static VALUE fcgi_stream_eof(VALUE self) { FCGX_Stream *stream; #if !defined(RUBY_API_VERSION_MAJOR) || (RUBY_API_VERSION_MAJOR < 3) if (rb_safe_level() >= 4 && !OBJ_TAINTED(self)) { rb_raise(rb_eSecurityError, "Insecure: operation on untainted IO"); } #endif Data_Get_Stream(self, stream); return FCGX_HasSeenEOF(stream) ? Qtrue : Qfalse; } static VALUE fcgi_stream_close(VALUE self) { FCGX_Stream *stream; #if !defined(RUBY_API_VERSION_MAJOR) || (RUBY_API_VERSION_MAJOR < 3) if (rb_safe_level() >= 4 && !OBJ_TAINTED(self)) { rb_raise(rb_eSecurityError, "Insecure: can't close"); } #endif Data_Get_Stream(self, stream); if (FCGX_FClose(stream) == EOF) CHECK_STREAM_ERROR(stream); return Qnil; } static VALUE fcgi_stream_closed(VALUE self) { FCGX_Stream *stream; Data_Get_Stream(self, stream); return stream->isClosed ? Qtrue : Qfalse; } static VALUE fcgi_stream_binmode(VALUE self) { #if !defined(RUBY_API_VERSION_MAJOR) || (RUBY_API_VERSION_MAJOR < 3) if (rb_safe_level() >= 4 && !OBJ_TAINTED(self)) { rb_raise(rb_eSecurityError, "Insecure: operation on untainted IO"); } #endif return self; } static VALUE fcgi_stream_isatty(VALUE self) { #if !defined(RUBY_API_VERSION_MAJOR) || (RUBY_API_VERSION_MAJOR < 3) if (rb_safe_level() >= 4 && !OBJ_TAINTED(self)) { rb_raise(rb_eSecurityError, "Insecure: operation on untainted IO"); } #endif return Qfalse; } static VALUE fcgi_stream_sync(VALUE self) { #if !defined(RUBY_API_VERSION_MAJOR) || (RUBY_API_VERSION_MAJOR < 3) if (rb_safe_level() >= 4 && !OBJ_TAINTED(self)) { rb_raise(rb_eSecurityError, "Insecure: operation on untainted IO"); } #endif return Qfalse; } static VALUE fcgi_stream_setsync(VALUE self,VALUE sync) { #if !defined(RUBY_API_VERSION_MAJOR) || (RUBY_API_VERSION_MAJOR < 3) if (rb_safe_level() >= 4 && !OBJ_TAINTED(self)) { rb_raise(rb_eSecurityError, "Insecure: operation on untainted IO"); } #endif return Qfalse; } void Init_fcgi() { FCGX_Init(); cFCGI = rb_define_class("FCGI", rb_cObject); eFCGIError =rb_define_class_under(cFCGI, "Error", rb_eStandardError); rb_define_singleton_method(cFCGI, "accept", fcgi_s_accept, 0); rb_define_singleton_method(cFCGI, "each", fcgi_s_each, 0); rb_define_singleton_method(cFCGI, "each_request", fcgi_s_each, 0); rb_define_singleton_method(cFCGI, "is_cgi?", fcgi_s_iscgi, 0); rb_define_method(cFCGI, "in", fcgi_in, 0); rb_define_method(cFCGI, "out", fcgi_out, 0); rb_define_method(cFCGI, "err", fcgi_err, 0); rb_define_method(cFCGI, "env", fcgi_env, 0); rb_define_method(cFCGI, "finish", fcgi_finish, 0); cFCGIStream = rb_define_class_under(cFCGI, "Stream", rb_cObject); eFCGIStreamError =rb_define_class_under(cFCGIStream, "Error", rb_eStandardError); eFCGIStreamUnsupportedVersionError = rb_define_class_under(cFCGIStream, "UnsupportedVersionError", eFCGIStreamError); eFCGIStreamProtocolError = rb_define_class_under(cFCGIStream, "ProtocolError", eFCGIStreamError); eFCGIStreamParamsError = rb_define_class_under(cFCGIStream, "ParamsError", eFCGIStreamError); eFCGIStreamCallSeqError = rb_define_class_under(cFCGIStream, "CallSeqError", eFCGIStreamError); rb_undef_method(CLASS_OF(cFCGIStream), "new"); rb_define_method(cFCGIStream, "putc", fcgi_stream_putc, 1); rb_define_method(cFCGIStream, "write", fcgi_stream_write, 1); rb_define_method(cFCGIStream, "print", fcgi_stream_print, -1); rb_define_method(cFCGIStream, "printf", fcgi_stream_printf, -1); rb_define_method(cFCGIStream, "puts", fcgi_stream_puts, -1); rb_define_method(cFCGIStream, "<<", fcgi_stream_addstr, 1); rb_define_method(cFCGIStream, "flush", fcgi_stream_flush, 0); rb_define_method(cFCGIStream, "getc", fcgi_stream_getc, 0); rb_define_method(cFCGIStream, "ungetc", fcgi_stream_ungetc, 1); rb_define_method(cFCGIStream, "gets", fcgi_stream_gets, 0); rb_define_method(cFCGIStream, "read", fcgi_stream_read, -1); rb_define_method(cFCGIStream, "eof", fcgi_stream_eof, 0); rb_define_method(cFCGIStream, "eof?", fcgi_stream_eof, 0); rb_define_method(cFCGIStream, "close", fcgi_stream_close, 0); rb_define_method(cFCGIStream, "closed?", fcgi_stream_closed, 0); rb_define_method(cFCGIStream, "binmode", fcgi_stream_binmode, 0); rb_define_method(cFCGIStream, "isatty", fcgi_stream_isatty, 0); rb_define_method(cFCGIStream, "tty?", fcgi_stream_isatty, 0); rb_define_method(cFCGIStream, "sync", fcgi_stream_sync, 0); rb_define_method(cFCGIStream, "sync=", fcgi_stream_setsync, 1); } fcgi-0.9.2.2/ext/fcgi/extconf.rb0000755000004100000410000000025314333123356016333 0ustar www-datawww-datarequire "mkmf" dir_config("fcgi") if (have_header("fcgiapp.h") || have_header("fastcgi/fcgiapp.h")) && have_library("fcgi", "FCGX_Accept") create_makefile("fcgi") end fcgi-0.9.2.2/VERSION0000644000004100000410000000001014333123356013664 0ustar www-datawww-data0.9.2.1