Imported Upstream version 15.0.0
[platform/upstream/libzypp.git] / zypp / url / UrlBase.cc
index 031a5b1..5238d52 100644 (file)
@@ -9,8 +9,10 @@
 /**
  * \file zypp/url/UrlBase.cc
  */
-#include <zypp/url/UrlBase.h>
-#include <zypp/base/String.h>
+#include "zypp/url/UrlBase.h"
+#include "zypp/base/String.h"
+#include "zypp/base/Gettext.h"
+#include "zypp/base/Regex.h"
 
 #include <stdexcept>
 #include <climits>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 
+#include <iostream>
+
+// in the Estonian locale, a-z excludes t, for example. #302525
+// http://en.wikipedia.org/wiki/Estonian_alphabet
+#define a_zA_Z "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
 
 // ---------------------------------------------------------------
 /*
 **
 ** host      = hostname | IPv4 | "[" IPv6-IP "]" | "[v...]"
 */
-#define RX_SPLIT_AUTHORITY \
-        "^(([^:@]*)([:]([^@]*))?@)?(\\[[^]]+\\]|[^:]+)?([:](.*))?"
-
-#define RX_VALID_SCHEME    "^[a-zA-Z][a-zA-Z0-9\\.+-]*$"
+#define RX_VALID_SCHEME    "^[" a_zA_Z "][" a_zA_Z "0-9\\.+-]*$"
 
 #define RX_VALID_PORT      "^[0-9]{1,5}$"
 
@@ -55,33 +59,44 @@ namespace zypp
     /*
     ** URL asString() view option constants:
     */
-    const ViewOptions ViewOptions::WITH_SCHEME       = 0x0001;
-    const ViewOptions ViewOptions::WITH_USERNAME     = 0x0002;
-    const ViewOptions ViewOptions::WITH_PASSWORD     = 0x0004;
-    const ViewOptions ViewOptions::WITH_HOST         = 0x0008;
-    const ViewOptions ViewOptions::WITH_PORT         = 0x0010;
-    const ViewOptions ViewOptions::WITH_PATH_NAME    = 0x0020;
-    const ViewOptions ViewOptions::WITH_PATH_PARAMS  = 0x0040;
-    const ViewOptions ViewOptions::WITH_QUERY_STR    = 0x0080;
-    const ViewOptions ViewOptions::WITH_FRAGMENT     = 0x0100;
-    const ViewOptions ViewOptions::EMPTY_AUTHORITY   = 0x0200;
-    const ViewOptions ViewOptions::EMPTY_PATH_NAME   = 0x0400;
-    const ViewOptions ViewOptions::EMPTY_PATH_PARAMS = 0x0800;
-    const ViewOptions ViewOptions::EMPTY_QUERY_STR   = 0x1000;
-    const ViewOptions ViewOptions::EMPTY_FRAGMENT    = 0x2000;
-    const ViewOptions ViewOptions::DEFAULTS          = 0x07bb;
+    const ViewOption  ViewOption::WITH_SCHEME       = 0x0001;
+    const ViewOption  ViewOption::WITH_USERNAME     = 0x0002;
+    const ViewOption  ViewOption::WITH_PASSWORD     = 0x0004;
+    const ViewOption  ViewOption::WITH_HOST         = 0x0008;
+    const ViewOption  ViewOption::WITH_PORT         = 0x0010;
+    const ViewOption  ViewOption::WITH_PATH_NAME    = 0x0020;
+    const ViewOption  ViewOption::WITH_PATH_PARAMS  = 0x0040;
+    const ViewOption  ViewOption::WITH_QUERY_STR    = 0x0080;
+    const ViewOption  ViewOption::WITH_FRAGMENT     = 0x0100;
+    const ViewOption  ViewOption::EMPTY_AUTHORITY   = 0x0200;
+    const ViewOption  ViewOption::EMPTY_PATH_NAME   = 0x0400;
+    const ViewOption  ViewOption::EMPTY_PATH_PARAMS = 0x0800;
+    const ViewOption  ViewOption::EMPTY_QUERY_STR   = 0x1000;
+    const ViewOption  ViewOption::EMPTY_FRAGMENT    = 0x2000;
+    const ViewOption  ViewOption::DEFAULTS          = 0x07bb;
     /*
-                      ViewOptions::WITH_SCHEME       +
-                      ViewOptions::WITH_USERNAME     +
-                      ViewOptions::WITH_HOST         +
-                      ViewOptions::WITH_PORT         +
-                      ViewOptions::WITH_PATH_NAME    +
-                      ViewOptions::WITH_QUERY_STR    +
-                      ViewOptions::WITH_FRAGMENT     +
-                      ViewOptions::EMPTY_AUTHORITY   +
-                      ViewOptions::EMPTY_PATH_NAME;
+    const ViewOption  ViewOption::DEFAULTS          =
+                      ViewOption::WITH_SCHEME       +
+                      ViewOption::WITH_USERNAME     +
+                      ViewOption::WITH_HOST         +
+                      ViewOption::WITH_PORT         +
+                      ViewOption::WITH_PATH_NAME    +
+                      ViewOption::WITH_QUERY_STR    +
+                      ViewOption::WITH_FRAGMENT     +
+                      ViewOption::EMPTY_AUTHORITY   +
+                      ViewOption::EMPTY_PATH_NAME;
     */
 
