From 3f13d1a0432b3d1e0bac0c4acbdd2626083fa902 Mon Sep 17 00:00:00 2001 From: mpl Date: Mon, 6 Jan 2014 22:14:46 -0800 Subject: [PATCH] pkg/client: fix method to find a server in the config http://camlistore.org/issue/325 Change-Id: I4aa6d5103848b73ed169ab7d7de3a60a33723b79 --- pkg/client/client.go | 5 ++- pkg/client/config.go | 36 +++++++++--------- pkg/client/config_test.go | 63 ++++++++++++++++++++++++++++++++ pkg/types/clientconfig/config.go | 19 ++++++++++ 4 files changed, 102 insertions(+), 21 deletions(-) create mode 100644 pkg/client/config_test.go diff --git a/pkg/client/client.go b/pkg/client/client.go index 60371dd72..e8cb264b9 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -126,7 +126,7 @@ const maxParallelHTTP = 5 // The provided server is either "host:port" (assumed http, not https) or a URL prefix, with or without a path, or a server alias from the client configuration file. A server alias should not be confused with a hostname, therefore it cannot contain any colon or period. // Errors are not returned until subsequent operations. func New(server string) *Client { - if !isHostname(server) { + if !isURLOrHostPort(server) { configOnce.Do(parseConfig) serverConf, ok := config.Servers[server] if !ok { @@ -560,10 +560,11 @@ func (c *Client) blobPrefix() (string, error) { return pfx, nil } +// discoRoot returns the user defined server for this client. It prepends "https://" if no scheme was specified. func (c *Client) discoRoot() string { s := c.server if !strings.HasPrefix(s, "http") { - s = "http://" + s + s = "https://" + s } return s } diff --git a/pkg/client/config.go b/pkg/client/config.go index 6f80acd56..5a6164fb1 100644 --- a/pkg/client/config.go +++ b/pkg/client/config.go @@ -99,7 +99,7 @@ func parseConfig() { for alias, vei := range servers { // An alias should never be confused with a host name, // so we forbid anything looking like one. - if isHostname(alias) { + if isURLOrHostPort(alias) { log.Fatalf("Server alias %q looks like a hostname; \".\" or \";\" are not allowed.", alias) } serverMap, ok := vei.(map[string]interface{}) @@ -125,8 +125,8 @@ func parseConfig() { } } -// isHostname return true if s looks like a host name, i.e it has at least a scheme or contains a period or a colon. -func isHostname(s string) bool { +// isURLOrHostPort returns true if s looks like a URL, or a hostname, i.e it starts with a scheme and/or it contains a period or a colon. +func isURLOrHostPort(s string) bool { return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") || strings.Contains(s, ".") || strings.Contains(s, ":") @@ -213,8 +213,10 @@ func serverKeyId() string { return keyId } +// cleanServer returns the canonical URL of the provided server, which must be a URL, IP, host (with dot), or host/ip:port. +// The returned canonical URL will have trailing slashes removed and be prepended with "https://" if no scheme is provided. func cleanServer(server string) string { - if !isHostname(server) { + if !isURLOrHostPort(server) { log.Fatalf("server %q does not look like a server address and could be confused with a server alias. It should look like [http[s]://]foo[.com][:port] with at least one of the optional parts.", server) } // Remove trailing slash if provided. @@ -235,7 +237,7 @@ func serverOrDie() string { return cleanServer(s) } if flagServer != "" { - if !isHostname(flagServer) { + if !isURLOrHostPort(flagServer) { configOnce.Do(parseConfig) serverConf, ok := config.Servers[flagServer] if ok { @@ -276,8 +278,7 @@ func (c *Client) useTLS() bool { return strings.HasPrefix(c.server, "https://") } -// SetupAuth sets the client's authMode from the client -// configuration file or from the environment. +// SetupAuth sets the client's authMode from the client configuration file or from the environment. func (c *Client) SetupAuth() error { // env var always takes precendence authMode, err := auth.FromEnv() @@ -299,17 +300,14 @@ func (c *Client) SetupAuth() error { return err } +// serverAuth returns the auth scheme for server from the config, or the empty string if the server was not found in the config. func serverAuth(server string) string { configOnce.Do(parseConfig) - if config == nil { + alias := config.Alias(server) + if alias == "" { return "" } - for _, serverConf := range config.Servers { - if serverConf.Server == server { - return serverConf.Auth - } - } - return "" + return config.Servers[alias].Auth } // SetupAuthFromString configures the clients authentication mode from @@ -420,14 +418,14 @@ func (c *Client) initTrustedCerts() { } } +// serverTrustedCerts returns the trusted certs for server from the config. func serverTrustedCerts(server string) []string { configOnce.Do(parseConfig) - for _, serverConf := range config.Servers { - if serverConf.Server == server { - return serverConf.TrustedCerts - } + alias := config.Alias(server) + if alias == "" { + return nil } - return nil + return config.Servers[alias].TrustedCerts } func (c *Client) getTrustedCerts() []string { diff --git a/pkg/client/config_test.go b/pkg/client/config_test.go new file mode 100644 index 000000000..9b7f695da --- /dev/null +++ b/pkg/client/config_test.go @@ -0,0 +1,63 @@ +/* +Copyright 2014 The Camlistore Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "testing" + + "camlistore.org/pkg/types/clientconfig" +) + +func TestAliasFromConfig(t *testing.T) { + servers := map[string]*clientconfig.Server{ + "foo": { + Server: "http://foo.com", + }, + "foobar": { + Server: "http://foo.com/bar", + }, + "foobaz": { + Server: "http://foo.com/baz", + }, + "foobarlong": { + Server: "http://foo.com/bar/long", + }, + } + config := &clientconfig.Config{ + Servers: servers, + } + urlWant := map[string]string{ + "http://foo.com/bs": "foo", + "http://foo.com": "foo", + "http://foo.com/bar/index-mysql": "foobar", + "http://foo.com/bar": "foobar", + "http://foo.com/baz/index-kv": "foobaz", + "http://foo.com/baz": "foobaz", + "http://foo.com/bar/long/disco": "foobarlong", + "http://foo.com/bar/long": "foobarlong", + } + for url, want := range urlWant { + alias := config.Alias(url) + if alias == "" { + t.Errorf("url %v matched nothing, wanted %v", url, want) + continue + } + if alias != want { + t.Errorf("url %v matched %v, wanted %v", url, alias, want) + } + } +} diff --git a/pkg/types/clientconfig/config.go b/pkg/types/clientconfig/config.go index 0fd043f6f..6a9517fd6 100644 --- a/pkg/types/clientconfig/config.go +++ b/pkg/types/clientconfig/config.go @@ -17,6 +17,10 @@ limitations under the License. // Package clientconfig provides types related to the client configuration file. package clientconfig +import ( + "strings" +) + // Config holds the values from the JSON client config file. type Config struct { Servers map[string]*Server `json:"servers"` // maps server alias to server config. @@ -32,3 +36,18 @@ type Server struct { IsDefault bool `json:"default,omitempty"` // whether this server is the default one. TrustedCerts []string `json:"trustedCerts,omitempty"` // list of trusted certificates fingerprints. } + +// Alias returns the alias of the server from conf that matches server, or the empty string if no match. A match means the server from the config is a prefix of the input server. The longest match prevails. +func (conf *Config) Alias(server string) string { + longestMatch := "" + serverAlias := "" + for alias, serverConf := range conf.Servers { + if strings.HasPrefix(server, serverConf.Server) { + if len(serverConf.Server) > len(longestMatch) { + longestMatch = serverConf.Server + serverAlias = alias + } + } + } + return serverAlias +}