Skip to content

Commit 692e30a

Browse files
committed
add thc
1 parent 7842ebf commit 692e30a

File tree

3 files changed

+131
-0
lines changed

3 files changed

+131
-0
lines changed

pkg/passive/sources.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import (
4848
"github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/securitytrails"
4949
"github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/shodan"
5050
"github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/sitedossier"
51+
"github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/thc"
5152
"github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/threatbook"
5253
"github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/threatcrowd"
5354
"github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/virustotal"
@@ -108,6 +109,7 @@ var AllSources = [...]subscraping.Source{
108109
&builtwith.Source{},
109110
&hudsonrock.Source{},
110111
&digitalyama.Source{},
112+
&thc.Source{},
111113
}
112114

113115
var sourceWarnings = mapsutil.NewSyncLockMap[string, string](

pkg/passive/sources_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ var (
6060
"builtwith",
6161
"hudsonrock",
6262
"digitalyama",
63+
"thc",
6364
}
6465

6566
expectedDefaultSources = []string{
@@ -100,6 +101,7 @@ var (
100101
// "reconcloud",
101102
"builtwith",
102103
"digitalyama",
104+
"thc",
103105
}
104106

105107
expectedDefaultRecursiveSources = []string{

pkg/subscraping/sources/thc/thc.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Package thc logic
2+
package thc
3+
4+
import (
5+
"bytes"
6+
"context"
7+
"encoding/json"
8+
"time"
9+
10+
jsoniter "github.com/json-iterator/go"
11+
12+
"github.com/projectdiscovery/subfinder/v2/pkg/subscraping"
13+
)
14+
15+
type response struct {
16+
Domains []struct {
17+
Domain string `json:"domain"`
18+
} `json:"domains"`
19+
NextPageState string `json:"next_page_state"`
20+
}
21+
22+
// Source is the passive scraping agent
23+
type Source struct {
24+
timeTaken time.Duration
25+
errors int
26+
results int
27+
skipped bool
28+
}
29+
30+
type requestBody struct {
31+
Domain string `json:"domain"`
32+
PageState string `json:"page_state"`
33+
Limit int `json:"limit"`
34+
}
35+
36+
// Run function returns all subdomains found with the service
37+
func (s *Source) Run(ctx context.Context, domain string, session *subscraping.Session) <-chan subscraping.Result {
38+
results := make(chan subscraping.Result)
39+
s.errors = 0
40+
s.results = 0
41+
42+
go func() {
43+
defer func(startTime time.Time) {
44+
s.timeTaken = time.Since(startTime)
45+
close(results)
46+
}(time.Now())
47+
48+
var pageState string
49+
headers := map[string]string{"Content-Type": "application/json"}
50+
apiURL := "https://ip.thc.org/api/v1/lookup/subdomains"
51+
52+
for {
53+
reqBody := requestBody{
54+
Domain: domain,
55+
PageState: pageState,
56+
Limit: 1000,
57+
}
58+
59+
bodyBytes, err := json.Marshal(reqBody)
60+
if err != nil {
61+
results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err}
62+
s.errors++
63+
return
64+
}
65+
66+
resp, err := session.Post(ctx, apiURL, "", headers, bytes.NewReader(bodyBytes))
67+
if err != nil {
68+
results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err}
69+
s.errors++
70+
session.DiscardHTTPResponse(resp)
71+
return
72+
}
73+
74+
var thcResponse response
75+
err = jsoniter.NewDecoder(resp.Body).Decode(&thcResponse)
76+
session.DiscardHTTPResponse(resp)
77+
if err != nil {
78+
results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err}
79+
s.errors++
80+
return
81+
}
82+
83+
for _, domainRecord := range thcResponse.Domains {
84+
results <- subscraping.Result{Source: s.Name(), Type: subscraping.Subdomain, Value: domainRecord.Domain}
85+
s.results++
86+
}
87+
88+
pageState = thcResponse.NextPageState
89+
90+
if pageState == "" {
91+
break
92+
}
93+
}
94+
}()
95+
96+
return results
97+
}
98+
99+
// Name returns the name of the source
100+
func (s *Source) Name() string {
101+
return "thc"
102+
}
103+
104+
func (s *Source) IsDefault() bool {
105+
return true
106+
}
107+
108+
func (s *Source) HasRecursiveSupport() bool {
109+
return false
110+
}
111+
112+
func (s *Source) NeedsKey() bool {
113+
return false
114+
}
115+
116+
func (s *Source) AddApiKeys(_ []string) {
117+
// No API keys needed for THC
118+
}
119+
120+
func (s *Source) Statistics() subscraping.Statistics {
121+
return subscraping.Statistics{
122+
Errors: s.errors,
123+
Results: s.results,
124+
TimeTaken: s.timeTaken,
125+
Skipped: s.skipped,
126+
}
127+
}

0 commit comments

Comments
 (0)