aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bpkg/manifest95
-rw-r--r--bpkg/manifest.cxx252
-rw-r--r--tests/buildfile2
-rw-r--r--tests/repository-location/buildfile9
-rw-r--r--tests/repository-location/driver.cxx153
5 files changed, 481 insertions, 30 deletions
diff --git a/bpkg/manifest b/bpkg/manifest
index a4bf6f2..e8c2714 100644
--- a/bpkg/manifest
+++ b/bpkg/manifest
@@ -7,9 +7,11 @@
#include <string>
#include <vector>
-#include <cstdint> // uint16
+#include <cstdint> // uint16_t
#include <algorithm> // move()
+#include <stdexcept> // logic_error
+#include <butl/path>
#include <butl/optional>
namespace bpkg
@@ -20,8 +22,9 @@ namespace bpkg
using strings = std::vector<std::string>;
- struct version
+ class version
{
+ public:
// Create a special empty version.
//
version (): epoch_ (0), revision_ (0) {}
@@ -145,8 +148,9 @@ namespace bpkg
// description
// description-file
//
- struct description: std::string
+ class description: public std::string
{
+ public:
bool file;
std::string comment;
@@ -164,8 +168,9 @@ namespace bpkg
// license
//
- struct licenses: strings
+ class licenses: public strings
{
+ public:
std::string comment;
explicit
@@ -175,8 +180,9 @@ namespace bpkg
// change
// change-file
//
- struct change: std::string
+ class change: public std::string
{
+ public:
bool file;
std::string comment;
@@ -194,8 +200,9 @@ namespace bpkg
// url
// package-url
//
- struct url: std::string
+ class url: public std::string
{
+ public:
std::string comment;
explicit
@@ -206,8 +213,9 @@ namespace bpkg
// email
// package-email
//
- struct email: std::string
+ class email: public std::string
{
+ public:
std::string comment;
explicit
@@ -231,8 +239,9 @@ namespace bpkg
butl::optional<version_comparison> version;
};
- struct dependency_alternatives: std::vector<dependency>
+ class dependency_alternatives: public std::vector<dependency>
{
+ public:
bool conditional;
std::string comment;
@@ -243,8 +252,9 @@ namespace bpkg
// requires
//
- struct requirement_alternatives: strings
+ class requirement_alternatives: public strings
{
+ public:
bool conditional;
std::string comment;
@@ -287,17 +297,76 @@ namespace bpkg
class repository_location
{
- // @@ Move all the location verification/canonical name calculation
- // here.
+ public:
+ // Create a special empty repository_location.
//
+ repository_location () = default;
+
+ explicit
+ repository_location (const std::string&);
+
+ const std::string&
+ canonical_name () const noexcept {return canonical_name_;}
+
+ bool
+ empty () const noexcept {return canonical_name_.empty ();}
+
+ bool
+ local () const
+ {
+ if (empty ())
+ throw std::logic_error ("empty location");
+
+ return host_.empty ();
+ }
- // ...
+ const butl::dir_path&
+ path () const
+ {
+ if (empty ())
+ throw std::logic_error ("empty location");
+
+ return path_;
+ }
+
+ const std::string&
+ host () const
+ {
+ if (local ())
+ throw std::logic_error ("local location");
+
+ return host_;
+ }
+
+ // Value 0 indicated that no port was specified explicitly.
+ //
+ std::uint16_t
+ port () const
+ {
+ if (local ())
+ throw std::logic_error ("local location");
+
+ return port_;
+ }
+
+ // Note that this is not necessarily syntactically the same
+ // string as what was used to initialize this location. But
+ // it should be semantically equivalent.
+ //
+ std::string
+ string () const;
+
+ private:
+ std::string canonical_name_;
+ std::string host_;
+ std::uint16_t port_;
+ butl::dir_path path_;
};
class repository_manifest
{
public:
- std::string location;
+ repository_location location;
public:
repository_manifest (manifest_parser&);
diff --git a/bpkg/manifest.cxx b/bpkg/manifest.cxx
index 2576bea..44a5eff 100644
--- a/bpkg/manifest.cxx
+++ b/bpkg/manifest.cxx
@@ -4,19 +4,26 @@
#include <bpkg/manifest>
+#include <strings.h> // strncasecmp()
+
#include <string>
#include <ostream>
#include <sstream>
#include <cassert>
#include <cstring> // strncmp()
#include <utility> // move()
-#include <algorithm> // find()
+#include <cstdint> // uint64_t, uint16_t, UINT16_MAX
+#include <iterator> // back_insert_iterator
+#include <algorithm> // find(), transform()
#include <stdexcept> // invalid_argument
+#include <butl/path>
+
#include <bpkg/manifest-parser>
#include <bpkg/manifest-serializer>
using namespace std;
+using namespace butl;
namespace bpkg
{
@@ -32,23 +39,32 @@ namespace bpkg
static const string spaces (" \t");
inline static bool
- space (char c)
+ space (char c) noexcept
{
return c == ' ' || c == '\t';
}
inline static bool
- digit (char c)
+ digit (char c) noexcept
{
return c >= '0' && c <= '9';
}
inline static bool
- alpha (char c)
+ alpha (char c) noexcept
{
return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';
}
+ // Replace std::tolower to keep things locale independent.
+ //
+ inline static char
+ lowercase (char c) noexcept
+ {
+ const unsigned char shift ('a' - 'A');
+ return c >= 'A' && c <='Z' ? c + shift : c;
+ }
+
static ostream&
operator<< (ostream& o, const dependency& d)
{
@@ -175,7 +191,9 @@ namespace bpkg
version::
version (const char* v, bool upstream_only): version () // Delegate
{
- using std::string; // Otherwise compiler get confused with string() member.
+ // Otherwise compiler gets confused with string() member.
+ //
+ using std::string;
assert (v != nullptr);
@@ -211,16 +229,8 @@ namespace bpkg
}
else
{
- // Don't use tolower to keep things locale independent.
- //
- static unsigned char shift ('a' - 'A');
-
for (const char* i (b); i != e; ++i)
- {
- char c (*i);
- canonical_upstream_.append (
- 1, c >= 'A' && c <='Z' ? c + shift : c);
- }
+ canonical_upstream_.append (1, lowercase (*i));
return true;
}
@@ -736,6 +746,204 @@ namespace bpkg
s.next ("", ""); // End of manifest.
}
+ // repository_location
+ //
+ repository_location::
+ repository_location (const std::string& l): port_ (0)
+ {
+ // Otherwise compiler gets confused with string() member. Same reason
+ // constructor parameter type is fully qualified.
+ //
+ using std::string;
+
+ if (::strncasecmp (l.c_str (), "http://", 7) == 0)
+ {
+ // Split location into host, port and path components. Calculate
+ // canonical name <host> part removing www. and pkg. prefixes.
+ //
+ auto p (l.find ('/', 7));
+
+ // Chop the path part. Note that we translate empty path to "/".
+ //
+ path_ = p != string::npos
+ ? dir_path (l, p, string::npos)
+ : dir_path ("/");
+
+ // Put the lower-cased version of the host part into host_.
+ // Chances are good it will stay unmodified.
+ //
+ transform (l.cbegin () + 7,
+ p == string::npos ? l.cend () : l.cbegin () + p,
+ back_inserter (host_),
+ lowercase);
+
+ // Validate host name according to "2.3.1. Preferred name syntax" and
+ // "2.3.4. Size limits" of https://tools.ietf.org/html/rfc1035.
+ //
+ // Check that there is no empty labels and ones containing chars
+ // different from alpha-numeric and hyphen. Label should start from
+ // letter, do not end with hypen and be not longer than 63 chars.
+ // Total host name length should be not longer than 255 chars.
+ //
+ auto hb (host_.cbegin ());
+ auto he (host_.cend ());
+ auto ls (hb); // Host domain name label begin.
+ auto pt (he); // Port begin.
+
+ for (auto i (hb); i != he; ++i)
+ {
+ char c (*i);
+
+ if (pt == he) // Didn't reach port specification yet.
+ {
+ if (c == ':') // Port specification reached.
+ pt = i;
+ else
+ {
+ auto n (i + 1);
+
+ // Validate host name.
+ //
+
+ // Is first label char.
+ //
+ bool flc (i == ls);
+
+ // Is last label char.
+ //
+ bool llc (n == he || *n == '.' || *n == ':');
+
+ // Validate char.
+ //
+ bool valid (alpha (c) ||
+ digit (c) && !flc ||
+ (c == '-' || c == '.') && !flc && !llc);
+
+ // Validate length.
+ //
+ if (valid)
+ valid = i - ls < 64 && i - hb < 256;
+
+ if (!valid)
+ throw invalid_argument ("invalid host");
+
+ if (c == '.')
+ ls = n;
+ }
+ }
+ else
+ {
+ // Validate port.
+ //
+ if (!digit (c))
+ throw invalid_argument ("invalid port");
+ }
+ }
+
+ // Chop the port, if present.
+ //
+ if (pt != he)
+ {
+ unsigned long long n (++pt == he ? 0 : stoull (string (pt, he)));
+ if (n == 0 || n > UINT16_MAX)
+ throw invalid_argument ("invalid port");
+
+ port_ = static_cast<uint16_t> (n);
+ host_.resize (pt - hb - 1);
+ }
+
+ if (host_.empty ())
+ throw invalid_argument ("invalid host");
+
+ // Ok, the last thing we need to do is add the host and port
+ // parts to the canonical_name_ name. Here we also need to
+ // chop off the special "www" and "pkg" prefixes. Strictly
+ // speaking we can end up with comething bogus like "com"
+ // if the host is "pkg.com".
+ //
+ if (host_.compare (0, 4, "www.") == 0 ||
+ host_.compare (0, 4, "pkg.") == 0)
+ canonical_name_.assign (host_, 4, string::npos);
+ else
+ canonical_name_ = host_;
+
+ // For canonical name and for the HTTP protocol, treat a.com
+ // and a.com:80 as the same name.
+ //
+ if (port_ != 0 && port_ != 80)
+ canonical_name_ += ':' + to_string (port_);
+ }
+ else
+ path_ = dir_path (l);
+
+ // Normalize path to avoid different representations of the same location
+ // and canonical name. So a/b/../c/1/x/../y and a/c/1/y to be considered
+ // as same location.
+ //
+ try
+ {
+ path_.normalize ();
+ }
+ catch (const invalid_path&)
+ {
+ throw invalid_argument ("invalid path");
+ }
+
+ // Search for the version path component preceeding canonical name
+ // <path> component.
+ //
+ auto b (path_.rbegin ()), i (b), e (path_.rend ());
+
+ // Find the version component.
+ //
+ for (; i != e; ++i)
+ {
+ const string& c (*i);
+
+ if (!c.empty () && c.find_first_not_of ("1234567890") == string::npos)
+ break;
+ }
+
+ if (i == e)
+ throw invalid_argument ("missing repository version");
+
+ // Validate the version. At the moment the only valid value is 1.
+ //
+ if (stoul (*i) != 1)
+ throw invalid_argument ("unsupported repository version");
+
+ // Note: allow empty paths (e.g., http://stable.cppget.org/1/).
+ //
+ string d (dir_path (b, i).posix_string ());
+
+ if (!canonical_name_.empty () && !d.empty ()) // If we have host and dir.
+ canonical_name_ += '/';
+
+ canonical_name_ += d;
+
+ // But don't allow empty canonical names.
+ //
+ if (canonical_name_.empty ())
+ throw invalid_argument ("empty repository name");
+ }
+
+ string repository_location::
+ string () const
+ {
+ if (empty ())
+ return "";
+
+ if (local ())
+ return path_.string ();
+
+ std::string p ("http://" + host_);
+
+ if (port_ != 0)
+ p += ":" + to_string (port_);
+
+ return p + path_.posix_string ();
+ }
+
// repository_manifest
//
repository_manifest::
@@ -773,7 +981,19 @@ namespace bpkg
string& v (nv.value);
if (n == "location")
- location = move (v);
+ {
+ if (!v.empty ())
+ {
+ try
+ {
+ location = repository_location (move (v));
+ }
+ catch (const invalid_argument& e)
+ {
+ bad_value (e.what ());
+ }
+ }
+ }
else
bad_name ("unknown name '" + n + "' in repository manifest");
}
@@ -789,7 +1009,7 @@ namespace bpkg
s.next ("", "1"); // Start of manifest.
if (!location.empty ())
- s.next ("location", location);
+ s.next ("location", location.string ());
s.next ("", ""); // End of manifest.
}
diff --git a/tests/buildfile b/tests/buildfile
index 7162cc7..bb53854 100644
--- a/tests/buildfile
+++ b/tests/buildfile
@@ -3,7 +3,7 @@
# license : MIT; see accompanying LICENSE file
d = manifest-parser/ manifest-serializer/ manifest-roundtrip/ manifest/ \
- package-version/
+ package-version/ repository-location/
.: $d
include $d
diff --git a/tests/repository-location/buildfile b/tests/repository-location/buildfile
new file mode 100644
index 0000000..24c6335
--- /dev/null
+++ b/tests/repository-location/buildfile
@@ -0,0 +1,9 @@
+# file : tests/repository-location/buildfile
+# copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+exe{driver}: cxx{driver} ../../bpkg/lib{bpkg}
+
+include ../../bpkg/
+
+# test: ./driver
diff --git a/tests/repository-location/driver.cxx b/tests/repository-location/driver.cxx
new file mode 100644
index 0000000..be51eb0
--- /dev/null
+++ b/tests/repository-location/driver.cxx
@@ -0,0 +1,153 @@
+// file : tests/repository-location/driver.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <string>
+#include <cassert>
+#include <iostream>
+#include <exception>
+#include <stdexcept> // invalid_argument
+
+#include <bpkg/manifest>
+
+using namespace std;
+using namespace bpkg;
+
+static bool
+bad_location (const string& l)
+{
+ try
+ {
+ repository_location bl (l);
+ return false;
+ }
+ catch (const invalid_argument&)
+ {
+ return true;
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ if (argc != 1)
+ {
+ cerr << "usage: " << argv[0] << endl;
+ return 1;
+ }
+
+ try
+ {
+ // Test invalid locations.
+ //
+
+ // Invalid host.
+ //
+ assert (bad_location ("http:///aa/bb"));
+ assert (bad_location ("http://1/aa/bb"));
+ assert (bad_location ("http:///1/aa/bb"));
+ assert (bad_location ("http://1a/aa/bb"));
+ assert (bad_location ("http://a..a/aa/bb"));
+ assert (bad_location ("http://.a.a/aa/bb"));
+ assert (bad_location ("http://a.a./aa/bb"));
+ assert (bad_location ("http://a.1a/aa/bb"));
+ assert (bad_location ("http://a.1a.a/aa/bb"));
+ assert (bad_location ("http://a.-ab/aa/bb"));
+ assert (bad_location ("http://a.-ab.a/aa/bb"));
+ assert (bad_location ("http://a.ab-/aa/bb"));
+ assert (bad_location ("http://a.ab-.a/aa/bb"));
+ assert (bad_location ("http://a.ab-:80/aa/bb"));
+ assert (bad_location ("http://a.ab.:80/aa/bb"));
+ assert (bad_location ("http://1.1.1.1.r/1/b"));
+ assert (bad_location ("http://www./aa/1/bb"));
+
+ assert (bad_location ("http://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaa.org/1/b/"));
+
+ // Invalid port.
+ //
+ assert (bad_location ("http://a:/aa/bb"));
+ assert (bad_location ("http://a:1b/aa/bb"));
+ assert (bad_location ("http://c.ru:8a80/1/b"));
+ assert (bad_location ("http://c.ru:8:80/1/b"));
+ assert (bad_location ("http://a:0/aa/bb"));
+ assert (bad_location ("http://c.ru:65536/1/b"));
+
+ // Invalid path.
+ //
+ assert (bad_location (""));
+ assert (bad_location ("1"));
+ assert (bad_location ("1/"));
+ assert (bad_location ("bbb"));
+ assert (bad_location ("aaa/bbb"));
+ assert (bad_location ("/aaa/bbb"));
+ assert (bad_location ("http://aa/bb"));
+ assert (bad_location ("http://a.com/../c/1/aa"));
+
+ // Invalid version.
+ //
+ assert (bad_location ("3/aaa/bbb"));
+
+ // Test valid locations.
+ //
+ {
+ repository_location l ("1/aa/bb");
+ assert (l.string () == "1/aa/bb");
+ assert (l.canonical_name () == "aa/bb");
+ }
+ {
+ repository_location l ("/a/b/../c/1/aa/../bb");
+ assert (l.string () == "/a/c/1/bb");
+ assert (l.canonical_name () == "bb");
+ }
+ {
+ repository_location l ("../c/../c/./1/aa/../bb");
+ assert (l.string () == "../c/1/bb");
+ assert (l.canonical_name () == "bb");
+ }
+ {
+ repository_location l ("http://www.a.com:80/1/aa/bb");
+ assert (l.string () == "http://www.a.com:80/1/aa/bb");
+ assert (l.canonical_name () == "a.com/aa/bb");
+ }
+ {
+ repository_location l ("http://www.a.com:8080/dd/1/aa/bb");
+ assert (l.string () == "http://www.a.com:8080/dd/1/aa/bb");
+ assert (l.canonical_name () == "a.com:8080/aa/bb");
+ }
+ {
+ repository_location l ("http://a.com/a/b/../c/1/aa/../bb");
+ assert (l.string () == "http://a.com/a/c/1/bb");
+ assert (l.canonical_name () == "a.com/bb");
+ }
+ {
+ repository_location l ("http://www.CPPget.org/qw/1/a/b/");
+ assert (l.string () == "http://www.cppget.org/qw/1/a/b");
+ assert (l.canonical_name () == "cppget.org/a/b");
+ }
+ {
+ repository_location l ("http://abc.cppget.org/qw/1/a/b/");
+ assert (l.string () == "http://abc.cppget.org/qw/1/a/b");
+ assert (l.canonical_name () == "abc.cppget.org/a/b");
+ }
+ {
+ repository_location l ("http://pkg.www.cppget.org/qw/1/a/b/");
+ assert (l.string () == "http://pkg.www.cppget.org/qw/1/a/b");
+ assert (l.canonical_name () == "www.cppget.org/a/b");
+ }
+ {
+ repository_location l ("http://cppget.org/qw//1/a//b/");
+ assert (l.string () == "http://cppget.org/qw/1/a/b");
+ assert (l.canonical_name () == "cppget.org/a/b");
+ }
+ {
+ repository_location l ("http://stable.cppget.org/1/");
+ assert (l.canonical_name () == "stable.cppget.org");
+ }
+ }
+ catch (const exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}