From 470aea9b8c0717d56d61924c7731b0235ef615e8 Mon Sep 17 00:00:00 2001 From: sundowndev Date: Thu, 3 Aug 2023 16:36:36 +0400 Subject: [PATCH 1/4] feat: add custom format support --- cmd/scan.go | 4 ++- lib/number/number.go | 46 +++++++++++++++++++++++++- lib/number/number_test.go | 53 ++++++++++++++++++++++++++++++ lib/remote/googlecse_scanner.go | 9 +++++ lib/remote/googlesearch_scanner.go | 18 ++++++++++ web/v2/api/handlers/numbers.go | 23 +++++++------ web/v2/api/handlers/scanners.go | 8 +++-- 7 files changed, 146 insertions(+), 15 deletions(-) diff --git a/cmd/scan.go b/cmd/scan.go index b2bc32474..feb3fea90 100644 --- a/cmd/scan.go +++ b/cmd/scan.go @@ -18,6 +18,7 @@ type ScanCmdOptions struct { DisabledScanners []string PluginPaths []string EnvFiles []string + Formats []string } func init() { @@ -31,6 +32,7 @@ func init() { cmd.PersistentFlags().StringArrayVarP(&opts.DisabledScanners, "disable", "D", []string{}, "Scanner to skip for this scan") cmd.PersistentFlags().StringArrayVar(&opts.PluginPaths, "plugin", []string{}, "Extra scanner plugin to use for the scan") cmd.PersistentFlags().StringSliceVar(&opts.EnvFiles, "env-file", []string{}, "Env files to parse environment variables from (looks for .env by default)") + cmd.PersistentFlags().StringSliceVar(&opts.Formats, "format", []string{}, "Additional format for the given phone number(s)") // scanCmd.PersistentFlags().StringVarP(&input, "input", "i", "", "Text file containing a list of phone numbers to scan (one per line)") // scanCmd.PersistentFlags().StringVarP(&output, "output", "o", "", "Output to save scan results") } @@ -62,7 +64,7 @@ func runScan(opts *ScanCmdOptions) { exitWithError(errors.New("given phone number is not valid")) } - num, err := number.NewNumber(opts.Number) + num, err := number.NewNumber(opts.Number, opts.Formats...) if err != nil { exitWithError(err) } diff --git a/lib/number/number.go b/lib/number/number.go index e69520ccd..526cac521 100644 --- a/lib/number/number.go +++ b/lib/number/number.go @@ -1,9 +1,15 @@ package number import ( + "bytes" + "fmt" "github.com/nyaruka/phonenumbers" + "strings" + "text/template" ) +const templateDigitPlaceholder = "x" + // Number is a phone number type Number struct { Valid bool @@ -14,9 +20,15 @@ type Number struct { CountryCode int32 Country string Carrier string + CustomFormats []string +} + +type FormatTemplateData struct { + CountryCode string + Country string } -func NewNumber(number string) (res *Number, err error) { +func NewNumber(number string, formats ...string) (res *Number, err error) { n := "+" + FormatNumber(number) country := ParseCountryCode(n) @@ -34,7 +46,39 @@ func NewNumber(number string) (res *Number, err error) { CountryCode: num.GetCountryCode(), Country: country, Carrier: num.GetPreferredDomesticCarrierCode(), + CustomFormats: []string{}, + } + + for _, format := range formats { + res.CustomFormats = append(res.CustomFormats, convertFormatTemplate(res, format)) } return res, nil } + +func convertFormatTemplate(n *Number, f string) string { + countryCodeStr := fmt.Sprintf("%d", n.CountryCode) + + var out []string + splitNumber := strings.Split(strings.Replace(n.International, countryCodeStr, "", 1), "") + splitFormat := strings.Split(f, "") + for _, j := range splitFormat { + if strings.ToLower(j) == templateDigitPlaceholder && len(splitNumber) > 0 { + j = splitNumber[0] + splitNumber = splitNumber[1:] + } + out = append(out, j) + } + + t := template.Must(template.New("custom-format").Parse(strings.Join(out, ""))) + + var tpl bytes.Buffer + err := t.Execute(&tpl, FormatTemplateData{ + CountryCode: countryCodeStr, + Country: n.Country, + }) + if err != nil { + return strings.Join(out, "") + } + return tpl.String() +} diff --git a/lib/number/number_test.go b/lib/number/number_test.go index 5555eb728..b84f1b68b 100644 --- a/lib/number/number_test.go +++ b/lib/number/number_test.go @@ -25,6 +25,7 @@ func TestNumber(t *testing.T) { CountryCode: 33, Country: "FR", Carrier: "", + CustomFormats: []string{}, }, }, { @@ -39,6 +40,7 @@ func TestNumber(t *testing.T) { CountryCode: 1, Country: "", Carrier: "", + CustomFormats: []string{}, }, }, @@ -58,3 +60,54 @@ func TestNumber(t *testing.T) { }) } } + +func TestFormatTemplate(t *testing.T) { + cases := []struct { + name string + number string + format string + expected []string + wantErr error + }{ + { + name: "should succeed to format with template", + number: "+15552221212", + format: "xxx-xxx-xxxx", + expected: []string{"555-222-1212"}, + }, + { + name: "should succeed to format with template", + number: "+33678342211", + format: "0x-Xx-xx-xX-xx", + expected: []string{"06-78-34-22-11"}, + }, + { + name: "should succeed to format with template", + number: "+911401871759", + format: "+{{.CountryCode}} xxxx-xxxxxx", + expected: []string{"+91 1401-871759"}, + }, + { + name: "should fail to format with template", + number: "+911401871759", + format: "+{{.DummyVar}} xxxx-xxxxxx", + expected: []string{"+{{.DummyVar}} 1401-871759"}, + }, + { + name: "should fail to format with template", + number: "+333333", + format: "+{{.DummyVar}} xxxx-xxxxxx", + expected: []string{"+{{.DummyVar}} 3333-xxxxxx"}, + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + n, err := NewNumber(tt.number, tt.format) + if err != nil { + t.Error(err) + } + assert.Equal(t, tt.expected, n.CustomFormats) + }) + } +} diff --git a/lib/remote/googlecse_scanner.go b/lib/remote/googlecse_scanner.go index 58eecc29a..d0d32de32 100644 --- a/lib/remote/googlecse_scanner.go +++ b/lib/remote/googlecse_scanner.go @@ -206,6 +206,15 @@ func (s *googleCSEScanner) generateDorkQueries(number number.Number) (results [] InText(number.Local), } + // Add custom formats to dork queries + if len(number.CustomFormats) > 0 { + for _, d := range dorks { + for _, f := range number.CustomFormats { + d.Or().InText(f) + } + } + } + for _, dork := range dorks { results = append(results, &GoogleSearchDork{ Number: number.E164, diff --git a/lib/remote/googlesearch_scanner.go b/lib/remote/googlesearch_scanner.go index 42380b186..805428225 100644 --- a/lib/remote/googlesearch_scanner.go +++ b/lib/remote/googlesearch_scanner.go @@ -268,6 +268,15 @@ func getSocialMediaDorks(number number.Number) (results []*GoogleSearchDork) { InText(number.RawLocal), } + // Add custom formats to dork queries + if len(number.CustomFormats) > 0 { + for _, d := range dorks { + for _, f := range number.CustomFormats { + d.Or().InText(f) + } + } + } + for _, dork := range dorks { results = append(results, &GoogleSearchDork{ Number: number.E164, @@ -377,6 +386,15 @@ func getGeneralDorks(number number.Number) (results []*GoogleSearchDork) { InText(number.RawLocal), } + // Add custom formats to dork queries + if len(number.CustomFormats) > 0 { + for _, d := range dorks { + for _, f := range number.CustomFormats { + d.Or().InText(f) + } + } + } + for _, dork := range dorks { results = append(results, &GoogleSearchDork{ Number: number.E164, diff --git a/web/v2/api/handlers/numbers.go b/web/v2/api/handlers/numbers.go index 911557634..9199e0224 100644 --- a/web/v2/api/handlers/numbers.go +++ b/web/v2/api/handlers/numbers.go @@ -8,18 +8,20 @@ import ( ) type AddNumberInput struct { - Number string `json:"number" binding:"number,required"` + Number string `json:"number" binding:"number,required"` + Formats []string `json:"formats" binding:"formats"` } type AddNumberResponse struct { - Valid bool `json:"valid"` - RawLocal string `json:"rawLocal"` - Local string `json:"local"` - E164 string `json:"e164"` - International string `json:"international"` - CountryCode int32 `json:"countryCode"` - Country string `json:"country"` - Carrier string `json:"carrier"` + Valid bool `json:"valid"` + RawLocal string `json:"rawLocal"` + Local string `json:"local"` + E164 string `json:"e164"` + International string `json:"international"` + CountryCode int32 `json:"countryCode"` + Country string `json:"country"` + Carrier string `json:"carrier"` + CustomFormats []string `json:"custom_formats"` } // AddNumber is an HTTP handler @@ -43,7 +45,7 @@ func AddNumber(ctx *gin.Context) *api.Response { } } - num, err := number.NewNumber(input.Number) + num, err := number.NewNumber(input.Number, input.Formats...) if err != nil { return &api.Response{ Code: http.StatusBadRequest, @@ -64,6 +66,7 @@ func AddNumber(ctx *gin.Context) *api.Response { CountryCode: num.CountryCode, Country: num.Country, Carrier: num.Carrier, + CustomFormats: num.CustomFormats, }, } } diff --git a/web/v2/api/handlers/scanners.go b/web/v2/api/handlers/scanners.go index 5294a2462..916f4658a 100644 --- a/web/v2/api/handlers/scanners.go +++ b/web/v2/api/handlers/scanners.go @@ -43,7 +43,8 @@ func GetAllScanners(*gin.Context) *api.Response { } type DryRunScannerInput struct { - Number string `json:"number" binding:"number,required"` + Number string `json:"number" binding:"number,required"` + Formats []string `json:"formats" binding:"formats"` } type DryRunScannerResponse struct { @@ -114,7 +115,8 @@ func DryRunScanner(ctx *gin.Context) *api.Response { } type RunScannerInput struct { - Number string `json:"number" binding:"number,required"` + Number string `json:"number" binding:"number,required"` + Formats []string `json:"formats" binding:"formats"` } type RunScannerResponse struct { @@ -153,7 +155,7 @@ func RunScanner(ctx *gin.Context) *api.Response { } } - num, err := number.NewNumber(input.Number) + num, err := number.NewNumber(input.Number, input.Formats...) if err != nil { return &api.Response{ Code: http.StatusBadRequest, From e82460a36212b16ffcadfa0bca47a2e92ebe1fd6 Mon Sep 17 00:00:00 2001 From: sundowndev Date: Thu, 3 Aug 2023 17:21:54 +0400 Subject: [PATCH 2/4] docs: custom formats --- cmd/scan.go | 2 +- docs/getting-started/usage.md | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/cmd/scan.go b/cmd/scan.go index feb3fea90..f18b8dcfa 100644 --- a/cmd/scan.go +++ b/cmd/scan.go @@ -32,7 +32,7 @@ func init() { cmd.PersistentFlags().StringArrayVarP(&opts.DisabledScanners, "disable", "D", []string{}, "Scanner to skip for this scan") cmd.PersistentFlags().StringArrayVar(&opts.PluginPaths, "plugin", []string{}, "Extra scanner plugin to use for the scan") cmd.PersistentFlags().StringSliceVar(&opts.EnvFiles, "env-file", []string{}, "Env files to parse environment variables from (looks for .env by default)") - cmd.PersistentFlags().StringSliceVar(&opts.Formats, "format", []string{}, "Additional format for the given phone number(s)") + cmd.PersistentFlags().StringSliceVarP(&opts.Formats, "format", "f", []string{}, "Additional format for the given phone number(s)") // scanCmd.PersistentFlags().StringVarP(&input, "input", "i", "", "Text file containing a list of phone numbers to scan (one per line)") // scanCmd.PersistentFlags().StringVarP(&output, "output", "o", "", "Output to save scan results") } diff --git a/docs/getting-started/usage.md b/docs/getting-started/usage.md index 0567219c6..fa100179a 100644 --- a/docs/getting-started/usage.md +++ b/docs/getting-started/usage.md @@ -16,6 +16,30 @@ phoneinfoga scan -n "+1 555-444-3333" !!! note "Note that the country code is essential. You don't know which country code to use ? [Find it here](https://www.countrycode.org/)" +## Custom formats + +There's several ways to write down phone numbers and the way they're indexed by search engine makes it difficult to find them without applying custom formatting according to the country they come from. So you can specify a custom template that will be used by scanners and enhance the results. + +The letter `x` will be replaced by each digit of the local phone number (excluding 0). Some variables can be used in the template such as : + +| Variable | Description | Example | +|:------------|:------------------------------------------|:-------------| +| CountryCode | Country code of the phone number | 34 | +| Country | Country of the phone number as letters | US | + +### Examples + +```bash +# Add custom format +1 555.444.3333 +phoneinfoga scan -n "+1 555-444-3333" -f "+{{.CountryCode}} xxx.xxx.xxxx" + +# You can specify multiple formats at once +phoneinfoga scan -n "+1 555-444-3333" -f "xxx.xxx.xxxx" -f "xxx-xxx-xxxx" + +# Add custom format 06.78.23.22.11 +phoneinfoga scan -n "+34678232211" -f "0x.xx.xx.xx.xx" +``` +