Skip to content

Commit f4b0a7c

Browse files
guscarreonGus CarreonGus CarreonGus Carreon
authored
Validate External Cache Host (prebid#1422)
* first draft * Little tweaks * Scott's review part 1 * Scott's review corrections part 2 * Scotts refactor * correction in config_test.go * Correction and refactor * Multiple return statements * Test case refactor Co-authored-by: Gus Carreon <[email protected]> Co-authored-by: Gus Carreon <[email protected]> Co-authored-by: Gus Carreon <[email protected]>
1 parent 21b41ff commit f4b0a7c

File tree

2 files changed

+123
-2
lines changed

2 files changed

+123
-2
lines changed

config/config.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package config
22

33
import (
44
"bytes"
5+
"errors"
56
"fmt"
67
"net/url"
78
"reflect"
@@ -111,6 +112,7 @@ func (cfg *Configuration) validate() configErrors {
111112
errs = cfg.CurrencyConverter.validate(errs)
112113
errs = validateAdapters(cfg.Adapters, errs)
113114
errs = cfg.Debug.validate(errs)
115+
errs = cfg.ExtCacheURL.validate(errs)
114116
return errs
115117
}
116118

@@ -128,6 +130,40 @@ func (cfg *AuctionTimeouts) validate(errs configErrors) configErrors {
128130
return errs
129131
}
130132

133+
func (data *ExternalCache) validate(errs configErrors) configErrors {
134+
if data.Host == "" && data.Path == "" {
135+
// Both host and path can be blank. No further validation needed
136+
return errs
137+
}
138+
139+
// Either host or path or both not empty, validate.
140+
if data.Host == "" && data.Path != "" || data.Host != "" && data.Path == "" {
141+
return append(errs, errors.New("External cache Host and Path must both be specified"))
142+
}
143+
if strings.HasSuffix(data.Host, "/") {
144+
return append(errs, errors.New(fmt.Sprintf("External cache Host '%s' must not end with a path separator", data.Host)))
145+
}
146+
if strings.ContainsAny(data.Host, "://") {
147+
return append(errs, errors.New(fmt.Sprintf("External cache Host must not specify a protocol. '%s'", data.Host)))
148+
}
149+
if !strings.HasPrefix(data.Path, "/") {
150+
return append(errs, errors.New(fmt.Sprintf("External cache Path '%s' must begin with a path separator", data.Path)))
151+
}
152+
153+
urlObj, err := url.Parse("https://" + data.Host + data.Path)
154+
if err != nil {
155+
return append(errs, errors.New(fmt.Sprintf("External cache Path validation error: %s ", err.Error())))
156+
}
157+
if urlObj.Host != data.Host {
158+
return append(errs, errors.New(fmt.Sprintf("External cache Host '%s' is invalid", data.Host)))
159+
}
160+
if urlObj.Path != data.Path {
161+
return append(errs, errors.New("External cache Path is invalid"))
162+
}
163+
164+
return errs
165+
}
166+
131167
// LimitAuctionTimeout returns the min of requested or cfg.MaxAuctionTimeout.
132168
// Both values treat "0" as "infinite".
133169
func (cfg *AuctionTimeouts) LimitAuctionTimeout(requested time.Duration) time.Duration {

config/config_test.go

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,91 @@ import (
1414
"github.com/stretchr/testify/assert"
1515
)
1616

17+
func TestExternalCacheURLValidate(t *testing.T) {
18+
testCases := []struct {
19+
desc string
20+
data ExternalCache
21+
expErrors int
22+
}{
23+
{
24+
desc: "With http://",
25+
data: ExternalCache{Host: "http://www.google.com", Path: "/path/v1"},
26+
expErrors: 1,
27+
},
28+
{
29+
desc: "Without http://",
30+
data: ExternalCache{Host: "www.google.com", Path: "/path/v1"},
31+
expErrors: 0,
32+
},
33+
{
34+
desc: "No scheme but '//' prefix",
35+
data: ExternalCache{Host: "//www.google.com", Path: "/path/v1"},
36+
expErrors: 1,
37+
},
38+
{
39+
desc: "// appears twice",
40+
data: ExternalCache{Host: "//www.google.com//", Path: "path/v1"},
41+
expErrors: 1,
42+
},
43+
{
44+
desc: "Host has an only // value",
45+
data: ExternalCache{Host: "//", Path: "path/v1"},
46+
expErrors: 1,
47+
},
48+
{
49+
desc: "only scheme host, valid path",
50+
data: ExternalCache{Host: "http://", Path: "/path/v1"},
51+
expErrors: 1,
52+
},
53+
{
54+
desc: "No host, path only",
55+
data: ExternalCache{Host: "", Path: "path/v1"},
56+
expErrors: 1,
57+
},
58+
{
59+
desc: "No host, nor path",
60+
data: ExternalCache{Host: "", Path: ""},
61+
expErrors: 0,
62+
},
63+
{
64+
desc: "Invalid http at the end",
65+
data: ExternalCache{Host: "www.google.com", Path: "http://"},
66+
expErrors: 1,
67+
},
68+
{
69+
desc: "Host has an unknown scheme",
70+
data: ExternalCache{Host: "unknownscheme://host", Path: "/path/v1"},
71+
expErrors: 1,
72+
},
73+
{
74+
desc: "Wrong colon side in scheme",
75+
data: ExternalCache{Host: "http//:www.appnexus.com", Path: "/path/v1"},
76+
expErrors: 1,
77+
},
78+
{
79+
desc: "Missing '/' in scheme",
80+
data: ExternalCache{Host: "http:/www.appnexus.com", Path: "/path/v1"},
81+
expErrors: 1,
82+
},
83+
{
84+
desc: "host with scheme, no path",
85+
data: ExternalCache{Host: "http://www.appnexus.com", Path: ""},
86+
expErrors: 1,
87+
},
88+
{
89+
desc: "scheme, no host nor path",
90+
data: ExternalCache{Host: "http://", Path: ""},
91+
expErrors: 1,
92+
},
93+
}
94+
for _, test := range testCases {
95+
var errs configErrors
96+
errs = test.data.validate(errs)
97+
98+
assert.Equal(t, test.expErrors, len(errs), "Test case threw unexpected number of errors. Desc: %s errMsg = %v \n", test.desc, errs)
99+
}
100+
}
101+
17102
func TestDefaults(t *testing.T) {
18103
v := viper.New()
19104
SetupViper(v, "")
@@ -66,7 +151,7 @@ cache:
66151
query: uuid=%PBS_CACHE_UUID%
67152
external_cache:
68153
host: www.externalprebidcache.net
69-
path: endpoints/cache
154+
path: /endpoints/cache
70155
http_client:
71156
max_connections_per_host: 10
72157
max_idle_connections: 500
@@ -223,7 +308,7 @@ func TestFullConfig(t *testing.T) {
223308
cmpStrings(t, "cache.host", cfg.CacheURL.Host, "prebidcache.net")
224309
cmpStrings(t, "cache.query", cfg.CacheURL.Query, "uuid=%PBS_CACHE_UUID%")
225310
cmpStrings(t, "external_cache.host", cfg.ExtCacheURL.Host, "www.externalprebidcache.net")
226-
cmpStrings(t, "external_cache.path", cfg.ExtCacheURL.Path, "endpoints/cache")
311+
cmpStrings(t, "external_cache.path", cfg.ExtCacheURL.Path, "/endpoints/cache")
227312
cmpInts(t, "http_client.max_connections_per_host", cfg.Client.MaxConnsPerHost, 10)
228313
cmpInts(t, "http_client.max_idle_connections", cfg.Client.MaxIdleConns, 500)
229314
cmpInts(t, "http_client.max_idle_connections_per_host", cfg.Client.MaxIdleConnsPerHost, 20)

0 commit comments

Comments
 (0)