Imported Upstream version 14.46.0 upstream/14.46.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 2 Sep 2019 07:09:30 +0000 (16:09 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 2 Sep 2019 07:09:30 +0000 (16:09 +0900)
15 files changed:
CMakeLists.txt
VERSION.cmake
package/libzypp.changes
tests/zypp/CMakeLists.txt
tests/zypp/PoolQueryCC_test.cc [new file with mode: 0644]
tests/zypp/PoolQuery_test.cc
tests/zypp/StrMatcher_test.cc
tests/zypp/base/String_test.cc
tests/zypp/data/PoolQuery/savedqueries
tests/zypp/data/PoolQueryCC/rxnames.xml [new file with mode: 0644]
zypp/Locks.cc
zypp/PoolQuery.cc
zypp/PoolQuery.h
zypp/base/String.cc
zypp/base/String.h

index 47786d4..3a476d3 100644 (file)
@@ -91,8 +91,6 @@ MACRO(ADD_TESTS)
 ENDMACRO(ADD_TESTS)
 
 ####################################################################
-# prefer packages using the same install prefix as we do
-SET(CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX} usr/localX /usr/local /usr)
 
 FIND_PACKAGE(Rpm REQUIRED)
 IF ( NOT RPM_FOUND)
index 496bf85..b75578d 100644 (file)
@@ -60,9 +60,9 @@
 #
 SET(LIBZYPP_MAJOR "14")
 SET(LIBZYPP_COMPATMINOR "39")
-SET(LIBZYPP_MINOR "45")
-SET(LIBZYPP_PATCH "18")
+SET(LIBZYPP_MINOR "46")
+SET(LIBZYPP_PATCH "0")
 #
-# LAST RELEASED: 14.45.18 (39)
+# LAST RELEASED: 14.46.0 (39)
 # (The number in parenthesis is LIBZYPP_COMPATMINOR)
 #=======
index 7181c36..ce915aa 100644 (file)
@@ -1,4 +1,11 @@
 -------------------------------------------------------------------
