Imported Upstream version 16.3.2
[platform/upstream/libzypp.git] / zypp / CpeId.cc
index 9e22dda..e8b3015 100644 (file)
 /** \file      zypp/CpeId.cc
  */
 #include <iostream>
-//#include "zypp/base/LogTools.h"
+#include <array>
+
+#include "zypp/base/String.h"
+#include "zypp/base/LogTools.h"
 #include "zypp/base/NonCopyable.h"
 
 #include "zypp/CpeId.h"
 
 using std::endl;
 
+/** Initializer list with all wfn attributes */
+#define WFN_ATTRIBUTES {\
+  Attribute::part,     \
+  Attribute::vendor,   \
+  Attribute::product,  \
+  Attribute::version,  \
+  Attribute::update,   \
+  Attribute::edition,  \
+  Attribute::language, \
+  Attribute::sw_edition,\
+  Attribute::target_sw,        \
+  Attribute::target_hw,        \
+  Attribute::other,    \
+}
+
 ///////////////////////////////////////////////////////////////////
 namespace zypp
 {
   ///////////////////////////////////////////////////////////////////
+  namespace
+  {
+    /** Hex-digit to number or -1. */
+    inline int heDecodeCh( char ch )
+    {
+      if ( '0' <= ch && ch <= '9' )
+       return( ch - '0' );
+      if ( 'A' <= ch && ch <= 'F' )
+       return( ch - 'A' + 10 );
+      if ( 'a' <= ch && ch <= 'f' )
+       return( ch - 'a' + 10 );
+      return -1;
+    }
+
+    /** Printable non whitespace in [0x00,0x7f] valid in WFN */
+    inline bool chIsValidRange( char ch )
+    { return( '!' <= ch && ch <= '~' ); }
+
+    /** Alpha */
+    inline bool chIsAlpha( char ch )
+    { return( ( 'a' <= ch && ch <= 'z' ) || ( 'A' <= ch && ch <= 'Z' ) ); }
+
+    /** Digit */
+    inline bool chIsNum( char ch )
+    { return( '0' <= ch && ch <= '9' ); }
+
+    /** Alphanum */
+    inline bool chIsAlNum( char ch )
+    { return( chIsAlpha( ch ) || chIsNum( ch ) ); }
+
+    /** Alphanum or \c underscore are unescaped in WFN */
+    inline bool chIsWfnUnescaped( char ch )
+    { return( chIsAlNum( ch ) || ch == '_' ); }
+
+  } // namespace
+  ///////////////////////////////////////////////////////////////////
+
+  ///////////////////////////////////////////////////////////////////
   /// \class CpeId::Impl
   /// \brief CpeId implementation.
   ///////////////////////////////////////////////////////////////////
   class CpeId::Impl : private base::NonCopyable
   {
-    friend std::ostream & operator<<( std::ostream & str, const Impl & obj );
+    typedef std::array<Value,Attribute::numAttributes> Wfn;
+
   public:
     Impl() {}
 
     Impl( const std::string & cpe_r )
-      : _cpe( cpe_r )
+      : _wfn( unbind( cpe_r ) )
     {}
 
-    Impl( const std::string & cpe_r, NoThrowType )
-      : _cpe( cpe_r )
-    {}
+  public:
+    explicit operator bool() const
+    { for ( const auto & val : _wfn ) if ( ! val.isANY() ) return true; return false; }
+
+    std::string asFs() const
+    {
+      str::Str ret;
+      ret << "cpe:2.3";
+      for ( auto ai : WFN_ATTRIBUTES )
+      {
+       ret << ':' << _wfn[ai].asFs();
+      }
+      return ret;
+    }
+
+    std::string asUri() const
+    {
+      str::Str ret;
+      ret << "cpe:/";
+      std::string val;
+      unsigned colon = 0;      // to remember trailing colons
+      for ( auto ai : WFN_ATTRIBUTES )
+      {
+       val = _wfn[ai].asUri();
+
+       if ( ai == Attribute::edition )
+       {
+         if ( ! ( _wfn[Attribute::sw_edition].isANY()
+               && _wfn[Attribute::target_sw].isANY()
+               && _wfn[Attribute::target_hw].isANY()
+               && _wfn[Attribute::other].isANY() ) )
+         {
+           // packing is needed
+           val = str::Str()
+                 << '~' << val//Attribute::edition
+                 << '~' << _wfn[Attribute::sw_edition].asUri()
+                 << '~' << _wfn[Attribute::target_sw].asUri()
+                 << '~' << _wfn[Attribute::target_hw].asUri()
+                 << '~' << _wfn[Attribute::other].asUri();
+         }
+       }
+
+       if ( ! val.empty() )
+       {
+         if ( colon )
+           ret << std::string( colon, ':' );
+         ret << val;
+         colon = 1;
+       }
+       else
+         ++colon;
+
+       if ( ai == Attribute::language )
+         break;        // remaining attrs packaed in edition
+      }
+      return ret;
+    }
+
+    std::string asWfn() const
+    {
+      str::Str ret;
+      ret << "wfn:[";
+      for ( auto ai : WFN_ATTRIBUTES )
+      {
+       const Value & val( _wfn[ai] );
+       if ( ! val.isANY() )
+       {
+         if ( ai ) ret << ',';
+         ret << Attribute::asString( ai ) << '=';
+         if ( val.isString() )
+           ret << '"' << val << '"';
+         else
+           ret << "NA";        // as ANY is omitted, it must be NA
+       }
+      }
+      return ret << "]";
+    }
 
   public:
-    const std::string & asString() const
-    { return _cpe; }
+    SetCompare setRelationMixinCompare( const Impl & trg ) const
+    {
+      SetCompare ret = SetCompare::equal;
+      for ( auto ai : WFN_ATTRIBUTES )
+      {
+       switch ( _wfn[ai].compare( trg._wfn[ai] ).asEnum() )
+       {
+         case SetCompare::uncomparable:
+           ret = SetCompare::uncomparable;
+           break;
 
-    Match match( const Impl & rhs ) const
+         case SetCompare::equal:
+           break;
+
+         case SetCompare::properSubset:
+           if ( ret == SetCompare::equal )
+             ret = SetCompare::properSubset;
+           else if ( ret != SetCompare::properSubset )
+             ret = SetCompare::uncomparable;
+           break;
+
+         case SetCompare::properSuperset:
+           if ( ret == SetCompare::equal )
+             ret = SetCompare::properSuperset;
+           else if ( ret != SetCompare::properSuperset )
+             ret = SetCompare::uncomparable;
+           break;
+
+         case SetCompare::disjoint:
+           ret = SetCompare::disjoint;
+           break;
+       }
+       if ( ret == SetCompare::uncomparable || ret == SetCompare::disjoint )
+         break;
+      }
+      return ret;
+    }
+
+  private:
+    /** Assign \a val_r if it meets \a attr_r specific contraints.
+     * \throws std::invalid_argument if string is malformed
+     */
+    static void assignAttr( Wfn & wfn_r, Attribute attr_r, const Value & val_r )
     {
-      return _cpe == rhs._cpe ? Match::equal : Match::undefined;
+      if ( val_r.isString() )
+      {
+       switch ( attr_r.asEnum() )
+       {
+         case Attribute::part:
+           {
+             const std::string & wfn( val_r.asWfn() );
+             switch ( wfn[0] )
+             {
+               case 'h':
+               case 'o':
+               case 'a':
+                 if ( wfn[1] == '\0' )
+                   break;
+                 // else: fallthrough
+               default:
+                 throw std::invalid_argument( str::Str() << "CpeId:Wfn:part: '" << wfn << "' illegal value; expected: 'h' | 'o' | 'a'"   );
+                 break;
+             }
+           }
+           break;
+
+         case Attribute::language:
+           {
+             const std::string & wfn( val_r.asWfn() );
+             std::string::size_type len = 0;
+             // (2*3ALPHA) ["-" (2ALPHA / 3DIGIT)]
+             if ( chIsAlpha( wfn[0] ) && chIsAlpha( wfn[1] ) )
+             {
+               len = chIsAlpha( wfn[2] ) ? 3 : 2;
+               if ( wfn[len] == '-' )
+               {
+                 if ( chIsAlpha( wfn[len+1] ) && chIsAlpha( wfn[len+2] ) )
+                   len += 3;
+                 else if ( chIsNum( wfn[len+1] ) && chIsNum( wfn[len+2] ) && chIsNum( wfn[len+3] ) )
+                   len += 4;
+               }
+             }
+             if ( wfn.size() != len )
+               throw std::invalid_argument( str::Str() << "CpeId:Wfn:language: '" << wfn << "' illegal value; expected RFC5646 conform: language ['-' region]" );
+           }
+           break;
+
+         default:
+           // no contraints
+           break;
+       }
+      }
+      wfn_r[attr_r.asIntegral()] = val_r;
     }
 
   private:
-    std::string _cpe;
+    /** Parse magic and unbind accordingly
+     * \throws std::invalid_argument if string is malformed
+     */
+    static Wfn unbind( const std::string & cpe_r );
+
+    /** Parse Uri and unbind
+     * \throws std::invalid_argument if string is malformed
+     */
+    static Wfn unbindUri( const std::string & cpe_r );
+
+    /** Parse Fs and unbind
+     * \throws std::invalid_argument if string is malformed
+     */
+    static Wfn unbindFs( const std::string & cpe_r );
+
+  private:
+    Wfn _wfn;
   };
 
+  CpeId::Impl::Wfn CpeId::Impl::unbind( const std::string & cpe_r )
+  {
+    Wfn ret;
+    if ( cpe_r[0] == 'c'
+      && cpe_r[1] == 'p'
+      && cpe_r[2] == 'e'
+      && cpe_r[3] == ':' )
+    {
+      if ( cpe_r[4] == '/' )
+      {
+       ret = unbindUri( cpe_r );
+      }
+      else if ( cpe_r[4] == '2'
+            && cpe_r[5] == '.'
+            && cpe_r[6] == '3'
+            && cpe_r[7] == ':' )
+      {
+       ret = unbindFs( cpe_r );
+      }
+      else
+       throw std::invalid_argument( "CpeId: bad magic; expected: 'cpe:2.3:' | 'cpe:/'" );
+    }
+    else if ( cpe_r[0] != '\0' )
+      throw std::invalid_argument( "CpeId: bad magic; expected: 'cpe:2.3:' | 'cpe:/'" );
+    return ret;
+  }
+
+  CpeId::Impl::Wfn CpeId::Impl::unbindUri( const std::string & cpe_r )
+  {
+    Wfn ret;
+
+    static constexpr unsigned numUriAttr = 7u; // basic URI attibutes
+    std::vector<std::string> field;
+    field.reserve( Attribute::numAttributes ); // reserve 7 + 4 for packed extened attrs in edition
+    if ( str::splitFields( cpe_r.c_str()+5/* skip magic 'cpe:/' */, std::back_inserter(field), ":" ) > numUriAttr )
+      throw std::invalid_argument( str::Str() << "CpeId:Uri: too many fields (" << field.size() << "); expected " << numUriAttr );
+    field.resize( Attribute::numAttributes );  // fillup with ANY(""),
+
+    for ( auto ai : WFN_ATTRIBUTES )
+    {
+      if ( ai == Attribute::edition && field[ai][0] == '~' )
+      {
+       // unpacking is needed
+       static constexpr unsigned numPacks = 6u;        // dummy_before_~ + edition + 4 extended attributes
+       std::vector<std::string> pack;
+       pack.reserve( numPacks );
+       if ( str::splitFields( field[ai], std::back_inserter(pack), "~" ) > numPacks )
+         throw std::invalid_argument( str::Str() << "CpeId:Uri:edition: too many packs (" << pack.size() << "); expected " << numPacks );
+       pack.resize( numPacks );        // fillup with ANY(""), should be noOP
+
+       pack[1].swap( field[Attribute::edition] );
+       pack[2].swap( field[Attribute::sw_edition] );
+       pack[3].swap( field[Attribute::target_sw] );
+       pack[4].swap( field[Attribute::target_hw] );
+       pack[5].swap( field[Attribute::other] );
+      }
+      assignAttr( ret, ai, Value( field[ai], Value::uriFormat ) );
+    }
+    return ret;
+  }
+
+  CpeId::Impl::Wfn CpeId::Impl::unbindFs( const std::string & cpe_r )
+  {
+    Wfn ret;
+
+    std::vector<std::string> field;
+    field.reserve( Attribute::numAttributes );
+    if ( str::splitFields( cpe_r.c_str()+8/* skip magic 'cpe:2.3:' */, std::back_inserter(field), ":" ) > Attribute::numAttributes )
+      throw std::invalid_argument( str::Str() << "CpeId:Fs: too many fields (" << field.size() << "); expected 11" /*<< Attribute::numAttributes but g++ currently can't resoolve this as constexpr*/ );
+    if ( !field.empty() && field.back().empty() )      // A trailing ':' leads to an empty (illegal) field, but we fillup missing fields with ANY|"*"
+      field.back() = "*";
+    field.resize( Attribute::numAttributes, "*" );     // fillup with ANY|"*"
+
+    for ( auto ai : WFN_ATTRIBUTES )
+    {
+      assignAttr( ret, ai, Value( field[ai], Value::fsFormat ) );
+    }
+    return ret;
+  }
+
+
   ///////////////////////////////////////////////////////////////////
-  //
-  //   CLASS NAME : CpeId
-  //
+  //   class CpeId
   ///////////////////////////////////////////////////////////////////
 
+  std::string CpeId::NoThrowType::lastMalformed;
+
   CpeId::CpeId()
     : _pimpl( new Impl )
   {}
@@ -65,20 +380,672 @@ namespace zypp
   {}
 
   CpeId::CpeId( const std::string & cpe_r, NoThrowType )
-    : _pimpl( new Impl( cpe_r, noThrow ) )
-  {}
+  {
+    try
+    {
+      _pimpl.reset( new Impl( cpe_r ) );
+      NoThrowType::lastMalformed.clear();
+    }
+    catch(...)
+    {
+      _pimpl.reset( new Impl );
+      NoThrowType::lastMalformed = cpe_r;
+    }
+  }
 
   CpeId::~CpeId()
   {}
 
   CpeId::operator bool() const
-  { return !_pimpl->asString().empty(); }
+  { return bool(*_pimpl); }
+
+  std::string CpeId::asFs() const
+  { return _pimpl->asFs(); }
+
+  std::string CpeId::asUri() const
+  { return _pimpl->asUri(); }
+
+  std::string CpeId::asWfn() const
+  { return _pimpl->asWfn(); }
+
+  SetCompare CpeId::setRelationMixinCompare( const CpeId & trg ) const
+  { return _pimpl->setRelationMixinCompare( *trg._pimpl ); }
+
+  ///////////////////////////////////////////////////////////////////
+  //   class CpeId::WfnAttribute
+  ///////////////////////////////////////////////////////////////////
+
+  const std::string & CpeId::EAttributeDef::asString( Enum val_r )
+  {
+    static std::map<Enum,std::string> _table = {
+#define OUTS(N) { N, #N }
+      OUTS( part ),
+      OUTS( vendor ),
+      OUTS( product ),
+      OUTS( version ),
+      OUTS( update ),
+      OUTS( edition ),
+      OUTS( language ),
+      OUTS( sw_edition ),
+      OUTS( target_sw ),
+      OUTS( target_hw ),
+      OUTS( other ),
+#undef OUTS
+    };
+    return _table[val_r];
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  //   class CpeId::Value
+  ///////////////////////////////////////////////////////////////////
+
+  const CpeId::Value CpeId::Value::ANY;
+  const CpeId::Value CpeId::Value::NA( "" );
 
-  std::string CpeId::asString() const
-  { return _pimpl->asString(); }
+  CpeId::Value::Value( const std::string & value_r )
+  {
+    if ( value_r.empty() )     // NA
+    {
+      if ( ! CpeId::Value::NA._value ) // initialized by this ctor!
+       _value.reset( new std::string );
+      else
+       _value = CpeId::Value::NA._value;
+    }
+    else if ( value_r != "*" ) // ANY is default constructed
+    {
+      bool starting = true;    // false after the 1st non-?
+      for_( chp, value_r.begin(), value_r.end() )
+      {
+       switch ( *chp )
+       {
+         case '\\':    // quoted
+           ++chp;
+           if ( ! chIsValidRange( *chp )  )
+           {
+             if ( *chp )
+               throw std::invalid_argument( str::Str() << "CpeId:Wfn: illegal quoted character '\\" << reinterpret_cast<void*>(*chp) << "'" );
+             else
+               throw std::invalid_argument( "CpeId:Wfn: Backslash escapes nothing" );
+           }
+           else if ( chIsWfnUnescaped( *chp ) )
+             throw std::invalid_argument( str::Str() << "CpeId:Wfn: unnecessarily quoted character '\\" << *chp << "'" );
+           else if ( starting && *chp == '-' && chp+1 == value_r.end() )
+             throw std::invalid_argument( str::Str() << "CpeId:Wfn: '\\-' is illegal value" );
+           break;
+
+         case '?':     // sequence at beginning or end of string
+           while ( *(chp+1) == '?' )
+             ++chp;
+           if ( ! ( starting || chp+1 == value_r.end() ) )
+             throw std::invalid_argument( "CpeId:Wfn: embedded ?" );
+           break;
+
+         case '*':     // single at beginning or end of string
+           if ( ! ( starting || chp+1 == value_r.end() ) )
+             throw std::invalid_argument( "CpeId:Wfn: embedded *" );
+           break;
+
+         default:      // everything else unquoted
+           if ( ! chIsWfnUnescaped( *chp ) )
+           {
+             if ( chIsValidRange( *chp ) )
+               throw std::invalid_argument( str::Str() << "CpeId:Wfn: missing quote before '" << *chp << "'" );
+             else
+               throw std::invalid_argument( str::Str() << "CpeId:Wfn: illegal character '" << reinterpret_cast<void*>(*chp) << "'" );
+           }
+           break;
+       }
+       if ( starting )
+         starting = false;
+      }
+      _value.reset( new std::string( value_r ) );
+    }
+  }
+
+  CpeId::Value::Value( const std::string & encoded_r, FsFormatType )
+  {
+    if ( encoded_r != "*" )    // ANY is default constructed
+    {
+      if ( encoded_r == "-" )  // NA
+      {
+       _value = CpeId::Value::NA._value;
+      }
+      else
+      {
+       str::Str result;
+       bool starting = true;   // false after the 1st non-?
+       for_( chp, encoded_r.begin(), encoded_r.end() )
+       {
+         switch ( *chp )
+         {
+           case '\\':  // may stay quoted
+             ++chp;
+             if ( chIsWfnUnescaped( *chp ) )
+               result << *chp;
+             else if ( chIsValidRange( *chp ) )
+               result << '\\' << *chp;
+             else if ( *chp )
+               throw std::invalid_argument( str::Str() << "CpeId:Fs: illegal quoted character '\\" << *chp << "'" );
+             else
+               throw std::invalid_argument( "CpeId:Fs: Backslash escapes nothing" );
+             break;
+
+           case '?':   // sequence at beginning or end of string
+             result << '?';
+             while ( *(chp+1) == '?' )
+             {
+               ++chp;
+               result << '?';
+             }
+             if ( ! ( starting || chp+1 == encoded_r.end() ) )
+               throw std::invalid_argument( "CpeId:Fs: embedded ?" );
+             break;
+
+           case '*':   // single at beginning or end of string
+             if ( starting || chp+1 == encoded_r.end() )
+               result << '*';
+             else
+               throw std::invalid_argument( "CpeId:Fs: embedded *" );
+             break;
+
+           default:
+             if ( chIsWfnUnescaped( *chp ) )
+               result << *chp;
+             else if ( chIsValidRange( *chp ) )
+               result << '\\' << *chp;
+             else
+               throw std::invalid_argument( str::Str() << "CpeId:Fs: illegal character '" << reinterpret_cast<void*>(*chp) << "'" );
+             break;
+         }
+         if ( starting )
+           starting = false;
+       }
+       if ( starting )
+         throw std::invalid_argument( "CpeId:Fs: '' value is illegal" );
+       _value.reset( new std::string( result ) );
+      }
+    }
+  }
+
+  CpeId::Value::Value( const std::string & encoded_r, UriFormatType )
+  {
+    if ( ! encoded_r.empty() ) // ANY is default constructed
+    {
+      if ( encoded_r == "-" )  // NA
+      {
+       _value = CpeId::Value::NA._value;
+      }
+      else
+      {
+       str::Str result;
+       bool starting = true;   // false after the 1st non-? (%01)
+       for_( chp, encoded_r.begin(), encoded_r.end() )
+       {
+         char ch = *chp;
+
+         if ( ch == '%' )      // legal '%xx' sequence first
+         {
+           int d1 = heDecodeCh( *(chp+1) );
+           if ( d1 != -1 )
+           {
+             int d2 = heDecodeCh( *(chp+2) );
+             if ( d2 != -1 )
+             {
+               chp += 2;       // skip sequence
+               if ( d1 == 0 )
+               {
+                 if ( d2 == 1 )        // %01 - ? valid sequence at begin or end
+                 {
+                   result << '?';
+                   while ( *(chp+1) == '%' && *(chp+2) == '0' && *(chp+3) == '1' )
+                   {
+                     chp += 3;
+                     result << '?';
+                   }
+                   if ( starting || chp+1 == encoded_r.end() )
+                   {
+                     starting = false;
+                     continue; // -> continue;
+                   }
+                   else
+                     throw std::invalid_argument( "CpeId:Uri: embedded %01" );
+                 }
+                 else if ( d2 == 2 )   // %02 - * valid at begin or end
+                 {
+                   if ( starting || chp+1 == encoded_r.end() )
+                   {
+                     result << '*';
+                     starting = false;
+                     continue; // -> continue;
+                   }
+                   else
+                     throw std::invalid_argument( "CpeId:Uri: embedded %02" );
+                 }
+               }
+               ch = (d1<<4)|d2;
+               if ( ! chIsValidRange( ch ) )
+                 throw std::invalid_argument( str::Str() << "CpeId:Uri: illegal % encoded character '" << reinterpret_cast<void*>(ch) << "'" );
+             }
+           }
+         }
+         else if ( ! chIsValidRange( ch ) )
+           throw std::invalid_argument( str::Str() << "CpeId:Uri: illegal character '" << reinterpret_cast<void*>(ch) << "'" );
+
+         if ( chIsWfnUnescaped( ch ) )
+           result << ch;
+         else
+           result << '\\' << ch;
+
+         if ( starting )
+           starting = false;
+       }
+       _value.reset( new std::string( result ) );
+      }
+    }
+  }
+
+  std::string CpeId::Value::asWfn() const
+  {
+    std::string ret;
+    if ( ! _value )
+    {
+      static const std::string any( "*" );
+      ret = any;
+    }
+    else
+      ret = *_value;   // includes "" for NA
+    return ret;
+  }
+
+  std::string CpeId::Value::asFs() const
+  {
+    std::string ret;
+    if ( isANY() )
+    {
+      static const std::string asterisk( "*" );
+      ret = asterisk;
+    }
+    else if ( isNA() )
+    {
+      static const std::string dash( "-" );
+      ret = dash;
+    }
+    else
+    {
+      str::Str result;
+      for_( chp, _value->begin(), _value->end() )
+      {
+       if ( *chp != '\\' )
+         result << *chp;
+       else
+       {
+         ++chp;
+         switch ( *chp )
+         {
+           case '-':
+           case '.':
+           case '_':
+             result << *chp;   // without escaping
+             break;
+
+           case '\0':
+             throw std::invalid_argument( "CpeId:Wfn: Backslash escapes nothing" );
+             break;
+
+           default:
+             result << '\\' << *chp;
+             break;
+         }
+       }
+      }
+      ret = result;
+    }
+    return ret;
+  }
+
+  std::string CpeId::Value::asUri() const
+  {
+    std::string ret;   // ANY
+    if ( ! isANY() )
+    {
+      if ( isNA() )
+      {
+       static const std::string dash( "-" );
+       ret = dash;
+      }
+      else
+      {
+       str::Str result;
+       for_( chp, _value->begin(), _value->end() )
+       {
+         if ( chIsWfnUnescaped( *chp ) )
+         {
+           result << *chp;
+         }
+         else
+         {
+           static const char *const hdig = "0123456789abcdef";
+           switch ( *chp )
+           {
+             case '\\':
+               ++chp;
+               switch ( *chp )
+               {
+                 case '-':
+                 case '.':
+                   result << *chp;     // without encodeing
+                   break;
+
+                 case '\0':
+                   throw std::invalid_argument( "CpeId:Wfn: Backslash escapes nothing" );
+                   break;
+
+                 default:
+                   result << '%' << hdig[(unsigned char)(*chp)/16] << hdig[(unsigned char)(*chp)%16];
+                   break;
+               }
+               break;
+
+             case '?':
+               result << "%01";
+               break;
+
+             case '*':
+               result << "%02";
+               break;
+
+             default:
+               throw std::invalid_argument( str::Str() << "CpeId:Wfn: illegal char '" << *chp << "' in WFN" );
+               break;
+           }
+         }
+       }
+       ret = result;
+      }
+    }
+    return ret;
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  namespace
+  {
+    /** Whether it's a wildcard character (<tt>[*?]</tt>). */
+    inline bool isWildchar( char ch_r )
+    { return( ch_r == '*' || ch_r == '?' ); }
+
+    /** Whether there is an even number of consecutive backslashes before and including \a rbegin_r
+     * An even number of backslashes means the character following is unescaped.
+     */
+    inline bool evenNumberOfBackslashes( std::string::const_reverse_iterator rbegin_r, std::string::const_reverse_iterator rend_r )
+    {
+      unsigned backslashes = 0;
+      for_( it, rbegin_r, rend_r )
+      {
+       if ( *it == '\\' )
+         ++backslashes;
+       else
+         break;
+      }
+      return !(backslashes & 1U);
+    }
+
+    /** Number of chars (not counting escaping backslashes) in <tt>[begin_r,end_r[</tt> */
+    inline unsigned trueCharsIn( const std::string & str_r, std::string::size_type begin_r, std::string::size_type end_r )
+    {
+      unsigned chars = 0;
+      for_( it, begin_r, end_r )
+      {
+       ++chars;
+       if ( str_r[it] == '\\' )
+       {
+         if ( ++it == end_r )
+           break;
+       }
+      }
+      return chars;
+    }
+
+    /** Match helper comparing 2 Wildcardfree string values (case insensitive). */
+    inline bool matchWildcardfreeString( const std::string & lhs, const std::string & rhs )
+    { return( str::compareCI( lhs, rhs ) == 0 ); }
+
+    /** Match helper matching Wildcarded source against Wildcardfree target.
+     *
+     * Constraints on usage of the unquoted question mark (zero or one char in \a trg):
+     * 1. An unquoted question mark MAY be used at the beginning and/or the end of an
+     *    attribute-value string.
+     * 2. A contiguous sequence of unquoted question marks MAY appear at the beginning
+     *    and/or the end of an attribute-value string.
+     * 3. An unquoted question mark SHALL NOT be used in any other place in an
+     *    attribute-value string.
+     *
+     * Constraints on usage of the unquoted asterisk  (zero or more chars in \a trg):
+     * 1. A single unquoted asterisk MAY be used as the entire attribute-value string.
+     * 2. A single unquoted asterisk MAY be used at the beginning and/or end of an
+     *    attribute-value string.
+     * 3. An unquoted asterisk SHALL NOT be used in any other place in an attribute-value
+     *    string.
+     *
+     * Unquoted question marks and asterisks MAY appear in the same attribute-value string
+     * as long as they meet the constraints above.
+     *
+     * Example of illegal usage: "foo?bar", "bar??baz", "q??x",
+     *                           "foo*bar", "**foo", "bar***",
+     *                           "*?foobar", "foobar*?"
+     *
+     * \note Relies on \a src and \a trg being wellformed.
+     */
+    inline bool matchWildcardedString( std::string src, std::string trg )
+    {
+      // std::string::npos remembers an asterisk
+      // unescaped wildcard prefix
+      std::string::size_type prefx = 0;
+      switch ( *src.begin() )  // wellformed implies not empty
+      {
+       case '*':
+         if ( src.size() == 1 )
+           return true;        // "*" matches always: superset
+         else
+           prefx = std::string::npos;
+           src.erase( 0, 1 );
+         break;
+       case '?':
+         ++prefx;
+         for_( it, ++src.begin(), src.end() )
+         { if ( *it == '?' ) ++prefx; else break; }
+         if ( src.size() == prefx )
+           return( trg.size() <= prefx );      // "??..?": superset if at most #prefx chars
+         else
+           src.erase( 0, prefx );
+         break;
+       default:
+         break;
+      }
+      // unescaped wildcard suffix
+      std::string::size_type suffx = 0;
+      if ( ! src.empty() )
+      {
+       switch ( *src.rbegin() )
+       {
+         case '*':
+           if ( evenNumberOfBackslashes( ++src.rbegin(), src.rend() ) )
+           {
+             suffx = std::string::npos;
+             src.erase( src.size()-1 );
+           }
+           break;
+         case '?':
+           ++suffx;
+           for_( it, ++src.rbegin(), src.rend() )
+           { if ( *it == '?' ) ++suffx; else break; }
+           if ( ! evenNumberOfBackslashes( src.rbegin()+suffx, src.rend() ) )
+             --suffx;  // last '?' was escaped.
+           src.erase( src.size()-suffx );
+           break;
+         default:
+           break;
+       }
+      }
+      // now match; find src in trg an check surrounding wildcards
+      src = str::toLower( src );
+      trg = str::toLower( trg );
+      for ( std::string::size_type match = trg.find( src, 0 );
+           match != std::string::npos;
+            match = trg.find( src, match+1 ) )
+      {
+       if ( prefx != std::string::npos && trueCharsIn( trg, 0, match ) > prefx )
+         break;        // not "*", and already more chars than "?"s before match: disjoint
+       std::string::size_type frontSize = match + src.size();
+       if ( suffx != std::string::npos && trueCharsIn( trg, frontSize, trg.size() ) > suffx )
+         continue;     // not "*", and still more chars than "?"s after match: check next match
+       return true;    // match: superset
+      }
+      return false;    // disjoint
+    }
+  } // namespace
+  ///////////////////////////////////////////////////////////////////
+
+  bool CpeId::Value::containsWildcard() const
+  {
+    const std::string & value( *_value );
+    return ( isWildchar( *value.begin() )
+       || ( isWildchar( *value.rbegin() ) && evenNumberOfBackslashes( ++value.rbegin(), value.rend() ) ) );
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  /// Symmetric attribute compare if wildcards are involved!
+  /// The specs define any comarison with a wildcarded attribute as
+  /// target to return \c uncomparable:
+  /// \code
+  ///    wildcardfree  <=>  wildcarded    ==>  uncomparable,
+  ///    wildcarded    <=>  wildcardfree  ==>  superset or disjoint
+  /// \endcode
+  /// But a symmetric result is much more intuitive:
+  /// \code
+  ///    wildcardfree  <=>  wildcarded    ==>  subset or disjoint
+  ///    wildcarded    <=>  wildcardfree  ==>  superset or disjoint
+  /// \endcode
+  ///////////////////////////////////////////////////////////////////
+#define WFN_STRICT_SPEC 0
+#if WFN_STRICT_SPEC
+  //SetCompare CpeId::Value::setRelationMixinCompare( const CpeId::Value & trg ) const
+  {
+    static const SetCompare kNeedsCloserLook( SetCompare::Enum(-1) );  // artificial Compare value
+    static const SetCompare matchTabel[4][4] = {{
+      /* ANY,          ANY             */ SetCompare::equal,
+      /* ANY,          NA              */ SetCompare::properSuperset,
+      /* ANY,          wildcardfree    */ SetCompare::properSuperset,
+      /* ANY,          wildcarded      */ SetCompare::uncomparable,
+    },{
+      /* NA,           ANY             */ SetCompare::properSubset,
+      /* NA,           NA              */ SetCompare::equal,
+      /* NA,           wildcardfree    */ SetCompare::disjoint,
+      /* NA,           wildcarded      */ SetCompare::uncomparable,
+    },{
+      /* wildcardfree, ANY             */ SetCompare::properSubset,
+      /* wildcardfree, NA              */ SetCompare::disjoint,
+      /* wildcardfree, wildcardfree    */ kNeedsCloserLook,    // equal or disjoint
+      /* wildcardfree, wildcarded      */ SetCompare::uncomparable,
+    },{
+      /* wildcarded,   ANY             */ SetCompare::properSubset,
+      /* wildcarded,   NA              */ SetCompare::disjoint,
+      /* wildcarded,   wildcardfree    */ kNeedsCloserLook,    // superset or disjoint
+      /* wildcarded,   wildcarded      */ SetCompare::uncomparable,
+    }};
+
+    Type srcType = type();
+    Type trgType = trg.type();
+    SetCompare ret = matchTabel[srcType.asIntegral()][trgType.asIntegral()];
+    if ( ret == kNeedsCloserLook )
+    {
+      if ( srcType == Type::wildcardfree )     // trgType == Type::wildcardfree
+      {
+       // simple string compare
+       ret = matchWildcardfreeString( *_value, *trg._value ) ? SetCompare::equal : SetCompare::disjoint;
+      }
+      else if ( srcType == Type::wildcarded )  // trgType == Type::wildcardfree
+      {
+       // Needs wildcard compare
+       ret = matchWildcardedString( *_value, *trg._value ) ? SetCompare::properSuperset : SetCompare::disjoint;
+     }
+    }
+    return ret;
+  }
+#else
+  SetCompare CpeId::Value::setRelationMixinCompare( const CpeId::Value & trg ) const
+  {
+    ///////////////////////////////////////////////////////////////////
+    // ANY,            ANY             => equal
+    // ANY,            NA              => properSuperset
+    // ANY,            wildcardfree    => properSuperset
+    // ANY,            wildcarded      => properSuperset
+    //
+    // NA,             ANY             => properSubset
+    // NA,             NA              => equal
+    // NA,             wildcardfree    => disjoint
+    // NA,             wildcarded      => disjoint
+    //
+    // wildcardfree,   ANY             => properSubset
+    // wildcardfree,   NA              => disjoint
+    // wildcardfree,   wildcardfree    => NeedsCloserLook:     equal or disjoint
+    // wildcardfree,   wildcarded      => NeedsCloserLook:     subset or disjoint
+    //
+    // wildcarded,     ANY             => properSubset
+    // wildcarded,     NA              => disjoint
+    // wildcarded,     wildcardfree    => NeedsCloserLook:     superset or disjoint
+    // wildcarded,     wildcarded      => NeedsCloserLook"     equal or uncomparable
+    ///////////////////////////////////////////////////////////////////
+
+    SetCompare ret = SetCompare::disjoint;
+
+    if ( isANY() )
+    {
+      ret = trg.isANY() ? SetCompare::equal : SetCompare::properSuperset;
+    }
+    else if ( trg.isANY() )
+    {
+      ret = SetCompare::properSubset;
+    }
+    else if ( isNA() )
+    {
+      if ( trg.isNA() ) ret = SetCompare::equal; // else: SetCompare::disjoint;
+    }
+    else if ( ! trg.isNA() ) // else: SetCompare::disjoint;
+    {
+      // NeedsCloserLook:
+      if ( isWildcarded() )
+      {
+       if ( trg.isWildcarded() )
+       {
+         // simple string compare just to detect 'equal'
+         ret = matchWildcardfreeString( *_value, *trg._value ) ? SetCompare::equal : SetCompare::uncomparable;
+       }
+       else
+       {
+         // Needs wildcard compare (src,trg)
+         if ( matchWildcardedString( *_value, *trg._value ) ) ret = SetCompare::properSuperset; // else: SetCompare::disjoint;
+       }
+      }
+      else
+      {
+       if ( trg.isWildcarded() )
+       {
+         // Needs wildcard compare (trg,src)
+         if ( matchWildcardedString( *trg._value, *_value ) ) ret = SetCompare::properSubset; // else: SetCompare::disjoint;
+       }
+       else
+       {
+         // simple string compare
+         if ( matchWildcardfreeString( *_value, *trg._value ) ) ret = SetCompare::equal; // else: SetCompare::disjoint;
+       }
+      }
+    }
+    return ret;
+  }
+#endif // WFN_STRICT_SPEC
 
-  CpeId::Match CpeId::match( const CpeId & rhs ) const
-  { return _pimpl->match( *rhs._pimpl ); }
+  std::ostream & operator<<( std::ostream & str, const CpeId::Value & obj )
+  { return str << obj.asString(); }
 
 } // namespace zypp
 ///////////////////////////////////////////////////////////////////