diff --git a/infra/conf/geodata.go b/infra/conf/geodata.go index f79a37e23..42b0961bf 100644 --- a/infra/conf/geodata.go +++ b/infra/conf/geodata.go @@ -4,6 +4,7 @@ import ( "net/url" "path/filepath" "strings" + "time" "github.com/robfig/cron/v3" "github.com/xtls/xray-core/app/geodata" @@ -44,6 +45,17 @@ func validateHTTPS(s string) error { return nil } +func validateGeodataCron(spec string) error { + schedule, err := cron.ParseStandard(spec) + if err != nil { + return err + } + if interval, ok := schedule.(cron.ConstantDelaySchedule); ok && interval.Delay < time.Minute { + return errors.New("interval must be at least 1 minute") + } + return nil +} + type GeodataConfig struct { Cron *string `json:"cron"` Outbound string `json:"outbound"` @@ -54,7 +66,7 @@ func (c *GeodataConfig) Build() (proto.Message, error) { config := &geodata.Config{} if c.Cron != nil { - if _, err := cron.ParseStandard(*c.Cron); err != nil { + if err := validateGeodataCron(*c.Cron); err != nil { return nil, errors.New("invalid geodata cron").Base(err) } config.Cron = *c.Cron diff --git a/infra/conf/geodata_test.go b/infra/conf/geodata_test.go index 4944d850a..97fe03087 100644 --- a/infra/conf/geodata_test.go +++ b/infra/conf/geodata_test.go @@ -106,3 +106,42 @@ func TestGeodataAssetConfigFileExtension(t *testing.T) { } } } + +func TestGeodataConfigCronFrequency(t *testing.T) { + for _, tc := range []struct { + cron string + wantError string + }{ + {"0 * * * *", ""}, + {"15 */2 * * *", ""}, + {"0 4 * * *", ""}, + {"*/60 * * * *", ""}, + {"0,0 * * * *", ""}, + {"CRON_TZ=Asia/Singapore 30 * * * *", ""}, + {"@hourly", ""}, + {"@daily", ""}, + {"@every 1m", ""}, + {"@every 1h", ""}, + {"@every 1h30m", ""}, + {"* * * * *", ""}, + {"*/30 * * * *", ""}, + {"0,59 4 * * *", ""}, + {"0-1 4 1 1 *", ""}, + {"@every 59m59s", ""}, + {"@every 1s", "interval must be at least 1 minute"}, + {"@every 59s", "interval must be at least 1 minute"}, + {"@every 59.999s", "interval must be at least 1 minute"}, + {"invalid", "invalid geodata cron"}, + } { + t.Run(tc.cron, func(t *testing.T) { + _, err := (&GeodataConfig{Cron: &tc.cron}).Build() + if tc.wantError == "" { + if err != nil { + t.Fatal(err) + } + } else if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("Build() error = %v; want %q", err, tc.wantError) + } + }) + } +}