Imported Upstream version 17.25.4
[platform/upstream/libzypp.git] / zypp / url / UrlBase.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /**
10  * \file zypp/url/UrlBase.cc
11  */
12 #include <zypp/url/UrlBase.h>
13 #include <zypp/base/String.h>
14 #include <zypp/base/Gettext.h>
15 #include <zypp/base/Regex.h>
16 #include <zypp/base/StringV.h>
17
18 #include <stdexcept>
19 #include <climits>
20 #include <errno.h>
21 #include <sys/types.h>
22 #include <sys/socket.h>
23 #include <arpa/inet.h>
24
25 #include <iostream>
26
27 // in the Estonian locale, a-z excludes t, for example. #302525
28 // http://en.wikipedia.org/wiki/Estonian_alphabet
29 #define a_zA_Z "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
30
31 // ---------------------------------------------------------------
32 /*
33 ** authority = //[user [:password] @ ] host [:port]
34 **
35 ** host      = hostname | IPv4 | "[" IPv6-IP "]" | "[v...]"
36 */
37 #define RX_VALID_SCHEME    "^[" a_zA_Z "][" a_zA_Z "0-9\\.+-]*$"
38
39 #define RX_VALID_PORT      "^[0-9]{1,5}$"
40
41 #define RX_VALID_HOSTNAME  "^[[:alnum:]${_}]+([\\.-][[:alnum:]${_}]+)*$"
42
43 #define RX_VALID_HOSTIPV4  \
44         "^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$"
45
46 #define RX_VALID_HOSTIPV6  \
47         "^\\[[:a-fA-F0-9]+(:[0-9]{1,3}(\\.[0-9]{1,3}){3})?\\]$"
48
49
50 //////////////////////////////////////////////////////////////////////
51 namespace zypp
52 { ////////////////////////////////////////////////////////////////////
53
54   ////////////////////////////////////////////////////////////////////
55   namespace url
56   { //////////////////////////////////////////////////////////////////
57
58
59     // ---------------------------------------------------------------
60     /*
61     ** URL asString() view option constants:
62     */
63     const ViewOption  ViewOption::WITH_SCHEME       = 0x0001;
64     const ViewOption  ViewOption::WITH_USERNAME     = 0x0002;
65     const ViewOption  ViewOption::WITH_PASSWORD     = 0x0004;
66     const ViewOption  ViewOption::WITH_HOST         = 0x0008;
67     const ViewOption  ViewOption::WITH_PORT         = 0x0010;
68     const ViewOption  ViewOption::WITH_PATH_NAME    = 0x0020;
69     const ViewOption  ViewOption::WITH_PATH_PARAMS  = 0x0040;
70     const ViewOption  ViewOption::WITH_QUERY_STR    = 0x0080;
71     const ViewOption  ViewOption::WITH_FRAGMENT     = 0x0100;
72     const ViewOption  ViewOption::EMPTY_AUTHORITY   = 0x0200;
73     const ViewOption  ViewOption::EMPTY_PATH_NAME   = 0x0400;
74     const ViewOption  ViewOption::EMPTY_PATH_PARAMS = 0x0800;
75     const ViewOption  ViewOption::EMPTY_QUERY_STR   = 0x1000;
76     const ViewOption  ViewOption::EMPTY_FRAGMENT    = 0x2000;
77     const ViewOption  ViewOption::DEFAULTS          = 0x07bb;
78
79     const ViewOption  ViewOption::hotfix1050625     = 0x8000;
80     /*
81     const ViewOption  ViewOption::DEFAULTS          =
82                       ViewOption::WITH_SCHEME       +
83                       ViewOption::WITH_USERNAME     +
84                       ViewOption::WITH_HOST         +
85                       ViewOption::WITH_PORT         +
86                       ViewOption::WITH_PATH_NAME    +
87                       ViewOption::WITH_QUERY_STR    +
88                       ViewOption::WITH_FRAGMENT     +
89                       ViewOption::EMPTY_AUTHORITY   +
90                       ViewOption::EMPTY_PATH_NAME;
91     */
92
93     // ---------------------------------------------------------------
94     ViewOption::ViewOption()
95       : opt(0x07bb)
96     {}
97
98     // ---------------------------------------------------------------
99     ViewOption::ViewOption(int option)
100       : opt(option)
101     {}
102
103
104     // ---------------------------------------------------------------
105     /*
106     ** Behaviour configuration variables.
107     */
108     typedef std::map< std::string, std::string > UrlConfig;
109
110
111     // ---------------------------------------------------------------
112
113     /// \brief Hide passwords embedded in a querystr,
114     ///
115     /// Stores the full querystring and maintains a safe version with
116     /// password field stripped. Url::asString will print the passwords
117     /// on demand only.
118     ///
119     /// \see bsc#1050625: VUL-1: CVE-2017-9271: zypper: proxy credentials written to log files
120     class SafeQuerystr
121     {
122     public:
123       SafeQuerystr()
124       {}
125
126       SafeQuerystr( std::string rhs )
127       { _assign( std::move(rhs) ); }
128
129       SafeQuerystr & operator=( std::string rhs )
130       { _assign( std::move(rhs) ); return *this; }
131
132
133       operator const std::string &() const
134       { return str(); }
135
136       const std::string & str() const
137       { return fullStr(); }
138
139       const std::string & str( const ViewOptions & viewopts_r ) const
140       { return (viewopts_r.has( ViewOptions::WITH_PASSWORD ) || viewopts_r.has( ViewOptions::hotfix1050625 )) ? fullStr() : safeStr(); }
141
142       const std::string & fullStr() const
143       { return _fullQuerytsr; }
144
145       const std::string & safeStr() const
146       { return _safeQuerytsr ? _safeQuerytsr.value() : _fullQuerytsr; }
147
148     private:
149       void _assign( std::string && rhs )
150       {
151         _fullQuerytsr = std::move(rhs);
152
153         static constexpr std::string_view tag { "proxypass=" };
154         if ( _fullQuerytsr.find( tag ) != std::string::npos )
155         {
156           std::string safe;
157           strv::split( _fullQuerytsr, "&", [&safe]( std::string_view val ) {
158             if ( val.substr( 0, tag.size()  ) != tag ) {
159               if ( ! safe.empty() )
160                 safe += "&";
161               safe += val;
162             }
163           });
164           _safeQuerytsr = std::move(safe);
165         }
166         else
167           _safeQuerytsr = std::nullopt;
168       }
169     private:
170       std::string                _fullQuerytsr; ///<
171       std::optional<std::string> _safeQuerytsr; ///<.
172     };
173
174     /**
175      * \brief Internal data used by UrlBase.
176      */
177     class UrlBaseData
178     {
179     public:
180       UrlBaseData()
181       {}
182
183       UrlBaseData(const UrlConfig &conf)
184         : config(conf)
185       {}
186
187       UrlConfig       config;
188       ViewOptions     vopts;
189
190       std::string     scheme;
191       std::string     user;
192       std::string     pass;
193       std::string     host;
194       std::string     port;
195       std::string     pathname;
196       std::string     pathparams;
197       SafeQuerystr    querystr;
198       std::string     fragment;
199     };
200
201
202     // ---------------------------------------------------------------
203     /*
204     ** Anonymous/internal utility namespace:
205     */
206     namespace // anonymous
207     {
208
209                         // -------------------------------------------------------------
210       inline void
211       checkUrlData(const std::string &data,
212                    const std::string &name,
213                    const std::string &regx,
214                    bool               show=true)
215       {
216         if( regx.empty() || regx == "^$")
217         {
218           ZYPP_THROW(UrlNotAllowedException(
219             str::form(_("Url scheme does not allow a %s"), name.c_str())
220           ));
221         }
222         else
223         {
224           bool valid = false;
225           try
226           {
227             str::regex rex(regx);
228             valid = str::regex_match(data, rex);
229           }
230           catch( ... )
231           {}
232
233           if( !valid)
234           {
235             if( show)
236             {
237               ZYPP_THROW(UrlBadComponentException(
238                 str::form(_("Invalid %s component '%s'"),
239                           name.c_str(), data.c_str())
240               ));
241             }
242             else
243             {
244               ZYPP_THROW(UrlBadComponentException(
245                 str::form(_("Invalid %s component"), name.c_str())
246               ));
247             }
248           }
249         }
250       }
251
252     } // namespace
253
254
255     // ---------------------------------------------------------------
256     UrlBase::~UrlBase()
257     {
258       delete m_data;
259       m_data = NULL;
260     }
261
262
263     // ---------------------------------------------------------------
264     UrlBase::UrlBase()
265       : m_data( new UrlBaseData())
266     {
267       configure();
268     }
269
270
271     // ---------------------------------------------------------------
272     UrlBase::UrlBase(const UrlBase &url)
273       : m_data( new UrlBaseData( *(url.m_data)))
274     {
275     }
276
277
278     // ---------------------------------------------------------------
279     UrlBase::UrlBase(const std::string &scheme,
280                      const std::string &authority,
281                      const std::string &pathdata,
282                      const std::string &querystr,
283                      const std::string &fragment)
284       : m_data( new UrlBaseData())
285     {
286       configure();
287       init(scheme, authority, pathdata, querystr, fragment);
288     }
289
290
291     // ---------------------------------------------------------------
292     void
293     UrlBase::init(const std::string &scheme,
294                   const std::string &authority,
295                   const std::string &pathdata,
296                   const std::string &querystr,
297                   const std::string &fragment)
298     {
299       if ( scheme.empty() && *pathdata.c_str() == '/' )
300         setScheme("file");
301       else
302         setScheme(scheme);
303
304       setAuthority(authority);
305       setPathData(pathdata);
306       setQueryString(querystr);
307       setFragment(fragment, zypp::url::E_ENCODED);
308     }
309
310
311     // ---------------------------------------------------------------
312     void
313     UrlBase::configure()
314     {
315       config("sep_pathparams",  ";");
316       config("psep_pathparam",  ",");
317       config("vsep_pathparam",  "=");
318
319       config("psep_querystr",   "&");
320       config("vsep_querystr",   "=");
321
322       config("safe_username",   "~!$&'()*+=,;");
323       config("safe_password",   "~!$&'()*+=,:;");
324       config("safe_hostname",   "[:]${_}");
325       config("safe_pathname",   "~!$&'()*+=,:@/");
326       config("safe_pathparams", "~!$&'()*+=,:;@/");
327       config("safe_querystr",   "~!$&'()*+=,:;@/?");
328       config("safe_fragment",   "~!$&'()*+=,:;@/?");
329
330       // y=yes (allowed)
331       // n=no  (disallowed, exception if !empty)
332       config("with_authority",  "y");
333       config("with_port",       "y");
334
335       // y=yes (required but don't throw if empty)
336       // n=no  (not required, ignore if empty)
337       // m=mandatory (exception if empty)
338       config("require_host",    "n");
339       config("require_pathname","n");
340
341       // y=yes (encode 2. slash even if authority present)
342       // n=no  (don't encode 2. slash if authority present)
343       config("path_encode_slash2", "n");
344
345       config("rx_username",     "^([" a_zA_Z "0-9!$&'\\(\\)*+=,;~\\._-]|%[a-fA-F0-9]{2})+$");
346       config("rx_password",     "^([" a_zA_Z "0-9!$&'\\(\\)*+=,:;~\\._-]|%[a-fA-F0-9]{2})+$");
347
348       config("rx_pathname",     "^([" a_zA_Z "0-9!$&'\\(\\){}*+=,:@/~\\._-]|%[a-fA-F0-9]{2})+$");
349       config("rx_pathparams",   "^([" a_zA_Z "0-9!$&'\\(\\){}*+=,:;@/~\\._-]|%[a-fA-F0-9]{2})+$");
350
351       config("rx_querystr",     "^([" a_zA_Z "0-9!$&'\\(\\){}*+=,:;@/?~\\._-]|%[a-fA-F0-9]{2})+$");
352       config("rx_fragment",     "^([" a_zA_Z "0-9!$&'\\(\\){}*+=,:;@/?~\\._-]|%[a-fA-F0-9]{2})+$");
353     }
354
355
356     // ---------------------------------------------------------------
357     void
358     UrlBase::config(const std::string &opt, const std::string &val)
359     {
360       m_data->config[opt] = val;
361     }
362
363
364     // ---------------------------------------------------------------
365     std::string
366     UrlBase::config(const std::string &opt) const
367     {
368       UrlConfig::const_iterator v( m_data->config.find(opt));
369       if( v != m_data->config.end())
370         return v->second;
371       else
372         return std::string();
373     }
374
375
376     // ---------------------------------------------------------------
377     ViewOptions
378     UrlBase::getViewOptions() const
379     {
380       return m_data->vopts;
381     }
382
383
384     // ---------------------------------------------------------------
385     void
386     UrlBase::setViewOptions(const ViewOptions &vopts)
387     {
388         m_data->vopts = vopts;
389     }
390
391
392     // ---------------------------------------------------------------
393     void
394     UrlBase::clear()
395     {
396       zypp::url::UrlConfig   config(m_data->config);
397       zypp::url::ViewOptions vopts(m_data->vopts);
398       *m_data = UrlBaseData();
399       m_data->config = config;
400       m_data->vopts  = vopts;
401     }
402
403
404     // ---------------------------------------------------------------
405     UrlBase *
406     UrlBase::clone() const
407     {
408       return new UrlBase(*this);
409     }
410
411
412     // ---------------------------------------------------------------
413     zypp::url::UrlSchemes
414     UrlBase::getKnownSchemes() const
415     {
416       return UrlSchemes();
417     }
418
419
420     // ---------------------------------------------------------------
421     bool
422     UrlBase::isKnownScheme(const std::string &scheme) const
423     {
424       std::string                lscheme( str::toLower(scheme));
425       UrlSchemes                 schemes( getKnownSchemes());
426       UrlSchemes::const_iterator s;
427
428       for(s=schemes.begin(); s!=schemes.end(); ++s)
429       {
430         if( lscheme == str::toLower(*s))
431           return true;
432       }
433       return false;
434     }
435
436
437     // ---------------------------------------------------------------
438     bool
439     UrlBase::isValidScheme(const std::string &scheme) const
440     {
441       bool valid = false;
442       try
443       {
444         str::regex rex(RX_VALID_SCHEME);
445         valid = str::regex_match(scheme, rex);
446       }
447       catch( ... )
448       {}
449
450       if(valid)
451       {
452         std::string    lscheme( str::toLower(scheme));
453         UrlSchemes     schemes( getKnownSchemes());
454
455         if( schemes.empty())
456           return true;
457
458         UrlSchemes::const_iterator s;
459         for(s=schemes.begin(); s!=schemes.end(); ++s)
460         {
461           if( lscheme == str::toLower(*s))
462             return true;
463         }
464       }
465       return false;
466     }
467
468
469     // ---------------------------------------------------------------
470     bool
471     UrlBase::isValid() const
472     {
473       /*
474       ** scheme is the only mandatory component
475       ** for all url's and is already verified,
476       ** (except for empty Url instances), so
477       ** Url with empty scheme is never valid.
478       */
479       if( getScheme().empty())
480         return false;
481
482       std::string host( getHost(zypp::url::E_ENCODED));
483       if( host.empty() && config("require_host")     != "n")
484         return false;
485
486       std::string path( getPathName(zypp::url::E_ENCODED));
487       if( path.empty() && config("require_pathname") != "n")
488         return false;
489
490       /*
491       ** path has to begin with "/" if authority avaliable
492       ** if host is set after the pathname, we can't throw
493       */
494       if( !host.empty() && !path.empty() && path.at(0) != '/')
495         return false;
496
497       return true;
498     }
499
500
501     // ---------------------------------------------------------------
502     std::string
503     UrlBase::asString() const
504     {
505       return asString(getViewOptions());
506     }
507
508     std::string UrlBase::asString1050625() const
509     {
510       // Temp. fix to keep the proxypass in the query when writing the .repo files,
511       // but otherwise hiding it, when WITH_PASSWORD is not set.
512       return asString(getViewOptions()+ViewOptions::hotfix1050625);
513     }
514
515     // ---------------------------------------------------------------
516     std::string
517     UrlBase::asString(const zypp::url::ViewOptions &opts) const
518     {
519       std::string   url;
520       UrlBaseData   tmp;
521
522       if( opts.has(ViewOptions::WITH_SCHEME))
523       {
524         tmp.scheme = getScheme();
525         if( !tmp.scheme.empty())
526         {
527           url += tmp.scheme + ":";
528
529           if( opts.has(ViewOptions::WITH_HOST))
530           {
531             tmp.host = getHost(zypp::url::E_ENCODED);
532             if( !tmp.host.empty())
533             {
534               url += "//";
535
536               if( opts.has(ViewOptions::WITH_USERNAME))
537               {
538                 tmp.user = getUsername(zypp::url::E_ENCODED);
539                 if( !tmp.user.empty())
540                 {
541                   url += tmp.user;
542
543                   if( opts.has(ViewOptions::WITH_PASSWORD))
544                   {
545                     tmp.pass = getPassword(zypp::url::E_ENCODED);
546                     if( !tmp.pass.empty())
547                     {
548                       url += ":" + tmp.pass;
549                     }
550                   }
551                   url += "@";
552                 }
553               }
554
555               url += tmp.host;
556
557               if( opts.has(ViewOptions::WITH_PORT))
558               {
559                 tmp.port = getPort();
560                 if( !tmp.port.empty())
561                 {
562                   url += ":" + tmp.port;
563                 }
564               }
565             }
566             else if( opts.has(ViewOptions::EMPTY_AUTHORITY))
567             {
568               url += "//";
569             }
570           }
571           else if( opts.has(ViewOptions::EMPTY_AUTHORITY))
572           {
573             url += "//";
574           }
575         }
576       }
577
578       if( opts.has(ViewOptions::WITH_PATH_NAME))
579       {
580         tmp.pathname = getPathName(zypp::url::E_ENCODED);
581         if( !tmp.pathname.empty())
582         {
583           if(url.find("/") != std::string::npos)
584           {
585             // Url contains authority (that may be empty),
586             // we may need a rewrite of the encoded path.
587             tmp.pathname = cleanupPathName(tmp.pathname, true);
588             if(tmp.pathname.at(0) != '/')
589             {
590               url += "/";
591             }
592           }
593           url += tmp.pathname;
594
595           if( opts.has(ViewOptions::WITH_PATH_PARAMS))
596           {
597             tmp.pathparams = getPathParams();
598             if( !tmp.pathparams.empty())
599             {
600               url += ";" + tmp.pathparams;
601             }
602             else if( opts.has(ViewOptions::EMPTY_PATH_PARAMS))
603             {
604               url += ";";
605             }
606           }
607         }
608         else if( opts.has(ViewOptions::EMPTY_PATH_NAME)
609                  && url.find("/") != std::string::npos)
610         {
611           url += "/";
612           if( opts.has(ViewOptions::EMPTY_PATH_PARAMS))
613           {
614             url += ";";
615           }
616         }
617       }
618
619       if( opts.has(ViewOptions::WITH_QUERY_STR))
620       {
621         const std::string & querystr { getQueryString( opts ) };        // full or safe depending on opts
622         if( !querystr.empty() )
623         {
624           url += "?" + querystr;
625         }
626         else if( opts.has(ViewOptions::EMPTY_QUERY_STR))
627         {
628           url += "?";
629         }
630       }
631
632       if( opts.has(ViewOptions::WITH_FRAGMENT))
633       {
634         tmp.fragment = getFragment(zypp::url::E_ENCODED);
635         if( !tmp.fragment.empty())
636         {
637           url += "#" + tmp.fragment;
638         }
639         else if( opts.has(ViewOptions::EMPTY_FRAGMENT))
640         {
641           url += "#";
642         }
643       }
644
645       return url;
646     }
647
648
649     // ---------------------------------------------------------------
650     std::string
651     UrlBase::getScheme() const
652     {
653       return m_data->scheme;
654     }
655
656
657     // ---------------------------------------------------------------
658     std::string
659     UrlBase::getAuthority() const
660     {
661       std::string str;
662       if( !getHost(zypp::url::E_ENCODED).empty())
663       {
664         if( !getUsername(zypp::url::E_ENCODED).empty())
665         {
666           str = getUsername(zypp::url::E_ENCODED);
667           if( !getPassword(zypp::url::E_ENCODED).empty())
668           {
669             str += ":" + getPassword(zypp::url::E_ENCODED);
670           }
671           str += "@";
672         }
673
674         str += getHost(zypp::url::E_ENCODED);
675         if( !getPort().empty())
676         {
677           str += ":" + getPort();
678         }
679       }
680       return str;
681     }
682
683
684     // ---------------------------------------------------------------
685     std::string
686     UrlBase::getPathData() const
687     {
688       return getPathName(zypp::url::E_ENCODED) +
689              config("sep_pathparams") +
690              getPathParams();
691     }
692
693
694     // ---------------------------------------------------------------
695     std::string
696     UrlBase::getQueryString() const
697     {
698       return m_data->querystr;
699     }
700
701     std::string
702     UrlBase::getQueryString( const ViewOptions & viewopts_r ) const
703     {
704       return m_data->querystr.str( viewopts_r );
705     }
706
707     // ---------------------------------------------------------------
708     std::string
709     UrlBase::getFragment(EEncoding eflag) const
710     {
711       if(eflag == zypp::url::E_DECODED)
712         return zypp::url::decode(m_data->fragment);
713       else
714         return m_data->fragment;
715     }
716
717
718     // ---------------------------------------------------------------
719     std::string
720     UrlBase::getUsername(EEncoding eflag) const
721     {
722       if(eflag == zypp::url::E_DECODED)
723         return zypp::url::decode(m_data->user);
724       else
725         return m_data->user;
726     }
727
728
729     // ---------------------------------------------------------------
730     std::string
731     UrlBase::getPassword(EEncoding eflag) const
732     {
733       if(eflag == zypp::url::E_DECODED)
734         return zypp::url::decode(m_data->pass);
735       else
736         return m_data->pass;
737     }
738
739
740     // ---------------------------------------------------------------
741     std::string
742     UrlBase::getHost(EEncoding eflag) const
743     {
744       if(eflag == zypp::url::E_DECODED)
745         return zypp::url::decode(m_data->host);
746       else
747         return m_data->host;
748     }
749
750
751     // ---------------------------------------------------------------
752     std::string
753     UrlBase::getPort() const
754     {
755       return m_data->port;
756     }
757
758
759     // ---------------------------------------------------------------
760     std::string
761     UrlBase::getPathName(EEncoding eflag) const
762     {
763       if(eflag == zypp::url::E_DECODED)
764         return zypp::url::decode(m_data->pathname);
765       else
766         return cleanupPathName(m_data->pathname);
767     }
768
769
770     // ---------------------------------------------------------------
771     std::string
772     UrlBase::getPathParams() const
773     {
774       return m_data->pathparams;
775     }
776
777
778     // ---------------------------------------------------------------
779     zypp::url::ParamVec
780     UrlBase::getPathParamsVec() const
781     {
782       zypp::url::ParamVec pvec;
783       if( config("psep_pathparam").empty())
784       {
785         pvec.push_back(getPathParams());
786       }
787       else
788       {
789         zypp::url::split(
790           pvec,
791           getPathParams(),
792           config("psep_pathparam")
793         );
794       }
795       return pvec;
796     }
797
798
799     // ---------------------------------------------------------------
800     zypp::url::ParamMap
801     UrlBase::getPathParamsMap(EEncoding eflag) const
802     {
803       if( config("psep_pathparam").empty() ||
804           config("vsep_pathparam").empty())
805       {
806         ZYPP_THROW(UrlNotSupportedException(
807           "Path parameter parsing not supported for this URL"
808         ));
809       }
810       zypp::url::ParamMap pmap;
811       zypp::url::split(
812         pmap,
813         getPathParams(),
814         config("psep_pathparam"),
815         config("vsep_pathparam"),
816         eflag
817       );
818       return pmap;
819     }
820
821
822     // ---------------------------------------------------------------
823     std::string
824     UrlBase::getPathParam(const std::string &param, EEncoding eflag) const
825     {
826       zypp::url::ParamMap pmap( getPathParamsMap( eflag));
827       zypp::url::ParamMap::const_iterator i( pmap.find(param));
828
829       return i != pmap.end() ? i->second : std::string();
830     }
831
832
833     // ---------------------------------------------------------------
834     zypp::url::ParamVec
835     UrlBase::getQueryStringVec() const
836     {
837       zypp::url::ParamVec pvec;
838       if( config("psep_querystr").empty())
839       {
840         pvec.push_back(getQueryString());
841       }
842       else
843       {
844         zypp::url::split(
845           pvec,
846           getQueryString(),
847           config("psep_querystr")
848         );
849       }
850       return pvec;
851     }
852
853
854     // ---------------------------------------------------------------
855     zypp::url::ParamMap
856     UrlBase::getQueryStringMap(EEncoding eflag) const
857     {
858       if( config("psep_querystr").empty() ||
859           config("vsep_querystr").empty())
860       {
861         ZYPP_THROW(UrlNotSupportedException(
862           _("Query string parsing not supported for this URL")
863         ));
864       }
865       zypp::url::ParamMap pmap;
866       zypp::url::split(
867         pmap,
868         getQueryString(),
869         config("psep_querystr"),
870         config("vsep_querystr"),
871         eflag
872       );
873       return pmap;
874     }
875
876
877     // ---------------------------------------------------------------
878     std::string
879     UrlBase::getQueryParam(const std::string &param, EEncoding eflag) const
880     {
881       zypp::url::ParamMap pmap( getQueryStringMap( eflag));
882       zypp::url::ParamMap::const_iterator i( pmap.find(param));
883
884       return i != pmap.end() ? i->second : std::string();
885     }
886
887
888     // ---------------------------------------------------------------
889     void
890     UrlBase::setScheme(const std::string &scheme)
891     {
892       if( isValidScheme(scheme))
893       {
894         m_data->scheme = str::toLower(scheme);
895       }
896       else
897       if( scheme.empty())
898       {
899         ZYPP_THROW(UrlBadComponentException(
900           _("Url scheme is a required component")
901         ));
902       }
903       else
904       {
905         ZYPP_THROW(UrlBadComponentException(
906           str::form(_("Invalid Url scheme '%s'"), scheme.c_str())
907         ));
908       }
909     }
910
911
912     // ---------------------------------------------------------------
913     void
914     UrlBase::setAuthority(const std::string &authority)
915     {
916       std::string s = authority;
917       std::string::size_type p,q;
918
919       std::string username, password, host, port;
920
921       if ((p=s.find('@')) != std::string::npos)
922       {
923         q = s.find(':');
924         if (q != std::string::npos && q < p)
925         {
926           setUsername(s.substr(0, q), zypp::url::E_ENCODED);
927           setPassword(s.substr(q+1, p-q-1), zypp::url::E_ENCODED);
928         }
929         else
930           setUsername(s.substr(0, p), zypp::url::E_ENCODED);
931         s = s.substr(p+1);
932       }
933       if ((p = s.rfind(':')) != std::string::npos && ( (q = s.rfind(']')) == std::string::npos || q < p) )
934       {
935         setHost(s.substr(0, p));
936         setPort(s.substr(p+1));
937       }
938       else
939         setHost(s);
940     }
941
942     // ---------------------------------------------------------------
943     void
944     UrlBase::setPathData(const std::string &pathdata)
945     {
946       size_t      pos = std::string::npos;
947       std::string sep(config("sep_pathparams"));
948
949       if( !sep.empty())
950         pos = pathdata.find(sep);
951
952       if( pos != std::string::npos)
953       {
954         setPathName(pathdata.substr(0, pos),
955                     zypp::url::E_ENCODED);
956         setPathParams(pathdata.substr(pos + 1));
957       }
958       else
959       {
960         setPathName(pathdata,
961                     zypp::url::E_ENCODED);
962         setPathParams("");
963       }
964     }
965
966
967     // ---------------------------------------------------------------
968     void
969     UrlBase::setQueryString(const std::string &querystr)
970     {
971       if( querystr.empty())
972       {
973         m_data->querystr = querystr;
974       }
975       else
976       {
977         checkUrlData(querystr, "query string", config("rx_querystr"));
978
979         m_data->querystr = querystr;
980       }
981     }
982
983
984     // ---------------------------------------------------------------
985     void
986     UrlBase::setFragment(const std::string &fragment,
987                          EEncoding         eflag)
988     {
989       if( fragment.empty())
990       {
991         m_data->fragment = fragment;
992       }
993       else
994       {
995         if(eflag == zypp::url::E_ENCODED)
996         {
997           checkUrlData(fragment, "fragment", config("rx_fragment"));
998
999           m_data->fragment = fragment;
1000         }
1001         else
1002         {
1003           m_data->fragment = zypp::url::encode(
1004             fragment, config("safe_fragment")
1005           );
1006         }
1007       }
1008     }
1009
1010
1011     // ---------------------------------------------------------------
1012     void
1013     UrlBase::setUsername(const std::string &user,
1014                          EEncoding         eflag)
1015     {
1016       if( user.empty())
1017       {
1018         m_data->user = user;
1019       }
1020       else
1021       {
1022         if( config("with_authority") != "y")
1023         {
1024           ZYPP_THROW(UrlNotAllowedException(
1025             _("Url scheme does not allow a username")
1026           ));
1027         }
1028
1029         if(eflag == zypp::url::E_ENCODED)
1030         {
1031           checkUrlData(user, "username", config("rx_username"));
1032
1033           m_data->user = user;
1034         }
1035         else
1036         {
1037           m_data->user = zypp::url::encode(
1038             user, config("safe_username")
1039           );
1040         }
1041       }
1042     }
1043
1044
1045     // ---------------------------------------------------------------
1046     void
1047     UrlBase::setPassword(const std::string &pass,
1048                          EEncoding         eflag)
1049     {
1050       if( pass.empty())
1051       {
1052         m_data->pass = pass;
1053       }
1054       else
1055       {
1056         if( config("with_authority") != "y")
1057         {
1058           ZYPP_THROW(UrlNotAllowedException(
1059             _("Url scheme does not allow a password")
1060           ));
1061         }
1062
1063         if(eflag == zypp::url::E_ENCODED)
1064         {
1065           checkUrlData(pass, "password", config("rx_password"), false);
1066
1067           m_data->pass = pass;
1068         }
1069         else
1070         {
1071           m_data->pass = zypp::url::encode(
1072             pass, config("safe_password")
1073           );
1074         }
1075       }
1076     }
1077
1078
1079     // ---------------------------------------------------------------
1080     void
1081     UrlBase::setHost(const std::string &host)
1082     {
1083       if( host.empty())
1084       {
1085         if(config("require_host") == "m")
1086         {
1087           ZYPP_THROW(UrlNotAllowedException(
1088             _("Url scheme requires a host component")
1089           ));
1090         }
1091         m_data->host = host;
1092       }
1093       else
1094       {
1095         if( config("with_authority") != "y")
1096         {
1097           ZYPP_THROW(UrlNotAllowedException(
1098             _("Url scheme does not allow a host component")
1099           ));
1100         }
1101
1102         if( isValidHost(host))
1103         {
1104           std::string temp;
1105
1106           // always decode in case isValidHost()
1107           // is reimplemented and supports also
1108           // the [v ... ] notation.
1109           if( host.at(0) == '[')
1110           {
1111             temp = str::toUpper(zypp::url::decode(host));
1112           }
1113           else
1114           {
1115             temp = str::toLower(zypp::url::decode(host));
1116           }
1117
1118           m_data->host = zypp::url::encode(
1119             temp, config("safe_hostname")
1120           );
1121         }
1122         else
1123         {
1124           ZYPP_THROW(UrlBadComponentException(
1125             str::form(_("Invalid host component '%s'"), host.c_str())
1126           ));
1127         }
1128       }
1129     }
1130
1131
1132     // ---------------------------------------------------------------
1133     void
1134     UrlBase::setPort(const std::string &port)
1135     {
1136       if( port.empty())
1137       {
1138         m_data->port = port;
1139       }
1140       else
1141       {
1142         if( config("with_authority") != "y" ||
1143             config("with_port")      != "y")
1144         {
1145           ZYPP_THROW(UrlNotAllowedException(
1146             _("Url scheme does not allow a port")
1147           ));
1148         }
1149
1150         if( isValidPort(port))
1151         {
1152           m_data->port = port;
1153         }
1154         else
1155         {
1156           ZYPP_THROW(UrlBadComponentException(
1157             str::form(_("Invalid port component '%s'"), port.c_str())
1158           ));
1159         }
1160       }
1161     }
1162
1163
1164     // ---------------------------------------------------------------
1165     void
1166     UrlBase::setPathName(const std::string &path,
1167                          EEncoding         eflag)
1168     {
1169       if( path.empty())
1170       {
1171         if(config("require_pathname") == "m")
1172         {
1173           ZYPP_THROW(UrlNotAllowedException(
1174             _("Url scheme requires path name")
1175           ));
1176         }
1177         m_data->pathname = path;
1178       }
1179       else
1180       {
1181         if(eflag == zypp::url::E_ENCODED)
1182         {
1183           checkUrlData(path, "path name", config("rx_pathname"));
1184
1185           if( !getHost(zypp::url::E_ENCODED).empty())
1186           {
1187             // has to begin with a "/". For consistency with
1188             // setPathName while the host is empty, we allow
1189             // it in encoded ("%2f") form - cleanupPathName()
1190             // will fix / decode the first slash if needed.
1191             if(!(path.at(0) == '/' || (path.size() >= 3 &&
1192                  str::toLower(path.substr(0, 3)) == "%2f")))
1193             {
1194               ZYPP_THROW(UrlNotAllowedException(
1195                 _("Relative path not allowed if authority exists")
1196               ));
1197             }
1198           }
1199
1200           m_data->pathname = cleanupPathName(path);
1201         }
1202         else //     zypp::url::E_DECODED
1203         {
1204           if( !getHost(zypp::url::E_ENCODED).empty())
1205           {
1206             if(path.at(0) != '/')
1207             {
1208               ZYPP_THROW(UrlNotAllowedException(
1209                 _("Relative path not allowed if authority exists")
1210               ));
1211             }
1212           }
1213
1214           m_data->pathname = cleanupPathName(
1215             zypp::url::encode(
1216               path, config("safe_pathname")
1217             )
1218           );
1219         }
1220       }
1221     }
1222
1223
1224     // ---------------------------------------------------------------
1225     void
1226     UrlBase::setPathParams(const std::string &params)
1227     {
1228       if( params.empty())
1229       {
1230         m_data->pathparams = params;
1231       }
1232       else
1233       {
1234         checkUrlData(params, "path parameters", config("rx_pathparams"));
1235
1236         m_data->pathparams = params;
1237       }
1238     }
1239
1240
1241     // ---------------------------------------------------------------
1242     void
1243     UrlBase::setPathParamsVec(const zypp::url::ParamVec &pvec)
1244     {
1245       setPathParams(
1246         zypp::url::join(
1247           pvec,
1248           config("psep_pathparam")
1249         )
1250       );
1251     }
1252
1253
1254     // ---------------------------------------------------------------
1255     void
1256     UrlBase::setPathParamsMap(const zypp::url::ParamMap &pmap)
1257     {
1258       if( config("psep_pathparam").empty() ||
1259           config("vsep_pathparam").empty())
1260       {
1261         ZYPP_THROW(UrlNotSupportedException(
1262           "Path Parameter parsing not supported for this URL"
1263         ));
1264       }
1265       setPathParams(
1266         zypp::url::join(
1267           pmap,
1268           config("psep_pathparam"),
1269           config("vsep_pathparam"),
1270           config("safe_pathparams")
1271         )
1272       );
1273     }
1274
1275
1276     // ---------------------------------------------------------------
1277     void
1278     UrlBase::setPathParam(const std::string &param, const std::string &value)
1279     {
1280           zypp::url::ParamMap pmap( getPathParamsMap(zypp::url::E_DECODED));
1281           pmap[param] = value;
1282           setPathParamsMap(pmap);
1283     }
1284
1285
1286     // ---------------------------------------------------------------
1287     void
1288     UrlBase::setQueryStringVec(const zypp::url::ParamVec &pvec)
1289     {
1290       setQueryString(
1291         zypp::url::join(
1292           pvec,
1293           config("psep_querystr")
1294         )
1295       );
1296     }
1297
1298
1299     // ---------------------------------------------------------------
1300     void
1301     UrlBase::setQueryStringMap(const zypp::url::ParamMap &pmap)
1302     {
1303       if( config("psep_querystr").empty() ||
1304           config("vsep_querystr").empty())
1305       {
1306         ZYPP_THROW(UrlNotSupportedException(
1307           _("Query string parsing not supported for this URL")
1308         ));
1309       }
1310       setQueryString(
1311         zypp::url::join(
1312           pmap,
1313           config("psep_querystr"),
1314           config("vsep_querystr"),
1315           config("safe_querystr")
1316         )
1317       );
1318     }
1319
1320     // ---------------------------------------------------------------
1321     void
1322     UrlBase::setQueryParam(const std::string &param, const std::string &value)
1323     {
1324           zypp::url::ParamMap pmap( getQueryStringMap(zypp::url::E_DECODED));
1325           pmap[param] = value;
1326           setQueryStringMap(pmap);
1327     }
1328
1329     // ---------------------------------------------------------------
1330     void
1331     UrlBase::delQueryParam(const std::string &param)
1332     {
1333           zypp::url::ParamMap pmap( getQueryStringMap(zypp::url::E_DECODED));
1334           pmap.erase(param);
1335           setQueryStringMap(pmap);
1336     }
1337
1338
1339     // ---------------------------------------------------------------
1340     std::string
1341     UrlBase::cleanupPathName(const std::string &path) const
1342     {
1343       bool authority = !getHost(zypp::url::E_ENCODED).empty();
1344       return cleanupPathName(path, authority);
1345     }
1346
1347     // ---------------------------------------------------------------
1348     std::string
1349     UrlBase::cleanupPathName(const std::string &path, bool authority) const
1350     {
1351       std::string copy( path);
1352
1353       // decode the first slash if it is encoded ...
1354       if(copy.size() >= 3 && copy.at(0) != '/' &&
1355          str::toLower(copy.substr(0, 3)) == "%2f")
1356       {
1357         copy.replace(0, 3, "/");
1358       }
1359
1360       // if path begins with a double slash ("//"); encode the second
1361       // slash [minimal and IMO sufficient] before the first path
1362       // segment, to fulfill the path-absolute rule of RFC 3986
1363       // disallowing a "//" if no authority is present.
1364       if( authority)
1365       {
1366         //
1367         // rewrite of "//" to "/%2f" not required, use config
1368         //
1369         if(config("path_encode_slash2") == "y")
1370         {
1371           // rewrite "//" ==> "/%2f"
1372           if(copy.size() >= 2 && copy.at(0) == '/' && copy.at(1) == '/')
1373           {
1374             copy.replace(1, 1, "%2F");
1375           }
1376         }
1377         else
1378         {
1379           // rewrite "/%2f" ==> "//"
1380           if(copy.size() >= 4 && copy.at(0) == '/' &&
1381              str::toLower(copy.substr(1, 4)) == "%2f")
1382           {
1383             copy.replace(1, 4, "/");
1384           }
1385         }
1386       }
1387       else
1388       {
1389         // rewrite of "//" to "/%2f" is required (no authority)
1390         if(copy.size() >= 2 && copy.at(0) == '/' && copy.at(1) == '/')
1391         {
1392           copy.replace(1, 1, "%2F");
1393         }
1394       }
1395       return copy;
1396     }
1397
1398
1399     // ---------------------------------------------------------------
1400     bool
1401     UrlBase::isValidHost(const std::string &host) const
1402     {
1403       try
1404       {
1405         str::regex regx(RX_VALID_HOSTIPV6);
1406         if( str::regex_match(host, regx))
1407         {
1408           struct in6_addr ip;
1409           std::string temp( host.substr(1, host.size()-2));
1410
1411           return inet_pton(AF_INET6, temp.c_str(), &ip) > 0;
1412         }
1413         else
1414         {
1415           // matches also IPv4 dotted-decimal adresses...
1416           std::string temp( zypp::url::decode(host));
1417           str::regex  regx(RX_VALID_HOSTNAME);
1418           return str::regex_match(temp, regx);
1419         }
1420       }
1421       catch( ... )
1422       {}
1423
1424       return false;
1425     }
1426
1427
1428     // ---------------------------------------------------------------
1429     bool
1430     UrlBase::isValidPort(const std::string &port) const
1431     {
1432       try
1433       {
1434         str::regex regx(RX_VALID_PORT);
1435         if( str::regex_match(port, regx))
1436         {
1437           long pnum = str::strtonum<long>(port);
1438           return ( pnum >= 1 && pnum <= USHRT_MAX);
1439         }
1440       }
1441       catch( ... )
1442       {}
1443       return false;
1444     }
1445
1446
1447     //////////////////////////////////////////////////////////////////
1448   } // namespace url
1449   ////////////////////////////////////////////////////////////////////
1450
1451   ////////////////////////////////////////////////////////////////////
1452 } // namespace zypp
1453 //////////////////////////////////////////////////////////////////////
1454 /*
1455 ** vim: set ts=2 sts=2 sw=2 ai et:
1456 */