+    // ---------------------------------------------------------------
+    ViewOption::ViewOption()
+      : opt(0x07bb)
+    {}
+
+    // ---------------------------------------------------------------
+    ViewOption::ViewOption(int option)
+      : opt(option)
+    {}
+
 
     // ---------------------------------------------------------------
     /*
@@ -136,7 +151,7 @@ namespace zypp
         if( regx.empty() || regx == "^$")
         {
           ZYPP_THROW(UrlNotAllowedException(
-            std::string("Url scheme does not allow a " + name)
+            str::form(_("Url scheme does not allow a %s"), name.c_str())
           ));
         }
         else
@@ -155,14 +170,14 @@ namespace zypp
             if( show)
             {
               ZYPP_THROW(UrlBadComponentException(
-                std::string("Invalid " + name + " component '" +
-                            data + "'")
+                str::form(_("Invalid %s component '%s'"),
+                          name.c_str(), data.c_str())
               ));
             }
             else
             {
               ZYPP_THROW(UrlBadComponentException(
-                std::string("Invalid " + name + " component")
+                str::form(_("Invalid %s component"), name.c_str())
               ));
             }
           }
@@ -216,7 +231,11 @@ namespace zypp
                   const std::string &querystr,
                   const std::string &fragment)
     {
-      setScheme(scheme);
+      if ( scheme.empty() && *pathdata.c_str() == '/' )
+       setScheme("file");
+      else
+       setScheme(scheme);
+
       setAuthority(authority);
       setPathData(pathdata);
       setQueryString(querystr);
@@ -243,17 +262,29 @@ namespace zypp
       config("safe_querystr",   "~!$&'()*+=,:;@/?");
       config("safe_fragment",   "~!$&'()*+=,:;@/?");
 
+      // y=yes (allowed)
+      // n=no  (disallowed, exception if !empty)
       config("with_authority",  "y");
-      config("require_scheme",  "y");
+      config("with_port",       "y");
+
+      // y=yes (required but don't throw if empty)
+      // n=no  (not required, ignore if empty)
+      // m=mandatory (exception if empty)
+      config("require_host",    "n");
+      config("require_pathname","n");
+
+      // y=yes (encode 2. slash even if authority present)
+      // n=no  (don't encode 2. slash if authority present)
+      config("path_encode_slash2", "n");
 
-      config("rx_username",     "^([a-zA-Z0-9!$&'\\(\\)*+=,;~\\._-]|%[a-fA-F0-9]{2})+$");
-      config("rx_password",     "^([a-zA-Z0-9!$&'\\(\\)*+=,:;~\\._-]|%[a-fA-F0-9]{2})+$");
+      config("rx_username",     "^([" a_zA_Z "0-9!$&'\\(\\)*+=,;~\\._-]|%[a-fA-F0-9]{2})+$");
+      config("rx_password",     "^([" a_zA_Z "0-9!$&'\\(\\)*+=,:;~\\._-]|%[a-fA-F0-9]{2})+$");
 
-      config("rx_pathname",     "^([a-zA-Z0-9!$&'\\(\\)*+=,:@/~\\._-]|%[a-fA-F0-9]{2})+$");
-      config("rx_pathparams",   "^([a-zA-Z0-9!$&'\\(\\)*+=,:;@/~\\._-]|%[a-fA-F0-9]{2})+$");
+      config("rx_pathname",     "^([" a_zA_Z "0-9!$&'\\(\\)*+=,:@/~\\._-]|%[a-fA-F0-9]{2})+$");
+      config("rx_pathparams",   "^([" a_zA_Z "0-9!$&'\\(\\)*+=,:;@/~\\._-]|%[a-fA-F0-9]{2})+$");
 
-      config("rx_querystr",     "^([a-zA-Z0-9!$&'\\(\\)*+=,:;@/?~\\._-]|%[a-fA-F0-9]{2})+$");
-      config("rx_fragment",     "^([a-zA-Z0-9!$&'\\(\\)*+=,:;@/?~\\._-]|%[a-fA-F0-9]{2})+$");
+      config("rx_querystr",     "^([" a_zA_Z "0-9!$&'\\(\\)*+=,:;@/?~\\._-]|%[a-fA-F0-9]{2})+$");
+      config("rx_fragment",     "^([" a_zA_Z "0-9!$&'\\(\\)*+=,:;@/?~\\._-]|%[a-fA-F0-9]{2})+$");
     }
 
 
@@ -351,7 +382,7 @@ namespace zypp
       catch( ... )
       {}
 
-      if((scheme.empty() && config("require_scheme") != "y") || valid)
+      if(valid)
       {
         std::string    lscheme( str::toLower(scheme));
         UrlSchemes     schemes( getKnownSchemes());
@@ -374,7 +405,31 @@ namespace zypp
     bool
     UrlBase::isValid() const
     {
-      return !getScheme().empty();
+      /*
+      ** scheme is the only mandatory component
+      ** for all url's and is already verified,
+      ** (except for empty Url instances), so
+      ** Url with empty scheme is never valid.
+      */
+      if( getScheme().empty())
+        return false;
+
+      std::string host( getHost(zypp::url::E_ENCODED));
+      if( host.empty() && config("require_host")     != "n")
+        return false;
+
+      std::string path( getPathName(zypp::url::E_ENCODED));
+      if( path.empty() && config("require_pathname") != "n")
+        return false;
+
+      /*
+      ** path has to begin with "/" if authority avaliable
+      ** if host is set after the pathname, we can't throw
+      */
+      if( !host.empty() && !path.empty() && path.at(0) != '/')
+        return false;
+
+      return true;
     }
 
 