+Tue Oct  2 12:36:18 CEST 2018 - ma@suse.de
+
+- Fix conversion of string and glob to regex when compiling queries
+  (bsc#1099982, bsc#939392, bsc#556664)
+- version 14.46.0 (39)
+
+-------------------------------------------------------------------
 Mon Oct  1 14:37:39 CEST 2018 - ma@suse.de
 
 - Fix blocking wait for finished child process (bsc#1109877)
index a2d5d9c..cba941b 100644 (file)
@@ -26,6 +26,7 @@ ADD_TESTS(
   PathInfo
   Pathname
   PluginFrame
+  PoolQueryCC
   PoolQuery
   ProgressData
   PtrTypes
diff --git a/tests/zypp/PoolQueryCC_test.cc b/tests/zypp/PoolQueryCC_test.cc
new file mode 100644 (file)
index 0000000..6c50db4
--- /dev/null
@@ -0,0 +1,121 @@
+#include "TestSetup.h"
+#include <zypp/base/String.h>
+#include <zypp/base/LogTools.h>
+
+#include "zypp/PoolQuery.h"
+#include "zypp/PoolQueryUtil.tcc"
+
+#define BOOST_TEST_MODULE PoolQuery_CC
+
+using boost::unit_test::test_case;
+using std::cin;
+using std::cout;
+using std::cerr;
+using std::endl;
+using namespace zypp;
+
+static TestSetup test;
+
+/////////////////////////////////////////////////////////////////////////////
+template <class TCont>
+std::ostream & nlist( std::ostream & str, const TCont & set_r )
+{
+  str << "[" << set_r.size() << "]: ";
+  for ( const auto & solv : set_r )
+    str << " \"" << solv.name() << "\"";
+  return str << endl;
+}
+
+BOOST_AUTO_TEST_CASE(init)
+{
+  test.loadTargetHelix( TESTS_SRC_DIR "/zypp/data/PoolQueryCC/rxnames.xml" );
+  nlist( cout << "repo ", ResPool::instance() );
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Basic issue: Multiple match strings are compiled into a singe regex. The
+// semantic of the individual match strings must be preserved. I.e. a literal
+// "." must become "\.". Globbing patterns must match the whole string, so they
+// need to be anchored within the regex. Etc.
+/////////////////////////////////////////////////////////////////////////////
+static const unsigned qtestSIZEMISS    = unsigned(-1);
+static const unsigned qtestRXFAIL      = unsigned(-2);
+static const unsigned qtestRXFAILCOMB  = unsigned(-3);
+
+unsigned qtest( const std::string & pattern_r, Match::Mode mode_r, bool verbose_r = false )
+{
+  static constexpr const bool noMatchInvalidRegexException = false;
+
+  typedef std::set<sat::Solvable> Result;
+  PoolQuery q;
+  q.addAttribute(sat::SolvAttr::name);
+  switch ( mode_r )
+  {
+    case Match::STRING:                q.setMatchExact();      break;
+    case Match::SUBSTRING:     q.setMatchSubstring();  break;
+    case Match::OTHER:         q.setMatchWord();       break;  // OTHER missused for matchWord()
+    case Match::GLOB:          q.setMatchGlob();       break;
+    case Match::REGEX:         q.setMatchRegex();      break;
+    default:
+      throw( "unhandled match mode" );
+      break;
+  }
+  q.addString( pattern_r );
+  Result o;
+  try {
+    o = Result( q.begin(), q.end() );  // original query
+  }
+  catch ( const zypp::MatchInvalidRegexException & excpt )
+  {
+    cout << "Caught: " << excpt << endl;
+    return qtestRXFAIL;
+  }
+
+  q.addString( "more" );
+  try {
+    Result r( q.begin(), q.end() );    // compiles into RX (o|more)
+
+    BOOST_CHECK( o == r );
+    if ( o != r || verbose_r )
+    {
+      cout << '"' << pattern_r << "\"  " << mode_r << endl;
+      nlist( cout << "    o", o );
+      nlist( cout << "    r", r );
+      if ( ! verbose_r )
+       return qtestSIZEMISS;
+    }
+  }
+  catch ( const zypp::MatchInvalidRegexException & excpt )
+  {
+    BOOST_CHECK( noMatchInvalidRegexException );
+    cout << "Caught: " << excpt << endl;
+    return qtestRXFAILCOMB;
+  }
+
+  return o.size();
+}
+
+inline unsigned qtest( const std::string & pattern_r, bool verbose_r = false )
+{ return qtest( pattern_r, Match::SUBSTRING, verbose_r ); }
+
+/////////////////////////////////////////////////////////////////////////////
+BOOST_AUTO_TEST_CASE(pool_query_init)
+{
+  // NOTE: qtest( , Match::OTHER ) is missused for matchWord()
+  BOOST_CHECK_EQUAL( qtest( "?", Match::SUBSTRING ),   1 );
+  BOOST_CHECK_EQUAL( qtest( "?", Match::STRING ),      1 );
+  BOOST_CHECK_EQUAL( qtest( "?", Match::OTHER ),       0 );    // not word boundary
+  BOOST_CHECK_EQUAL( qtest( "?", Match::GLOB ),                15 );
+  BOOST_CHECK_EQUAL( qtest( "\\?", Match::GLOB ),      1 );
+  BOOST_CHECK_EQUAL( qtest( "?", Match::REGEX ),       qtestRXFAIL );
+  BOOST_CHECK_EQUAL( qtest( "\\?", Match::REGEX ),     1 );
+
+  BOOST_CHECK_EQUAL( qtest( "A", Match::SUBSTRING ),   4 );
+  BOOST_CHECK_EQUAL( qtest( "A", Match::OTHER ),       2 );
+  BOOST_CHECK_EQUAL( qtest( "A*", Match::OTHER ),      0 );
+  BOOST_CHECK_EQUAL( qtest( "*A", Match::OTHER ),      0 );
+  BOOST_CHECK_EQUAL( qtest( "A*", Match::GLOB ),       2 );
+  BOOST_CHECK_EQUAL( qtest( "*A", Match::GLOB ),       1 );
+}
+
+/////////////////////////////////////////////////////////////////////////////
index 1f9e3a3..544a21d 100644 (file)
@@ -606,7 +606,6 @@ BOOST_AUTO_TEST_CASE(pool_query_recovery)
   q.addRepo("opensuse");
   q.addKind(ResKind::patch);
   q.setMatchRegex();
-  q.setRequireAll();
   q.setCaseSensitive();
   q.setUninstalledOnly();
   q.setEdition(Edition("0.8.3"),Rel::NE);
index ec2e157..3dddf0c 100644 (file)
@@ -103,6 +103,8 @@ BOOST_AUTO_TEST_CASE(StrMatcher_STRING)
   BOOST_CHECK( !m( "" ) );
   BOOST_CHECK( !m( "a" ) );
   BOOST_CHECK( m( "fau" ) );
+  BOOST_CHECK( !m( "fault" ) );
+  BOOST_CHECK( !m( "defau" ) );
   BOOST_CHECK( !m( "default" ) );
 }
 
@@ -113,6 +115,7 @@ BOOST_AUTO_TEST_CASE(StrMatcher_STRINGSTART)
   BOOST_CHECK( !m( "a" ) );
   BOOST_CHECK( m( "fau" ) );
   BOOST_CHECK( m( "fault" ) );
+  BOOST_CHECK( !m( "defau" ) );
   BOOST_CHECK( !m( "default" ) );
 }
 
@@ -122,12 +125,61 @@ BOOST_AUTO_TEST_CASE(StrMatcher_STRINGEND)
   BOOST_CHECK( !m( "" ) );
   BOOST_CHECK( !m( "a" ) );
   BOOST_CHECK( m( "fau" ) );
+  BOOST_CHECK( !m( "fault" ) );
   BOOST_CHECK( m( "defau" ) );
   BOOST_CHECK( !m( "default" ) );
 }
 
+BOOST_AUTO_TEST_CASE(StrMatcher_GLOB)
+{
+  // GLOB must match whole word
+  StrMatcher m( "f[a]u", Match::GLOB );
+  BOOST_CHECK( !m( "" ) );
+  BOOST_CHECK( !m( "a" ) );
+  BOOST_CHECK( m( "fau" ) );
+  BOOST_CHECK( !m( "fault" ) );
+  BOOST_CHECK( !m( "defau" ) );
+  BOOST_CHECK( !m( "default" ) );
+}
+
 BOOST_AUTO_TEST_CASE(StrMatcher_REGEX)
 {
+  // REGEX matches substring (unless anchored)
+  StrMatcher m( "f[a]u", Match::REGEX );
+  BOOST_CHECK( !m( "" ) );
+  BOOST_CHECK( !m( "a" ) );
+  BOOST_CHECK( m( "fau" ) );
+  BOOST_CHECK( m( "fault" ) );
+  BOOST_CHECK( m( "defau" ) );
+  BOOST_CHECK( m( "default" ) );
+
+  m.setSearchstring( "^f[a]u" );
+  BOOST_CHECK( !m( "" ) );
+  BOOST_CHECK( !m( "a" ) );
+  BOOST_CHECK( m( "fau" ) );
+  BOOST_CHECK( m( "fault" ) );
+  BOOST_CHECK( !m( "defau" ) );
+  BOOST_CHECK( !m( "default" ) );
+
+  m.setSearchstring( "f[a]u$" );
+  BOOST_CHECK( !m( "" ) );
+  BOOST_CHECK( !m( "a" ) );
+  BOOST_CHECK( m( "fau" ) );
+  BOOST_CHECK( !m( "fault" ) );
+  BOOST_CHECK( m( "defau" ) );
+  BOOST_CHECK( !m( "default" ) );
+
+  m.setSearchstring( "^f[a]u$" );
+  BOOST_CHECK( !m( "" ) );
+  BOOST_CHECK( !m( "a" ) );
+  BOOST_CHECK( m( "fau" ) );
+  BOOST_CHECK( !m( "fault" ) );
+  BOOST_CHECK( !m( "defau" ) );
+  BOOST_CHECK( !m( "default" ) );
+}
+
+BOOST_AUTO_TEST_CASE(StrMatcher_RX)
+{
   StrMatcher m( "fau" );
 
   BOOST_CHECK( !m.isCompiled() );
index 152a229..2e82ab8 100644 (file)
@@ -11,6 +11,56 @@ using namespace std;
 using namespace zypp;
 using namespace zypp::str;
 
+#define RXspecial "\\^.[$()|*+?{"
+
+BOOST_AUTO_TEST_CASE(str2rx)
+{
+  char s[] = "c";
+  char x[] = "\\c";
+  for ( const char * ch = RXspecial; *ch; ++ch )
+  {
+    s[0] = x[1] = *ch;
+    BOOST_CHECK_EQUAL( str::rxEscapeStr( s ), x );
+  }
+}
+
+BOOST_AUTO_TEST_CASE(glob2rx)
+{
+  {
+    char s[] = "c";
+    char x[] = "\\c";
+    for ( const char * ch = RXspecial; *ch; ++ch )
+    {
+      s[0] = x[1] = *ch;
+      if ( *ch == '?' )
+       BOOST_CHECK_EQUAL( str::rxEscapeGlob( s ), "." );
+      else if ( *ch == '*' )
+       BOOST_CHECK_EQUAL( str::rxEscapeGlob( s ), ".*" );
+      else if ( *ch == '[' )
+       BOOST_CHECK_EQUAL( str::rxEscapeGlob( s ), "\\[" );     // no closing ] so it is literal
+       else if ( *ch == '\\' )
+         BOOST_CHECK_EQUAL( str::rxEscapeGlob( s ), "\\" );    // actually an input error as "\" is not a valid GLOB
+         else
+         {
+           s[0] = x[1] = *ch;
+           BOOST_CHECK_EQUAL( str::rxEscapeGlob( s ), x );
+         }
+    }
+    std::string a( str::rxEscapeStr( RXspecial ) );    // all rx/glob special chars are literally (\-escaped)
+    BOOST_CHECK_EQUAL( str::rxEscapeGlob( a ), a );    // nothing more to escape.
+
+    // character class: contains "]["
+    BOOST_CHECK_EQUAL( str::rxEscapeGlob( "[][]" ),    "[][]" );
+    BOOST_CHECK_EQUAL( str::rxEscapeGlob( "[^][]" ),   "[^][]" );
+    BOOST_CHECK_EQUAL( str::rxEscapeGlob( "[!][]" ),   "[^][]" );      // glob allows ! and ^ to negate a cclass
+
+    // no character class: no closing ']' so take it literally (the ] would be member of the cclass, not the closing ])
+    BOOST_CHECK_EQUAL( str::rxEscapeGlob( "[]" ),      "\\[]" );
+    BOOST_CHECK_EQUAL( str::rxEscapeGlob( "[!]" ),     "\\[!]" );
+    BOOST_CHECK_EQUAL( str::rxEscapeGlob( "[^]" ),     "\\[\\^]" );
+  }
+}
+
 BOOST_AUTO_TEST_CASE(gsubTest)
 {
   string olds = "olds";
index 79c689b..26d56e6 100644 (file)
@@ -6,7 +6,6 @@ query_string: ma*
 repo: opensuse
 type: patch
 match_type: regex
-require_all: on
 case_sensitive: on
 install_status: not-installed
 version: != 0.8.3
diff --git a/tests/zypp/data/PoolQueryCC/rxnames.xml b/tests/zypp/data/PoolQueryCC/rxnames.xml
new file mode 100644 (file)
index 0000000..d3be84b
--- /dev/null
@@ -0,0 +1,21 @@
+<channel>
+  <subchannel>
+    <package><name>.</name></package>
+    <package><name>?</name></package>
+    <package><name>*</name></package>
+    <package><name>+</name></package>
+    <package><name>[</name></package>
+    <package><name>]</name></package>
+    <package><name>(</name></package>
+    <package><name>)</name></package>
+    <package><name>{</name></package>
+    <package><name>}</name></package>
+    <package><name>|</name></package>
+    <package><name>^</name></package>
+    <package><name>$</name></package>
+    <package><name>\</name></package>
+    <package><name>A</name></package>
+    <package><name>AB</name></package>
+    <package><name>BAB</name></package>
+    <package><name>.A.</name></package>
+</subchannel></channel>
index 721a1cc..fff8b9d 100644 (file)
@@ -223,8 +223,7 @@ void Locks::removeLock( const ResKind &kind_r, const IdString &name_r )
   q.addKind( kind_r );
   q.setMatchExact();
   q.setCaseSensitive(true);
-  q.requireAll();
-  DBG << "remove lock by selectactable" << endl;
+  DBG << "remove lock by Selectable" << endl;
   removeLock(q);
 }
 
index 4e286b2..94489ff 100644 (file)
@@ -357,7 +357,6 @@ namespace zypp
     Impl()
       : _flags( Match::SUBSTRING | Match::NOCASE | Match::SKIP_KIND )
       , _match_word(false)
-      , _require_all(false)
       , _status_flags(ALL)
     {}
 
@@ -380,7 +379,6 @@ namespace zypp
     /** Sat solver search flags */
     Match _flags;
     bool _match_word;
-    bool _require_all;
 
     /** Sat solver status flags */
     StatusFilter _status_flags;
@@ -417,7 +415,6 @@ namespace zypp
              && _attrs == rhs._attrs
              && _uncompiledPredicated == rhs._uncompiledPredicated
              && _match_word == rhs._match_word
-             && _require_all == rhs._require_all
              && _status_flags == rhs._status_flags
              && _edition == rhs._edition
              && _op == rhs._op
@@ -441,8 +438,10 @@ namespace zypp
     mutable AttrMatchList _attrMatchList;
 
   private:
-    /** Pass flags from \ref compile, as they may have been changed. */
-    string createRegex( const StrContainer & container, const Match & flags ) const;
+    /** Join patterns in \a container_r according to \a flags_r into a single \ref StrMatcher.
+     * The \ref StrMatcher returned will be a REGEX if more than one pattern was passed.
+     */
+    StrMatcher joinedStrMatcher( const StrContainer & container_r, const Match & flags_r ) const;
 
   private:
     friend Impl * rwcowClone<Impl>( const Impl * rhs );
@@ -479,13 +478,8 @@ namespace zypp
   {
     _attrMatchList.clear();
 
-    Match cflags( _flags );
-    if ( cflags.mode() == Match::OTHER ) // this will never succeed...
-      ZYPP_THROW( MatchUnknownModeException( cflags ) );
-
-    /** Compiled search strings. */
-    string rcstrings;
-
+    if ( _flags.mode() == Match::OTHER ) // this will never succeed...
+      ZYPP_THROW( MatchUnknownModeException( _flags ) );
 
     // 'different'         - will have to iterate through all and match by ourselves (slow)
     // 'same'              - will pass the compiled string to dataiterator_init
@@ -510,11 +504,8 @@ namespace zypp
       StrContainer joined;
       invokeOnEach(_strings.begin(), _strings.end(), EmptyFilter(), MyInserter(joined));
       invokeOnEach(_attrs.begin()->second.begin(), _attrs.begin()->second.end(), EmptyFilter(), MyInserter(joined));
-      rcstrings = createRegex(joined, cflags);
-      if (joined.size() > 1) // switch to regex for multiple strings
-        cflags.setModeRegex();
-      _attrMatchList.push_back( AttrMatchData( _attrs.begin()->first,
-                                StrMatcher( rcstrings, cflags ) ) );
+
+      _attrMatchList.push_back( AttrMatchData( _attrs.begin()->first, joinedStrMatcher( joined, _flags ) ) );
     }
 
     // // MULTIPLE ATTRIBUTES
@@ -522,16 +513,21 @@ namespace zypp
     {
       // check whether there are any per-attribute strings
       bool attrvals_empty = true;
-      for (AttrRawStrMap::const_iterator ai = _attrs.begin(); ai != _attrs.end(); ++ai)
-        if (!ai->second.empty())
-          for(StrContainer::const_iterator it = ai->second.begin();
-              it != ai->second.end(); it++)
-            if (!it->empty())
-            {
-              attrvals_empty = false;
-              goto attremptycheckend;
-            }
-attremptycheckend:
+      for_( ai, _attrs.begin(), _attrs.end() )
+      {
+        if ( ai->second.empty() )
+         continue;
+       for_( it, ai->second.begin(), ai->second.end() )
+       {
+         if ( !it->empty() )
+         {
+           attrvals_empty = false;
+           break;
+         }
+       }
+        if ( ! attrvals_empty )
+         break;
+      }
 
       // chceck whether the per-attribute strings are all the same
       bool attrvals_thesame = true;
@@ -562,18 +558,15 @@ attremptycheckend:
         if (attrvals_empty)
         {
           invokeOnEach(_strings.begin(), _strings.end(), EmptyFilter(), MyInserter(joined));
-          rcstrings = createRegex(joined, cflags);
         }
         else
         {
           invokeOnEach(_strings.begin(), _strings.end(), EmptyFilter(), MyInserter(joined));
           invokeOnEach(_attrs.begin()->second.begin(), _attrs.begin()->second.end(), EmptyFilter(), MyInserter(joined));
-          rcstrings = createRegex(joined, cflags);
         }
-        if (joined.size() > 1) // switch to regex for multiple strings
-          cflags.setModeRegex();
+
         // May use the same StrMatcher for all
-        StrMatcher matcher( rcstrings, cflags );
+        StrMatcher matcher( joinedStrMatcher( joined, _flags ) );
         for_( ai, _attrs.begin(), _attrs.end() )
         {
           _attrMatchList.push_back( AttrMatchData( ai->first, matcher ) );
@@ -591,11 +584,8 @@ attremptycheckend:
           StrContainer joined;
           invokeOnEach(_strings.begin(), _strings.end(), EmptyFilter(), MyInserter(joined));
           invokeOnEach(ai->second.begin(), ai->second.end(), EmptyFilter(), MyInserter(joined));
-          string s = createRegex(joined, cflags);
-          if (joined.size() > 1) // switch to regex for multiple strings
-            cflags.setModeRegex();
-          _attrMatchList.push_back( AttrMatchData( ai->first,
-                                    StrMatcher( s, cflags ) ) );
+
+          _attrMatchList.push_back( AttrMatchData( ai->first, joinedStrMatcher( joined, _flags ) ) );
         }
       }
     }
@@ -615,14 +605,10 @@ attremptycheckend:
           if ( ! mstr.empty() )
             joined.insert( mstr );
 
-          cflags = _flags;
-          rcstrings = createRegex( joined, cflags );
-          if ( joined.size() > 1 ) // switch to regex for multiple strings
-            cflags.setModeRegex();
-
-          _attrMatchList.push_back( AttrMatchData( it->attr,
-                                    StrMatcher( rcstrings, cflags ),
-                                                      it->predicate, it->predicateStr ) );
+         // copy and exchange the StrMatcher
+         AttrMatchData nattr( *it );
+         nattr.strMatcher = joinedStrMatcher( joined, _flags );
+          _attrMatchList.push_back( std::move(nattr) );
         }
         else
         {
@@ -635,12 +621,7 @@ attremptycheckend:
     // If no attributes defined at all, then add 'query all'
     if ( _attrMatchList.empty() )
     {
-      cflags = _flags;
-      rcstrings = createRegex( _strings, cflags );
-      if ( _strings.size() > 1 ) // switch to regex for multiple strings
-        cflags.setModeRegex();
-      _attrMatchList.push_back( AttrMatchData( sat::SolvAttr::allAttr,
-                                StrMatcher( rcstrings, cflags ) ) );
+      _attrMatchList.push_back( AttrMatchData( sat::SolvAttr::allAttr, joinedStrMatcher( _strings, _flags ) ) );
     }
 
     // Finally check here, whether all involved regex compile.
@@ -651,102 +632,62 @@ attremptycheckend:
     //DBG << asString() << endl;
   }
 
