CpeId: Basic functionality incl. matching
[platform/upstream/libzypp.git] / zypp / CpeId.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/CpeId.cc
10  */
11 #include <iostream>
12
13 #include "zypp/base/String.h"
14 #include "zypp/base/LogTools.h"
15 #include "zypp/base/NonCopyable.h"
16
17 #include "zypp/CpeId.h"
18
19 using std::endl;
20
21 /** Initializer list with all wfn attributes */
22 #define WFN_ATTRIBUTES {\
23   Attribute::part,      \
24   Attribute::vendor,    \
25   Attribute::product,   \
26   Attribute::version,   \
27   Attribute::update,    \
28   Attribute::edition,   \
29   Attribute::language,  \
30   Attribute::sw_edition,\
31   Attribute::target_sw, \
32   Attribute::target_hw, \
33   Attribute::other,     \
34 }
35
36 ///////////////////////////////////////////////////////////////////
37 namespace zypp
38 {
39   ///////////////////////////////////////////////////////////////////
40   namespace
41   {
42     /** Hex-digit to number or -1. */
43     inline int heDecodeCh( char ch )
44     {
45       if ( '0' <= ch && ch <= '9' )
46         return( ch - '0' );
47       if ( 'A' <= ch && ch <= 'F' )
48         return( ch - 'A' + 10 );
49       if ( 'a' <= ch && ch <= 'f' )
50         return( ch - 'a' + 10 );
51       return -1;
52     }
53
54     /** Printable non whitespace in [0x00,0x7f] valid in WFN */
55     inline bool chIsValidRange( char ch )
56     { return( '!' <= ch && ch <= '~' ); }
57
58     /** Alpha */
59     inline bool chIsAlpha( char ch )
60     { return( ( 'a' <= ch && ch <= 'z' ) || ( 'A' <= ch && ch <= 'Z' ) ); }
61
62     /** Digit */
63     inline bool chIsNum( char ch )
64     { return( '0' <= ch && ch <= '9' ); }
65
66     /** Alphanum */
67     inline bool chIsAlNum( char ch )
68     { return( chIsAlpha( ch ) || chIsNum( ch ) ); }
69
70     /** Alphanum or \c underscore are unescaped in WFN */
71     inline bool chIsWfnUnescaped( char ch )
72     { return( chIsAlNum( ch ) || ch == '_' ); }
73
74   } // namespace
75   ///////////////////////////////////////////////////////////////////
76
77   ///////////////////////////////////////////////////////////////////
78   /// \class CpeId::Impl
79   /// \brief CpeId implementation.
80   ///////////////////////////////////////////////////////////////////
81   class CpeId::Impl : private base::NonCopyable
82   {
83     typedef std::array<Value,Attribute::numAttributes> Wfn;
84
85   public:
86     Impl() {}
87
88     Impl( const std::string & cpe_r )
89       : _wfn( unbind( cpe_r ) )
90     {}
91
92   public:
93     explicit operator bool() const
94     { for ( const auto & val : _wfn ) if ( ! val.isANY() ) return true; return false; }
95
96     std::string asFs() const
97     {
98       str::Str ret;
99       ret << "cpe:2.3";
100       for ( auto ai : WFN_ATTRIBUTES )
101       {
102         ret << ':' << _wfn[ai].asFs();
103       }
104       return ret;
105     }
106
107     std::string asUri() const
108     {
109       str::Str ret;
110       ret << "cpe:/";
111       std::string val;
112       unsigned colon = 0;       // to remember trailing colons
113       for ( auto ai : WFN_ATTRIBUTES )
114       {
115         val = _wfn[ai].asUri();
116
117         if ( ai == Attribute::edition )
118         {
119           if ( ! ( _wfn[Attribute::sw_edition].isANY()
120                 && _wfn[Attribute::target_sw].isANY()
121                 && _wfn[Attribute::target_hw].isANY()
122                 && _wfn[Attribute::other].isANY() ) )
123           {
124             // packing is needed
125             val = str::Str()
126                   << '~' << val//Attribute::edition
127                   << '~' << _wfn[Attribute::sw_edition].asUri()
128                   << '~' << _wfn[Attribute::target_sw].asUri()
129                   << '~' << _wfn[Attribute::target_hw].asUri()
130                   << '~' << _wfn[Attribute::other].asUri();
131           }
132         }
133
134         if ( ! val.empty() )
135         {
136           if ( colon )
137             ret << std::string( colon, ':' );
138           ret << val;
139           colon = 1;
140         }
141         else
142           ++colon;
143
144         if ( ai == Attribute::language )
145           break;        // remaining attrs packaed in edition
146       }
147       return ret;
148     }
149
150     std::string asWfn() const
151     {
152       str::Str ret;
153       ret << "wfn:[";
154       for ( auto ai : WFN_ATTRIBUTES )
155       {
156         const Value & val( _wfn[ai] );
157         if ( ! val.isANY() )
158         {
159           if ( ai ) ret << ',';
160           ret << Attribute::asString( ai ) << '=';
161           if ( val.isString() )
162             ret << '"' << val << '"';
163           else
164             ret << "NA";        // as ANY is omitted, it must be NA
165         }
166       }
167       return ret << "]";
168     }
169
170   public:
171     SetCompare setRelationMixinCompare( const Impl & trg ) const
172     {
173       SetCompare ret = SetCompare::equal;
174       for ( auto ai : WFN_ATTRIBUTES )
175       {
176         switch ( _wfn[ai].compare( trg._wfn[ai] ).asEnum() )
177         {
178           case SetCompare::uncomparable:
179             ret = SetCompare::uncomparable;
180             break;
181
182           case SetCompare::equal:
183             break;
184
185           case SetCompare::properSubset:
186             if ( ret == SetCompare::equal )
187               ret = SetCompare::properSubset;
188             else if ( ret != SetCompare::properSubset )
189               ret = SetCompare::uncomparable;
190             break;
191
192           case SetCompare::properSuperset:
193             if ( ret == SetCompare::equal )
194               ret = SetCompare::properSuperset;
195             else if ( ret != SetCompare::properSuperset )
196               ret = SetCompare::uncomparable;
197             break;
198
199           case SetCompare::disjoint:
200             ret = SetCompare::disjoint;
201             break;
202         }
203         if ( ret == SetCompare::uncomparable || ret == SetCompare::disjoint )
204           break;
205       }
206       return ret;
207     }
208
209   private:
210     /** Assign \a val_r if it meets \a attr_r specific contraints.
211      * \throws std::invalid_argument if string is malformed
212      */
213     static void assignAttr( Wfn & wfn_r, Attribute attr_r, const Value & val_r )
214     {
215       if ( val_r.isString() )
216       {
217         switch ( attr_r.asEnum() )
218         {
219           case Attribute::part:
220             {
221               const std::string & wfn( val_r.asWfn() );
222               switch ( wfn[0] )
223               {
224                 case 'h':
225                 case 'o':
226                 case 'a':
227                   if ( wfn[1] == '\0' )
228                     break;
229                   // else: fallthrough
230                 default:
231                   throw std::invalid_argument( "CpeId:Wfn:part: illegal value" );
232                   break;
233               }
234             }
235             break;
236
237           case Attribute::language:
238             {
239               const std::string & wfn( val_r.asWfn() );
240               std::string::size_type len = 0;
241               // (2*3ALPHA) ["-" (2ALPHA / 3DIGIT)]
242               if ( chIsAlpha( wfn[0] ) && chIsAlpha( wfn[1] ) )
243               {
244                 len = chIsAlpha( wfn[2] ) ? 3 : 2;
245                 if ( wfn[len] == '-' )
246                 {
247                   if ( chIsAlpha( wfn[len+1] ) && chIsAlpha( wfn[len+2] ) )
248                     len += 3;
249                   else if ( chIsNum( wfn[len+1] ) && chIsNum( wfn[len+2] ) && chIsNum( wfn[len+3] ) )
250                     len += 4;
251                 }
252               }
253               if ( wfn.size() != len )
254                 throw std::invalid_argument( "CpeId:Wfn:language: illegal value" );
255             }
256             break;
257
258           default:
259             // no contraints
260             break;
261         }
262       }
263       wfn_r[attr_r.asIntegral()] = val_r;
264     }
265
266   private:
267     /** Parse magic and unbind accordingly
268      * \throws std::invalid_argument if string is malformed
269      */
270     static Wfn unbind( const std::string & cpe_r );
271
272     /** Parse Uri and unbind
273      * \throws std::invalid_argument if string is malformed
274      */
275     static Wfn unbindUri( const std::string & cpe_r );
276
277     /** Parse Fs and unbind
278      * \throws std::invalid_argument if string is malformed
279      */
280     static Wfn unbindFs( const std::string & cpe_r );
281
282   private:
283     Wfn _wfn;
284   };
285
286   CpeId::Impl::Wfn CpeId::Impl::unbind( const std::string & cpe_r )
287   {
288     Wfn ret;
289     if ( cpe_r[0] == 'c'
290       && cpe_r[1] == 'p'
291       && cpe_r[2] == 'e'
292       && cpe_r[3] == ':' )
293     {
294       if ( cpe_r[4] == '/' )
295       {
296         ret = unbindUri( cpe_r );
297       }
298       else if ( cpe_r[4] == '2'
299              && cpe_r[5] == '.'
300              && cpe_r[6] == '3'
301              && cpe_r[7] == ':' )
302       {
303         ret = unbindFs( cpe_r );
304       }
305       else
306         throw std::invalid_argument( "CpeId: bad magic" );
307     }
308     else if ( cpe_r[0] != '\0' )
309       throw std::invalid_argument( "CpeId: bad magic" );
310     return ret;
311   }
312
313   CpeId::Impl::Wfn CpeId::Impl::unbindUri( const std::string & cpe_r )
314   {
315     Wfn ret;
316
317     std::vector<std::string> field;
318     field.reserve( Attribute::numAttributes );
319     if ( str::splitFields( cpe_r.c_str()+5/* skip magic 'cpe:/' */, std::back_inserter(field), ":" ) > Attribute::numAttributes )
320       throw std::invalid_argument( "CpeId:Uri: too many fields" );
321     field.resize( Attribute::numAttributes );   // fillup with ANY("")
322
323     for ( auto ai : WFN_ATTRIBUTES )
324     {
325       if ( ai == Attribute::edition && field[ai][0] == '~' )
326       {
327         // unpacking is needed
328         static constexpr unsigned numPacks = 6u;        // dummy_before_~ + edition + 4 extended attributes
329         std::vector<std::string> pack;
330         pack.reserve( numPacks );
331         if ( str::splitFields( field[ai], std::back_inserter(pack), "~" ) > numPacks )
332           throw std::invalid_argument( "CpeId:Uri: too many packs" );
333         pack.resize( numPacks );        // fillup with ANY(""), should be noOP
334
335         pack[1].swap( field[Attribute::edition] );
336         pack[2].swap( field[Attribute::sw_edition] );
337         pack[3].swap( field[Attribute::target_sw] );
338         pack[4].swap( field[Attribute::target_hw] );
339         pack[5].swap( field[Attribute::other] );
340       }
341       assignAttr( ret, ai, Value( field[ai], Value::uriFormat ) );
342     }
343     return ret;
344   }
345
346   CpeId::Impl::Wfn CpeId::Impl::unbindFs( const std::string & cpe_r )
347   {
348     Wfn ret;
349
350     std::vector<std::string> field;
351     field.reserve( Attribute::numAttributes );
352     if ( str::splitFields( cpe_r.c_str()+8/* skip magic 'cpe:2.3:' */, std::back_inserter(field), ":" ) > Attribute::numAttributes )
353       throw std::invalid_argument( "CpeId:Fs: too many fields" );
354     field.resize( Attribute::numAttributes, "*" );      // fillup with ANY|"*"
355
356     for ( auto ai : WFN_ATTRIBUTES )
357     {
358       assignAttr( ret, ai, Value( field[ai], Value::fsFormat ) );
359     }
360     return ret;
361   }
362
363
364   ///////////////////////////////////////////////////////////////////
365   //    class CpeId
366   ///////////////////////////////////////////////////////////////////
367
368   CpeId::CpeId()
369     : _pimpl( new Impl )
370   {}
371
372   CpeId::CpeId( const std::string & cpe_r )
373     : _pimpl( new Impl( cpe_r ) )
374   {}
375
376   CpeId::CpeId( const std::string & cpe_r, NoThrowType )
377   {
378     try
379     { _pimpl.reset( new Impl( cpe_r ) ); }
380     catch(...)
381     { _pimpl.reset( new Impl ); }
382   }
383
384   CpeId::~CpeId()
385   {}
386
387   CpeId::operator bool() const
388   { return bool(*_pimpl); }
389
390   std::string CpeId::asFs() const
391   { return _pimpl->asFs(); }
392
393   std::string CpeId::asUri() const
394   { return _pimpl->asUri(); }
395
396   std::string CpeId::asWfn() const
397   { return _pimpl->asWfn(); }
398
399   SetCompare CpeId::setRelationMixinCompare( const CpeId & trg ) const
400   { return _pimpl->setRelationMixinCompare( *trg._pimpl ); }
401
402   ///////////////////////////////////////////////////////////////////
403   //    class CpeId::WfnAttribute
404   ///////////////////////////////////////////////////////////////////
405
406   const std::string & CpeId::_AttributeDef::asString( Enum val_r )
407   {
408     static std::map<Enum,std::string> _table = {
409 #define OUTS(N) { N, #N }
410       OUTS( part ),
411       OUTS( vendor ),
412       OUTS( product ),
413       OUTS( version ),
414       OUTS( update ),
415       OUTS( edition ),
416       OUTS( language ),
417       OUTS( sw_edition ),
418       OUTS( target_sw ),
419       OUTS( target_hw ),
420       OUTS( other ),
421 #undef OUTS
422     };
423     return _table[val_r];
424   }
425
426   ///////////////////////////////////////////////////////////////////
427   //    class CpeId::Value
428   ///////////////////////////////////////////////////////////////////
429
430   const CpeId::Value CpeId::Value::ANY;
431   const CpeId::Value CpeId::Value::NA( "" );
432
433   CpeId::Value::Value( const std::string & value_r )
434   {
435     if ( value_r.empty() )      // NA
436     {
437       if ( ! CpeId::Value::NA._value )  // initialized by this ctor!
438         _value.reset( new std::string );
439       else
440         _value = CpeId::Value::NA._value;
441     }
442     else if ( value_r != "*" )  // ANY is default constructed
443     {
444       bool starting = true;     // false after the 1st non-?
445       for_( chp, value_r.begin(), value_r.end() )
446       {
447         switch ( *chp )
448         {
449           case '\\':    // quoted
450             ++chp;
451             if ( ! chIsValidRange( *chp )  )
452             {
453               if ( *chp )
454                 throw std::invalid_argument( "CpeId:Wfn: illegal quoted character" );
455               else
456                 throw std::invalid_argument( "CpeId:Wfn: Backslash escapes nothing" );
457             }
458             else if ( chIsWfnUnescaped( *chp ) )
459               throw std::invalid_argument( "CpeId:Wfn: unnecessarily quoted character" );
460             else if ( starting && *chp == '-' && chp+1 == value_r.end() )
461               throw std::invalid_argument( "CpeId:Wfn: '\\-' is illegal value" );
462             break;
463
464           case '?':     // sequence at beginning or end of string
465             while ( *(chp+1) == '?' )
466               ++chp;
467             if ( ! ( starting || chp+1 == value_r.end() ) )
468               throw std::invalid_argument( "CpeId:Wfn: embedded ?" );
469             break;
470
471           case '*':     // single at beginning or end of string
472             if ( ! ( starting || chp+1 == value_r.end() ) )
473               throw std::invalid_argument( "CpeId:Wfn: embedded *" );
474             break;
475
476           default:      // everything else unquoted
477             if ( ! chIsWfnUnescaped( *chp ) )
478             {
479               if ( chIsValidRange( *chp ) )
480                 throw std::invalid_argument( "CpeId:Wfn: missing quote" );
481               else
482                 throw std::invalid_argument( "CpeId:Wfn: illegal character" );
483             }
484             break;
485         }
486         if ( starting )
487           starting = false;
488       }
489       _value.reset( new std::string( value_r ) );
490     }
491   }
492
493   CpeId::Value::Value( const std::string & encoded_r, FsFormatType )
494   {
495     if ( encoded_r != "*" )     // ANY is default constructed
496     {
497       if ( encoded_r == "-" )   // NA
498       {
499         _value = CpeId::Value::NA._value;
500       }
501       else
502       {
503         str::Str result;
504         bool starting = true;   // false after the 1st non-?
505         for_( chp, encoded_r.begin(), encoded_r.end() )
506         {
507           switch ( *chp )
508           {
509             case '\\':  // may stay quoted
510               ++chp;
511               if ( chIsWfnUnescaped( *chp ) )
512                 result << *chp;
513               else if ( chIsValidRange( *chp ) )
514                 result << '\\' << *chp;
515               else if ( *chp )
516                 throw std::invalid_argument( "CpeId:Fs: illegal quoted character" );
517               else
518                 throw std::invalid_argument( "CpeId:Fs: Backslash escapes nothing" );
519               break;
520
521             case '?':   // sequence at beginning or end of string
522               result << '?';
523               while ( *(chp+1) == '?' )
524               {
525                 ++chp;
526                 result << '?';
527               }
528               if ( ! ( starting || chp+1 == encoded_r.end() ) )
529                 throw std::invalid_argument( "CpeId:Fs: embedded ?" );
530               break;
531
532             case '*':   // single at beginning or end of string
533               if ( starting || chp+1 == encoded_r.end() )
534                 result << '*';
535               else
536                 throw std::invalid_argument( "CpeId:Fs: embedded *" );
537               break;
538
539             default:
540               if ( chIsWfnUnescaped( *chp ) )
541                 result << *chp;
542               else if ( chIsValidRange( *chp ) )
543                 result << '\\' << *chp;
544               else
545                 throw std::invalid_argument( "CpeId:Fs: illegal character" );
546               break;
547           }
548           if ( starting )
549             starting = false;
550         }
551         if ( starting )
552           throw std::invalid_argument( "CpeId:Fs: '' is illegal" );
553         _value.reset( new std::string( result ) );
554       }
555     }
556   }
557
558   CpeId::Value::Value( const std::string & encoded_r, UriFormatType )
559   {
560     if ( ! encoded_r.empty() )  // ANY is default constructed
561     {
562       if ( encoded_r == "-" )   // NA
563       {
564         _value = CpeId::Value::NA._value;
565       }
566       else
567       {
568         str::Str result;
569         bool starting = true;   // false after the 1st non-? (%01)
570         for_( chp, encoded_r.begin(), encoded_r.end() )
571         {
572           char ch = *chp;
573
574           if ( ch == '%' )      // legal '%xx' sequence first
575           {
576             int d1 = heDecodeCh( *(chp+1) );
577             if ( d1 != -1 )
578             {
579               int d2 = heDecodeCh( *(chp+2) );
580               if ( d2 != -1 )
581               {
582                 chp += 2;       // skip sequence
583                 if ( d1 == 0 )
584                 {
585                   if ( d2 == 1 )        // %01 - ? valid sequence at begin or end
586                   {
587                     result << '?';
588                     while ( *(chp+1) == '%' && *(chp+2) == '0' && *(chp+3) == '1' )
589                     {
590                       chp += 3;
591                       result << '?';
592                     }
593                     if ( starting || chp+1 == encoded_r.end() )
594                     {
595                       starting = false;
596                       continue; // -> continue;
597                     }
598                     else
599                       throw std::invalid_argument( "CpeId:Uri: embedded %01" );
600                   }
601                   else if ( d2 == 2 )   // %02 - * valid at begin or end
602                   {
603                     if ( starting || chp+1 == encoded_r.end() )
604                     {
605                       result << '*';
606                       starting = false;
607                       continue; // -> continue;
608                     }
609                     else
610                       throw std::invalid_argument( "CpeId:Uri: embedded %02" );
611                   }
612                 }
613                 ch = (d1<<4)|d2;
614                 if ( ! chIsValidRange( ch ) )
615                   throw std::invalid_argument( "CpeId:Uri: illegal % encoded character" );
616               }
617             }
618           }
619           else if ( ! chIsValidRange( ch ) )
620             throw std::invalid_argument( "CpeId:Uri: illegal character" );
621
622           if ( chIsWfnUnescaped( ch ) )
623             result << ch;
624           else
625             result << '\\' << ch;
626
627           if ( starting )
628             starting = false;
629         }
630         _value.reset( new std::string( result ) );
631       }
632     }
633   }
634
635   std::string CpeId::Value::asWfn() const
636   {
637     std::string ret;
638     if ( ! _value )
639     {
640       static const std::string any( "*" );
641       ret = any;
642     }
643     else
644       ret = *_value;    // includes "" for NA
645     return ret;
646   }
647
648   std::string CpeId::Value::asFs() const
649   {
650     std::string ret;
651     if ( isANY() )
652     {
653       static const std::string asterisk( "*" );
654       ret = asterisk;
655     }
656     else if ( isNA() )
657     {
658       static const std::string dash( "-" );
659       ret = dash;
660     }
661     else
662     {
663       str::Str result;
664       for_( chp, _value->begin(), _value->end() )
665       {
666         if ( *chp != '\\' )
667           result << *chp;
668         else
669         {
670           ++chp;
671           switch ( *chp )
672           {
673             case '-':
674             case '.':
675             case '_':
676               result << *chp;   // without escaping
677               break;
678
679             case '\0':
680               throw std::invalid_argument( "CpeId:Wfn: Backslash escapes nothing" );
681               break;
682
683             default:
684               result << '\\' << *chp;
685               break;
686           }
687         }
688       }
689       ret = result;
690     }
691     return ret;
692   }
693
694   std::string CpeId::Value::asUri() const
695   {
696     std::string ret;    // ANY
697     if ( ! isANY() )
698     {
699       if ( isNA() )
700       {
701         static const std::string dash( "-" );
702         ret = dash;
703       }
704       else
705       {
706         str::Str result;
707         for_( chp, _value->begin(), _value->end() )
708         {
709           if ( chIsWfnUnescaped( *chp ) )
710           {
711             result << *chp;
712           }
713           else
714           {
715             static const char *const hdig = "0123456789abcdef";
716             switch ( *chp )
717             {
718               case '\\':
719                 ++chp;
720                 switch ( *chp )
721                 {
722                   case '-':
723                   case '.':
724                     result << *chp;     // without encodeing
725                     break;
726
727                   case '\0':
728                     throw std::invalid_argument( "CpeId:Wfn: Backslash escapes nothing" );
729                     break;
730
731                   default:
732                     result << '%' << hdig[(unsigned char)(*chp)/16] << hdig[(unsigned char)(*chp)%16];
733                     break;
734                 }
735                 break;
736
737               case '?':
738                 result << "%01";
739                 break;
740
741               case '*':
742                 result << "%02";
743                 break;
744
745               default:
746                 throw std::invalid_argument( str::Str() << "CpeId:Wfn: illegal char '" << *chp << "' in WFN" );
747                 break;
748             }
749           }
750         }
751         ret = result;
752       }
753     }
754     return ret;
755   }
756
757   ///////////////////////////////////////////////////////////////////
758   namespace
759   {
760     /** Whether it's a wildcard character (<tt>[*?]</tt>). */
761     inline bool isWildchar( char ch_r )
762     { return( ch_r == '*' || ch_r == '?' ); }
763
764     /** Whether there is an even number of consecutive backslashes before and including \a rbegin_r
765      * An even number of backslashes means the character following is unescaped.
766      */
767     inline bool evenNumberOfBackslashes( std::string::const_reverse_iterator rbegin_r, std::string::const_reverse_iterator rend_r )
768     {
769       unsigned backslashes = 0;
770       for_( it, rbegin_r, rend_r )
771       {
772         if ( *it == '\\' )
773           ++backslashes;
774         else
775           break;
776       }
777       return !(backslashes & 1U);
778     }
779
780     /** Number of chars (not counting escaping backslashes) in <tt>[begin_r,end_r[</tt> */
781     inline unsigned trueCharsIn( const std::string & str_r, std::string::size_type begin_r, std::string::size_type end_r )
782     {
783       unsigned chars = 0;
784       for_( it, begin_r, end_r )
785       {
786         ++chars;
787         if ( str_r[it] == '\\' )
788         {
789           if ( ++it == end_r )
790             break;
791         }
792       }
793       return chars;
794     }
795
796     /** Match helper comparing 2 Wildcardfree string values (case insensitive). */
797     inline bool matchWildcardfreeString( const std::string & lhs, const std::string & rhs )
798     { return( str::compareCI( lhs, rhs ) == 0 ); }
799
800     /** Match helper matching Wildcarded source against Wildcardfree target.
801      *
802      * Constraints on usage of the unquoted question mark (zero or one char in \a trg):
803      * 1. An unquoted question mark MAY be used at the beginning and/or the end of an
804      *    attribute-value string.
805      * 2. A contiguous sequence of unquoted question marks MAY appear at the beginning
806      *    and/or the end of an attribute-value string.
807      * 3. An unquoted question mark SHALL NOT be used in any other place in an
808      *    attribute-value string.
809      *
810      * Constraints on usage of the unquoted asterisk  (zero or more chars in \a trg):
811      * 1. A single unquoted asterisk MAY be used as the entire attribute-value string.
812      * 2. A single unquoted asterisk MAY be used at the beginning and/or end of an
813      *    attribute-value string.
814      * 3. An unquoted asterisk SHALL NOT be used in any other place in an attribute-value
815      *    string.
816      *
817      * Unquoted question marks and asterisks MAY appear in the same attribute-value string
818      * as long as they meet the constraints above.
819      *
820      * Example of illegal usage: "foo?bar", "bar??baz", "q??x",
821      *                           "foo*bar", "**foo", "bar***",
822      *                           "*?foobar", "foobar*?"
823      *
824      * \note Relies on \a src and \a trg being wellformed.
825      */
826     inline bool matchWildcardedString( std::string src, std::string trg )
827     {
828       // std::string::npos remembers an asterisk
829       // unescaped wildcard prefix
830       std::string::size_type prefx = 0;
831       switch ( *src.begin() )   // wellformed implies not empty
832       {
833         case '*':
834           if ( src.size() == 1 )
835             return true;        // "*" matches always: superset
836           else
837             prefx = std::string::npos;
838             src.erase( 0, 1 );
839           break;
840         case '?':
841           ++prefx;
842           for_( it, ++src.begin(), src.end() )
843           { if ( *it == '?' ) ++prefx; else break; }
844           if ( src.size() == prefx )
845             return( trg.size() <= prefx );      // "??..?": superset if at most #prefx chars
846           else
847             src.erase( 0, prefx );
848           break;
849         default:
850           break;
851       }
852       // unescaped wildcard suffix
853       std::string::size_type suffx = 0;
854       if ( ! src.empty() )
855       {
856         switch ( *src.rbegin() )
857         {
858           case '*':
859             if ( evenNumberOfBackslashes( ++src.rbegin(), src.rend() ) )
860             {
861               suffx = std::string::npos;
862               src.erase( src.size()-1 );
863             }
864             break;
865           case '?':
866             ++suffx;
867             for_( it, ++src.rbegin(), src.rend() )
868             { if ( *it == '?' ) ++suffx; else break; }
869             if ( ! evenNumberOfBackslashes( src.rbegin()+suffx, src.rend() ) )
870               --suffx;  // last '?' was escaped.
871             src.erase( src.size()-suffx );
872             break;
873           default:
874             break;
875         }
876       }
877       // now match; find src in trg an check surrounding wildcards
878       src = str::toLower( src );
879       trg = str::toLower( trg );
880       for ( std::string::size_type match = trg.find( src, 0 );
881             match != std::string::npos;
882             match = trg.find( src, match+1 ) )
883       {
884         if ( prefx != std::string::npos && trueCharsIn( trg, 0, match ) > prefx )
885           break;        // not "*", and already more chars than "?"s before match: disjoint
886         std::string::size_type frontSize = match + src.size();
887         if ( suffx != std::string::npos && trueCharsIn( trg, frontSize, trg.size() ) > suffx )
888           continue;     // not "*", and still more chars than "?"s after match: check next match
889         return true;    // match: superset
890       }
891       return false;     // disjoint
892     }
893   } // namespace
894   ///////////////////////////////////////////////////////////////////
895
896   bool CpeId::Value::containsWildcard() const
897   {
898     const std::string & value( *_value );
899     return ( isWildchar( *value.begin() )
900         || ( isWildchar( *value.rbegin() ) && evenNumberOfBackslashes( ++value.rbegin(), value.rend() ) ) );
901   }
902
903   SetCompare CpeId::Value::setRelationMixinCompare( const CpeId::Value & trg ) const
904   {
905     static const SetCompare _NeedsCloserLook( SetCompare::Enum(-1) );   // artificial Compare value
906     static const SetCompare matchTabel[4][4] = {{
907       /* ANY,           ANY             */ SetCompare::equal,
908       /* ANY,           NA              */ SetCompare::properSuperset,
909       /* ANY,           wildcardfree    */ SetCompare::properSuperset,
910       /* ANY,           wildcarded      */ SetCompare::uncomparable,
911     },{
912       /* NA,            ANY             */ SetCompare::properSubset,
913       /* NA,            NA              */ SetCompare::equal,
914       /* NA,            wildcardfree    */ SetCompare::disjoint,
915       /* NA,            wildcarded      */ SetCompare::uncomparable,
916     },{
917       /* wildcardfree,  ANY             */ SetCompare::properSubset,
918       /* wildcardfree,  NA              */ SetCompare::disjoint,
919       /* wildcardfree,  wildcardfree    */ _NeedsCloserLook,    // equal or disjoint
920       /* wildcardfree,  wildcarded      */ SetCompare::uncomparable,
921     },{
922       /* wildcarded,    ANY             */ SetCompare::properSubset,
923       /* wildcarded,    NA              */ SetCompare::disjoint,
924       /* wildcarded,    wildcardfree    */ _NeedsCloserLook,    // superset or disjoint
925       /* wildcarded,    wildcarded      */ SetCompare::uncomparable,
926     }};
927
928     Type srcType = type();
929     Type trgType = trg.type();
930     SetCompare ret = matchTabel[srcType.asIntegral()][trgType.asIntegral()];
931     if ( ret == _NeedsCloserLook )
932     {
933       if ( srcType == Type::wildcardfree )      // trgType == Type::wildcardfree
934       {
935         // simple string compare
936         ret = matchWildcardfreeString( *_value, *trg._value ) ? SetCompare::equal : SetCompare::disjoint;
937       }
938       else if ( srcType == Type::wildcarded )   // trgType == Type::wildcardfree
939       {
940         // Needs wildcard compare
941         ret = matchWildcardedString( *_value, *trg._value ) ? SetCompare::properSuperset : SetCompare::disjoint;
942      }
943     }
944     return ret;
945   }
946
947   std::ostream & operator<<( std::ostream & str, const CpeId::Value & obj )
948   { return str << obj.asString(); }
949
950 } // namespace zypp
951 ///////////////////////////////////////////////////////////////////