| 1 | package console |
| 2 | |
| 3 | import ( |
| 4 | "net" |
| 5 | |
| 6 | "github.com/carlosframework/platform/internal/geo" |
| 7 | ) |
| 8 | |
| 9 | // guessInput carries a request's location hints for ONE call. Neither |
| 10 | // field is ever stored, logged, audited or echoed (spec §6.5). |
| 11 | type guessInput struct { |
| 12 | TZ string |
| 13 | IP net.IP |
| 14 | } |
| 15 | |
| 16 | // guessResult is a guess in the record vocabulary and its source word. |
| 17 | type guessResult struct{ Code, Source string } |
| 18 | |
| 19 | // guessRegion is spec §6.4: |
| 20 | // |
| 21 | // 1. filter candidates to eligible ones the policy allows; |
| 22 | // 2. nearest among those with a point, from the timezone, else (only |
| 23 | // when the timezone is unmappable) the IP; |
| 24 | // 3. else the home, ONLY if the home is itself a filtered candidate; |
| 25 | // 4. else no guess. |
| 26 | func guessRegion(cands []regionCandidate, allows func(code string) bool, in guessInput, loc geo.Locator) (guessResult, bool) { |
| 27 | var pick []geo.Candidate |
| 28 | homeIn := false |
| 29 | for _, c := range cands { |
| 30 | if !c.Eligible || !allows(c.Code) { |
| 31 | continue |
| 32 | } |
| 33 | pick = append(pick, geo.Candidate{Code: c.Code, Home: c.Home, AWSRegion: c.AWSRegion}) |
| 34 | homeIn = homeIn || c.Home |
| 35 | } |
| 36 | if len(pick) == 0 { |
| 37 | return guessResult{}, false |
| 38 | } |
| 39 | if p, ok := geo.ZonePoint(in.TZ); ok { |
| 40 | if code, ok := geo.Nearest(p, pick); ok { |
| 41 | return guessResult{code, "timezone"}, true |
| 42 | } |
| 43 | } else if loc != nil && in.IP != nil { |
| 44 | if p, ok := loc.Locate(in.IP); ok { |
| 45 | if code, ok := geo.Nearest(p, pick); ok { |
| 46 | return guessResult{code, "ip"}, true |
| 47 | } |
| 48 | } |
| 49 | } |
| 50 | if homeIn { |
| 51 | return guessResult{"", "default"}, true |
| 52 | } |
| 53 | return guessResult{}, false |
| 54 | } |
| 55 | |