-
-  /**
-   * Converts '*' and '?' wildcards within str into their regex equivalents.
-   */
-  static string wildcards2regex(const string & str)
-  {
-    string regexed = str;
-
-    string r_all(".*"); // regex equivalent of '*'
-    string r_one(".");  // regex equivalent of '?'
-    string::size_type pos;
-
-    // replace all "*" in input with ".*"
-    for (pos = 0; (pos = regexed.find("*", pos)) != std::string::npos; pos+=2)
-      regexed = regexed.replace(pos, 1, r_all);
-
-    // replace all "?" in input with "."
-    for (pos = 0; (pos = regexed.find('?', pos)) != std::string::npos; ++pos)
-      regexed = regexed.replace(pos, 1, r_one);
-
-    return regexed;
-  }
-
-  string PoolQuery::Impl::createRegex( const StrContainer & container, const Match & flags ) const
+  ///////////////////////////////////////////////////////////////////
+  namespace
   {
-//! macro for word boundary tags for regexes
-#define WB (_match_word ? string("\\b") : string())
-    string rstr;
-
-    if (container.empty())
-      return rstr;
-
-    if (container.size() == 1)
+    /** Escape \a str_r for use in a regex.
+     * \a flags_r determines whether the input string is interpreted
+     * as regex, glob or plain string.
+     */
+    std::string rxEscape( std::string str_r, const Match & flags_r )
     {
-      return WB + *container.begin() + WB;
-    }
-
-    // multiple strings
+      if ( str_r.empty() || flags_r.isModeRegex() )
+       return str_r;
 
-    bool use_wildcards = flags.isModeGlob();
-    StrContainer::const_iterator it = container.begin();
-    string tmp;
+      if ( flags_r.isModeGlob() )
+       return str::rxEscapeGlob( std::move(str_r) );
 
-    if (use_wildcards)
-      tmp = wildcards2regex(*it);
-    else
-      tmp = *it;
-
-    if (_require_all)
-    {
-      if ( ! flags.isModeString() ) // not match exact
-        tmp += ".*" + WB + tmp;
-      rstr = "(?=" + tmp + ")";
+      return str::rxEscapeStr( std::move(str_r) );
     }
-    else
-    {
-      if ( flags.isModeString() || flags.isModeGlob() )
-        rstr = "^";
-      rstr += WB + "(" + tmp;
-    }
-
-    ++it;
+  } // namespace
+  ///////////////////////////////////////////////////////////////////
 
