require 'cgi' unless ENV['MOD_RUBY']
# TODO: Add some more of the Apache::Request methods, especially those dealing with authentication.
module Iowa
# Class to encapsulate the data received from a file upload.
class FileData < Array
attr_accessor :name, :content_type
def initialize(name,content_type,*content)
@name = name
@content_type = content_type
self.concat(content)
end
def to_s
self.join
end
# Create a file with a path/name as in @name, and write the current
# data to it. Convenience method.
# After an Iowa app received an uploaded file and it checks/sets
# @name as appropriate, a single call to store() will write the file.
# If @name is blank or nil, the content will instead be written to
# an instance of Tempfile.
def store
handle = nil
# Make sure you know what is in @name before calling this!
@name = @name.to_s
if @name == ''
require 'tempfile'
handle = Tempfile.new('IowaFile','/tmp')
else
handle = File.new(@name,'w+')
end
if handle
handle.write self.join
handle.rewind
end
handle
end
end
module R
# A serializeable representation of an Apache::Table to use with
# Iowa::Request.
class Table < Hash
alias :old_get :[]
def [](idx)
if self.old_get(idx).instance_of? Array
return self.old_get(idx)[0]
else
return self.old_get(idx)
end
end
alias :get :[]
alias :old_set :[]=
def []=(idx,val)
self.old_set(idx,val)
end
def set(idx,val)
self.old_set(idx,val)
end
alias :setn :set
def merge(idx,val)
unless self[idx].instance_of? Array
self[idx] = []
end
self.old_get(idx)[0] = self.old_get(idx)[0].to_s + val.to_s
end
alias :mergen :merge
def add(idx,val)
unless self[idx].instance_of? Array
self[idx] = []
end
self.old_get(idx).push(val)
end
alias :addn :add
alias :unset :delete
alias :old_each :each
def each
self.old_each do |key,ary|
ary.each do |val|
yield key,val
end
end
end
end
end
# A serializeable class that represents (most of) the contents of
# an Apache::Request object.
class Request
MIMERegexp = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
CR = "\015"
LF = "\012"
EOL = CR + LF
attr_accessor :hostname, :unparsed_uri, :uri, :filename, :request_time,
:request_method, :header_only, :args, :headers_in, :params,
:status_line, :headers_out, :content_type, :content_encoding,
:content_languages, :content, :auth_type, :auth_name,:remote_host,
:cache_resp, :content_encoding, :content_languages
# Unescapes URL encoded strings.
def unescape(str)
str.gsub(/\+/, ' ').gsub(/%([0-9a-fA-F]{2})/){ [$1.hex].pack("c") }
end
def read_multipart(boundary, content_length,request)
content_length = content_length.to_i
params = Hash.new([])
boundary = "--" + boundary
buf = ""
bufsize = 10 * 1024
# start multipart/form-data
$stdin.binmode if defined? $stdin.binmode
boundary_size = boundary.size + EOL.size
content_length -= boundary_size
status = $stdin.read(boundary_size)
loop do
head = nil
body = ''
until head and /#{boundary}(?:#{EOL}|--)/n.match(buf)
if (not head) and /#{EOL}#{EOL}/n.match(buf)
buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
head = $1.dup
""
end
next
end
if head and ( (EOL + boundary + EOL).size < buf.size )
body << buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
end
c = if bufsize < content_length
$stdin.read(bufsize) or ''
else
$stdin.read(content_length) or ''
end
buf.concat(c)
content_length -= c.size
end
buf = buf.sub(/\A((?:.|\n)*?)(?:#{EOL})?#{boundary}(#{EOL}|--)/n) do
body << $1
if "--" == $2
content_length = -1
end
""
end
/Content-Disposition:.* filename="?([^\";]*)"?/ni.match(head)
filename = ($1 or "")
if /Mac/ni.match(request.headers_in['HTTP_USER_AGENT']) and
/Mozilla/ni.match(request.headers_in['HTTP_USER_AGENT']) and
(not /MSIE/ni.match(request.headers_in['HTTP_USER_AGENT']))
filename = CGI::unescape(filename)
end
/Content-Type: (.*)/ni.match(head)
content_type = ($1 or "")
/Content-Disposition:.* name="?([^\";]*)"?/ni.match(head)
name = $1.dup
data = nil
if content_type.to_s != ''
data = Iowa::FileData.new(filename,content_type,body)
else
data = body
end
if params.has_key?(name)
if params[name].respond_to?(:push)
params[name].push data
else
params[name] = [params[name]] << data
end
else
params[name] = data
end
break if buf.size == 0
break if content_length === -1
end
params
end
# Takes a real Apache::Request object and plucks data from it to form the
# Iowa::Request object. If an Apache::Request object is not provided,
# the code will look at the MOD_RUBY enviornment variable to attempt
# to ascertain if it is running in a mod_ruby environment. If it is,
# an Apache::Request object will be created to populate the
# Iowa::Request object. If it is not, the code will attempt to populate
# the object from other data that is available.
def initialize(request=nil)
unless request
request = Apache.request if ENV['MOD_RUBY']
end
if request
@hostname = request.hostname
@unparsed_uri = request.unparsed_uri
@uri = request.uri
@filename = request.filename
@request_time = request.request_time
@request_method = request.request_method
@header_only = request.header_only?
# In an Apache::Request object, request.args is a string. We want
# to make a hash of these contents available for use, too.
@args = request.args
@args = '' if args.nil?
@content_type = request.content_type
@content_encoding = request.content_encoding
@content_languages = request.content_languages
@remote_host = request.remote_host
@auth_type = request.auth_type
@auth_name = request.auth_name
@cache_resp = request.cache_resp
@content = ''
@params = Hash.new
if @args.to_s != ''
@args.split('&').each do |a|
k,v = a.split('=',2).collect{|x| unescape(x)}
if @params.has_key? k
@params[k] += "\0" + (v or "")
else
@params[k] = (v or "")
end
end
else
if m = MIMERegexp.match(request.headers_in['Content-Type'])
boundary = m[1]
@params = read_multipart(boundary,request.headers_in['Content-Length'],request)
else
@content = $stdin.read
@content = '' if @content.nil?
@content.split(/[&;]/).each do |x|
key, val = x.split(/=/,2).collect{|x| unescape(x)}
if @params.include?(key)
@params[key] += "\0" + (val or "")
else
@params[key] = (val or "")
end
end
end
end
@headers_in = Iowa::R::Table.new
request.headers_in.each do |key,value|
@headers_in[key] = value
end
@status_line = request.status_line
@headers_out = Iowa::R::Table.new
request.headers_out.each do |key,value|
@headers_out[key] = value
end
else
@hostname = ENV['SERVER_NAME']
@unparsed_uri = nil
@uri = ENV['REQUEST_URI']
@filename = ENV['SCRIPT_FILENAME']
@request_time = Time.now.asctime
@request_method = ENV['REQUEST_METHOD']
@remote_host = ENV['REMOTE_HOST']
@headers_only = (@request_method.to_s == 'HEAD') ? true : false
c = CGI.new
# CGI will give us a hash of the params, but @args is supposed
# to be a String.
@params = c.params
@params.each_pair do |k,v|
if (v.class.to_s == 'StringIO') or (v.class.to_s == 'Tempfile')
@params[k] = Iowa::FileData.new(v.original_filename,v.content_type,v.read)
end
end
@args = @request_method == 'POST' ? '' : @params.keys.collect {|i| "#{i}=#{@params[i]}"}.join('&')
@headers_in = Iowa::R::Table.new
ENV.each do |k,v|
key = k.dup
value = v.dup
next unless key =~ /^HTTP_/
key.sub!(/^HTTP_/,'')
key.gsub!(/([^_]+_?)/) do |s|
s.capitalize
end
@headers_in[key] = value
end
@status_line = nil
@headers_out = Iowa::R::Table.new
@content_type = nil
@content_encoding = nil
@content_languages = nil
@content = ''
end
end
def header_only?
return @header_only
end
end
end