add zypp-cpeid tool
[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( str::Str() << "CpeId:Wfn:part: '" << wfn << "' illegal value; expected: 'h' | 'o' | 'a'"   );
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( str::Str() << "CpeId:Wfn:language: '" << wfn << "' illegal value; expected RFC5646 conform: language ['-' region]" );
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; expected: 'cpe:2.3:' | 'cpe:/'" );
307     }
308     else if ( cpe_r[0] != '\0' )
309       throw std::invalid_argument( "CpeId: bad magic; expected: 'cpe:2.3:' | 'cpe:/'" );
310     return ret;
311   }
312
313   CpeId::Impl::Wfn CpeId::Impl::unbindUri( const std::string & cpe_r )
314   {
315     Wfn ret;
316
317     static constexpr unsigned numUriAttr = 7u;  // basic URI attibutes
318     std::vector<std::string> field;
319     field.reserve( Attribute::numAttributes );  // reserve 7 + 4 for packed extened attrs in edition
320     if ( str::splitFields( cpe_r.c_str()+5/* skip magic 'cpe:/' */, std::back_inserter(field), ":" ) > numUriAttr )
321       throw std::invalid_argument( str::Str() << "CpeId:Uri: too many fields (" << field.size() << "); expected " << numUriAttr );
322     field.resize( Attribute::numAttributes );   // fillup with ANY(""),
323
324     for ( auto ai : WFN_ATTRIBUTES )
325     {
326       if ( ai == Attribute::edition && field[ai][0] == '~' )
327       {
328         // unpacking is needed
329         static constexpr unsigned numPacks = 6u;        // dummy_before_~ + edition + 4 extended attributes
330         std::vector<std::string> pack;
331         pack.reserve( numPacks );
332         if ( str::splitFields( field[ai], std::back_inserter(pack), "~" ) > numPacks )
333           throw std::invalid_argument( str::Str() << "CpeId:Uri:edition: too many packs (" << pack.size() << "); expected " << numPacks );
334         pack.resize( numPacks );        // fillup with ANY(""), should be noOP
335
336         pack[1].swap( field[Attribute::edition] );
337         pack[2].swap( field[Attribute::sw_edition] );
338         pack[3].swap( field[Attribute::target_sw] );
339         pack[4].swap( field[Attribute::target_hw] );
340         pack[5].swap( field[Attribute::other] );
341       }
342       assignAttr( ret, ai, Value( field[ai], Value::uriFormat ) );
343     }
344     return ret;
345   }
346
347   CpeId::Impl::Wfn CpeId::Impl::unbindFs( const std::string & cpe_r )
348   {
349     Wfn ret;
350
351     std::vector<std::string> field;
352     field.reserve( Attribute::numAttributes );
353     if ( str::splitFields( cpe_r.c_str()+8/* skip magic 'cpe:2.3:' */, std::back_inserter(field), ":" ) > Attribute::numAttributes )
354       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*/ );
355     if ( field.back().empty() ) // A trailing ':' leads to an empty (illegal) field, but we fillup missing fields with ANY|"*"
356       field.back() = "*";
357     field.resize( Attribute::numAttributes, "*" );      // fillup with ANY|"*"
358
359     for ( auto ai : WFN_ATTRIBUTES )
360     {
361       assignAttr( ret, ai, Value( field[ai], Value::fsFormat ) );
362     }
363     return ret;
364   }
365
366
367   ///////////////////////////////////////////////////////////////////
368   //    class CpeId
369   ///////////////////////////////////////////////////////////////////
370
371   std::string CpeId::NoThrowType::lastMalformed;
372
373   CpeId::CpeId()
374     : _pimpl( new Impl )
375   {}
376
377   CpeId::CpeId( const std::string & cpe_r )
378     : _pimpl( new Impl( cpe_r ) )
379   {}
380
381   CpeId::CpeId( const std::string & cpe_r, NoThrowType )
382   {
383     try
384     {
385       _pimpl.reset( new Impl( cpe_r ) );
386       NoThrowType::lastMalformed.clear();
387     }
388     catch(...)
389     {
390       _pimpl.reset( new Impl );
391       NoThrowType::lastMalformed = cpe_r;
392     }
393   }
394
395   CpeId::~CpeId()
396   {}
397
398   CpeId::operator bool() const
399   { return bool(*_pimpl); }
400
401   std::string CpeId::asFs() const
402   { return _pimpl->asFs(); }
403
404   std::string CpeId::asUri() const
405   { return _pimpl->asUri(); }
406
407   std::string CpeId::asWfn() const
408   { return _pimpl->asWfn(); }
409
410   SetCompare CpeId::setRelationMixinCompare( const CpeId & trg ) const
411   { return _pimpl->setRelationMixinCompare( *trg._pimpl ); }
412
413   ///////////////////////////////////////////////////////////////////
414   //    class CpeId::WfnAttribute
415   ///////////////////////////////////////////////////////////////////
416
417   const std::string & CpeId::_AttributeDef::asString( Enum val_r )
418   {
419     static std::map<Enum,std::string> _table = {
420 #define OUTS(N) { N, #N }
421       OUTS( part ),
422       OUTS( vendor ),
423       OUTS( product ),
424       OUTS( version ),
425       OUTS( update ),
426       OUTS( edition ),
427       OUTS( language ),
428       OUTS( sw_edition ),
429       OUTS( target_sw ),
430       OUTS( target_hw ),
431       OUTS( other ),
432 #undef OUTS
433     };
434     return _table[val_r];
435   }
436
437   ///////////////////////////////////////////////////////////////////
438   //    class CpeId::Value
439   ///////////////////////////////////////////////////////////////////
440
441   const CpeId::Value CpeId::Value::ANY;
442   const CpeId::Value CpeId::Value::NA( "" );
443
444   CpeId::Value::Value( const std::string & value_r )
445   {
446     if ( value_r.empty() )      // NA
447     {
448       if ( ! CpeId::Value::NA._value )  // initialized by this ctor!
449         _value.reset( new std::string );
450       else
451         _value = CpeId::Value::NA._value;
452     }
453     else if ( value_r != "*" )  // ANY is default constructed
454     {
455       bool starting = true;     // false after the 1st non-?
456       for_( chp, value_r.begin(), value_r.end() )
457       {
458         switch ( *chp )
459         {
460           case '\\':    // quoted
461             ++chp;
462             if ( ! chIsValidRange( *chp )  )
463             {
464               if ( *chp )
465                 throw std::invalid_argument( str::Str() << "CpeId:Wfn: illegal quoted character '\\" << reinterpret_cast<void*>(*chp) << "'" );
466               else
467                 throw std::invalid_argument( "CpeId:Wfn: Backslash escapes nothing" );
468             }
469             else if ( chIsWfnUnescaped( *chp ) )
470               throw std::invalid_argument( str::Str() << "CpeId:Wfn: unnecessarily quoted character '\\" << *chp << "'" );
471             else if ( starting && *chp == '-' && chp+1 == value_r.end() )
472               throw std::invalid_argument( str::Str() << "CpeId:Wfn: '\\-' is illegal value" );
473             break;
474
475           case '?':     // sequence at beginning or end of string
476             while ( *(chp+1) == '?' )
477               ++chp;
478             if ( ! ( starting || chp+1 == value_r.end() ) )
479               throw std::invalid_argument( "CpeId:Wfn: embedded ?" );
480             break;
481
482           case '*':     // single at beginning or end of string
483             if ( ! ( starting || chp+1 == value_r.end() ) )
484               throw std::invalid_argument( "CpeId:Wfn: embedded *" );
485             break;
486
487           default:      // everything else unquoted
488             if ( ! chIsWfnUnescaped( *chp ) )
489             {
490               if ( chIsValidRange( *chp ) )
491                 throw std::invalid_argument( str::Str() << "CpeId:Wfn: missing quote before '" << *chp << "'" );
492               else
493                 throw std::invalid_argument( str::Str() << "CpeId:Wfn: illegal character '" << reinterpret_cast<void*>(*chp) << "'" );
494             }
495             break;
496         }
497         if ( starting )
498           starting = false;
499       }
500       _value.reset( new std::string( value_r ) );
501     }
502   }
503
504   CpeId::Value::Value( const std::string & encoded_r, FsFormatType )
505   {
506     if ( encoded_r != "*" )     // ANY is default constructed
507     {
508       if ( encoded_r == "-" )   // NA
509       {
510         _value = CpeId::Value::NA._value;
511       }
512       else
513       {
514         str::Str result;
515         bool starting = true;   // false after the 1st non-?
516         for_( chp, encoded_r.begin(), encoded_r.end() )
517         {
518           switch ( *chp )
519           {
520             case '\\':  // may stay quoted
521               ++chp;
522               if ( chIsWfnUnescaped( *chp ) )
523                 result << *chp;
524               else if ( chIsValidRange( *chp ) )
525                 result << '\\' << *chp;
526               else if ( *chp )
527                 throw std::invalid_argument( str::Str() << "CpeId:Fs: illegal quoted character '\\" << *chp << "'" );
528               else
529                 throw std::invalid_argument( "CpeId:Fs: Backslash escapes nothing" );
530               break;
531
532             case '?':   // sequence at beginning or end of string
533               result << '?';
534               while ( *(chp+1) == '?' )
535               {
536                 ++chp;
537                 result << '?';
538               }
539               if ( ! ( starting || chp+1 == encoded_r.end() ) )
540                 throw std::invalid_argument( "CpeId:Fs: embedded ?" );
541               break;
542
543             case '*':   // single at beginning or end of string
544               if ( starting || chp+1 == encoded_r.end() )
545                 result << '*';
546               else
547                 throw std::invalid_argument( "CpeId:Fs: embedded *" );
548               break;
549
550             default:
551               if ( chIsWfnUnescaped( *chp ) )
552                 result << *chp;
553               else if ( chIsValidRange( *chp ) )
554                 result << '\\' << *chp;
555               else
556                 throw std::invalid_argument( str::Str() << "CpeId:Fs: illegal character '" << reinterpret_cast<void*>(*chp) << "'" );
557               break;
558           }
559           if ( starting )
560             starting = false;
561         }
562         if ( starting )
563           throw std::invalid_argument( "CpeId:Fs: '' value is illegal" );
564         _value.reset( new std::string( result ) );
565       }
566     }
567   }
568
569   CpeId::Value::Value( const std::string & encoded_r, UriFormatType )
570   {
571     if ( ! encoded_r.empty() )  // ANY is default constructed
572     {
573       if ( encoded_r == "-" )   // NA
574       {
575         _value = CpeId::Value::NA._value;
576       }
577       else
578       {
579         str::Str result;
580         bool starting = true;   // false after the 1st non-? (%01)
581         for_( chp, encoded_r.begin(), encoded_r.end() )
582         {
583           char ch = *chp;
584
585           if ( ch == '%' )      // legal '%xx' sequence first
586           {
587             int d1 = heDecodeCh( *(chp+1) );
588             if ( d1 != -1 )
589             {
590               int d2 = heDecodeCh( *(chp+2) );
591               if ( d2 != -1 )
592               {
593                 chp += 2;       // skip sequence
594                 if ( d1 == 0 )
595                 {
596                   if ( d2 == 1 )        // %01 - ? valid sequence at begin or end
597                   {
598                     result << '?';
599                     while ( *(chp+1) == '%' && *(chp+2) == '0' && *(chp+3) == '1' )
600                     {
601                       chp += 3;
602                       result << '?';
603                     }
604                     if ( starting || chp+1 == encoded_r.end() )
605                     {
606                       starting = false;
607                       continue; // -> continue;
608                     }
609                     else
610                       throw std::invalid_argument( "CpeId:Uri: embedded %01" );
611                   }
612                   else if ( d2 == 2 )   // %02 - * valid at begin or end
613                   {
614                     if ( starting || chp+1 == encoded_r.end() )
615                     {
616                       result << '*';
617                       starting = false;
618                       continue; // -> continue;
619                     }
620                     else
621                       throw std::invalid_argument( "CpeId:Uri: embedded %02" );
622                   }
623                 }
624                 ch = (d1<<4)|d2;
625                 if ( ! chIsValidRange( ch ) )
626                   throw std::invalid_argument( str::Str() << "CpeId:Uri: illegal % encoded character '" << reinterpret_cast<void*>(ch) << "'" );
627               }
628             }
629           }
630           else if ( ! chIsValidRange( ch ) )
631             throw std::invalid_argument( str::Str() << "CpeId:Uri: illegal character '" << reinterpret_cast<void*>(ch) << "'" );
632
633           if ( chIsWfnUnescaped( ch ) )
634             result << ch;
635           else
636             result << '\\' << ch;
637
638           if ( starting )
639             starting = false;
640         }
641         _value.reset( new std::string( result ) );
642       }
643     }
644   }
645
646   std::string CpeId::Value::asWfn() const
647   {
648     std::string ret;
649     if ( ! _value )
650     {
651       static const std::string any( "*" );
652       ret = any;
653     }
654     else
655       ret = *_value;    // includes "" for NA
656     return ret;
657   }
658
659   std::string CpeId::Value::asFs() const
660   {
661     std::string ret;
662     if ( isANY() )
663     {
664       static const std::string asterisk( "*" );
665       ret = asterisk;
666     }
667     else if ( isNA() )
668     {
669       static const std::string dash( "-" );
670       ret = dash;
671     }
672     else
673     {
674       str::Str result;
675       for_( chp, _value->begin(), _value->end() )
676       {
677         if ( *chp != '\\' )
678           result << *chp;
679         else
680         {
681           ++chp;
682           switch ( *chp )
683           {
684             case '-':
685             case '.':
686             case '_':
687               result << *chp;   // without escaping
688               break;
689
690             case '\0':
691               throw std::invalid_argument( "CpeId:Wfn: Backslash escapes nothing" );
692               break;
693
694             default:
695               result << '\\' << *chp;
696               break;
697           }
698         }
699       }
700       ret = result;
701     }
702     return ret;
703   }
704
705   std::string CpeId::Value::asUri() const
706   {
707     std::string ret;    // ANY
708     if ( ! isANY() )
709     {
710       if ( isNA() )
711       {
712         static const std::string dash( "-" );
713         ret = dash;
714       }
715       else
716       {
717         str::Str result;
718         for_( chp, _value->begin(), _value->end() )
719         {
720           if ( chIsWfnUnescaped( *chp ) )
721           {
722             result << *chp;
723           }
724           else
725           {
726             static const char *const hdig = "0123456789abcdef";
727             switch ( *chp )
728             {
729               case '\\':
730                 ++chp;
731                 switch ( *chp )
732                 {
733                   case '-':
734                   case '.':
735                     result << *chp;     // without encodeing
736                     break;
737
738                   case '\0':
739                     throw std::invalid_argument( "CpeId:Wfn: Backslash escapes nothing" );
740                     break;
741
742                   default:
743                     result << '%' << hdig[(unsigned char)(*chp)/16] << hdig[(unsigned char)(*chp)%16];
744                     break;
745                 }
746                 break;
747
748               case '?':
749                 result << "%01";
750                 break;
751
752               case '*':
753                 result << "%02";
754                 break;
755
756               default:
757                 throw std::invalid_argument( str::Str() << "CpeId:Wfn: illegal char '" << *chp << "' in WFN" );
758                 break;
759             }
760           }
761         }
762         ret = result;
763       }
764     }
765     return ret;
766   }
767
768   ///////////////////////////////////////////////////////////////////
769   namespace
770   {
771     /** Whether it's a wildcard character (<tt>[*?]</tt>). */
772     inline bool isWildchar( char ch_r )
773     { return( ch_r == '*' || ch_r == '?' ); }
774
775     /** Whether there is an even number of consecutive backslashes before and including \a rbegin_r
776      * An even number of backslashes means the character following is unescaped.
777      */
778     inline bool evenNumberOfBackslashes( std::string::const_reverse_iterator rbegin_r, std::string::const_reverse_iterator rend_r )
779     {
780       unsigned backslashes = 0;
781       for_( it, rbegin_r, rend_r )
782       {
783         if ( *it == '\\' )
784           ++backslashes;
785         else
786           break;
787       }
788       return !(backslashes & 1U);
789     }
790
791     /** Number of chars (not counting escaping backslashes) in <tt>[begin_r,end_r[</tt> */
792     inline unsigned trueCharsIn( const std::string & str_r, std::string::size_type begin_r, std::string::size_type end_r )
793     {
794       unsigned chars = 0;
795       for_( it, begin_r, end_r )
796       {
797         ++chars;
798         if ( str_r[it] == '\\' )
799         {
800           if ( ++it == end_r )
801             break;
802         }
803       }
804       return chars;
805     }
806
807     /** Match helper comparing 2 Wildcardfree string values (case insensitive). */
808     inline bool matchWildcardfreeString( const std::string & lhs, const std::string & rhs )
809     { return( str::compareCI( lhs, rhs ) == 0 ); }
810
811     /** Match helper matching Wildcarded source against Wildcardfree target.
812      *
813      * Constraints on usage of the unquoted question mark (zero or one char in \a trg):
814      * 1. An unquoted question mark MAY be used at the beginning and/or the end of an
815      *    attribute-value string.
816      * 2. A contiguous sequence of unquoted question marks MAY appear at the beginning
817      *    and/or the end of an attribute-value string.
818      * 3. An unquoted question mark SHALL NOT be used in any other place in an
819      *    attribute-value string.
820      *
821      * Constraints on usage of the unquoted asterisk  (zero or more chars in \a trg):
822      * 1. A single unquoted asterisk MAY be used as the entire attribute-value string.
823      * 2. A single unquoted asterisk MAY be used at the beginning and/or end of an
824      *    attribute-value string.
825      * 3. An unquoted asterisk SHALL NOT be used in any other place in an attribute-value
826      *    string.
827      *
828      * Unquoted question marks and asterisks MAY appear in the same attribute-value string
829      * as long as they meet the constraints above.
830      *
831      * Example of illegal usage: "foo?bar", "bar??baz", "q??x",
832      *                           "foo*bar", "**foo", "bar***",
833      *                           "*?foobar", "foobar*?"
834      *
835      * \note Relies on \a src and \a trg being wellformed.
836      */
837     inline bool matchWildcardedString( std::string src, std::string trg )
838     {
839       // std::string::npos remembers an asterisk
840       // unescaped wildcard prefix
841       std::string::size_type prefx = 0;
842       switch ( *src.begin() )   // wellformed implies not empty
843       {
844         case '*':
845           if ( src.size() == 1 )
846             return true;        // "*" matches always: superset
847           else
848             prefx = std::string::npos;
849             src.erase( 0, 1 );
850           break;
851         case '?':
852           ++prefx;
853           for_( it, ++src.begin(), src.end() )
854           { if ( *it == '?' ) ++prefx; else break; }
855           if ( src.size() == prefx )
856             return( trg.size() <= prefx );      // "??..?": superset if at most #prefx chars
857           else
858             src.erase( 0, prefx );
859           break;
860         default:
861           break;
862       }
863       // unescaped wildcard suffix
864       std::string::size_type suffx = 0;
865       if ( ! src.empty() )
866       {
867         switch ( *src.rbegin() )
868         {
869           case '*':
870             if ( evenNumberOfBackslashes( ++src.rbegin(), src.rend() ) )
871             {
872               suffx = std::string::npos;
873               src.erase( src.size()-1 );
874             }
875             break;
876           case '?':
877             ++suffx;
878             for_( it, ++src.rbegin(), src.rend() )
879             { if ( *it == '?' ) ++suffx; else break; }
880             if ( ! evenNumberOfBackslashes( src.rbegin()+suffx, src.rend() ) )
881               --suffx;  // last '?' was escaped.
882             src.erase( src.size()-suffx );
883             break;
884           default:
885             break;
886         }
887       }
888       // now match; find src in trg an check surrounding wildcards
889       src = str::toLower( src );
890       trg = str::toLower( trg );
891       for ( std::string::size_type match = trg.find( src, 0 );
892             match != std::string::npos;
893             match = trg.find( src, match+1 ) )
894       {
895         if ( prefx != std::string::npos && trueCharsIn( trg, 0, match ) > prefx )
896           break;        // not "*", and already more chars than "?"s before match: disjoint
897         std::string::size_type frontSize = match + src.size();
898         if ( suffx != std::string::npos && trueCharsIn( trg, frontSize, trg.size() ) > suffx )
899           continue;     // not "*", and still more chars than "?"s after match: check next match
900         return true;    // match: superset
901       }
902       return false;     // disjoint
903     }
904   } // namespace
905   ///////////////////////////////////////////////////////////////////
906
907   bool CpeId::Value::containsWildcard() const
908   {
909     const std::string & value( *_value );
910     return ( isWildchar( *value.begin() )
911         || ( isWildchar( *value.rbegin() ) && evenNumberOfBackslashes( ++value.rbegin(), value.rend() ) ) );
912   }
913
914   ///////////////////////////////////////////////////////////////////
915   /// Symmetric attribute compare if wildcards are involved!
916   /// The specs define any comarison with a wildcarded attribute as
917   /// target to return \c uncomparable:
918   /// \code
919   ///    wildcardfree  <=>  wildcarded    ==>  uncomparable,
920   ///    wildcarded    <=>  wildcardfree  ==>  superset or disjoint
921   /// \endcode
922   /// But a symmetric result is much more intuitive:
923   /// \code
924   ///    wildcardfree  <=>  wildcarded    ==>  subset or disjoint
925   ///    wildcarded    <=>  wildcardfree  ==>  superset or disjoint
926   /// \endcode
927   ///////////////////////////////////////////////////////////////////
928 #define WFN_STRICT_SPEC 0
929 #if WFN_STRICT_SPEC
930   //SetCompare CpeId::Value::setRelationMixinCompare( const CpeId::Value & trg ) const
931   {
932     static const SetCompare _NeedsCloserLook( SetCompare::Enum(-1) );   // artificial Compare value
933     static const SetCompare matchTabel[4][4] = {{
934       /* ANY,           ANY             */ SetCompare::equal,
935       /* ANY,           NA              */ SetCompare::properSuperset,
936       /* ANY,           wildcardfree    */ SetCompare::properSuperset,
937       /* ANY,           wildcarded      */ SetCompare::uncomparable,
938     },{
939       /* NA,            ANY             */ SetCompare::properSubset,
940       /* NA,            NA              */ SetCompare::equal,
941       /* NA,            wildcardfree    */ SetCompare::disjoint,
942       /* NA,            wildcarded      */ SetCompare::uncomparable,
943     },{
944       /* wildcardfree,  ANY             */ SetCompare::properSubset,
945       /* wildcardfree,  NA              */ SetCompare::disjoint,
946       /* wildcardfree,  wildcardfree    */ _NeedsCloserLook,    // equal or disjoint
947       /* wildcardfree,  wildcarded      */ SetCompare::uncomparable,
948     },{
949       /* wildcarded,    ANY             */ SetCompare::properSubset,
950       /* wildcarded,    NA              */ SetCompare::disjoint,
951       /* wildcarded,    wildcardfree    */ _NeedsCloserLook,    // superset or disjoint
952       /* wildcarded,    wildcarded      */ SetCompare::uncomparable,
953     }};
954
955     Type srcType = type();
956     Type trgType = trg.type();
957     SetCompare ret = matchTabel[srcType.asIntegral()][trgType.asIntegral()];
958     if ( ret == _NeedsCloserLook )
959     {
960       if ( srcType == Type::wildcardfree )      // trgType == Type::wildcardfree
961       {
962         // simple string compare
963         ret = matchWildcardfreeString( *_value, *trg._value ) ? SetCompare::equal : SetCompare::disjoint;
964       }
965       else if ( srcType == Type::wildcarded )   // trgType == Type::wildcardfree
966       {
967         // Needs wildcard compare
968         ret = matchWildcardedString( *_value, *trg._value ) ? SetCompare::properSuperset : SetCompare::disjoint;
969      }
970     }
971     return ret;
972   }
973 #else
974   SetCompare CpeId::Value::setRelationMixinCompare( const CpeId::Value & trg ) const
975   {
976     ///////////////////////////////////////////////////////////////////
977     // ANY,             ANY             => equal
978     // ANY,             NA              => properSuperset
979     // ANY,             wildcardfree    => properSuperset
980     // ANY,             wildcarded      => properSuperset
981     //
982     // NA,              ANY             => properSubset
983     // NA,              NA              => equal
984     // NA,              wildcardfree    => disjoint
985     // NA,              wildcarded      => disjoint
986     //
987     // wildcardfree,    ANY             => properSubset
988     // wildcardfree,    NA              => disjoint
989     // wildcardfree,    wildcardfree    => NeedsCloserLook:     equal or disjoint
990     // wildcardfree,    wildcarded      => NeedsCloserLook:     subset or disjoint
991     //
992     // wildcarded,      ANY             => properSubset
993     // wildcarded,      NA              => disjoint
994     // wildcarded,      wildcardfree    => NeedsCloserLook:     superset or disjoint
995     // wildcarded,      wildcarded      => NeedsCloserLook"     equal or uncomparable
996     ///////////////////////////////////////////////////////////////////
997
998     SetCompare ret = SetCompare::disjoint;
999
1000     if ( isANY() )
1001     {
1002       ret = trg.isANY() ? SetCompare::equal : SetCompare::properSuperset;
1003     }
1004     else if ( trg.isANY() )
1005     {
1006       ret = SetCompare::properSubset;
1007     }
1008     else if ( isNA() )
1009     {
1010       if ( trg.isNA() ) ret = SetCompare::equal; // else: SetCompare::disjoint;
1011     }
1012     else if ( ! trg.isNA() ) // else: SetCompare::disjoint;
1013     {
1014       // NeedsCloserLook:
1015       if ( isWildcarded() )
1016       {
1017         if ( trg.isWildcarded() )
1018         {
1019           // simple string compare just to detect 'equal'
1020           ret = matchWildcardfreeString( *_value, *trg._value ) ? SetCompare::equal : SetCompare::uncomparable;
1021         }
1022         else
1023         {
1024           // Needs wildcard compare (src,trg)
1025           if ( matchWildcardedString( *_value, *trg._value ) ) ret = SetCompare::properSuperset; // else: SetCompare::disjoint;
1026         }
1027       }
1028       else
1029       {
1030         if ( trg.isWildcarded() )
1031         {
1032           // Needs wildcard compare (trg,src)
1033           if ( matchWildcardedString( *trg._value, *_value ) ) ret = SetCompare::properSubset; // else: SetCompare::disjoint;
1034         }
1035         else
1036         {
1037           // simple string compare
1038           if ( matchWildcardfreeString( *_value, *trg._value ) ) ret = SetCompare::equal; // else: SetCompare::disjoint;
1039         }
1040       }
1041     }
1042     return ret;
1043   }
1044 #endif // WFN_STRICT_SPEC
1045
1046   std::ostream & operator<<( std::ostream & str, const CpeId::Value & obj )
1047   { return str << obj.asString(); }
1048
1049 } // namespace zypp
1050 ///////////////////////////////////////////////////////////////////