@@ -437,6 +492,10 @@ namespace zypp
                 }
               }
             }
+            else if( opts.has(ViewOptions::EMPTY_AUTHORITY))
+            {
+              url += "//";
+            }
           }
           else if( opts.has(ViewOptions::EMPTY_AUTHORITY))
           {
@@ -450,10 +509,15 @@ namespace zypp
         tmp.pathname = getPathName(zypp::url::E_ENCODED);
         if( !tmp.pathname.empty())
         {
-          if( (!tmp.host.empty() || opts.has(ViewOptions::EMPTY_AUTHORITY))
-              && (tmp.pathname.at(0) != '/'))
+          if(url.find("/") != std::string::npos)
           {
-            url += "/";
+            // Url contains authority (that may be empty),
+            // we may need a rewrite of the encoded path.
+            tmp.pathname = cleanupPathName(tmp.pathname, true);
+            if(tmp.pathname.at(0) != '/')
+            {
+              url += "/";
+            }
           }
           url += tmp.pathname;
 
@@ -470,7 +534,8 @@ namespace zypp
             }
           }
         }
-        else if( opts.has(ViewOptions::EMPTY_PATH_NAME))
+        else if( opts.has(ViewOptions::EMPTY_PATH_NAME)
+                 && url.find("/") != std::string::npos)
         {
           url += "/";
           if( opts.has(ViewOptions::EMPTY_PATH_PARAMS))
@@ -622,7 +687,7 @@ namespace zypp
       if(eflag == zypp::url::E_DECODED)
         return zypp::url::decode(m_data->pathname);
       else
-        return m_data->pathname;
+        return cleanupPathName(m_data->pathname);
     }
 
 
@@ -718,7 +783,7 @@ namespace zypp
           config("vsep_querystr").empty())
       {
         ZYPP_THROW(UrlNotSupportedException(
-          "Query string parsing not supported for this URL"
+          _("Query string parsing not supported for this URL")
         ));
       }
       zypp::url::ParamMap pmap;
@@ -756,13 +821,13 @@ namespace zypp
       if( scheme.empty())
       {
         ZYPP_THROW(UrlBadComponentException(
-          std::string("Url scheme is a required component")
+          _("Url scheme is a required component")
         ));
       }
       else
       {
         ZYPP_THROW(UrlBadComponentException(
-          std::string("Invalid Url scheme '" + scheme + "'")
+          str::form(_("Invalid Url scheme '%s'"), scheme.c_str())
         ));
       }
     }
