safely interpret integers in packed values

This commit is contained in:
Adrian Sampson 2010-08-06 11:17:57 -07:00
parent 633b97b302
commit c3988f7300
3 changed files with 74 additions and 31 deletions

1
NEWS
View file

@ -45,6 +45,7 @@
Windows users can now just type "beet" at the prompt to run beets.
* Fixed an occasional bug where Mutagen would complain that a tag was
already present.
* Fixed a bug with reading invalid integers from ID3 tags.
1.0b3
-----

View file

@ -62,6 +62,50 @@ TYPES = {
}
# Utility.
def _safe_cast(out_type, val):
"""Tries to covert val to out_type but will never raise an
exception. If the value can't be converted, then a sensible
default value is returned. out_type should be bool, int, or
unicode; otherwise, the value is just passed through.
"""
if out_type == int:
if val is None:
return 0
elif isinstance(val, int) or isinstance(val, float):
# Just a number.
return int(val)
else:
# Process any other type as a string.
if not isinstance(val, basestring):
val = unicode(val)
# Get a number from the front of the string.
val = re.match('[0-9]*', val.strip()).group(0)
if not val:
return 0
else:
return int(val)
elif out_type == bool:
if val is None:
return False
else:
try:
# Should work for strings, bools, ints:
return bool(int(val))
except ValueError:
return False
elif out_type == unicode:
if val is None:
return u''
else:
return unicode(val)
else:
return val
# Flags for encoding field behavior.
class Enumeration(object):
@ -146,9 +190,9 @@ class Packed(object):
out = None
if out is None or out == self.none_val or out == '':
return self.out_type(self.none_val)
return _safe_cast(self.out_type, self.none_val)
else:
return self.out_type(out)
return _safe_cast(self.out_type, out)
def __setitem__(self, index, value):
@ -336,35 +380,7 @@ class MediaField(object):
if style.packing:
out = Packed(out, style.packing)[style.pack_pos]
# return the appropriate type
if self.out_type == int:
if out is None:
return 0
elif isinstance(out, int) or isinstance(out, float):
# Just a number.
return int(out)
else:
# Process any other type as a string.
if not isinstance(out, basestring):
out = unicode(out)
# Get a number from the front of the string.
out = re.match('[0-9]*', out.strip()).group(0)
if not out:
return 0
else:
return int(out)
elif self.out_type == bool:
if out is None:
return False
else:
return bool(int(out)) # should work for strings, bools, ints
elif self.out_type == unicode:
if out is None:
return u''
else:
return unicode(out)
else:
return out
return _safe_cast(self.out_type, out)
def __set__(self, obj, val):
"""Set the value of this metadata field.

View file

@ -61,6 +61,32 @@ class EdgeTest(unittest.TestCase):
self.assertEqual(f.disc, 4)
self.assertEqual(f.disctotal, 5)
_sc = beets.mediafile._safe_cast
class InvalidValueToleranceTest(unittest.TestCase):
def test_packed_integer_with_extra_chars(self):
pack = beets.mediafile.Packed("06a", beets.mediafile.packing.SLASHED)
self.assertEqual(pack[0], 6)
def test_packed_integer_invalid(self):
pack = beets.mediafile.Packed("blah", beets.mediafile.packing.SLASHED)
self.assertEqual(pack[0], 0)
def test_packed_index_out_of_range(self):
pack = beets.mediafile.Packed("06", beets.mediafile.packing.SLASHED)
self.assertEqual(pack[1], 0)
def test_safe_cast_string_to_int(self):
self.assertEqual(_sc(int, 'something'), 0)
def test_safe_cast_int_string_to_int(self):
self.assertEqual(_sc(int, '20'), 20)
def test_safe_cast_string_to_bool(self):
self.assertEqual(_sc(bool, 'whatever'), False)
def test_safe_cast_intstring_to_bool(self):
self.assertEqual(_sc(bool, '5'), True)
class SafetyTest(unittest.TestCase):
def _exccheck(self, fn, exc):
fn = os.path.join('rsrc', fn)