The Ruby CGI library as it stands creates a lot of files for multi-part forms.
Creating temporary files for all the parts of a multi-part encoded form is pure silliness.
If there is some reason "it must be done" then at least you could only do it only for "large" objects.
Oh and they are "Tempfile" not "File" leading to issues when passed to libraries such as Ruby/GD.
Bellow is a patch against ruby 1.6.7 cgi.rb that doesn't create a temp file for each parameter on multi/part form data.
I contains a modification to escape_url as well because I got some strange results with the original one.
It as well disable SimpleDelegator from Cookie Class because it slow down the cookie processing specially when using session.
The session diff against ruby 1.6.7 cgi/session as well follows it try to modify session to don't rely on SimpleDelegator.
In fact I don't know exact if there is unwanted side efects for session what I know is that it was working for me and a lot faster tha before.
Before open a session was consuming 40ms and now around 5ms.
Any question send to me at domingo@dad-it.com
cgi.diff start
---
6a7,8
> Version 2.1.4
>
170,172c172,174
CGI.new("html4") # html4.0 (Strict)<br />
> CGI.new("html4Tr") # html4.0 Transitional
> CGI.new("html4Fr") # html4.0 Frameset
186c188,192
VERSION = '2.1.4'<br />
> RELEASE_DATE = '2001-04-18'
> VERSION_CODE = 214
> RELEASE_CODE = 20010418
> REVISION = '$Id: cgi.rb.dad,v 1.1 2001/09/04
10:35:56 mingo Exp $'
238,240c244,247
string.gsub(/[^a-zA-Z0-9_-.]/n){ sprintf("%%%02X", $&.unpack("C")[0]) }<br />
> # string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
> # '%' + $1.unpack('H2' * $1.size).join('%').upcase
> # end.tr(' ', '+')
249,251c256,259
string.gsub(/+/, ' ').gsub(/%([0-9a-fA-F]{2})/){ [$1.hex].pack("c") }<br />
> # string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
> # [$1.delete('%')].pack('H*')
> # end
408,409c416
if options.kind_of?(String)<br />
411,412d417
options["type"].concat( "; charset=" )<br />
> options["type"].concat( options.delete("charset") )
425,428c431,439
buf.concat( (env_table["SERVER_PROTOCOL"] or "HTTP/1.0") + " " )<br />
> buf.concat( (HTTP_STATUS[options["status"]] or
> options["status"] or
> "200 OK"
> ) + EOL
> )
> buf.concat(
> "Date: " +
CGI::rfc1123_date(Time.now) + EOL
> )
442,443c453,454
status = (HTTP_STATUS[options["status"]] or options["status"])<br />
> buf.concat("Status: " + status + EOL)
448c459
buf.concat("Server: " + options.delete("server") + EOL)<br />
452c463
buf.concat("Connection: " + options.delete("connection") + EOL)<br />
455c466
buf.concat("Content-Type: " + options.delete("type") + EOL)<br />
458c469
buf.concat("Content-Length: " + options.delete("length").to_s + EOL)<br />
462c473
buf.concat("Content-Language: " + options.delete("language") + EOL)<br />
466c477
buf.concat("Expires: " + CGI::rfc1123_date( options.delete("expires") ) + EOL)<br />
472c483
buf.concat("Set-Cookie: " + options.delete("cookie").to_s + EOL)<br />
475c486
buf.concat("Set-Cookie: " + cookie.to_s + EOL)<br />
479c490
buf.concat("Set-Cookie: " + cookie.to_s + EOL)<br />
485c496
buf.concat("Set-Cookie: " + cookie.to_s + EOL)<br />
490c501
buf.concat(key + ": " + value + EOL)<br />
502d512
class Cookie # #super(@value)<br />
661c668
buf.concat(@name + '=')<br />
664c671
buf.concat CGI::escape(@value)<br />
666c673
buf.concat(@value.collect{|v| CGI::escape(v) }.join("&"))<br />
670c677
buf.concat('; domain=' + @domain)<br />
674c681
buf.concat('; path=' + @path)<br />
678c685
buf.concat('; expires=' + CGI::rfc1123_date(@expires))<br />
682c689
buf.concat('; secure')<br />
705c712,714
if cookies.has_key?(name)<br />
> cookies[name].value.push(*values)
> else
737a747
> eval_methods = ''
739c749
eval_methods eval_methods eval(eval_methods)<br />
>
799,800c811,812
bodyvar = ''<br />
> isFile = false
813c825
bodyvar += buf[0 ... (buf.size - (EOL + boundary + EOL).size)]<br />
822c834
buf.concat c<br />
828c840
bodyvar += $1<br />
835,841d846
if $1 && (bodyvar.size&gt; 0) # Only create a temp file if necessary<br />
> isFile = true
> body = Tempfile.new("CGI")
> body.binmode
> body.print bodyvar
>
> body.rewind
>
> eval def body.local_path
> #{body.path.dump}
> end
> def body.original_filename
> #{
> filename = ($1 or "").dup
> if /Mac/ni.match(env_table['HTTP_USER_AGENT']) and
> /Mozilla/ni.match(env_table['HTTP_USER_AGENT']) and
> (not /MSIE/ni.match(env_table['HTTP_USER_AGENT']))
>
CGI::unescape(filename)
> else
> filename
> end.dump.untaint
> }.taint
> end
> END
>
> /Content-Type: (.*)/ni.match(head)
> eval def body.content_type
> #{($1 or "").dump.untaint}.taint
> end
> END
> end
866c883
/Content-Disposition:.* name="?([^";]*)"?/ni.match(head)<br />
870c887
isFile ? params[name].push(body) : params[name].push(bodyvar)<br />
872c889
isFile ? params[name] = [body] : params[name] = [bodyvar]<br />
1270c1287
body.concat hidden<br />
1344c1361
buf.concat( attributes.delete("DOCTYPE") )<br />
1349c1366
buf.concat( doctype )<br />
1353c1370
buf.concat( super(attributes){ yield } )<br />
1355c1372
buf.concat( super(attributes) )<br />
1734c1751
methods.concat( methods.concat( methods.concat( methods.concat( methods.concat( methods.concat( methods.concat( methods.concat( methods.concat( extend TagMaker<br />
> extend Html4Tr
> element_init()
1870c1890
methods.concat( methods.concat( eval "CGI_PARAMS = @params.nil? ? nil : @params.dup<br />
> CGI_COOKIES = @cookies.nil? ? nil : @cookies.dup"
1918,1919d1937
md5.hexdigest[0,32]<br />
40c40,45
#id, = request.cookies[session_key]<br />
> begin
> id = request.cookies[session_key].value[0]
> rescue Exception
> id = nil
> end
session.diff end
---
Multi-Part and Files (, 2002-03-16 13:41:28)
Creating temporary files for all the parts of a multi-part encoded form is pure silliness.
If there is some reason "it must be done" then at least you could only do it only for "large" objects.
Oh and they are "Tempfile" not "File" leading to issues when passed to libraries such as Ruby/GD.
cure for silliness and the blues.... (patsplat, 2002-03-16 23:14:16)
I agree that it's pretty silly to have tempfile objects for every parameter. It makes sense for uploaded files to be made into Tempfiles, but not much else. I got bothered the most, though, by the interface change.
My patch to don't create a temp file for each cgi parameter (, 2002-05-10 03:26:11)
I contains a modification to escape_url as well because I got some strange results with the original one.
It as well disable SimpleDelegator from Cookie Class because it slow down the cookie processing specially when using session.
The session diff against ruby 1.6.7 cgi/session as well follows it try to modify session to don't rely on SimpleDelegator.
In fact I don't know exact if there is unwanted side efects for session what I know is that it was working for me and a lot faster tha before.
Before open a session was consuming 40ms and now around 5ms.
Any question send to me at domingo@dad-it.com
cgi.diff start
---
6a7,8
> Version 2.1.4
>
170,172c172,174
> CGI.new("html4Tr") # html4.0 Transitional
> CGI.new("html4Fr") # html4.0 Frameset
186c188,192
> RELEASE_DATE = '2001-04-18'
> VERSION_CODE = 214
> RELEASE_CODE = 20010418
> REVISION = '$Id: cgi.rb.dad,v 1.1 2001/09/04 10:35:56 mingo Exp $'
238,240c244,247
> # string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
> # '%' + $1.unpack('H2' * $1.size).join('%').upcase
> # end.tr(' ', '+')
249,251c256,259
> # string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
> # [$1.delete('%')].pack('H*')
> # end
408,409c416
411,412d417
> options["type"].concat( options.delete("charset") )
425,428c431,439
> buf.concat( (HTTP_STATUS[options["status"]] or
> options["status"] or
> "200 OK"
> ) + EOL
> )
> buf.concat(
> "Date: " + CGI::rfc1123_date(Time.now) + EOL
> )
442,443c453,454
> buf.concat("Status: " + status + EOL)
448c459
452c463
455c466
458c469
462c473
466c477
472c483
475c486
479c490
485c496
490c501
502d512
661c668
664c671
666c673
670c677
674c681
678c685
682c689
705c712,714
> cookies[name].value.push(*values)
> else
737a747
> eval_methods = ''
739c749
>
799,800c811,812
> isFile = false
813c825
822c834
828c840
835,841d846
> isFile = true
> body = Tempfile.new("CGI")
> body.binmode
> body.print bodyvar
>
> body.rewind
>
> eval def body.local_path
> #{body.path.dump}
> end
> def body.original_filename
> #{
> filename = ($1 or "").dup
> if /Mac/ni.match(env_table['HTTP_USER_AGENT']) and
> /Mozilla/ni.match(env_table['HTTP_USER_AGENT']) and
> (not /MSIE/ni.match(env_table['HTTP_USER_AGENT']))
> CGI::unescape(filename)
> else
> filename
> end.dump.untaint
> }.taint
> end
> END
>
> /Content-Type: (.*)/ni.match(head)
> eval def body.content_type
> #{($1 or "").dump.untaint}.taint
> end
> END
> end
866c883
870c887
872c889
1270c1287
1344c1361
1349c1366
1353c1370
1355c1372
1734c1751
> extend Html4Tr
> element_init()
1870c1890
> CGI_COOKIES = @cookies.nil? ? nil : @cookies.dup"
1918,1919d1937
40c40,45
> begin
> id = request.cookies[session_key].value[0]
> rescue Exception
> id = nil
> end
session.diff end
---
Re: unify cgi handling of normal and multipart forms (patsplat, 2002-09-12 17:39:30)
so now I've rolled this change, and others into:
>