-    for (; it != container.end(); ++it)
+  StrMatcher PoolQuery::Impl::joinedStrMatcher( const StrContainer & container_r, const Match & flags_r ) const
+  {
+    if ( container_r.empty() )
+      return StrMatcher( std::string(), flags_r );
+
+    if ( container_r.size() == 1 && !_match_word )     // use RX to match words
+      return StrMatcher( *container_r.begin(), flags_r );
+
+    // Convert to a regex.
+    // Note: Modes STRING and GLOB match whole strings (anchored ^ $)
+    //       SUBSTRING and REGEX match substrings      (match_word anchores SUBSTRING \b)
+    Match retflags( flags_r );
+    retflags.setModeRegex();
+    str::Str ret;
+
+    if ( flags_r.isModeString() || flags_r.isModeGlob() )
+      ret << "^";
+    else if ( _match_word )
+      ret << "\\b";
+
+    // (..|..|..)
+    char sep = '(';
+    for ( const::std::string & s : container_r )
     {
-      if (use_wildcards)
-        tmp = wildcards2regex(*it);
-      else
-        tmp = *it;
-
-      if (_require_all)
-      {
-        if ( ! flags.isModeString() ) // not match exact
-          tmp += ".*" + WB + tmp;
-        rstr += "(?=" + tmp + ")";
-      }
-      else
-      {
-        rstr += "|" + tmp;
-      }
+      ret << sep << rxEscape( s, flags_r );
+      if ( sep == '(' )
+       sep = '|';
     }
+    ret << ')';
 
-    if (_require_all)
-    {
-      if ( ! flags.isModeString() ) // not match exact
-        rstr += WB + ".*";
-    }
-    else
-    {
-      rstr += ")" + WB;
-      if ( flags.isModeString() || flags.isModeGlob() )
-        rstr += "$";
-    }
+    if ( flags_r.isModeString() || flags_r.isModeGlob() )
+      ret << "$";
+    else if ( _match_word )
+      ret << "\\b";
 
-    return rstr;
-#undef WB
+    return StrMatcher( ret, retflags );
   }
 
   string PoolQuery::Impl::asString() const
