mirror of https://github.com/daffainfo/nuclei.git
413 lines
11 KiB
Go
413 lines
11 KiB
Go
package generator
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/importer"
|
|
"go/parser"
|
|
"go/token"
|
|
"go/types"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
|
|
_ "embed"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler"
|
|
)
|
|
|
|
var (
|
|
//go:embed templates/js_class.tmpl
|
|
jsClassFile string
|
|
//go:embed templates/go_class.tmpl
|
|
goClassFile string
|
|
//go:embed templates/markdown_class.tmpl
|
|
markdownClassFile string
|
|
)
|
|
|
|
// TemplateData contains the parameters for the JS code generator
|
|
type TemplateData struct {
|
|
PackageName string
|
|
PackagePath string
|
|
PackageFuncs map[string]string
|
|
PackageInterfaces map[string]string
|
|
PackageFuncsExtraNoType map[string]PackageFunctionExtra
|
|
PackageFuncsExtra map[string]PackageFuncExtra
|
|
PackageVars map[string]string
|
|
PackageVarsValues map[string]string
|
|
PackageTypes map[string]string
|
|
PackageTypesExtra map[string]PackageTypeExtra
|
|
|
|
typesPackage *types.Package
|
|
|
|
// NativeScripts contains the list of native scripts
|
|
// that should be included in the package.
|
|
NativeScripts []string
|
|
}
|
|
|
|
// PackageTypeExtra contains extra information about a type
|
|
type PackageTypeExtra struct {
|
|
Fields map[string]string
|
|
}
|
|
|
|
// PackageFuncExtra contains extra information about a function
|
|
type PackageFuncExtra struct {
|
|
Items map[string]PackageFunctionExtra
|
|
Doc string
|
|
}
|
|
|
|
// PackageFunctionExtra contains extra information about a function
|
|
type PackageFunctionExtra struct {
|
|
Args []string
|
|
Name string
|
|
Returns []string
|
|
Doc string
|
|
}
|
|
|
|
// newTemplateData creates a new template data structure
|
|
func newTemplateData(packagePrefix, pkgName string) *TemplateData {
|
|
return &TemplateData{
|
|
PackageName: pkgName,
|
|
PackagePath: packagePrefix + pkgName,
|
|
PackageFuncs: make(map[string]string),
|
|
PackageFuncsExtraNoType: make(map[string]PackageFunctionExtra),
|
|
PackageFuncsExtra: make(map[string]PackageFuncExtra),
|
|
PackageVars: make(map[string]string),
|
|
PackageVarsValues: make(map[string]string),
|
|
PackageTypes: make(map[string]string),
|
|
PackageInterfaces: make(map[string]string),
|
|
PackageTypesExtra: make(map[string]PackageTypeExtra),
|
|
}
|
|
}
|
|
|
|
// GetLibraryModules takes a directory and returns subdirectories as modules
|
|
func GetLibraryModules(directory string) ([]string, error) {
|
|
dirs, err := os.ReadDir(directory)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not read directory")
|
|
}
|
|
var modules []string
|
|
for _, dir := range dirs {
|
|
if dir.IsDir() {
|
|
modules = append(modules, dir.Name())
|
|
}
|
|
}
|
|
return modules, nil
|
|
}
|
|
|
|
// CreateTemplateData creates a TemplateData structure from a directory
|
|
// of go source code.
|
|
func CreateTemplateData(directory string, packagePrefix string) (*TemplateData, error) {
|
|
fmt.Println(directory)
|
|
fset := token.NewFileSet()
|
|
|
|
pkgs, err := parser.ParseDir(fset, directory, nil, parser.ParseComments)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not parse directory")
|
|
}
|
|
if len(pkgs) != 1 {
|
|
return nil, fmt.Errorf("expected 1 package, got %d", len(pkgs))
|
|
}
|
|
|
|
config := &types.Config{
|
|
Importer: importer.ForCompiler(fset, "source", nil),
|
|
}
|
|
var packageName string
|
|
var files []*ast.File
|
|
for k, v := range pkgs {
|
|
packageName = k
|
|
for _, f := range v.Files {
|
|
files = append(files, f)
|
|
}
|
|
break
|
|
}
|
|
|
|
pkg, err := config.Check(packageName, fset, files, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not check package")
|
|
}
|
|
|
|
var pkgMain *ast.Package
|
|
for _, p := range pkgs {
|
|
pkgMain = p
|
|
break
|
|
}
|
|
|
|
log.Printf("[create] [discover] Package: %s\n", pkgMain.Name)
|
|
data := newTemplateData(packagePrefix, pkgMain.Name)
|
|
data.typesPackage = pkg
|
|
data.gatherPackageData(pkgMain, data)
|
|
|
|
for item, v := range data.PackageFuncsExtra {
|
|
if len(v.Items) == 0 {
|
|
delete(data.PackageFuncsExtra, item)
|
|
}
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
// InitNativeScripts initializes the native scripts array
|
|
// with all the exported functions from the runtime
|
|
func (d *TemplateData) InitNativeScripts() {
|
|
compiler := compiler.New()
|
|
runtime := compiler.VM()
|
|
|
|
exports := runtime.Get("exports")
|
|
if exports == nil {
|
|
return
|
|
}
|
|
exportsObj := exports.Export()
|
|
if exportsObj == nil {
|
|
return
|
|
}
|
|
for v := range exportsObj.(map[string]interface{}) {
|
|
d.NativeScripts = append(d.NativeScripts, v)
|
|
}
|
|
}
|
|
|
|
// gatherPackageData gathers data about the package
|
|
func (d *TemplateData) gatherPackageData(pkg *ast.Package, data *TemplateData) {
|
|
ast.Inspect(pkg, func(node ast.Node) bool {
|
|
switch node := node.(type) {
|
|
case *ast.FuncDecl:
|
|
extra := d.collectFuncDecl(node)
|
|
if extra.Name == "" {
|
|
return true
|
|
}
|
|
data.PackageFuncsExtraNoType[node.Name.Name] = extra
|
|
data.PackageFuncs[node.Name.Name] = node.Name.Name
|
|
case *ast.TypeSpec:
|
|
if !node.Name.IsExported() {
|
|
return true
|
|
}
|
|
if node.Type == nil {
|
|
return true
|
|
}
|
|
structDecl, ok := node.Type.(*ast.StructType)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
packageTypes := PackageTypeExtra{
|
|
Fields: make(map[string]string),
|
|
}
|
|
for _, field := range structDecl.Fields.List {
|
|
fieldName := field.Names[0].Name
|
|
|
|
var fieldTypeValue string
|
|
switch fieldType := field.Type.(type) {
|
|
case *ast.Ident: // Field type is a simple identifier
|
|
fieldTypeValue = fieldType.Name
|
|
case *ast.ArrayType:
|
|
switch fieldType.Elt.(type) {
|
|
case *ast.Ident:
|
|
fieldTypeValue = fmt.Sprintf("[]%s", fieldType.Elt.(*ast.Ident).Name)
|
|
case *ast.StarExpr:
|
|
fieldTypeValue = fmt.Sprintf("[]%s", d.handleStarExpr(fieldType.Elt.(*ast.StarExpr)))
|
|
}
|
|
case *ast.SelectorExpr: // Field type is a qualified identifier
|
|
fieldTypeValue = fmt.Sprintf("%s.%s", fieldType.X, fieldType.Sel)
|
|
}
|
|
packageTypes.Fields[fieldName] = fieldTypeValue
|
|
}
|
|
if len(packageTypes.Fields) == 0 {
|
|
return true
|
|
}
|
|
data.PackageTypesExtra[node.Name.Name] = packageTypes
|
|
case *ast.GenDecl:
|
|
identifyGenDecl(pkg, node, data)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
func identifyGenDecl(pkg *ast.Package, decl *ast.GenDecl, data *TemplateData) {
|
|
for _, spec := range decl.Specs {
|
|
switch spec := spec.(type) {
|
|
case *ast.ValueSpec:
|
|
if !spec.Names[0].IsExported() {
|
|
continue
|
|
}
|
|
if spec.Values == nil || len(spec.Values) == 0 {
|
|
continue
|
|
}
|
|
data.PackageVars[spec.Names[0].Name] = spec.Names[0].Name
|
|
data.PackageVarsValues[spec.Names[0].Name] = spec.Values[0].(*ast.BasicLit).Value
|
|
case *ast.TypeSpec:
|
|
if !spec.Name.IsExported() {
|
|
continue
|
|
}
|
|
if spec.Type == nil {
|
|
continue
|
|
}
|
|
|
|
switch spec.Type.(type) {
|
|
case *ast.InterfaceType:
|
|
data.PackageInterfaces[spec.Name.Name] = convertCommentsToJavascript(decl.Doc.Text())
|
|
|
|
case *ast.StructType:
|
|
data.PackageFuncsExtra[spec.Name.Name] = PackageFuncExtra{
|
|
Items: make(map[string]PackageFunctionExtra),
|
|
Doc: convertCommentsToJavascript(decl.Doc.Text()),
|
|
}
|
|
|
|
// Traverse the AST.
|
|
collectStructFuncsFromAST(pkg, spec, data)
|
|
data.PackageTypes[spec.Name.Name] = spec.Name.Name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func collectStructFuncsFromAST(pkg *ast.Package, spec *ast.TypeSpec, data *TemplateData) {
|
|
ast.Inspect(pkg, func(n ast.Node) bool {
|
|
if fn, isFunc := n.(*ast.FuncDecl); isFunc && fn.Name.IsExported() {
|
|
processFunc(fn, spec, data)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
func processFunc(fn *ast.FuncDecl, spec *ast.TypeSpec, data *TemplateData) {
|
|
if fn.Recv == nil || len(fn.Recv.List) == 0 {
|
|
return
|
|
}
|
|
|
|
if t, ok := fn.Recv.List[0].Type.(*ast.StarExpr); ok {
|
|
if ident, ok := t.X.(*ast.Ident); ok && spec.Name.Name == ident.Name {
|
|
processFunctionDetails(fn, ident, data)
|
|
}
|
|
}
|
|
}
|
|
|
|
func processFunctionDetails(fn *ast.FuncDecl, ident *ast.Ident, data *TemplateData) {
|
|
extra := PackageFunctionExtra{
|
|
Name: fn.Name.Name,
|
|
Args: extractArgs(fn),
|
|
Doc: convertCommentsToJavascript(fn.Doc.Text()),
|
|
Returns: data.extractReturns(fn),
|
|
}
|
|
data.PackageFuncsExtra[ident.Name].Items[fn.Name.Name] = extra
|
|
}
|
|
|
|
func extractArgs(fn *ast.FuncDecl) []string {
|
|
args := make([]string, 0)
|
|
for _, arg := range fn.Type.Params.List {
|
|
for _, name := range arg.Names {
|
|
args = append(args, name.Name)
|
|
}
|
|
}
|
|
return args
|
|
}
|
|
|
|
func (d *TemplateData) extractReturns(fn *ast.FuncDecl) []string {
|
|
returns := make([]string, 0)
|
|
if fn.Type.Results == nil {
|
|
return returns
|
|
}
|
|
for _, ret := range fn.Type.Results.List {
|
|
returnType := d.extractReturnType(ret)
|
|
if returnType != "" {
|
|
returns = append(returns, returnType)
|
|
}
|
|
}
|
|
return returns
|
|
}
|
|
|
|
func (d *TemplateData) extractReturnType(ret *ast.Field) string {
|
|
switch v := ret.Type.(type) {
|
|
case *ast.ArrayType:
|
|
if v, ok := v.Elt.(*ast.Ident); ok {
|
|
return fmt.Sprintf("[]%s", v.Name)
|
|
}
|
|
if v, ok := v.Elt.(*ast.StarExpr); ok {
|
|
return fmt.Sprintf("[]%s", d.handleStarExpr(v))
|
|
}
|
|
case *ast.Ident:
|
|
return v.Name
|
|
case *ast.StarExpr:
|
|
return d.handleStarExpr(v)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (d *TemplateData) handleStarExpr(v *ast.StarExpr) string {
|
|
switch vk := v.X.(type) {
|
|
case *ast.Ident:
|
|
return vk.Name
|
|
case *ast.SelectorExpr:
|
|
if vk.X != nil {
|
|
d.collectTypeFromExternal(d.typesPackage, vk.X.(*ast.Ident).Name, vk.Sel.Name)
|
|
}
|
|
return vk.Sel.Name
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (d *TemplateData) collectTypeFromExternal(pkg *types.Package, pkgName, name string) {
|
|
extra := PackageTypeExtra{
|
|
Fields: make(map[string]string),
|
|
}
|
|
|
|
for _, importValue := range pkg.Imports() {
|
|
if importValue.Name() != pkgName {
|
|
continue
|
|
}
|
|
obj := importValue.Scope().Lookup(name)
|
|
if obj == nil || !obj.Exported() {
|
|
continue
|
|
}
|
|
typeName, ok := obj.(*types.TypeName)
|
|
if !ok {
|
|
continue
|
|
}
|
|
underlying, ok := typeName.Type().Underlying().(*types.Struct)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for i := 0; i < underlying.NumFields(); i++ {
|
|
field := underlying.Field(i)
|
|
fieldType := field.Type().String()
|
|
|
|
if val, ok := field.Type().Underlying().(*types.Pointer); ok {
|
|
fieldType = field.Name()
|
|
d.collectTypeFromExternal(pkg, pkgName, val.Elem().(*types.Named).Obj().Name())
|
|
}
|
|
if _, ok := field.Type().Underlying().(*types.Struct); ok {
|
|
fieldType = field.Name()
|
|
d.collectTypeFromExternal(pkg, pkgName, field.Name())
|
|
}
|
|
extra.Fields[field.Name()] = fieldType
|
|
}
|
|
if len(extra.Fields) > 0 {
|
|
d.PackageTypesExtra[name] = extra
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *TemplateData) collectFuncDecl(decl *ast.FuncDecl) (extra PackageFunctionExtra) {
|
|
if decl.Recv != nil {
|
|
return
|
|
}
|
|
if !decl.Name.IsExported() {
|
|
return
|
|
}
|
|
extra.Name = decl.Name.Name
|
|
extra.Doc = convertCommentsToJavascript(decl.Doc.Text())
|
|
|
|
for _, arg := range decl.Type.Params.List {
|
|
for _, name := range arg.Names {
|
|
extra.Args = append(extra.Args, name.Name)
|
|
}
|
|
}
|
|
extra.Returns = d.extractReturns(decl)
|
|
return extra
|
|
}
|
|
|
|
// convertCommentsToJavascript converts comments to javascript comments.
|
|
func convertCommentsToJavascript(comments string) string {
|
|
suffix := strings.Trim(strings.TrimSuffix(strings.ReplaceAll(comments, "\n", "\n// "), "// "), "\n")
|
|
return fmt.Sprintf("// %s", suffix)
|
|
}
|