From 25ad776b6fad5158bf741f2bc3a79925c7b07148 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 12 Mar 2026 15:03:16 +0100 Subject: [PATCH] Propagate doppelganger to config --- internal/config/config.go | 9 ++- internal/config/parse.go | 5 ++ internal/config/type_https_url.go | 53 +++++++++++++ internal/config/type_https_url_test.go | 100 +++++++++++++++++++++++++ 4 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 internal/config/type_https_url.go create mode 100644 internal/config/type_https_url_test.go diff --git a/internal/config/config.go b/internal/config/config.go index e3d8bd2..91c93b0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -47,8 +47,13 @@ type Config struct { MaxSize TypeBytes `json:"maxSize"` ErrorRate TypeErrorRate `json:"errorRate"` } `json:"antiReplay"` - Blocklist ListConfig `json:"blocklist"` - Allowlist ListConfig `json:"allowlist"` + Blocklist ListConfig `json:"blocklist"` + Allowlist ListConfig `json:"allowlist"` + Doppelganger struct { + URLs []TypeHttpsURL `json:"urls"` + Repeats TypeConcurrency `json:"repeats_per_raid"` + UpdateEach TypeDuration `json:"raid_each"` + } `json:"doppelganger"` } `json:"defense"` Network struct { Timeout struct { diff --git a/internal/config/parse.go b/internal/config/parse.go index 1186769..06183c7 100644 --- a/internal/config/parse.go +++ b/internal/config/parse.go @@ -44,6 +44,11 @@ type tomlConfig struct { URLs []string `toml:"urls" json:"urls,omitempty"` UpdateEach string `toml:"update-each" json:"updateEach,omitempty"` } `toml:"allowlist" json:"allowlist,omitempty"` + Doppelganger struct { + URLs []string `toml:"urls" json:"urls,omitempty"` + Repeats uint `toml:"repeats-per-raid" json:"repeats_per_raid,omitempty"` + UpdateEach string `toml:"raid-each" json:"raid_each,omitempty"` + } `toml:"doppelganger" json:"doppelganger,omitempty"` } `toml:"defense" json:"defense,omitempty"` Network struct { Timeout struct { diff --git a/internal/config/type_https_url.go b/internal/config/type_https_url.go new file mode 100644 index 0000000..6509064 --- /dev/null +++ b/internal/config/type_https_url.go @@ -0,0 +1,53 @@ +package config + +import ( + "fmt" + "net/url" +) + +type TypeHttpsURL struct { + Value *url.URL +} + +func (t *TypeHttpsURL) Set(value string) error { + parsedURL, err := url.Parse(value) + if err != nil { + return fmt.Errorf("value is not correct URL (%s): %w", value, err) + } + + if parsedURL.Host == "" { + return fmt.Errorf("url has to have a schema: %s", value) + } + + if parsedURL.Scheme != "https" { + return fmt.Errorf("unsupported schema: %s", parsedURL.Scheme) + } + + t.Value = parsedURL + + return nil +} + +func (t *TypeHttpsURL) Get(defaultValue *url.URL) *url.URL { + if t.Value == nil { + return defaultValue + } + + return t.Value +} + +func (t *TypeHttpsURL) UnmarshalText(data []byte) error { + return t.Set(string(data)) +} + +func (t TypeHttpsURL) MarshalText() ([]byte, error) { + return []byte(t.String()), nil +} + +func (t TypeHttpsURL) String() string { + if t.Value == nil { + return "" + } + + return t.Value.String() +} diff --git a/internal/config/type_https_url_test.go b/internal/config/type_https_url_test.go new file mode 100644 index 0000000..6c7db83 --- /dev/null +++ b/internal/config/type_https_url_test.go @@ -0,0 +1,100 @@ +package config_test + +import ( + "encoding/json" + "net/url" + "testing" + + "github.com/9seconds/mtg/v2/internal/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type typeHttpsURLTestStruct struct { + Value config.TypeHttpsURL `json:"value"` +} + +type HttpsURLTestSuite struct { + suite.Suite +} + +func (suite *HttpsURLTestSuite) TestUnmarshalFail() { + testData := []string{ + "", + "https://", + "://lala", + "/path", + "http://example.com", + "socks5://example.com", + } + + for _, v := range testData { + data, err := json.Marshal(map[string]string{ + "value": v, + }) + suite.NoError(err) + + suite.T().Run(v, func(t *testing.T) { + assert.Error(t, json.Unmarshal(data, &typeHttpsURLTestStruct{})) + }) + } +} + +func (suite *HttpsURLTestSuite) TestUnmarshalOk() { + testData := map[string]string{ + "https://example.com": "https://example.com", + "https://example.com:8443": "https://example.com:8443", + "https://example.com/path?q=1": "https://example.com/path?q=1", + "https://user:pass@example.com": "https://user:pass@example.com", + } + + for k, v := range testData { + value := v + + data, err := json.Marshal(map[string]string{ + "value": k, + }) + suite.NoError(err) + + suite.T().Run(k, func(t *testing.T) { + testStruct := &typeHttpsURLTestStruct{} + assert.NoError(t, json.Unmarshal(data, testStruct)) + + parsed, _ := url.Parse(value) + + assert.Equal(t, parsed.Scheme, testStruct.Value.Get(nil).Scheme) + assert.Equal(t, parsed.Host, testStruct.Value.Get(nil).Host) + assert.Equal(t, parsed.RawQuery, testStruct.Value.Get(nil).RawQuery) + assert.Equal(t, parsed.Path, testStruct.Value.Get(nil).Path) + }) + } +} + +func (suite *HttpsURLTestSuite) TestMarshalOk() { + parsed, _ := url.Parse("https://example.com/path?q=1") + testStruct := &typeHttpsURLTestStruct{ + Value: config.TypeHttpsURL{ + Value: parsed, + }, + } + + encodedJSON, err := json.Marshal(testStruct) + suite.NoError(err) + suite.JSONEq(`{"value": "https://example.com/path?q=1"}`, + string(encodedJSON)) +} + +func (suite *HttpsURLTestSuite) TestGet() { + emptyURL := &url.URL{} + + value := config.TypeHttpsURL{} + suite.Equal(emptyURL, value.Get(emptyURL)) + + value.Value = &url.URL{} + suite.Equal(value.Value, value.Get(emptyURL)) +} + +func TestTypeHttpsURL(t *testing.T) { + t.Parallel() + suite.Run(t, &HttpsURLTestSuite{}) +}