@@ -911,15 +852,11 @@ attremptycheckend:
     _pimpl->_op = op;
   }
 
-  void PoolQuery::setMatchSubstring()  { _pimpl->_flags.setModeSubstring(); }
-  void PoolQuery::setMatchExact()      { _pimpl->_flags.setModeString(); }
-  void PoolQuery::setMatchRegex()      { _pimpl->_flags.setModeRegex(); }
-  void PoolQuery::setMatchGlob()       { _pimpl->_flags.setModeGlob(); }
-  void PoolQuery::setMatchWord()
-  {
-    _pimpl->_match_word = true;
-    _pimpl->_flags.setModeRegex();
-  }
+  void PoolQuery::setMatchSubstring()  { _pimpl->_flags.setModeSubstring();    _pimpl->_match_word = false; }
+  void PoolQuery::setMatchExact()      { _pimpl->_flags.setModeString();       _pimpl->_match_word = false; }
+  void PoolQuery::setMatchRegex()      { _pimpl->_flags.setModeRegex();        _pimpl->_match_word = false; }
+  void PoolQuery::setMatchGlob()       { _pimpl->_flags.setModeGlob();         _pimpl->_match_word = false; }
+  void PoolQuery::setMatchWord()       { _pimpl->_flags.setModeSubstring();    _pimpl->_match_word = true; }
 
   Match PoolQuery::flags() const
   { return _pimpl->_flags; }
