<commit_before>package gobustervhost

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"net/url"
	"strings"
	"text/tabwriter"

	"github.com/OJ/gobuster/v3/helper"
	"github.com/OJ/gobuster/v3/libgobuster"
	"github.com/google/uuid"
)

// GobusterVhost is the main type to implement the interface
type GobusterVhost struct {
	options    *OptionsVhost
	globalopts *libgobuster.Options
	http       *libgobuster.HTTPClient
	domain     string
	baseline1  []byte
	baseline2  []byte
}

// NewGobusterVhost creates a new initialized GobusterDir
func NewGobusterVhost(globalopts *libgobuster.Options, opts *OptionsVhost) (*GobusterVhost, error) {
	if globalopts == nil {
		return nil, fmt.Errorf("please provide valid global options")
	}

	if opts == nil {
		return nil, fmt.Errorf("please provide valid plugin options")
	}

	g := GobusterVhost{
		options:    opts,
		globalopts: globalopts,
	}

	basicOptions := libgobuster.BasicHTTPOptions{
		Proxy:           opts.Proxy,
		Timeout:         opts.Timeout,
		UserAgent:       opts.UserAgent,
		NoTLSValidation: opts.NoTLSValidation,
	}

	httpOpts := libgobuster.HTTPOptions{
		BasicHTTPOptions: basicOptions,
		FollowRedirect:   opts.FollowRedirect,
		Username:         opts.Username,
		Password:         opts.Password,
		Headers:          opts.Headers,
		Cookies:          opts.Cookies,
		Method:           opts.Method,
	}

	h, err := libgobuster.NewHTTPClient(&httpOpts)
	if err != nil {
		return nil, err
	}
	g.http = h
	return &g, nil
}

// Name should return the name of the plugin
func (v *GobusterVhost) Name() string {
	return "VHOST enumeration"
}

// RequestsPerRun returns the number of requests this plugin makes per single wordlist item
func (v *GobusterVhost) RequestsPerRun() int {
	return 1
}

// PreRun is the pre run implementation of gobusterdir
func (v *GobusterVhost) PreRun(ctx context.Context) error {
	// add trailing slash
	if !strings.HasSuffix(v.options.URL, "/") {
		v.options.URL = fmt.Sprintf("%s/", v.options.URL)
	}

	urlParsed, err := url.Parse(v.options.URL)
	if err != nil {
		return fmt.Errorf("invalid url %s: %w", v.options.URL, err)
	}
	v.domain = urlParsed.Host

	// request default vhost for baseline1
	_, _, _, tmp, err := v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{ReturnBody: true})
	if err != nil {
		return fmt.Errorf("unable to connect to %s: %w", v.options.URL, err)
	}
	v.baseline1 = tmp

	// request non existent vhost for baseline2
	subdomain := fmt.Sprintf("%s.%s", uuid.New(), v.domain)
	_, _, _, tmp, err = v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{Host: subdomain, ReturnBody: true})
	if err != nil {
		return fmt.Errorf("unable to connect to %s: %w", v.options.URL, err)
	}
	v.baseline2 = tmp
	return nil
}

