Path: blob/dev/pkg/catalog/loader/remote_loader.go
2070 views
package loader12import (3"bufio"4"fmt"5"net/url"6"strings"7"sync"89"github.com/pkg/errors"1011"github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions"12"github.com/projectdiscovery/nuclei/v3/pkg/utils"13"github.com/projectdiscovery/retryablehttp-go"14sliceutil "github.com/projectdiscovery/utils/slice"15stringsutil "github.com/projectdiscovery/utils/strings"16syncutil "github.com/projectdiscovery/utils/sync"17)1819type ContentType string2021const (22Template ContentType = "Template"23Workflow ContentType = "Workflow"24)2526type RemoteContent struct {27Content []string28Type ContentType29Error error30}3132func getRemoteTemplatesAndWorkflows(templateURLs, workflowURLs, remoteTemplateDomainList []string) ([]string, []string, error) {33var (34err error35muErr sync.Mutex36)37remoteTemplateList := sliceutil.NewSyncSlice[string]()38remoteWorkFlowList := sliceutil.NewSyncSlice[string]()3940awg, errAwg := syncutil.New(syncutil.WithSize(50))41if errAwg != nil {42return nil, nil, errAwg43}4445loadItem := func(URL string, contentType ContentType) {46defer awg.Done()4748remoteContent := getRemoteContent(URL, remoteTemplateDomainList, contentType)49if remoteContent.Error != nil {50muErr.Lock()51if err != nil {52err = errors.New(remoteContent.Error.Error() + ": " + err.Error())53} else {54err = remoteContent.Error55}56muErr.Unlock()57} else {58switch remoteContent.Type {59case Template:60remoteTemplateList.Append(remoteContent.Content...)61case Workflow:62remoteWorkFlowList.Append(remoteContent.Content...)63}64}65}6667for _, templateURL := range templateURLs {68awg.Add()69go loadItem(templateURL, Template)70}71for _, workflowURL := range workflowURLs {72awg.Add()73go loadItem(workflowURL, Workflow)74}7576awg.Wait()7778return remoteTemplateList.Slice, remoteWorkFlowList.Slice, err79}8081func getRemoteContent(URL string, remoteTemplateDomainList []string, contentType ContentType) RemoteContent {82if err := validateRemoteTemplateURL(URL, remoteTemplateDomainList); err != nil {83return RemoteContent{Error: err}84}85if strings.HasPrefix(URL, "http") && stringsutil.HasSuffixAny(URL, extensions.YAML) {86return RemoteContent{87Content: []string{URL},88Type: contentType,89}90}91response, err := retryablehttp.DefaultClient().Get(URL)92if err != nil {93return RemoteContent{Error: err}94}95defer func() {96_ = response.Body.Close()97}()98if response.StatusCode < 200 || response.StatusCode > 299 {99return RemoteContent{Error: fmt.Errorf("get \"%s\": unexpect status %d", URL, response.StatusCode)}100}101102scanner := bufio.NewScanner(response.Body)103var templateList []string104for scanner.Scan() {105text := strings.TrimSpace(scanner.Text())106if text == "" {107continue108}109if utils.IsURL(text) {110if err := validateRemoteTemplateURL(text, remoteTemplateDomainList); err != nil {111return RemoteContent{Error: err}112}113}114templateList = append(templateList, text)115}116117if err := scanner.Err(); err != nil {118return RemoteContent{Error: errors.Wrap(err, "get \"%s\"")}119}120121return RemoteContent{122Content: templateList,123Type: contentType,124}125}126127func validateRemoteTemplateURL(inputURL string, remoteTemplateDomainList []string) error {128parsedURL, err := url.Parse(inputURL)129if err != nil {130return err131}132if !utils.StringSliceContains(remoteTemplateDomainList, parsedURL.Host) {133return errors.Errorf("Remote template URL host (%s) is not present in the `remote-template-domain` list in nuclei config", parsedURL.Host)134}135return nil136}137138139