@@ -772,30 +837,30 @@ namespace zypp
     void
     UrlBase::setAuthority(const std::string &authority)
     {
-      str::smatch out;
-      bool        ret = false;
+      std::string s = authority;
+      std::string::size_type p,q;
 
-      try
-      {
-        str::regex  rex(RX_SPLIT_AUTHORITY);
-        ret = str::regex_match(authority, out, rex);
-      }
-      catch( ... )
-      {}
+      std::string username, password, host, port;
 
-      if( ret && out.size() == 8)
+      if ((p=s.find('@')) != std::string::npos)
       {
-        setUsername(out[2].str(), zypp::url::E_ENCODED);
-        setPassword(out[4].str(), zypp::url::E_ENCODED);
-        setHost(out[5].str());
-        setPort(out[7].str());
+        q = s.find(':');
+        if (q != std::string::npos && q < p)
+        {
+          setUsername(s.substr(0, q), zypp::url::E_ENCODED);
+          setPassword(s.substr(q+1, p-q-1), zypp::url::E_ENCODED);
+        }
+        else
+          setUsername(s.substr(0, p), zypp::url::E_ENCODED);
+        s = s.substr(p+1);
       }
-      else
+      if ((p = s.rfind(':')) != std::string::npos && ( (q = s.rfind(']')) == std::string::npos || q < p) )
       {
-        ZYPP_THROW(UrlParsingException(
-          "Unable to parse Url authority"
-        ));
+        setHost(s.substr(0, p));
+        setPort(s.substr(p+1));
       }
+      else
+        setHost(s);
     }
 
     // ---------------------------------------------------------------
@@ -860,7 +925,7 @@ namespace zypp
         else
         {
           m_data->fragment = zypp::url::encode(
-            fragment, config("safe_password")
+            fragment, config("safe_fragment")
           );
         }
       }
@@ -881,7 +946,7 @@ namespace zypp
         if( config("with_authority") != "y")
         {
           ZYPP_THROW(UrlNotAllowedException(
-            std::string("Url scheme does not allow a username")
+            _("Url scheme does not allow a username")
           ));
         }
 
@@ -915,7 +980,7 @@ namespace zypp
         if( config("with_authority") != "y")
         {
           ZYPP_THROW(UrlNotAllowedException(
-            std::string("Url scheme does not allow a password")
+            _("Url scheme does not allow a password")
           ));
         }
 
@@ -941,6 +1006,12 @@ namespace zypp
     {
       if( host.empty())
       {
+        if(config("require_host") == "m")
+        {
+          ZYPP_THROW(UrlNotAllowedException(
+            _("Url scheme requires a host component")
+          ));
+        }
         m_data->host = host;
       }
       else
@@ -948,7 +1019,7 @@ namespace zypp
         if( config("with_authority") != "y")
         {
           ZYPP_THROW(UrlNotAllowedException(
-            std::string("Url scheme does not allow a host")
+            _("Url scheme does not allow a host component")
           ));
         }
 
@@ -975,7 +1046,7 @@ namespace zypp
         else
         {
           ZYPP_THROW(UrlBadComponentException(
-            std::string("Invalid host argument '" + host + "'")
+            str::form(_("Invalid host component '%s'"), host.c_str())
           ));
         }
       }
@@ -992,10 +1063,11 @@ namespace zypp
       }
       else
       {
-        if( config("with_authority") != "y")
+        if( config("with_authority") != "y" ||
+            config("with_port")      != "y")
         {
           ZYPP_THROW(UrlNotAllowedException(
-            std::string("Url scheme does not allow a port")
+            _("Url scheme does not allow a port")
           ));
         }
 
@@ -1006,7 +1078,7 @@ namespace zypp
         else
         {
           ZYPP_THROW(UrlBadComponentException(
-            std::string("Invalid host argument '" + port + "'")
+            str::form(_("Invalid port component '%s'"), port.c_str())
           ));
         }
       }
@@ -1020,25 +1092,55 @@ namespace zypp
     {
       if( path.empty())
       {
+        if(config("require_pathname") == "m")
+        {
+          ZYPP_THROW(UrlNotAllowedException(
+            _("Url scheme requires path name")
+          ));
+        }
         m_data->pathname = path;
       }
       else
       {
-        std::string data;
         if(eflag == zypp::url::E_ENCODED)
         {
           checkUrlData(path, "path name", config("rx_pathname"));
 
-          data = cleanupPathName(zypp::url::decode(path));
+          if( !getHost(zypp::url::E_ENCODED).empty())
+          {
+            // has to begin with a "/". For consistency with
+            // setPathName while the host is empty, we allow
+            // it in encoded ("%2f") form - cleanupPathName()
+            // will fix / decode the first slash if needed.
+            if(!(path.at(0) == '/' || (path.size() >= 3 &&
+                 str::toLower(path.substr(0, 3)) == "%2f")))
+            {
+              ZYPP_THROW(UrlNotAllowedException(
+                _("Relative path not allowed if authority exists")
+              ));
+            }
+          }
+
+          m_data->pathname = cleanupPathName(path);
         }
-        else
+        else //     zypp::url::E_DECODED
         {
-          data = cleanupPathName(path);
-        }
+          if( !getHost(zypp::url::E_ENCODED).empty())
+          {
+            if(path.at(0) != '/')
+            {
+              ZYPP_THROW(UrlNotAllowedException(
+                _("Relative path not allowed if authority exists")
+              ));
+            }
+          }
 
-        m_data->pathname = zypp::url::encode(
-          data, config("safe_pathname")
-        );
+          m_data->pathname = cleanupPathName(
+            zypp::url::encode(
+              path, config("safe_pathname")
+            )
+          );
+        }
       }
     }
 
