Ara.T.Howard
2005-Mar-13 01:19 UTC
[BUG] time handling/impossible rails time verification situation
i''ve been looking into rails time verification because mysql time
handling
blows. this, of course, is the ''feb 31st'' issue which keeps
cropping up on
the rails list, but it stems from the system impl of mktime/localtime which
''normalizes'' (man mktime) bad dates like ''feb
31st'' silently into ''march 2rd''.
eg. the underlying system calls ruby uses accept month=02 and day=31 as
arguments. this is doccumented and un-likely to change. o.k. so this will
always work in ruby (and c, python, etc.) :
[ahoward@localhost ~]$ ruby -e''p(Time::mktime(2000, 02,
31))''
Thu Mar 02 00:00:00 MST 2000
it''s possible ruby core could be RCR''d so some sort of
validating time method
could be added, but that''s probably a pretty slippery method to wait
for. so
this is problem one standing in the way of verifying time input : ruby/c
accepts fubar time input (well, fubar from a web app perspective).
problem two:
active_record/base.rb defines execute_callstack_for_multiparameter_attributes
to parse out multiparamter form fields and reconstitue them into viable args
to pass to a class constructor. fair enough (and kind of slick too) but this
means by the time form input reaches models for verification it already IS a
fubar time object and, thus, is valid any way you slice it. true, you could
do something horrible in your controller to extract the multiparam fields and
compare them against the year, month, day, etc. fields of your (already
constituted) Time object - but that''s getting silly.
so what to do?
i''ve come up with the following heuristic: if i pass a bunch of values
into
Time::local, and they wrap, i''ve got bad input. eg. if Time::local
was
passed
[2005, 2, 31, 0, 0, 0, 0]
and ends up with
time.month #=> 3
then the system mktime/localtime has normalized me and i can assume that the
input was fubar (out of ''normal'' human ranges).
o.k. - how to do that. i can think of a couple ways at the moment:
1) hack a check into execute_callstack_for_multiparameter_attributes, for
instance something like
--- active_record/base.rb.org 2005-03-12 16:58:20.869614941 -0700
+++ active_record/base.rb 2005-03-12 17:39:31.648893648 -0700
@@ -1284,6 +1284,15 @@
send(name + "=", nil)
else
begin
+ value + if Time == klass
+ t = klass.local(*values)
+ tvalues = %w(year month day hour min sec usec).map{|m|
t.send m}
+ values.zip(tvalues){|tin,tout| raise RangeError unless
tin != tout}
+ t
+ else
+ klass.new(*values)
+ end
send(name + "=", Time == klass ?
klass.local(*values) : klass.new(*values))
rescue => ex
errors << AttributeAssignmentError.new("error on
assignment #{values.inspect} to #{name}", ex, name)
basically this just checks that the year, month, day, etc. input into
Time::local came out the other side - eg. it did not get
''normalized''.
this is _alright_ but still leaves a million other places in rails where
one
could do
party = 1999
Time::local party, 2, 31
and either mean it - or not mean it.
2) supplant the built-in Time::local (and mktime, gm, utc) to do something
similar after alias''ing the originals. alternatively rails could
add new
time methods with this behavior (validating) and use them where
it''s
obvious they should be (unpacking form input for example).
3) provide hooks to access unpacked form fields as arrays so these can be
checked. this still requires quite a bit additional coding in the case
of times though...
i HOPE i''m missing something and there''s an elegant solution
to this, but so
far reading the source hasn''t really yielded much insight...
thoughts?
-a
--
==============================================================================|
EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
| PHONE :: 303.497.6469
| When you do something, you should burn yourself completely, like a good
| bonfire, leaving no trace of yourself. --Shunryu Suzuki
===============================================================================