Example #1
0
        private static bool TryParseNewFormat(string str, out LightningConnectionString connectionString, out string error)
        {
            connectionString = null;
            error            = null;
            var parts = str.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            Dictionary <string, string> keyValues = new Dictionary <string, string>();

            foreach (var part in parts.Select(p => p.Trim()))
            {
                var idx = part.IndexOf('=', StringComparison.OrdinalIgnoreCase);
                if (idx == -1)
                {
                    error = "The format of the connectionString should a list of key=value delimited by semicolon";
                    return(false);
                }
                var key   = part.Substring(0, idx).Trim().ToLowerInvariant();
                var value = part.Substring(idx + 1).Trim();
                if (keyValues.ContainsKey(key))
                {
                    error = $"Duplicate key {key}";
                    return(false);
                }
                keyValues.Add(key, value);
            }

            var possibleTypes = String.Join(", ", typeMapping.Select(k => k.Key).ToArray());

            LightningConnectionString result = new LightningConnectionString();
            var type = Take(keyValues, "type");

            if (type == null)
            {
                error = $"The key 'type' is mandatory, possible values are {possibleTypes}";
                return(false);
            }

            if (!typeMapping.TryGetValue(type.ToLowerInvariant(), out var connectionType))
            {
                error = $"The key 'type' is invalid, possible values are {possibleTypes}";
                return(false);
            }

            result.ConnectionType = connectionType;

            switch (connectionType)
            {
            case LightningConnectionType.Charge:
            {
                var server = Take(keyValues, "server");
                if (server == null)
                {
                    error = $"The key 'server' is mandatory for charge connection strings";
                    return(false);
                }

                if (!Uri.TryCreate(server, UriKind.Absolute, out var uri) ||
                    (uri.Scheme != "http" && uri.Scheme != "https"))
                {
                    error = $"The key 'server' should be an URI starting by http:// or https://";
                    return(false);
                }

                parts = uri.UserInfo.Split(':');
                if (!string.IsNullOrEmpty(uri.UserInfo) && parts.Length == 2)
                {
                    result.Username = parts[0];
                    result.Password = parts[1];
                }
                else
                {
                    var apiToken = Take(keyValues, "api-token");
                    if (apiToken == null)
                    {
                        error = "The key 'api-token' is not found";
                        return(false);
                    }
                    result.Username = "******";
                    result.Password = apiToken;
                }
                result.BaseUri = new UriBuilder(uri)
                {
                    UserName = "", Password = ""
                }.Uri;
            }
            break;

            case LightningConnectionType.CLightning:
            {
                var server = Take(keyValues, "server");
                if (server == null)
                {
                    error = $"The key 'server' is mandatory for charge connection strings";
                    return(false);
                }

                if (server.StartsWith("//", StringComparison.OrdinalIgnoreCase))
                {
                    server = "unix:" + str;
                }
                else if (server.StartsWith("/", StringComparison.OrdinalIgnoreCase))
                {
                    server = "unix:/" + str;
                }

                if (!Uri.TryCreate(server, UriKind.Absolute, out var uri) ||
                    (uri.Scheme != "tcp" && uri.Scheme != "unix"))
                {
                    error = $"The key 'server' should be an URI starting by tcp:// or unix:// or a path to the 'lightning-rpc' unix socket";
                    return(false);
                }
                result.BaseUri = uri;
            }
            break;

            case LightningConnectionType.LndREST:
            {
                var server = Take(keyValues, "server");
                if (server == null)
                {
                    error = $"The key 'server' is mandatory for lnd connection strings";
                    return(false);
                }
                if (!Uri.TryCreate(server, UriKind.Absolute, out var uri) ||
                    (uri.Scheme != "http" && uri.Scheme != "https"))
                {
                    error = $"The key 'server' should be an URI starting by http:// or https://";
                    return(false);
                }
                parts = uri.UserInfo.Split(':');
                if (!string.IsNullOrEmpty(uri.UserInfo) && parts.Length == 2)
                {
                    result.Username = parts[0];
                    result.Password = parts[1];
                }
                result.BaseUri = new UriBuilder(uri)
                {
                    UserName = "", Password = ""
                }.Uri;

                var macaroon = Take(keyValues, "macaroon");
                if (macaroon != null)
                {
                    try
                    {
                        result.Macaroon = Encoder.DecodeData(macaroon);
                    }
                    catch
                    {
                        error = $"The key 'macaroon' format should be in hex";
                        return(false);
                    }
                }

                var macaroonFilePath = Take(keyValues, "macaroonfilepath");
                if (macaroonFilePath != null)
                {
                    if (macaroon != null)
                    {
                        error = $"The key 'macaroon' is already specified";
                        return(false);
                    }
                    if (!macaroonFilePath.EndsWith(".macaroon", StringComparison.OrdinalIgnoreCase))
                    {
                        error = $"The key 'macaroonfilepath' should point to a .macaroon file";
                        return(false);
                    }
                    result.MacaroonFilePath = macaroonFilePath;
                }

                string securitySet    = null;
                var    certthumbprint = Take(keyValues, "certthumbprint");
                if (certthumbprint != null)
                {
                    try
                    {
                        var bytes = Encoders.Hex.DecodeData(certthumbprint.Replace(":", string.Empty, StringComparison.OrdinalIgnoreCase));
                        if (bytes.Length != 32)
                        {
                            error = $"The key 'certthumbprint' has invalid length: it should be the SHA256 of the PEM format of the certificate (32 bytes)";
                            return(false);
                        }
                        result.CertificateThumbprint = bytes;
                    }
                    catch
                    {
                        error = $"The key 'certthumbprint' has invalid format: it should be the SHA256 of the PEM format of the certificate";
                        return(false);
                    }
                    securitySet = "certthumbprint";
                }

                var allowinsecureStr = Take(keyValues, "allowinsecure");

                if (allowinsecureStr != null)
                {
                    var allowedValues = new[] { "true", "false" };
                    if (!allowedValues.Any(v => v.Equals(allowinsecureStr, StringComparison.OrdinalIgnoreCase)))
                    {
                        error = $"The key 'allowinsecure' should be true or false";
                        return(false);
                    }

                    bool allowInsecure = allowinsecureStr.Equals("true", StringComparison.OrdinalIgnoreCase);
                    if (securitySet != null && allowInsecure)
                    {
                        error = $"The key 'allowinsecure' conflict with '{securitySet}'";
                        return(false);
                    }
                    result.AllowInsecure = allowInsecure;
                }

                if (!result.AllowInsecure && result.BaseUri.Scheme == "http")
                {
                    error = $"The key 'allowinsecure' is false, but server's Uri is not using https";
                    return(false);
                }
            }
            break;

            default:
                throw new NotSupportedException(connectionType.ToString());
            }

            if (keyValues.Count != 0)
            {
                error = $"Unknown keys ({String.Join(", ", keyValues.Select(k => k.Key).ToArray())})";
                return(false);
            }

            connectionString = result;
            return(true);
        }
