Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/catalog/loader/remote_loader.go
2070 views
1
package loader
2
3
import (
4
"bufio"
5
"fmt"
6
"net/url"
7
"strings"
8
"sync"
9
10
"github.com/pkg/errors"
11
12
"github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions"
13
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
14
"github.com/projectdiscovery/retryablehttp-go"
15
sliceutil "github.com/projectdiscovery/utils/slice"
16
stringsutil "github.com/projectdiscovery/utils/strings"
17
syncutil "github.com/projectdiscovery/utils/sync"
18
)
19
20
type ContentType string
21
22
const (
23
Template ContentType = "Template"
24
Workflow ContentType = "Workflow"
25
)
26
27
type RemoteContent struct {
28
Content []string
29
Type ContentType
30
Error error
31
}
32
33
func getRemoteTemplatesAndWorkflows(templateURLs, workflowURLs, remoteTemplateDomainList []string) ([]string, []string, error) {
34
var (
35
err error
36
muErr sync.Mutex
37
)
38
remoteTemplateList := sliceutil.NewSyncSlice[string]()
39
remoteWorkFlowList := sliceutil.NewSyncSlice[string]()
40
41
awg, errAwg := syncutil.New(syncutil.WithSize(50))
42
if errAwg != nil {
43
return nil, nil, errAwg
44
}
45
46
loadItem := func(URL string, contentType ContentType) {
47
defer awg.Done()
48
49
remoteContent := getRemoteContent(URL, remoteTemplateDomainList, contentType)
50
if remoteContent.Error != nil {
51
muErr.Lock()
52
if err != nil {
53
err = errors.New(remoteContent.Error.Error() + ": " + err.Error())
54
} else {
55
err = remoteContent.Error
56
}
57
muErr.Unlock()
58
} else {
59
switch remoteContent.Type {
60
case Template:
61
remoteTemplateList.Append(remoteContent.Content...)
62
case Workflow:
63
remoteWorkFlowList.Append(remoteContent.Content...)
64
}
65
}
66
}
67
68
for _, templateURL := range templateURLs {
69
awg.Add()
70
go loadItem(templateURL, Template)
71
}
72
for _, workflowURL := range workflowURLs {
73
awg.Add()
74
go loadItem(workflowURL, Workflow)
75
}
76
77
awg.Wait()
78
79
return remoteTemplateList.Slice, remoteWorkFlowList.Slice, err
80
}
81
82
func getRemoteContent(URL string, remoteTemplateDomainList []string, contentType ContentType) RemoteContent {
83
if err := validateRemoteTemplateURL(URL, remoteTemplateDomainList); err != nil {
84
return RemoteContent{Error: err}
85
}
86
if strings.HasPrefix(URL, "http") && stringsutil.HasSuffixAny(URL, extensions.YAML) {
87
return RemoteContent{
88
Content: []string{URL},
89
Type: contentType,
90
}
91
}
92
response, err := retryablehttp.DefaultClient().Get(URL)
93
if err != nil {
94
return RemoteContent{Error: err}
95
}
96
defer func() {
97
_ = response.Body.Close()
98
}()
99
if response.StatusCode < 200 || response.StatusCode > 299 {
100
return RemoteContent{Error: fmt.Errorf("get \"%s\": unexpect status %d", URL, response.StatusCode)}
101
}
102
103
scanner := bufio.NewScanner(response.Body)
104
var templateList []string
105
for scanner.Scan() {
106
text := strings.TrimSpace(scanner.Text())
107
if text == "" {
108
continue
109
}
110
if utils.IsURL(text) {
111
if err := validateRemoteTemplateURL(text, remoteTemplateDomainList); err != nil {
112
return RemoteContent{Error: err}
113
}
114
}
115
templateList = append(templateList, text)
116
}
117
118
if err := scanner.Err(); err != nil {
119
return RemoteContent{Error: errors.Wrap(err, "get \"%s\"")}
120
}
121
122
return RemoteContent{
123
Content: templateList,
124
Type: contentType,
125
}
126
}
127
128
func validateRemoteTemplateURL(inputURL string, remoteTemplateDomainList []string) error {
129
parsedURL, err := url.Parse(inputURL)
130
if err != nil {
131
return err
132
}
133
if !utils.StringSliceContains(remoteTemplateDomainList, parsedURL.Host) {
134
return errors.Errorf("Remote template URL host (%s) is not present in the `remote-template-domain` list in nuclei config", parsedURL.Host)
135
}
136
return nil
137
}
138
139