@@ -1126,7 +1228,7 @@ namespace zypp
           config("vsep_querystr").empty())
       {
         ZYPP_THROW(UrlNotSupportedException(
-          "Query string parsing not supported for this URL"
+          _("Query string parsing not supported for this URL")
         ));
       }
       setQueryString(
@@ -1148,30 +1250,79 @@ namespace zypp
           setQueryStringMap(pmap);
     }
 
+    // ---------------------------------------------------------------
+    void
+    UrlBase::delQueryParam(const std::string &param)
+    {
+          zypp::url::ParamMap pmap( getQueryStringMap(zypp::url::E_DECODED));
+          pmap.erase(param);
+          setQueryStringMap(pmap);
+    }
+
 
     // ---------------------------------------------------------------
     std::string
-    UrlBase::cleanupPathName(const std::string &path)
+    UrlBase::cleanupPathName(const std::string &path) const
     {
-      size_t pos = 0;
+      bool authority = !getHost(zypp::url::E_ENCODED).empty();
+      return cleanupPathName(path, authority);
+    }
 
-      while( pos < path.length() && path.at(pos) == '/')
-        pos++;
+    // ---------------------------------------------------------------
+    std::string
+    UrlBase::cleanupPathName(const std::string &path, bool authority) const
+    {
+      std::string copy( path);
 
-      if( pos > 1)
+      // decode the first slash if it is encoded ...
+      if(copy.size() >= 3 && copy.at(0) != '/' &&
+         str::toLower(copy.substr(0, 3)) == "%2f")
       {
-        // make sure, there is not more than
-        // _one_ leading "/" in the path name.
-        return path.substr(pos - 1);
+        copy.replace(0, 3, "/");
       }
 
-      return std::string(path);
+      // if path begins with a double slash ("//"); encode the second
+      // slash [minimal and IMO sufficient] before the first path
+      // segment, to fulfill the path-absolute rule of RFC 3986
+      // disallowing a "//" if no authority is present.
+      if( authority)
+      {
+        //
+        // rewrite of "//" to "/%2f" not required, use config
+        //
+        if(config("path_encode_slash2") == "y")
+        {
+          // rewrite "//" ==> "/%2f"
+          if(copy.size() >= 2 && copy.at(0) == '/' && copy.at(1) == '/')
+          {
+            copy.replace(1, 1, "%2F");
+          }
+        }
+        else
+        {
+          // rewrite "/%2f" ==> "//"
+          if(copy.size() >= 4 && copy.at(0) == '/' &&
+             str::toLower(copy.substr(1, 4)) == "%2f")
+          {
+            copy.replace(1, 4, "/");
+          }
+        }
+      }
+      else
+      {
+        // rewrite of "//" to "/%2f" is required (no authority)
+        if(copy.size() >= 2 && copy.at(0) == '/' && copy.at(1) == '/')
+        {
+          copy.replace(1, 1, "%2F");
+        }
+      }
+      return copy;
     }
 
 
     // ---------------------------------------------------------------
     bool
-    UrlBase::isValidHost(const std::string &host)
+    UrlBase::isValidHost(const std::string &host) const
     {
       try
       {
@@ -1180,7 +1331,7 @@ namespace zypp
         {
           struct in6_addr ip;
           std::string temp( host.substr(1, host.size()-2));
-          
+
           return inet_pton(AF_INET6, temp.c_str(), &ip) > 0;
         }
         else
@@ -1200,7 +1351,7 @@ namespace zypp
 
     // ---------------------------------------------------------------
     bool
-    UrlBase::isValidPort(const std::string &port)
+    UrlBase::isValidPort(const std::string &port) const
     {
       try
       {
@@ -1213,7 +1364,6 @@ namespace zypp
       }
       catch( ... )
       {}
-
       return false;
     }