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