summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Kalnischkies <david@kalnischkies.de>2017-07-17 10:52:09 +0200
committerDavid Kalnischkies <david@kalnischkies.de>2017-12-13 23:56:29 +0100
commit07cd99066c30e70a9f41851c80e7c51f4e507163 (patch)
tree5bdaf316c93482f8b77a2b16e3467c6d679d57f1
parent99813a2eaa7c0cce1d7d8c811827733ed66458de (diff)
support multiline values in LookupTag
LookupTag is a little helper to deal with rfc822-style strings we use in apt e.g. to pass acquire messages around for cases in which our usual rfc822 parser is too heavy. All the fields it had to deal with so far were single line, but if they aren't it should really produce the right output and not just return the first line. Error messages are a prime candidate for becoming multiline as at the moment they are stripped of potential newlines due to the previous insufficiency of LookupTag.
-rw-r--r--apt-pkg/contrib/strutl.cc92
-rw-r--r--test/libapt/strutil_test.cc42
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", ""));
+}