Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/actions/golangci-lint@v1
- uses: projectdiscovery/actions/golangci-lint/v2@v1

tests:
name: Test Builds
Expand Down
11 changes: 11 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: "2"

linters:
settings:
govet:
# The 'inline' analyzer (new in Go 1.25 via golang.org/x/tools) reports
# generic call sites that it cannot statically inline. maps.Clone and a
# few other generic helpers trigger this even though the code is
# correct, so we disable it until type-parameter inference is supported.
disable:
- inline
4 changes: 3 additions & 1 deletion dsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ func init() {
result.WriteRune(c)
} else {
for _, b := range []byte(string(c)) {
result.WriteString(fmt.Sprintf("%%%02X", b))
fmt.Fprintf(&result, "%%%02X", b)
}
}
}
Expand Down Expand Up @@ -1634,6 +1634,8 @@ func init() {
}),
)

registerWappalyzerFunction()

DefaultHelperFunctions = HelperFunctions()
FunctionNames = GetFunctionNames(DefaultHelperFunctions)
}
Expand Down
2 changes: 1 addition & 1 deletion dsl_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func BenchmarkDSLFunctionHash(b *testing.B) {
_, _ = sb.WriteString("-")

for i, arg := range args {
_, _ = sb.WriteString(fmt.Sprintf("%v", arg))
_, _ = fmt.Fprintf(&sb, "%v", arg)
if i < len(args)-1 {
_, _ = sb.WriteString(",")
}
Expand Down
1 change: 1 addition & 0 deletions dsl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ func TestGetPrintableDslFunctionSignatures(t *testing.T) {
url_decode(arg1 interface{}) interface{}
url_encode(s string, optionalEncodeAllSpecialChars bool) string
wait_for(seconds uint)
wappalyzer(headers, body string) []string
xor(args ...interface{}) interface{}
zip(file_entry string, content string, ... ) []byte
zlib(arg1 interface{}) interface{}
Expand Down
18 changes: 9 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
module github.com/projectdiscovery/dsl

go 1.24.1

toolchain go1.24.2
go 1.25.0

require (
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057
Expand All @@ -24,9 +22,11 @@ require (
github.com/stretchr/testify v1.11.1
github.com/vulncheck-oss/go-exploit v1.51.0
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
golang.org/x/text v0.32.0
golang.org/x/text v0.36.0
)

require github.com/projectdiscovery/wappalyzergo v0.2.81 // indirect

require (
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
Expand Down Expand Up @@ -67,10 +67,10 @@ require (
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/mod v0.30.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/tools v0.39.0 // indirect
golang.org/x/mod v0.34.0 // indirect
golang.org/x/net v0.53.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/tools v0.43.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ github.com/projectdiscovery/mapcidr v1.1.97 h1:7FkxNNVXp+m1rIu5Nv/2SrF9k4+LwP8Qu
github.com/projectdiscovery/mapcidr v1.1.97/go.mod h1:9dgTJh1SP02gYZdpzMjm6vtYFkEHQHoTyaVNvaeJ7lA=
github.com/projectdiscovery/utils v0.11.0 h1:CxImZSRyj9spy1wpB9HKJopr5MsIPm2r5iS8uyhAMoQ=
github.com/projectdiscovery/utils v0.11.0/go.mod h1:q2mZngH1s4WDO3knYxG7iyP1KcxoRSORJCWSpCKFc1s=
github.com/projectdiscovery/wappalyzergo v0.2.81 h1:E+lOy8Xhb5ALEGrXFq64dgbkoVa0JWJw7nU6+yGnAz0=
github.com/projectdiscovery/wappalyzergo v0.2.81/go.mod h1:qMRkcU6PkpfglNpvddSztt+1GxdSeOB0jVjzbK17veU=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
Expand Down Expand Up @@ -256,6 +258,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand All @@ -276,6 +280,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand All @@ -291,6 +297,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -316,6 +324,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
Expand All @@ -328,6 +338,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down Expand Up @@ -358,6 +370,8 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
2 changes: 1 addition & 1 deletion util.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ func strToNumEntities(s string) string {
}

r := rune(escaped[i])
result.WriteString(fmt.Sprintf("&#%d;", int(r)))
fmt.Fprintf(&result, "&#%d;", int(r))
i++
}

Expand Down
127 changes: 127 additions & 0 deletions wappalyzer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package dsl

import (
"bufio"
"errors"
"fmt"
"net/textproto"
"sort"
"strings"
"sync"

wappalyzer "github.com/projectdiscovery/wappalyzergo"
)

// Lazy singleton: wappalyzergo loads its embedded fingerprint database on
// construction and is safe for concurrent reads thereafter. Initializing
// on first use keeps the cost off the import path for callers that never
// touch the wappalyzer helper.
var (
wappalyzerOnce sync.Once
wappalyzerInstance *wappalyzer.Wappalyze
wappalyzerInitErr error
)

func getWappalyzer() (*wappalyzer.Wappalyze, error) {
wappalyzerOnce.Do(func() {
wappalyzerInstance, wappalyzerInitErr = wappalyzer.New()
})
return wappalyzerInstance, wappalyzerInitErr
}

// parseHeadersForWappalyzer turns a raw HTTP header block into the
// map[string][]string shape wappalyzergo wants. It accepts the textual
// form template authors typically have on hand (the contents of nuclei's
// {{header}} variable). Header names are kept as written - wappalyzergo
// lowercases them internally.
func parseHeadersForWappalyzer(raw string) (map[string][]string, error) {
raw = strings.TrimLeft(raw, "\r\n")
if raw == "" {
return map[string][]string{}, nil
}
if !strings.HasSuffix(raw, "\r\n\r\n") {
if !strings.HasSuffix(raw, "\r\n") {
raw += "\r\n"
}
raw += "\r\n"
}
tp := textproto.NewReader(bufio.NewReader(strings.NewReader(raw)))
mh, err := tp.ReadMIMEHeader()
if err != nil {
return nil, fmt.Errorf("invalid header block: %w", err)
}
return mh, nil
}

func toHeadersAndBody(args []interface{}) (map[string][]string, []byte, error) {
if len(args) != 2 {
return nil, nil, ErrInvalidDslFunction
}
var headerRaw string
switch v := args[0].(type) {
case string:
headerRaw = v
case []byte:
headerRaw = string(v)
case nil:
default:
return nil, nil, errors.New("first argument (headers) must be a string")
}
var body []byte
switch v := args[1].(type) {
case string:
body = []byte(v)
case []byte:
body = v
case nil:
default:
return nil, nil, errors.New("second argument (body) must be a string or bytes")
}
headers, err := parseHeadersForWappalyzer(headerRaw)
if err != nil {
return nil, nil, err
}
return headers, body, nil
}

// fingerprintsAsSortedSlice returns the technology names in a stable
// alphabetical order so that callers (and the DSL result cache) see a
// deterministic value for identical inputs.
func fingerprintsAsSortedSlice(m map[string]struct{}) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
sort.Strings(out)
return out
}

// registerWappalyzerFunction installs the wappalyzer(headers, body)
// helper. It is called from dsl.go's init so the function is present
// before DefaultHelperFunctions is materialized.
//
// wappalyzer(headers, body) []string
//
// Runs wappalyzergo's fingerprint engine against the provided
// response headers and body and returns the detected technology
// names. headers may be the raw HTTP header block as a string (the
// most common form in nuclei templates) or a pre-built byte slice
// with the same content.
func registerWappalyzerFunction() {
MustAddFunction(NewWithSingleSignature(
"wappalyzer",
"(headers, body string) []string",
true,
func(args ...interface{}) (interface{}, error) {
headers, body, err := toHeadersAndBody(args)
if err != nil {
return nil, err
}
w, err := getWappalyzer()
if err != nil {
return nil, fmt.Errorf("wappalyzer init: %w", err)
}
return fingerprintsAsSortedSlice(w.Fingerprint(headers, body)), nil
},
))
}
Loading
Loading