// Run is the process implementation of gobusterdir
func (v *GobusterVhost) Run(ctx context.Context, word string, resChannel chan<- libgobuster.Result) error {
	var subdomain string
	if v.options.AppendDomain {
		subdomain = fmt.Sprintf("%s.%s", word, v.domain)
	} else {
		// wordlist needs to include full domains
		subdomain = word
	}
	status, size, header, body, err := v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{Host: subdomain, ReturnBody: true})
	if err != nil {
		return err
	}

	// subdomain must not match default vhost and non existent vhost
	// or verbose mode is enabled
	found := !bytes.Equal(body, v.baseline1) && !bytes.Equal(body, v.baseline2)
	if found || v.globalopts.Verbose {
		resultStatus := false
		if found && !helper.SliceContains(v.options.ExcludeLength, int(size)) {
			resultStatus = true
		}
		resChannel <- Result{
			Found:      resultStatus,
			Vhost:      subdomain,
			StatusCode: *status,
			Size:       size,
			Header:     header,<commit_msg>another try<commit_after>package gobustervhost

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"net/url"
	"strings"
	"text/tabwriter"

	"github.com/OJ/gobuster/v3/helper"
	"github.com/OJ/gobuster/v3/libgobuster"
	"github.com/google/uuid"
)

// GobusterVhost is the main type to implement the interface
type GobusterVhost struct {
	options    *OptionsVhost
	globalopts *libgobuster.Options
	http       *libgobuster.HTTPClient
	domain     string
	baseline1  []byte
	baseline2  []byte
}

// NewGobusterVhost creates a new initialized GobusterDir
func NewGobusterVhost(globalopts *libgobuster.Options, opts *OptionsVhost) (*GobusterVhost, error) {
	if globalopts == nil {
		return nil, fmt.Errorf("please provide valid global options")
	}

	if opts == nil {
		return nil, fmt.Errorf("please provide valid plugin options")
	}

	g := GobusterVhost{
		options:    opts,
		globalopts: globalopts,
	}

	basicOptions := libgobuster.BasicHTTPOptions{
		Proxy:           opts.Proxy,
		Timeout:         opts.Timeout,
		UserAgent:       opts.UserAgent,
		NoTLSValidation: opts.NoTLSValidation,
	}

	httpOpts := libgobuster.HTTPOptions{
		BasicHTTPOptions: basicOptions,
		FollowRedirect:   opts.FollowRedirect,
		Username:         opts.Username,
		Password:         opts.Password,
		Headers:          opts.Headers,
		Cookies:          opts.Cookies,
		Method:           opts.Method,
	}

	h, err := libgobuster.NewHTTPClient(&httpOpts)
	if err != nil {
		return nil, err
	}
	g.http = h
	return &g, nil
}

// Name should return the name of the plugin
func (v *GobusterVhost) Name() string {
	return "VHOST enumeration"
}

// RequestsPerRun returns the number of requests this plugin makes per single wordlist item
func (v *GobusterVhost) RequestsPerRun() int {
	return 1
}

// PreRun is the pre run implementation of gobusterdir
func (v *GobusterVhost) PreRun(ctx context.Context) error {
	// add trailing slash
	if !strings.HasSuffix(v.options.URL, "/") {
		v.options.URL = fmt.Sprintf("%s/", v.options.URL)
	}

	urlParsed, err := url.Parse(v.options.URL)
	if err != nil {
		return fmt.Errorf("invalid url %s: %w", v.options.URL, err)
	}
	v.domain = urlParsed.Host

	// request default vhost for baseline1
	_, _, _, tmp, err := v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{ReturnBody: true})
	if err != nil {
		return fmt.Errorf("unable to connect to %s: %w", v.options.URL, err)
	}
	v.baseline1 = tmp

	// request non existent vhost for baseline2
	subdomain := fmt.Sprintf("%s.%s", uuid.New(), v.domain)
	_, _, _, tmp, err = v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{Host: subdomain, ReturnBody: true})
	if err != nil {
		return fmt.Errorf("unable to connect to %s: %w", v.options.URL, err)
	}
	v.baseline2 = tmp
	return nil
}

// Run is the process implementation of gobusterdir
func (v *GobusterVhost) Run(ctx context.Context, word string, resChannel chan<- libgobuster.Result) error {
	var subdomain string
	if v.options.AppendDomain {
		subdomain = fmt.Sprintf("%s.%s", word, v.domain)
	} else {
		// wordlist needs to include full domains
		subdomain = word
	}
	status, size, header, body, err := v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{Host: subdomain, ReturnBody: true})
	if err != nil {
		return err
	}

	// subdomain must not match default vhost and non existent vhost
	// or verbose mode is enabled
	found := !bytes.Equal(body, v.baseline1) && !bytes.Equal(body, v.baseline2)
	if (found && !helper.SliceContains(v.options.ExcludeLength, int(size))) || v.globalopts.Verbose {
		resultStatus := false
		if found {
			resultStatus = true
		}
		resChannel <- Result{
			Found:      resultStatus,
			Vhost:      subdomain,
			StatusCode: *status,
			Size:       size,
			Header:     header,
{'fraction_non_alphanumeric': 0.11904761904761904, 'fraction_numerical': 0.0029427501337613697, 'mean_word_length': 3.587116564417178, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1530477', 'n_tokens_mistral': 2686, 'n_tokens_neox': 2546, 'n_words': 686}