From 2c86ed6389d741cfe41323937e9e93b89935490e Mon Sep 17 00:00:00 2001 From: Pascal Terjan Date: Tue, 25 Oct 2022 20:29:46 +0000 Subject: Fix parsing of properties containing [ For example, the name extracted from a requirement of "python3.10dist(fonttools[unicode])[>= 4.10]" was sometimes "python3.10dist(fonttools" instead of the expected "python3.10dist(fonttools[unicode])". Code parsing such strings existed in many places, it now exists only in 2 places, a perl version in Resolve.pm and a C version in URPM.xs. Both codes used to handle both "foo >= 0" and "foo[>= 0]" but at least the perl code seems to only call it on provides/conflicts/obsoletes which are always using the second form so the support for it was dropped from the perl version for the sake of simplicity. --- URPM.xs | 223 +++++++++++++++++++++++++++++++++++++------------------- URPM/Resolve.pm | 26 ++++++- t/resolve.t | 46 ++++++++++++ t/synthesis.t | 52 ++++++++++++- 4 files changed, 268 insertions(+), 79 deletions(-) create mode 100644 t/resolve.t diff --git a/URPM.xs b/URPM.xs index 7d48acf..f1e37dd 100644 --- a/URPM.xs +++ b/URPM.xs @@ -309,6 +309,73 @@ ranges_overlap(rpmsenseFlags aflags, char *sa, rpmsenseFlags bflags, char *sb) { } } +struct property { + char *name; + rpmsenseFlags flags; + char *evr; +}; + +static void free_property(struct property *ps) { + free(ps->name); + ps->name = NULL; + ps->flags = 0; + free(ps->evr); + ps->evr = NULL; +} + +// This parses things like 'ocamlx(bar)[== 42]' or 'ocaml-bar < 42' +static int parse_property(char *s, struct property *ps) { + int l = strlen(s); + char *eon = NULL; + + ps->name = NULL; + ps->flags = 0; + ps->evr = NULL; + + if (l == 0) return -1; + + if (s[l-1] == ']') { + eon = strrchr(s, '['); + + if (eon == NULL || eon == s) { + // This is a strange one finishing with ] but without a [ before like + // "Provides: foo]" or something without a name before like + // "Provides: [foo]" + ps->name = strdup(s); + ps->evr = strdup(""); + return 0; + } + } + + /* Drop "[*]" if present at the end of the name */ + if (eon-s > 3 && !strncmp(eon-3, "[*]", 3)) { + eon = eon-3; + } + + if (eon == NULL) { + // This does not have [ ] at the end but could be in the form "gcc < 42" + eon = s; + while (*eon && *eon != ' ' && *eon != '<' && *eon != '>') ++eon; + } + + ps->name = strndup(s, eon-s); + l = strlen(eon); + s = eon; + while (*s) { + if (*s == ' ' || *s == '[' || *s == '*' || *s == ']'); + else if (*s == '<') ps->flags |= RPMSENSE_LESS; + else if (*s == '>') ps->flags |= RPMSENSE_GREATER; + else if (*s == '=') ps->flags |= RPMSENSE_EQUAL; + else break; + ++s; + --l; + } + ps->evr = strndup(s, l-1); + /* fprintf(stderr, "name=\"%s\" evr=\"%s\"\n", ps->name, ps->evr); */ + + return 0; +} + typedef int (*callback_list_str)(char *s, int slen, const char *name, const rpmsenseFlags flags, const char *evr, void *param); static int @@ -339,9 +406,8 @@ callback_list_str_overlap(char *s, int slen, const char *name, rpmsenseFlags fla struct cb_overlap_s *os = (struct cb_overlap_s *)param; int result = 0; char *eos = NULL; - char *eon = NULL; char eosc = '\0'; - char eonc = '\0'; + struct property ops; /* we need to extract name, flags and evr from a full sense information, store result in local copy */ if (s) { @@ -350,28 +416,16 @@ callback_list_str_overlap(char *s, int slen, const char *name, rpmsenseFlags fla eosc = *eos; *eos = 0; } - name = s; - while (*s && *s != ' ' && *s != '[' && *s != '<' && *s != '>' && *s != '=') ++s; - if (*s) { - eon = s; - while (*s) { - if (*s == ' ' || *s == '[' || *s == '*' || *s == ']'); - else if (*s == '<') flags |= RPMSENSE_LESS; - else if (*s == '>') flags |= RPMSENSE_GREATER; - else if (*s == '=') flags |= RPMSENSE_EQUAL; - else break; - ++s; - } - evr = s; - } else + if (!parse_property(s, &ops)) { + name = ops.name; + flags = ops.flags; + evr = ops.evr; + } else { + name = s; evr = ""; + } } - /* mark end of name */ - if (eon) { - eonc = *eon; - *eon = 0; - } /* names should be equal, else it will not overlap */ if (!strcmp(name, os->name)) { /* perform overlap according to direction needed, negative for left */ @@ -384,9 +438,12 @@ callback_list_str_overlap(char *s, int slen, const char *name, rpmsenseFlags fla /* fprintf(stderr, "cb_list_str_overlap result=%d, os->direction=%d, os->name=%s, os->evr=%s, name=%s, evr=%s\n", result, os->direction, os->name, os->evr, name, evr); */ - /* restore s if needed */ - if (eon) *eon = eonc; - if (eos) *eos = eosc; + if (s) { + free_property(&ops); + + /* restore s if needed */ + if (eos) *eos = eosc; + } return result; } @@ -408,21 +465,29 @@ return_list_str(char *s, const Header header, rpmTag tag_name, rpmTag tag_flags, if (f(s, 0, NULL, 0, NULL, param)) return -count; } else { - char *eos; + struct property ops; while(ps != NULL) { - *ps = 0; eos = strchr(s, '['); if (!eos) eos = strchr(s, ' '); - ++count; - if (f(s, eos ? eos-s : ps-s, NULL, 0, NULL, param)) { - *ps = '@'; - return -count; + *ps = 0; + if (!parse_property(s, &ops)) { + ++count; + if (f(ops.name, strlen(ops.name), NULL, 0, NULL, param)) { + *ps = '@'; + free_property(&ops); + return -count; + } + free_property(&ops); } - *ps = '@'; /* restore in memory modified char */ - s = ps + 1; ps = strchr(s, '@'); + *ps = '@'; /* restore in memory modified char */ + s = ps + 1; ps = strchr(s, '@'); } - eos = strchr(s, '['); if (!eos) eos = strchr(s, ' '); ++count; - if (f(s, eos ? eos-s : 0, NULL, 0, NULL, param)) - return -count; + if (!parse_property(s, &ops)) { + if (f(ops.name, strlen(ops.name), NULL, 0, NULL, param)) { + free_property(&ops); + return -count; + } + free_property(&ops); + } } } else if (header) { struct rpmtd_s list, flags, list_evr; @@ -879,6 +944,7 @@ update_provides(const URPM__Package pkg, HV *provides) { } } else { char *ps, *s, *es; + struct property ops; if ((s = pkg->requires) != NULL && *s != 0) { ps = strchr(s, '@'); @@ -900,12 +966,24 @@ update_provides(const URPM__Package pkg, HV *provides) { if ((s = pkg->provides) != NULL && *s != 0) { ps = strchr(s, '@'); while(ps != NULL) { - *ps = 0; es = strchr(s, '['); if (!es) es = strchr(s, ' '); *ps = '@'; - update_hash_entry(provides, s, es != NULL ? es-s : ps-s, 1, es != NULL, pkg); + *ps = 0; + if (parse_property(s, &ops)) { + /* Failed to parse, use the whole string */ + update_hash_entry(provides, s, ps-s, 1, 0, pkg); + } else { + update_hash_entry(provides, s, strlen(ops.name), 1, ops.flags != 0, pkg); + free_property(&ops); + } + *ps = '@'; s = ps + 1; ps = strchr(s, '@'); } - es = strchr(s, '['); if (!es) es = strchr(s, ' '); - update_hash_entry(provides, s, es != NULL ? es-s : 0, 1, es != NULL, pkg); + if (parse_property(s, &ops)) { + /* Failed to parse, use the whole string */ + update_hash_entry(provides, s, ps-s, 1, 0, pkg); + } else { + update_hash_entry(provides, s, strlen(ops.name), 1, ops.flags != 0, pkg); + free_property(&ops); + } } } } @@ -925,16 +1003,28 @@ update_obsoletes(const URPM__Package pkg, HV *obsoletes) { char *ps, *s; if ((s = pkg->obsoletes) != NULL && *s != 0) { - char *es; + struct property ops; ps = strchr(s, '@'); while(ps != NULL) { - *ps = 0; es = strchr(s, '['); if (!es) es = strchr(s, ' '); *ps = '@'; - update_hash_entry(obsoletes, s, es != NULL ? es-s : ps-s, 1, 0, pkg); + *ps = 0; + if (parse_property(s, &ops)) { + /* Failed to parse, use the whole string */ + update_hash_entry(obsoletes, s, ps-s, 1, 0, pkg); + } else { + update_hash_entry(obsoletes, s, strlen(ops.name), 1, 0, pkg); + free_property(&ops); + } + *ps = '@'; s = ps + 1; ps = strchr(s, '@'); } - es = strchr(s, '['); if (!es) es = strchr(s, ' '); - update_hash_entry(obsoletes, s, es != NULL ? es-s : 0, 1, 0, pkg); + if (parse_property(s, &ops)) { + /* Failed to parse, use the whole string */ + update_hash_entry(obsoletes, s, strlen(s), 1, 0, pkg); + } else { + update_hash_entry(obsoletes, s, ops.flags ? strlen(ops.name) : 0, 1, 0, pkg); + free_property(&ops); + } } } } @@ -1507,7 +1597,6 @@ static int get_e_v_r(URPM__Package pkg, int *epoch, char **version, char **relea return 0; } - MODULE = URPM PACKAGE = URPM::Package PREFIX = Pkg_ void @@ -1991,8 +2080,7 @@ Pkg_obsoletes_overlap(pkg, s) provides_overlap = 1 PREINIT: struct cb_overlap_s os; - char *eon = NULL; - char eonc = '\0'; + struct property ps; rpmTag tag_name; rpmTag tag_flags, tag_version; CODE: @@ -2008,33 +2096,20 @@ Pkg_obsoletes_overlap(pkg, s) tag_version = RPMTAG_OBSOLETEVERSION; break; } - os.name = s; - os.flags = 0; - while (*s && *s != ' ' && *s != '[' && *s != '<' && *s != '>' && *s != '=') ++s; - if (*s) { - eon = s; - while (*s) { - if (*s == ' ' || *s == '[' || *s == '*' || *s == ']'); - else if (*s == '<') os.flags |= RPMSENSE_LESS; - else if (*s == '>') os.flags |= RPMSENSE_GREATER; - else if (*s == '=') os.flags |= RPMSENSE_EQUAL; - else break; - ++s; - } - os.evr = s; - } else - os.evr = ""; - os.direction = ix == 0 ? -1 : 1; - /* mark end of name */ - if (eon) { - eonc = *eon; - *eon = 0; - } - /* return_list_str returns a negative value is the callback has returned non-zero */ - RETVAL = return_list_str(ix == 0 ? pkg->obsoletes : pkg->provides, pkg->h, tag_name, tag_flags, tag_version, - callback_list_str_overlap, &os) < 0; - /* restore end of name */ - if (eon) *eon = eonc; + if (parse_property(s, &ps)) { + fprintf(stderr, "provides_overlap: failed to parse property %s\n", s); + RETVAL = -1; + } else { + os.name = ps.name; + os.flags = ps.flags; + os.evr = ps.evr; + os.direction = ix == 0 ? -1 : 1; + fprintf(stderr, "provides_overlap: property=\"%s\" name=\"%s\" evr=\"%s\"\n", s, os.name, os.evr); + /* return_list_str returns a negative value is the callback has returned non-zero */ + RETVAL = return_list_str(ix == 0 ? pkg->obsoletes : pkg->provides, pkg->h, tag_name, tag_flags, tag_version, + callback_list_str_overlap, &os) < 0; + free_property(&ps); + } OUTPUT: RETVAL diff --git a/URPM/Resolve.pm b/URPM/Resolve.pm index dded564..1816eb1 100644 --- a/URPM/Resolve.pm +++ b/URPM/Resolve.pm @@ -58,7 +58,12 @@ Returns the property name (eg: "mageia-release" in above example) sub property2name { my ($property) = @_; - $property =~ /^([^\s\[]*)/ && $1; + my $name = $property; + if ($property =~ /^([^\s]+)\s*\[[^\[]*\]$/) { + $name = $1; + } + $name =~ s/\[\*\]$//; + return $name; } =item property2name_range($property) @@ -69,7 +74,14 @@ Returns the property name & range (eg: "mageia-release" & ">= 1" in above exampl sub property2name_range { my ($property) = @_; - $property =~ /^([^\s\[]*)(?:\[\*\])?\[?([^\s\]]*\s*[^\s\]]*)/; + my ($name, $range) = ($property, ""); + $name =~ s/\[\*\]$//; + if ($name =~ /^([^\s]+)\s*\[([^\[]*)\]$/) { + $name = $1; + $range = $2; + } + $name =~ s/\[\*\]$//; + return ($name, $range); } =item property2name_op_version($property) @@ -81,7 +93,15 @@ Returns the property name, operator & range (eg: "mageia-release", ">=", & "1" i sub property2name_op_version { my ($property) = @_; - $property =~ /^([^\s\[]*)(?:\[\*\])?\s*\[?([^\s\]]*)\s*([^\s\]]*)/; + my ($name, $op, $version) = ($property, "", ""); + $name =~ s/\[\*\]$//; + if ($name =~ /^([^\s]+)\s*\[([^\s\[]*)\s*([^\s\]]*)\]$/) { + $name = $1; + $op = $2; + $version = $3; + } + $name =~ s/\[\*\]$//; + return ($name, $op, $version); } diff --git a/t/resolve.t b/t/resolve.t new file mode 100644 index 0000000..56869b1 --- /dev/null +++ b/t/resolve.t @@ -0,0 +1,46 @@ +#!/usr/bin/perl + +use strict ; +use warnings ; +use Test::More; +use URPM::Resolve; + +my $testcases = [ + { + property => 'mageia-release[>= 1]', + name => 'mageia-release', + op => '>=', + version => '1', + }, { + property => 'python3.10dist(fonttools[unicode])[>= 4.10]', + name => 'python3.10dist(fonttools[unicode])', + op => '>=', + version => '4.10', + }, { + property => 'python3.10dist(fonttools[unicode])', + name => 'python3.10dist(fonttools[unicode])', + op => '', + version => '', + }, { + property => 'openssl[*][>= 0.9.7]', + name => 'openssl', + op => '>=', + version => '0.9.7', + }, { + property => 'openssl[*]', + name => 'openssl', + op => '', + version => '', + }]; + +foreach my $tc (@$testcases) { + is(URPM::property2name($tc->{property}), $tc->{name}, "property2name(\"$tc->{property}\")"); + my $expected = [$tc->{name}, $tc->{op} ? "$tc->{op} $tc->{version}" : ""]; + my @got = URPM::property2name_range($tc->{property}); + is_deeply(\@got, $expected, "property2name_range(\"$tc->{property}\")"); + $expected = [$tc->{name}, $tc->{op}, $tc->{version}]; + @got = URPM::property2name_op_version($tc->{property}); + is_deeply(\@got, $expected, "property2name_op_version(\"$tc->{property}\")"); +} + +done_testing(); diff --git a/t/synthesis.t b/t/synthesis.t index 8246b36..794fd7a 100644 --- a/t/synthesis.t +++ b/t/synthesis.t @@ -2,7 +2,7 @@ use strict ; use warnings ; -use Test::More tests => 95; +use Test::More tests => 113; use URPM; chdir 't' if -d 't'; @@ -27,7 +27,25 @@ $s =~ s/-devel//g; print $f $s; close $f; -END { unlink $file1, $file2 } +$s = <<'EOF'; +@provides@python3-fonttools+ufo[== 4.18.2-4.mga9]@python3dist(fonttools[ufo])[== 4.18.2] +@summary@Metapackage for python3-fonttools: ufo extras +@info@python3-fonttools+ufo-4.18.2-4.mga9.noarch@0@221683@Development/Other +@provides@config(dovecot)[== 2.3.19.1-2.mga9] +@requires@openssl[*]@rpm-helper[*][>= 0.21] +@summary@Secure IMAP and POP3 server +@info@dovecot-2.3.19.1-2.mga9.x86_64@0@17815993@System/Servers +@provides@openimageio[== 2.3.20.0-2.mga9]@font(:lang=aa) +@summary@Library for reading and writing images +@info@openimageio-2.3.20.0-2.mga9.x86_64@0@42@Development/Other +EOF + +my $file3 = 'synthesis.squarebrackets.cz'; +open $f, "| gzip -9 >$file3"; +print $f $s; +close $f; + +END { unlink $file1, $file2, $file3 } my $a = URPM->new; ok($a); @@ -161,3 +179,33 @@ ok($requires[8] eq 'rpmlib(CompressedFileNames)'); @provides = $pkg->provides_nosense; ok(@provides == 1); ok($provides[0] eq 'glibc-devel'); + +my $b = URPM->new; +$b->parse_synthesis($file3); +$pkg = $b->{depslist}[0]; +ok($pkg); +is($pkg->name, 'python3-fonttools+ufo'); +@provides = $pkg->provides; +is(@provides, 2); +is($provides[1], 'python3dist(fonttools[ufo])[== 4.18.2]'); +@provides = $pkg->provides_nosense; +is(@provides, 2); +is($provides[1], 'python3dist(fonttools[ufo])'); +$pkg = $b->{depslist}[1]; +ok($pkg); +is($pkg->name, 'dovecot'); +@requires = $pkg->requires; +is(@requires, 2); +is($requires[0], 'openssl[*]'); +is($requires[1], 'rpm-helper[*][>= 0.21]'); +@requires = $pkg->requires_nosense; +is(@requires, 2); +is($requires[0], 'openssl'); +is($requires[1], 'rpm-helper'); +$pkg = $b->{depslist}[2]; +ok($pkg); +is($pkg->name, 'openimageio'); +@provides = $pkg->provides; +is(@provides, 2); +is($provides[1], 'font(:lang=aa)'); + -- cgit v1.2.1