diff --git a/fdroidserver/metadata.py b/fdroidserver/metadata.py index 4cd6b04d..a4203b08 100644 --- a/fdroidserver/metadata.py +++ b/fdroidserver/metadata.py @@ -908,6 +908,11 @@ def _normalize_type_string(v): numbers. Like "versionName: 1.0" would be a YAML float, but should be a string. + SHA-256 values are string values, but YAML 1.2 can interpret some + unquoted values as decimal ints. This converts those to a string + if they are over 50 digits. In the wild, the longest 0 padding on + a SHA-256 key fingerprint I found was 8 zeros. + """ if isinstance(v, bool): if v: @@ -921,6 +926,9 @@ def _normalize_type_string(v): if v > 0: return '.inf' return '-.inf' + if v and isinstance(v, int): + if math.log10(v) > 50: # only if the int has this many digits + return '%064d' % v return str(v) diff --git a/tests/metadata.TestCase b/tests/metadata.TestCase index deaf51e9..777002bc 100755 --- a/tests/metadata.TestCase +++ b/tests/metadata.TestCase @@ -343,6 +343,13 @@ class MetadataTest(unittest.TestCase): self.assertEqual('false', metadata._normalize_type_string(False)) self.assertEqual('true', metadata._normalize_type_string(True)) + def test_normalize_type_string_sha256(self): + """SHA-256 values are TYPE_STRING, which YAML can parse as decimal ints.""" + yaml = ruamel.yaml.YAML(typ='safe') + for v in range(1, 1000): + s = '%064d' % (v * (10**51)) + self.assertEqual(s, metadata._normalize_type_string(yaml.load(s))) + def test_normalize_type_stringmap_none(self): self.assertEqual(dict(), metadata._normalize_type_stringmap('key', None)) @@ -420,13 +427,13 @@ class MetadataTest(unittest.TestCase): with self.assertRaises(MetaDataException): metadata.post_parse_yaml_metadata(yamldata) - def test_post_parse_yaml_metadata_0padding_fails(self): - """Special case: ideally 0 padding would be kept in string, but it is not.""" + def test_post_parse_yaml_metadata_0padding_sha256(self): + """SHA-256 values are strings, but YAML 1.2 will read some as decimal ints.""" v = '0027293472934293872934729834729834729834729834792837487293847926' yaml = ruamel.yaml.YAML(typ='safe') yamldata = yaml.load('AllowedAPKSigningKeys: ' + v) metadata.post_parse_yaml_metadata(yamldata) - self.assertNotEqual(yamldata['AllowedAPKSigningKeys'], v) + self.assertEqual(yamldata['AllowedAPKSigningKeys'], [v]) def test_post_parse_yaml_metadata_builds(self): yamldata = OrderedDict() @@ -543,28 +550,6 @@ class MetadataTest(unittest.TestCase): with self.assertRaises(MetaDataException): metadata.parse_yaml_metadata(mf) - def test_parse_yaml_metadata_0padding_fails(self): - """Special case: ideally this would keep 0 padding in string, but it does not. - - It seems the only option is to quote values like that, or add - quirks to the YAML parsing. - - """ - v = '0027293472934293872934729834729834729834729834792837487293847926' - mf = io.StringIO('AllowedAPKSigningKeys: %s' % v) - mf.name = 'mock_filename.yaml' - self.assertNotEqual( - v, - metadata.parse_yaml_metadata(mf)['AllowedAPKSigningKeys'][0], - ) - - mf = io.StringIO('AllowedAPKSigningKeys: "%s"' % v) - mf.name = 'mock_filename.yaml' - self.assertEqual( - v, - metadata.parse_yaml_metadata(mf)['AllowedAPKSigningKeys'][0], - ) - def test_parse_yaml_metadata_unknown_app_field(self): mf = io.StringIO( textwrap.dedent( @@ -2146,6 +2131,24 @@ class PostMetadataParseTest(unittest.TestCase): self.assertEqual(*self._post_metadata_parse_build_script(123456, ['123456'])) self.assertEqual(*self._post_metadata_parse_build_string(123456, '123456')) + def test_post_metadata_parse_sha256(self): + """Run a SHA-256 that YAML calls an int through the various types. + + The current f-droid.org signer set has SHA-256 values with a + maximum of two leading zeros, but this will handle more. + + """ + yaml = ruamel.yaml.YAML(typ='safe', pure=True) + str_sha256 = '0000000000000498456908409534729834729834729834792837487293847926' + sha256 = yaml.load('a: ' + str_sha256)['a'] + self.assertEqual(*self._post_metadata_parse_app_list(sha256, [str_sha256])) + self.assertEqual(*self._post_metadata_parse_app_string(sha256, str_sha256)) + self.assertEqual(*self._post_metadata_parse_build_bool(sha256, True)) + self.assertEqual(*self._post_metadata_parse_build_int(sha256, sha256)) + self.assertEqual(*self._post_metadata_parse_build_list(sha256, [str_sha256])) + self.assertEqual(*self._post_metadata_parse_build_script(sha256, [str_sha256])) + self.assertEqual(*self._post_metadata_parse_build_string(sha256, str_sha256)) + def test_post_metadata_parse_int_0(self): """Run the int 0 through the various field and flag types.""" self.assertEqual(*self._post_metadata_parse_app_list(0, ['0']))