/** \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 )
{}
{}
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
///////////////////////////////////////////////////////////////////