diff options
-rw-r--r-- | apt-pkg/contrib/strutl.cc | 92 | ||||
-rw-r--r-- | test/libapt/strutil_test.cc | 42 |
2 files changed, 111 insertions, 23 deletions
diff --git a/apt-pkg/contrib/strutl.cc b/apt-pkg/contrib/strutl.cc index f2fc0b6a6..638f09e9d 100644 --- a/apt-pkg/contrib/strutl.cc +++ b/apt-pkg/contrib/strutl.cc @@ -694,34 +694,80 @@ int stringcasecmp(string::const_iterator A,string::const_iterator AEnd, /*}}}*/ // LookupTag - Lookup the value of a tag in a taged string /*{{{*/ // --------------------------------------------------------------------- -/* The format is like those used in package files and the method +/* The format is like those used in package files and the method communication system */ -string LookupTag(const string &Message,const char *Tag,const char *Default) +std::string LookupTag(const std::string &Message, const char *TagC, const char *Default) { - // Look for a matching tag. - int Length = strlen(Tag); - for (string::const_iterator I = Message.begin(); I + Length < Message.end(); ++I) + std::string tag = std::string("\n") + TagC + ":"; + if (Default == nullptr) + Default = ""; + if (Message.length() < tag.length()) + return Default; + std::transform(tag.begin(), tag.end(), tag.begin(), tolower_ascii); + auto valuestart = Message.cbegin(); + // maybe the message starts directly with tag + if (Message[tag.length() - 2] == ':') { - // Found the tag - if (I[Length] == ':' && stringcasecmp(I,I+Length,Tag) == 0) + std::string lowstart = std::string("\n") + Message.substr(0, tag.length() - 1); + std::transform(lowstart.begin(), lowstart.end(), lowstart.begin(), tolower_ascii); + if (lowstart == tag) + valuestart = std::next(valuestart, tag.length() - 1); + } + // the tag is somewhere in the message + if (valuestart == Message.cbegin()) + { + auto const tagbegin = std::search(Message.cbegin(), Message.cend(), tag.cbegin(), tag.cend(), + [](char const a, char const b) { return tolower_ascii(a) == b; }); + if (tagbegin == Message.cend()) + return Default; + valuestart = std::next(tagbegin, tag.length()); + } + auto const is_whitespace = [](char const c) { return isspace_ascii(c) != 0 && c != '\n'; }; + auto const is_newline = [](char const c) { return c == '\n'; }; + std::string result; + valuestart = std::find_if_not(valuestart, Message.cend(), is_whitespace); + // is the first line of the value empty? + if (valuestart != Message.cend() && *valuestart == '\n') + { + valuestart = std::next(valuestart); + if (valuestart != Message.cend() && *valuestart == ' ') + valuestart = std::next(valuestart); + } + // extract the value over multiple lines removing trailing whitespace + while (valuestart < Message.cend()) + { + auto const linebreak = std::find_if(valuestart, Message.cend(), is_newline); + auto valueend = std::prev(linebreak); + // skip spaces at the end of the line + while (valueend > valuestart && is_whitespace(*valueend)) + valueend = std::prev(valueend); + // append found line to result { - // Find the end of line and strip the leading/trailing spaces - string::const_iterator J; - I += Length + 1; - for (; isspace_ascii(*I) != 0 && I < Message.end(); ++I); - for (J = I; *J != '\n' && J < Message.end(); ++J); - for (; J > I && isspace_ascii(J[-1]) != 0; --J); - - return string(I,J); + std::string tmp(valuestart, std::next(valueend)); + if (tmp != ".") + { + if (result.empty()) + result.assign(std::move(tmp)); + else + result.append(tmp); + } } - - for (; *I != '\n' && I < Message.end(); ++I); - } - - // Failed to find a match - if (Default == 0) - return string(); - return Default; + // see if the value is multiline + if (linebreak == Message.cend()) + break; + valuestart = std::next(linebreak); + if (valuestart == Message.cend() || *valuestart != ' ') + break; + result.append("\n"); + // skip the space leading a multiline (Keep all other whitespaces in the value) + valuestart = std::next(valuestart); + } + auto const valueend = result.find_last_not_of("\n"); + if (valueend == std::string::npos) + result.clear(); + else + result.erase(valueend + 1); + return result; } /*}}}*/ // StringToBool - Converts a string into a boolean /*{{{*/ diff --git a/test/libapt/strutil_test.cc b/test/libapt/strutil_test.cc index 9c192a58b..f531c2bf9 100644 --- a/test/libapt/strutil_test.cc +++ b/test/libapt/strutil_test.cc @@ -319,3 +319,45 @@ TEST(StrUtilTest,RFC1123StrToTime) EXPECT_FALSE(RFC1123StrToTime("Sunday, 06-Nov-94 08:49:37 -0100", t)); EXPECT_FALSE(RFC1123StrToTime("Sunday, 06-Nov-94 08:49:37 -0.1", t)); } +TEST(StrUtilTest, LookupTag) +{ + EXPECT_EQ("", LookupTag("", "Field", "")); + EXPECT_EQ("", LookupTag("", "Field", nullptr)); + EXPECT_EQ("default", LookupTag("", "Field", "default")); + EXPECT_EQ("default", LookupTag("Field1: yes", "Field", "default")); + EXPECT_EQ("default", LookupTag("Fiel: yes", "Field", "default")); + EXPECT_EQ("default", LookupTag("Fiel d: yes", "Field", "default")); + EXPECT_EQ("foo", LookupTag("Field: foo", "Field", "default")); + EXPECT_EQ("foo", LookupTag("Field: foo\n", "Field", "default")); + EXPECT_EQ("foo", LookupTag("\nField: foo\n", "Field", "default")); + EXPECT_EQ("foo", LookupTag("Field:foo", "Field", "default")); + EXPECT_EQ("foo", LookupTag("Field:foo\n", "Field", "default")); + EXPECT_EQ("foo", LookupTag("\nField:foo\n", "Field", "default")); + EXPECT_EQ("foo", LookupTag("Field:\tfoo\n", "Field", "default")); + EXPECT_EQ("foo", LookupTag("Field: foo \t", "Field", "default")); + EXPECT_EQ("foo", LookupTag("Field: foo \t\n", "Field", "default")); + EXPECT_EQ("Field : yes", LookupTag("Field: Field : yes \t\n", "Field", "default")); + EXPECT_EQ("Field : yes", LookupTag("Field:\n Field : yes \t\n", "Field", "default")); + EXPECT_EQ("Field : yes", LookupTag("Foo: bar\nField: Field : yes \t\n", "Field", "default")); + EXPECT_EQ("line1\nline2", LookupTag("Multi: line1\n line2", "Multi", "default")); + EXPECT_EQ("line1\nline2", LookupTag("Multi: line1\n line2\n", "Multi", "default")); + EXPECT_EQ("line1\nline2", LookupTag("Multi:\n line1\n line2\n", "Multi", "default")); + EXPECT_EQ("line1\n\nline2", LookupTag("Multi:\n line1\n .\n line2\n", "Multi", "default")); + EXPECT_EQ("line1\na\nline2", LookupTag("Multi:\n line1\n a\n line2\n", "Multi", "default")); + EXPECT_EQ("line1\nfoo\nline2", LookupTag("Multi:\n line1\n foo\n line2\n", "Multi", "default")); + EXPECT_EQ("line1\n line2", LookupTag("Multi: line1\n line2", "Multi", "default")); + EXPECT_EQ(" line1\n \t line2", LookupTag("Multi:\t \n line1\n \t line2\n", "Multi", "default")); + EXPECT_EQ(" line1\n\n\n \t line2", LookupTag("Multi:\t \n line1\n .\n . \n \t line2\n", "Multi", "default")); + + std::string const msg = + "Field1: Value1\nField2:Value2\nField3:\t Value3\n" + "Multi-Field1: Line1\n Line2\nMulti-Field2:\n Line1\n Line2\n" + "Field4: Value4\nField5:Value5"; + EXPECT_EQ("Value1", LookupTag(msg, "Field1", "")); + EXPECT_EQ("Value2", LookupTag(msg, "Field2", "")); + EXPECT_EQ("Value3", LookupTag(msg, "Field3", "")); + EXPECT_EQ("Line1\nLine2", LookupTag(msg, "Multi-Field1", "")); + EXPECT_EQ("Line1\nLine2", LookupTag(msg, "Multi-Field2", "")); + EXPECT_EQ("Value4", LookupTag(msg, "Field4", "")); + EXPECT_EQ("Value5", LookupTag(msg, "Field5", "")); +} |