320 lines
9.2 KiB
Go
320 lines
9.2 KiB
Go
// Package output contains utilities for processing results from zgrab2 scanners
|
|
// for eventual output and consumption by ztag.
|
|
package output
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
// ZGrabTag holds the information from the `zgrab` tag. Currently only supports
|
|
// the zgrab tag.
|
|
type ZGrabTag struct {
|
|
// Debug means that the field should only be output when doing verbose output.
|
|
Debug bool
|
|
}
|
|
|
|
// parseZGrabTag reads the `zgrab` tag and returns the corresponding parsed
|
|
// ZGrabTag. Currently only "debug" is recognized; other options should be
|
|
// comma separated.
|
|
func parseZGrabTag(value string) *ZGrabTag {
|
|
ret := ZGrabTag{Debug: false}
|
|
fields := strings.Split(value, ",")
|
|
for _, field := range fields {
|
|
switch strings.TrimSpace(field) {
|
|
case "debug":
|
|
ret.Debug = true
|
|
}
|
|
}
|
|
return &ret
|
|
}
|
|
|
|
// ProcessCallback is called for each element in a struct; if it returns
|
|
// a non-nil value, that value will be used and further processing on
|
|
// that element will be skipped.
|
|
type ProcessCallback func(*Processor, reflect.Value) *reflect.Value
|
|
|
|
type pathEntry struct {
|
|
field string
|
|
value reflect.Value
|
|
}
|
|
|
|
// Processor holds the state for a process run. A given processor should
|
|
// only be used on a single thread.
|
|
type Processor struct {
|
|
// Callback is a function that gets called on each element being
|
|
// processed. If the callback returns a non-nil value, that value is
|
|
// returned immediately instead of doing any further processing on
|
|
// the element.
|
|
Callback ProcessCallback
|
|
|
|
// Verbose determines whether `zgrab:"debug"` fields will be
|
|
// included in the output.
|
|
Verbose bool
|
|
|
|
// Path is the current path being processed, from the root element.
|
|
// Used for debugging purposes only.
|
|
// If a panic occurs, the path will point to the element where the
|
|
// element that caused the problem.
|
|
Path []pathEntry
|
|
}
|
|
|
|
// NewProcessor returns a new Processor instance with the default settings.
|
|
func NewProcessor() *Processor {
|
|
return &Processor{}
|
|
}
|
|
|
|
// getPath returns a string representation of the current path.
|
|
func (processor *Processor) getPath() string {
|
|
ret := make([]string, len(processor.Path))
|
|
for i, v := range processor.Path {
|
|
ret[i] = v.field
|
|
}
|
|
return strings.Join(ret, "->")
|
|
}
|
|
|
|
// callback invokes the callback (or the default, if none is present).
|
|
// The callback can return an on-nil value to override the default behavior.
|
|
func (processor *Processor) callback(v reflect.Value) *reflect.Value {
|
|
callback := processor.Callback
|
|
if callback == nil {
|
|
callback = NullProcessCallback
|
|
}
|
|
return callback(processor, v)
|
|
}
|
|
|
|
// NullProcessCallback is the default ProcessCallback; it just returns nil.
|
|
func NullProcessCallback(w *Processor, v reflect.Value) *reflect.Value {
|
|
return nil
|
|
}
|
|
|
|
// duplicate a *primitive* value by doing a set-by-value (non-primitive values
|
|
// should not use this).
|
|
func (processor *Processor) duplicate(v reflect.Value) reflect.Value {
|
|
ret := reflect.New(v.Type()).Elem()
|
|
ret.Set(v)
|
|
return ret
|
|
}
|
|
|
|
// Add a path with the given key and value to the stack.
|
|
func (processor *Processor) pushPath(key string, value reflect.Value) {
|
|
processor.Path = append(processor.Path, pathEntry{
|
|
field: key,
|
|
value: value,
|
|
})
|
|
}
|
|
|
|
// Get the most recent path entry.
|
|
func (processor *Processor) topPath() *pathEntry {
|
|
return &processor.Path[len(processor.Path)-1]
|
|
}
|
|
|
|
// Remove the most recent entry from the stack (and return it).
|
|
func (processor *Processor) popPath() *pathEntry {
|
|
ret := processor.topPath()
|
|
processor.Path = processor.Path[0 : len(processor.Path)-1]
|
|
return ret
|
|
}
|
|
|
|
// Helper to check if a value is nil. Non-nillable values are by definition
|
|
// not nil (though they may be "zero").
|
|
func isNil(v reflect.Value) bool {
|
|
return (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface || v.Kind() == reflect.Slice) && v.IsNil()
|
|
}
|
|
|
|
// Check if a field should be copied over to the return value.
|
|
// The only time a field should be wiped is if the field has the `zgrab:"debug"`
|
|
// tag set, and if the verbose flag is off.
|
|
// There is an additional caveat that, if the field is already nil, leave it
|
|
// (so that we don't set it to a non-nil "zero" value).
|
|
func (processor *Processor) shouldWipeField(parent reflect.Value, index int) bool {
|
|
tField := parent.Type().Field(index)
|
|
|
|
// Rather than zeroing out nil values, handle them at the outer level
|
|
if isNil(parent.Field(index)) {
|
|
//fmt.Printf("Bogus copy becase nil: %s (%#v) to zero\n", processor.getPath(), tField)
|
|
return false
|
|
}
|
|
|
|
tag := parseZGrabTag(tField.Tag.Get("zgrab"))
|
|
// The only time a field
|
|
return tag.Debug && !processor.Verbose
|
|
}
|
|
|
|
// Process the struct instance.
|
|
func (processor *Processor) processStruct(v reflect.Value) reflect.Value {
|
|
t := v.Type()
|
|
ret := reflect.New(v.Type()).Elem()
|
|
// Two possibilities:
|
|
// (a) do ret.Set(v), then explicitly zero-out any debug fields.
|
|
// (b) only copy over fields that are non-debug.
|
|
// Going with (a)
|
|
ret.Set(v)
|
|
for i := 0; i < v.NumField(); i++ {
|
|
tField := t.Field(i)
|
|
field := v.Field(i)
|
|
retField := ret.Field(i)
|
|
if !retField.CanSet() {
|
|
// skip non-exportable fields
|
|
continue
|
|
}
|
|
if processor.shouldWipeField(v, i) {
|
|
retField.Set(reflect.Zero(field.Type()))
|
|
continue
|
|
}
|
|
processor.pushPath(fmt.Sprintf("%s(%d)", tField.Name, i), field)
|
|
copy := processor.process(field)
|
|
processor.popPath()
|
|
retField.Set(copy)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// Process a pointer (make a new pointer pointing to a new copy of v's referent).
|
|
func (processor *Processor) processPtr(v reflect.Value) reflect.Value {
|
|
ret := reflect.New(v.Type().Elem()).Elem()
|
|
if v.IsNil() {
|
|
//fmt.Println("Goodbye to ", processor.getPath())
|
|
return ret.Addr()
|
|
}
|
|
processor.pushPath("*", v.Elem())
|
|
copy := processor.process(v.Elem())
|
|
processor.popPath()
|
|
ret.Set(copy)
|
|
return ret.Addr()
|
|
}
|
|
|
|
// Process an interface instance (make a new interface and point it to a copy of
|
|
// v's referent).
|
|
func (processor *Processor) processInterface(v reflect.Value) reflect.Value {
|
|
ret := reflect.New(v.Type()).Elem()
|
|
if v.IsNil() {
|
|
return ret.Addr()
|
|
}
|
|
|
|
processor.pushPath("[interface:"+v.Type().Name()+")]", v.Elem())
|
|
copy := processor.process(v.Elem())
|
|
processor.popPath()
|
|
ret.Set(copy)
|
|
return ret
|
|
}
|
|
|
|
// Process a map -- copy over all keys and (copies of) values into a new map.
|
|
func (processor *Processor) processMap(v reflect.Value) reflect.Value {
|
|
if v.IsNil() {
|
|
return reflect.New(v.Type()).Elem() // nil
|
|
}
|
|
// As with slices, the value returned by MakeMap cannot be set / addressed.
|
|
// So, we make a pointer to the map, then store the map in the pointer.
|
|
ret := reflect.New(v.Type()).Elem()
|
|
ret.Set(reflect.MakeMap(v.Type()))
|
|
|
|
keys := v.MapKeys()
|
|
|
|
for _, key := range keys {
|
|
value := v.MapIndex(key)
|
|
processor.pushPath(fmt.Sprintf("[%v]", key), value)
|
|
copy := processor.process(value)
|
|
processor.popPath()
|
|
ret.SetMapIndex(key, copy)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// Process an array (add copies of each element into a new array).
|
|
func (processor *Processor) processArray(v reflect.Value) reflect.Value {
|
|
ret := reflect.New(v.Type()).Elem()
|
|
for i := 0; i < v.Len(); i++ {
|
|
elt := v.Index(i)
|
|
processor.pushPath(fmt.Sprintf("[%d]", i), elt)
|
|
copy := processor.process(elt)
|
|
ret.Index(i).Set(copy)
|
|
processor.popPath()
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// Return a copy of the given byte-slice-compatible value.
|
|
func (processor *Processor) copyByteSlice(v reflect.Value) reflect.Value {
|
|
ret := reflect.New(v.Type()).Elem()
|
|
ret.Set(reflect.MakeSlice(v.Type(), v.Len(), v.Cap()))
|
|
reflect.Copy(ret, v)
|
|
return ret
|
|
}
|
|
|
|
// Process a slice (add copies of each element into a new slice with the same
|
|
// length and capacity).
|
|
func (processor *Processor) processSlice(v reflect.Value) reflect.Value {
|
|
if v.IsNil() {
|
|
panic(fmt.Errorf("Slice %#v (%s) is nil?\n", v, processor.getPath()))
|
|
}
|
|
if v.Type().Elem().Kind() == reflect.Uint8 {
|
|
return processor.copyByteSlice(v)
|
|
}
|
|
|
|
n := v.Len()
|
|
ret := reflect.New(v.Type()).Elem()
|
|
ret.Set(reflect.MakeSlice(v.Type(), n, v.Cap()))
|
|
for i := 0; i < n; i++ {
|
|
elt := v.Index(i)
|
|
processor.pushPath(fmt.Sprintf("[%d]", i), elt)
|
|
copy := processor.process(elt)
|
|
ret.Index(i).Set(copy)
|
|
processor.popPath()
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// Process an arbitrary value. Invokes the processor's callback; if it returns
|
|
// a non-nil value, return that. Otherwise, continue recursively processing
|
|
// the value.
|
|
func (processor *Processor) process(v reflect.Value) reflect.Value {
|
|
temp := processor.callback(v)
|
|
if temp != nil {
|
|
return *temp
|
|
}
|
|
if isNil(v) {
|
|
// Just leave nil values alone.
|
|
return v
|
|
}
|
|
|
|
t := v.Type()
|
|
switch t.Kind() {
|
|
case reflect.Struct:
|
|
return processor.processStruct(v)
|
|
case reflect.Ptr:
|
|
return processor.processPtr(v)
|
|
case reflect.Slice:
|
|
return processor.processSlice(v)
|
|
case reflect.Array:
|
|
return processor.processArray(v)
|
|
case reflect.Interface:
|
|
return processor.processInterface(v)
|
|
case reflect.Map:
|
|
return processor.processMap(v)
|
|
default:
|
|
return processor.duplicate(v)
|
|
}
|
|
}
|
|
|
|
// Process the given value recursively using the options in this processor.
|
|
func (processor *Processor) Process(v interface{}) (ret interface{}, err error) {
|
|
defer func() {
|
|
if thrown := recover(); thrown != nil {
|
|
cast, ok := thrown.(error)
|
|
if !ok {
|
|
panic(thrown)
|
|
}
|
|
err = cast
|
|
ret = nil
|
|
}
|
|
}()
|
|
return processor.process(reflect.ValueOf(v)).Interface(), nil
|
|
}
|
|
|
|
// Process the given value recursively using the default options.
|
|
func Process(v interface{}) (interface{}, error) {
|
|
return NewProcessor().Process(v)
|
|
}
|