@@ -935,10 +872,6 @@ attremptycheckend:
   { _pimpl->_status_flags = flags; }
 
 
-  void PoolQuery::setRequireAll(bool require_all)
-  { _pimpl->_require_all = require_all; }
-
-
   const PoolQuery::StrContainer &
   PoolQuery::strings() const
   { return _pimpl->_strings; }
@@ -981,15 +914,10 @@ attremptycheckend:
   { _pimpl->_flags.turn( Match::FILES, value ); }
 
   bool PoolQuery::matchExact() const           { return _pimpl->_flags.isModeString(); }
-  bool PoolQuery::matchSubstring() const       { return _pimpl->_flags.isModeSubstring(); }
+  bool PoolQuery::matchSubstring() const       { return _pimpl->_flags.isModeSubstring() && !_pimpl->_match_word; }
   bool PoolQuery::matchGlob() const            { return _pimpl->_flags.isModeGlob(); }
   bool PoolQuery::matchRegex() const           { return _pimpl->_flags.isModeRegex(); }
-
-  bool PoolQuery::matchWord() const
-  { return _pimpl->_match_word; }
-
-  bool PoolQuery::requireAll() const
-  { return _pimpl->_require_all; }
+  bool PoolQuery::matchWord() const            { return _pimpl->_flags.isModeSubstring() && _pimpl->_match_word; }
 
   PoolQuery::StatusFilter PoolQuery::statusFilterFlags() const
   { return _pimpl->_status_flags; }