Example #2
0
        private static bool TryParseLegacy(string str, out LightningConnectionString connectionString, out string error)
        {
            if (str.StartsWith('/'))
            {
                str = "unix:" + str;
            }
            var result = new LightningConnectionString();

            connectionString = null;
            error            = null;

            Uri uri;

            if (!Uri.TryCreate(str, UriKind.Absolute, out uri))
            {
                error = "Invalid URL";
                return(false);
            }

            var supportedDomains = new string[] { "unix", "tcp", "http", "https" };

            if (!supportedDomains.Contains(uri.Scheme))
            {
                var protocols = String.Join(",", supportedDomains);
                error = $"The url support the following protocols {protocols}";
                return(false);
            }
            if (uri.Scheme == "unix")
            {
                str = uri.AbsoluteUri.Substring("unix:".Length);
                while (str.Length >= 1 && str[0] == '/')
                {
                    str = str.Substring(1);
                }
                uri = new Uri("unix://" + str, UriKind.Absolute);
                result.ConnectionType = LightningConnectionType.CLightning;
            }

            if (uri.Scheme == "tcp")
            {
                result.ConnectionType = LightningConnectionType.CLightning;
            }

            if (uri.Scheme == "http" || uri.Scheme == "https")
            {
                var parts = uri.UserInfo.Split(':');
                if (string.IsNullOrEmpty(uri.UserInfo) || parts.Length != 2)
                {
                    error = "The url is missing user and password";
                    return(false);
                }
                result.Username       = parts[0];
                result.Password       = parts[1];
                result.ConnectionType = LightningConnectionType.Charge;
            }
            else if (!string.IsNullOrEmpty(uri.UserInfo))
            {
                error = "The url should not have user information";
                return(false);
            }
            result.BaseUri = new UriBuilder(uri)
            {
                UserName = "", Password = ""
            }.Uri;
            result.IsLegacy  = true;
            connectionString = result;
            return(true);
        }
Example #3
0
 public static bool TryParse(string str, bool supportLegacy, out LightningConnectionString connectionString)
 {
     return(TryParse(str, supportLegacy, out connectionString, out var error));
 }
 public LightningConnectionString Clone()
 {
     LightningConnectionString.TryParse(this.ToString(), false, out var result);
     return(result);
 }