@@ -1018,6 +946,9 @@ attremptycheckend:
   { invokeOnEach( begin(), end(), fnc); }
 
 
+  /*DEPRECATED LEGACY:*/void PoolQuery::setRequireAll( bool ) {}
+  /*DEPRECATED LEGACY:*/bool PoolQuery::requireAll() const    { return false; }
+
   ///////////////////////////////////////////////////////////////////
   //
   //  CLASS NAME : PoolQuery::Attr
@@ -1052,7 +983,7 @@ attremptycheckend:
     static const PoolQueryAttr kindAttr;
     static const PoolQueryAttr stringAttr;
     static const PoolQueryAttr stringTypeAttr;
-    static const PoolQueryAttr requireAllAttr;
+    static const PoolQueryAttr requireAllAttr; // LEAGACY: attribute was defined but never implemented.
     static const PoolQueryAttr caseSensitiveAttr;
     static const PoolQueryAttr installStatusAttr;
     static const PoolQueryAttr editionAttr;
@@ -1065,7 +996,7 @@ attremptycheckend:
   const PoolQueryAttr PoolQueryAttr::kindAttr( "type" );
   const PoolQueryAttr PoolQueryAttr::stringAttr( "query_string" );
   const PoolQueryAttr PoolQueryAttr::stringTypeAttr("match_type");
-  const PoolQueryAttr PoolQueryAttr::requireAllAttr("require_all");
+  const PoolQueryAttr PoolQueryAttr::requireAllAttr("require_all");    // LEAGACY: attribute was defined but never implemented.
   const PoolQueryAttr PoolQueryAttr::caseSensitiveAttr("case_sensitive");
   const PoolQueryAttr PoolQueryAttr::installStatusAttr("install_status");
   const PoolQueryAttr PoolQueryAttr::editionAttr("version");
@@ -1189,18 +1120,8 @@ attremptycheckend:
       }
       else if ( attribute==PoolQueryAttr::requireAllAttr )
       {
-        if ( str::strToTrue(attrValue) )
-        {
-          setRequireAll(true);
-        }
-        else if ( !str::strToFalse(attrValue) )
-        {
-          setRequireAll(false);
-        }
-        else
-        {
-          WAR << "unknown boolean value " << attrValue << endl;
-        }
+       // LEAGACY: attribute was defined but never implemented.
+       // Actually it should not occur outside our testcases.
       }
       else if ( attribute==PoolQueryAttr::caseSensitiveAttr )
       {
@@ -1344,19 +1265,6 @@ attremptycheckend:
       }
     }
 
-    if( requireAll() != q.requireAll() )
-    {
-      str << "require_all: ";
-      if (requireAll())
-      {
-        str << "on" << delim;
-      }
-      else
-      {
-        str << "off" << delim;
-      }
-    }
-
     if( statusFilterFlags() != q.statusFilterFlags() )
     {
       switch( statusFilterFlags() )
index aeb3b4b..6897674 100644 (file)
@@ -182,8 +182,7 @@ namespace zypp
      * This method can be used multiple times in which case the query strings
      * will be combined (together with strings added via addAttribute()) into
      * a regex. Searched attribute value will match this regex if <b>any</b>
-     * of these strings will match the value. This can be changed by
-     * (not yet implemented) \ref setRequireAll() method.
+     * of these strings will match the value.
      */
     void addString(const std::string & value);
 
@@ -195,8 +194,6 @@ namespace zypp
      * case the query strings will be combined (together with strings added
      * via addString()) into a regex. Searched attribute value will match
      * this regex if <b>any</b> of these strings will match the value.
-     * This can be changed by (not yet implemented) \ref setRequireAll()
-     * method.
      *
      * \note Though it is possible to use dependency attributes like
      * \ref Solv::Attr::provides here, note that the query string is
@@ -365,20 +362,11 @@ namespace zypp
     void setMatchGlob();
     /** Set to use the query strings as regexes */
     void setMatchRegex();
-    /** Set to match words (uses regex) */
+    /** Set substring to match words */
     void setMatchWord();
     //void setLocale(const Locale & locale);
     //@}
 
-    /**
-     * Require that all of the values set by addString or addAttribute
-     * match the values of respective attributes.
-     *
-     * \todo doesn't work yet, don't use this function
-     */
-    void setRequireAll( bool require_all = true );
-
-
     /** \name getters */
     //@{
 
@@ -421,12 +409,6 @@ namespace zypp
     Match::Mode matchMode() const
     { return flags().mode(); }
 
-    /**
-     * Whether all values added via addString() or addAttribute() are required
-     * to match the values of the respective attributes.
-     */
-    bool requireAll() const;
-
     StatusFilter statusFilterFlags() const;
     //@}
 
@@ -478,6 +460,12 @@ namespace zypp
     void setFlags( const Match & flags );
 
   public:
+    /** \deprecated Attribute was defined but never implemented/used. Will be removed in future versions. */
+    void setRequireAll( bool require_all = true ) ZYPP_DEPRECATED;
+    /** \deprecated Attribute was defined but never implemented/used. Will be removed in future versions. */
+    bool requireAll() const ZYPP_DEPRECATED;
+
+  public:
     class Impl;
   private:
     /** Pointer to implementation */
index fb7bd35..47ef8b1 100644 (file)
@@ -373,6 +373,91 @@ namespace zypp
       return std::string( buf.begin(), buf.end() );
     }
 
+
+    std::string bEscape( std::string str_r, const C_Str & special_r )
+    {
+      if ( str_r.empty() )
+       return str_r;
+
+      if ( str_r.find_first_of( special_r ) == std::string::npos
+       && ( ::strchr( special_r.c_str(), '\\' ) ||  !::strchr( str_r.c_str(), '\\' ) ) )
+       return str_r;
+
+      Str buf;
+      for_( s, str_r.c_str(), s+str_r.size() )
+      {
+       if ( *s == '\\' || ::strchr( special_r.c_str(), *s ) )
+         buf << '\\';
+       buf << *s;
+      }
+      return buf;
+    }
+
+    #define RXSPECIALCHARS "\\.*+?^$[()|{"
+
+    std::string rxEscapeStr( std::string str_r )
+    {
+      return bEscape( std::move(str_r), RXSPECIALCHARS );
+    }
+
+    std::string rxEscapeGlob( std::string str_r )
+    {
+      if ( str_r.empty() )
+       return str_r;
+
+      if ( str_r.find_first_of( RXSPECIALCHARS ) == std::string::npos )
+       return str_r;
+
+      Str buf;
+      for_( s, str_r.c_str(), s+str_r.size() )
+      {
+       if ( *s == '\\' )       // + next char literally
+       {
+         buf << '\\';
+         if ( *(s+1) ) { ++s; buf << *s; }
+       }
+       else if ( *s == '?' )   // translate
+       {
+         buf << '.';
+       }
+       else if ( *s == '*' )   // translate
+       {
+         buf << ".*";
+       }
+       else if ( *s == '[' )   // character class if closing ] is found, else literally
+       {
+         const char * e = s+1;
+         if ( *e == '^' || *e == '!' ) // negated cclass
+           ++e;
+         if ( *e == ']' )              // ] in cclass
+           ++e;
+         while ( *e && *e != ']' )     // ...to ] or \0
+           ++e;
+         if ( *e ) // on closing ']'
+         {
+           ++s;  buf << '[' << (*s == '!' ? '^' : *s );
+           while ( ++s != e )
+             buf << *s;
+           buf << ']';
+         }
+         else
+         {
+           buf << "\\[";
+         }
+       }
+       else if ( ::strchr( RXSPECIALCHARS, *s ) )      // escape
+       {
+         buf << '\\' << *s;
+       }
+       else
+       {
+         buf << *s;
+       }
+      }
+      return buf;
+    }
+
+
     std::string getline( std::istream & str, const Trim trim_r )
     {
       return trim( receiveUpTo( str, '\n' ), trim_r );
index acff23a..bb59e93 100644 (file)
@@ -917,6 +917,15 @@ namespace zypp
           str_r += escape( next_r, sep_r );
       }
 
+      /** Return \a str_r with '\'-escaped chars occurring in \a special_r (and '\'). */
+      std::string bEscape( std::string str_r, const C_Str & special_r );
+
+      /** Escape plain STRING \a str_r for use in a regex (not anchored by "^" or "$"). */
+      std::string rxEscapeStr( std::string str_r );
+
+      /** Escape GLOB \a str_r for use in a regex (not anchored by "^" or "$"). */
+      std::string rxEscapeGlob( std::string str_r );
+
       //! \todo unsecape()
 
     //@}