Skip to content

Commit 3ae260a

Browse files
authored
Merge pull request #20 from projectdiscovery/maintenance-code-refactor
code refactor
2 parents 2e0edf0 + c339cd7 commit 3ae260a

File tree

14 files changed

+416
-243
lines changed

14 files changed

+416
-243
lines changed

.github/workflows/build.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ jobs:
3232

3333
- name: Test
3434
run: go test .
35-
working-directory: .
35+
working-directory: cmd/simplehttpserver/
3636

3737
- name: Build
3838
run: go build .
39-
working-directory: .
39+
working-directory: cmd/simplehttpserver/

.goreleaser.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
builds:
22
- binary: simplehttpserver
3-
main: simplehttpserver.go
3+
main: cmd/simplehttpserver/simplehttpserver.go
44
goos:
55
- linux
66
- windows

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
FROM golang:1.14-alpine AS builder
1+
FROM golang:1.16-alpine AS builder
22
RUN apk add --no-cache git
3-
RUN GO111MODULE=auto go get -u -v github.com/projectdiscovery/simplehttpserver
3+
RUN GO111MODULE=auto go get -u -v github.com/projectdiscovery/simplehttpserver/cmd/simplehttpserver
44

55
FROM alpine:latest
66
COPY --from=builder /go/bin/simplehttpserver /usr/local/bin/

gen_cert.sh

Lines changed: 0 additions & 5 deletions
This file was deleted.

internal/runner/banner.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package runner
2+
3+
import "github.com/projectdiscovery/gologger"
4+
5+
const banner = `
6+
_ _ _ _ _
7+
___(_)_ __ ___ _ __ | | ___| |__ | |_| |_ _ __ ___ ___ _ ____ _____ _ __
8+
/ __| | '_ ' _ \| '_ \| |/ _ \ '_ \| __| __| '_ \/ __|/ _ \ '__\ \ / / _ \ '__|
9+
\__ \ | | | | | | |_) | | __/ | | | |_| |_| |_) \__ \ __/ | \ V / __/ |
10+
|___/_|_| |_| |_| .__/|_|\___|_| |_|\__|\__| .__/|___/\___|_| \_/ \___|_|
11+
|_| |_|
12+
`
13+
14+
// Version is the current version
15+
const Version = `0.0.1`
16+
17+
// showBanner is used to show the banner to the user
18+
func showBanner() {
19+
gologger.Print().Msgf("%s\n", banner)
20+
gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n")
21+
22+
gologger.Print().Msgf("Use with caution. You are responsible for your actions\n")
23+
gologger.Print().Msgf("Developers assume no liability and are not responsible for any misuse or damage.\n")
24+
}

internal/runner/doc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package runner contains the internal logic
2+
package runner

internal/runner/options.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package runner
2+
3+
import (
4+
"flag"
5+
"os"
6+
"strings"
7+
8+
"github.com/projectdiscovery/gologger"
9+
"github.com/projectdiscovery/gologger/levels"
10+
)
11+
12+
type Options struct {
13+
ListenAddress string
14+
Folder string
15+
BasicAuth string
16+
username string
17+
password string
18+
Realm string
19+
TLSCertificate string
20+
TLSKey string
21+
TLSDomain string
22+
HTTPS bool
23+
Verbose bool
24+
EnableUpload bool
25+
EnableTCP bool
26+
RulesFile string
27+
TCPWithTLS bool
28+
Version bool
29+
Silent bool
30+
}
31+
32+
// ParseOptions parses the command line options for application
33+
func ParseOptions() *Options {
34+
options := &Options{}
35+
flag.StringVar(&options.ListenAddress, "listen", "0.0.0.0:8000", "Address:Port")
36+
flag.BoolVar(&options.EnableTCP, "tcp", false, "TCP Server")
37+
flag.BoolVar(&options.TCPWithTLS, "tls", false, "Enable TCP TLS")
38+
flag.StringVar(&options.RulesFile, "rules", "", "Rules yaml file")
39+
flag.StringVar(&options.Folder, "path", ".", "Folder")
40+
flag.BoolVar(&options.EnableUpload, "upload", false, "Enable upload via PUT")
41+
flag.BoolVar(&options.HTTPS, "https", false, "HTTPS")
42+
flag.StringVar(&options.TLSCertificate, "cert", "", "HTTPS Certificate")
43+
flag.StringVar(&options.TLSKey, "key", "", "HTTPS Certificate Key")
44+
flag.StringVar(&options.TLSDomain, "domain", "local.host", "Domain")
45+
flag.BoolVar(&options.Verbose, "verbose", false, "Verbose")
46+
flag.StringVar(&options.BasicAuth, "basic-auth", "", "Basic auth (username:password)")
47+
flag.StringVar(&options.Realm, "realm", "Please enter username and password", "Realm")
48+
flag.BoolVar(&options.Version, "version", false, "Show version of the software")
49+
flag.BoolVar(&options.Silent, "silent", false, "Show only results in the output")
50+
51+
flag.Parse()
52+
53+
// Read the inputs and configure the logging
54+
options.configureOutput()
55+
56+
showBanner()
57+
58+
if options.Version {
59+
gologger.Info().Msgf("Current Version: %s\n", Version)
60+
os.Exit(0)
61+
}
62+
63+
options.validateOptions()
64+
65+
return options
66+
}
67+
68+
func (options *Options) validateOptions() {
69+
if flag.NArg() > 0 && options.Folder == "." {
70+
options.Folder = flag.Args()[0]
71+
}
72+
73+
if options.BasicAuth != "" {
74+
baTokens := strings.SplitN(options.BasicAuth, ":", 2)
75+
if len(baTokens) > 0 {
76+
options.username = baTokens[0]
77+
}
78+
if len(baTokens) > 1 {
79+
options.password = baTokens[1]
80+
}
81+
}
82+
}
83+
84+
// configureOutput configures the output on the screen
85+
func (options *Options) configureOutput() {
86+
// If the user desires verbose output, show verbose output
87+
if options.Verbose {
88+
gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)
89+
}
90+
if options.Silent {
91+
gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)
92+
}
93+
}

internal/runner/runner.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package runner
2+
3+
import (
4+
"github.com/projectdiscovery/simplehttpserver/pkg/httpserver"
5+
"github.com/projectdiscovery/simplehttpserver/pkg/tcpserver"
6+
)
7+
8+
// Runner is a client for running the enumeration process.
9+
type Runner struct {
10+
options *Options
11+
serverTCP *tcpserver.TCPServer
12+
httpServer *httpserver.HTTPServer
13+
}
14+
15+
func New(options *Options) (*Runner, error) {
16+
r := Runner{options: options}
17+
if r.options.EnableTCP {
18+
serverTCP, err := tcpserver.New(tcpserver.Options{
19+
Listen: r.options.ListenAddress,
20+
TLS: r.options.TCPWithTLS,
21+
Domain: "local.host",
22+
Verbose: r.options.Verbose,
23+
})
24+
if err != nil {
25+
return nil, err
26+
}
27+
err = serverTCP.LoadTemplate(r.options.RulesFile)
28+
if err != nil {
29+
return nil, err
30+
}
31+
r.serverTCP = serverTCP
32+
return &r, nil
33+
}
34+
35+
httpServer, err := httpserver.New(&httpserver.Options{
36+
Folder: r.options.Folder,
37+
EnableUpload: r.options.EnableUpload,
38+
ListenAddress: r.options.ListenAddress,
39+
TLS: r.options.HTTPS,
40+
Certificate: r.options.TLSCertificate,
41+
CertificateKey: r.options.TLSKey,
42+
CertificateDomain: r.options.TLSDomain,
43+
BasicAuthUsername: r.options.username,
44+
BasicAuthPassword: r.options.password,
45+
BasicAuthReal: r.options.Realm,
46+
Verbose: r.options.Verbose,
47+
})
48+
if err != nil {
49+
return nil, err
50+
}
51+
r.httpServer = httpServer
52+
53+
return &r, nil
54+
}
55+
56+
func (r *Runner) Run() error {
57+
if r.options.EnableTCP {
58+
return r.serverTCP.ListenAndServe()
59+
}
60+
61+
if r.options.HTTPS {
62+
return r.httpServer.ListenAndServeTLS()
63+
}
64+
65+
return r.httpServer.ListenAndServe()
66+
}
67+
68+
func (r *Runner) Close() error {
69+
return nil
70+
}

pkg/httpserver/authlayer.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package httpserver
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
)
7+
8+
func (t *HTTPServer) basicauthlayer(handler http.Handler) http.HandlerFunc {
9+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
10+
user, pass, ok := r.BasicAuth()
11+
if !ok || user != t.options.BasicAuthUsername || pass != t.options.BasicAuthPassword {
12+
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=\"%s\"", t.options.BasicAuthReal))
13+
w.WriteHeader(http.StatusUnauthorized)
14+
w.Write([]byte("Unauthorized.\n")) //nolint
15+
return
16+
}
17+
handler.ServeHTTP(w, r)
18+
})
19+
}

pkg/httpserver/httpserver.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package httpserver
2+
3+
import (
4+
"errors"
5+
"net"
6+
"net/http"
7+
"os"
8+
"runtime"
9+
"strconv"
10+
"syscall"
11+
12+
"github.com/projectdiscovery/gologger"
13+
"github.com/projectdiscovery/simplehttpserver/pkg/sslcert"
14+
)
15+
16+
type Options struct {
17+
Folder string
18+
EnableUpload bool
19+
ListenAddress string
20+
TLS bool
21+
Certificate string
22+
CertificateKey string
23+
CertificateDomain string
24+
BasicAuthUsername string
25+
BasicAuthPassword string
26+
BasicAuthReal string
27+
Verbose bool
28+
}
29+
30+
type HTTPServer struct {
31+
options *Options
32+
layers http.Handler
33+
listener net.Listener
34+
}
35+
36+
func New(options *Options) (*HTTPServer, error) {
37+
var h HTTPServer
38+
EnableUpload = options.EnableUpload
39+
EnableVerbose = options.Verbose
40+
layers := h.loglayer(http.FileServer(http.Dir(options.Folder)))
41+
if options.BasicAuthUsername != "" || options.BasicAuthPassword != "" {
42+
layers = h.loglayer(h.basicauthlayer(http.FileServer(http.Dir(options.Folder))))
43+
}
44+
45+
return &HTTPServer{options: options, layers: layers}, nil
46+
}
47+
48+
func (t *HTTPServer) ListenAndServe() error {
49+
var err error
50+
retry_listen:
51+
gologger.Print().Msgf("Serving %s on http://%s/...", t.options.Folder, t.options.ListenAddress)
52+
err = http.ListenAndServe(t.options.ListenAddress, t.layers)
53+
if err != nil {
54+
if isErrorAddressAlreadyInUse(err) {
55+
gologger.Print().Msgf("Can't listen on %s: %s - retrying with another port\n", t.options.ListenAddress, err)
56+
newListenAddress, err := incPort(t.options.ListenAddress)
57+
if err != nil {
58+
return err
59+
}
60+
t.options.ListenAddress = newListenAddress
61+
goto retry_listen
62+
}
63+
}
64+
return nil
65+
}
66+
67+
func (t *HTTPServer) ListenAndServeTLS() error {
68+
gologger.Print().Msgf("Serving %s on https://%s/...", t.options.Folder, t.options.ListenAddress)
69+
if t.options.Certificate == "" || t.options.CertificateKey == "" {
70+
tlsOptions := sslcert.DefaultOptions
71+
tlsOptions.Host = t.options.CertificateDomain
72+
tlsConfig, err := sslcert.NewTLSConfig(tlsOptions)
73+
if err != nil {
74+
return err
75+
}
76+
httpServer := &http.Server{
77+
Addr: t.options.ListenAddress,
78+
TLSConfig: tlsConfig,
79+
}
80+
httpServer.Handler = t.layers
81+
return httpServer.ListenAndServeTLS("", "")
82+
}
83+
return http.ListenAndServeTLS(t.options.ListenAddress, t.options.Certificate, t.options.CertificateKey, t.layers)
84+
}
85+
86+
func isErrorAddressAlreadyInUse(err error) bool {
87+
var eOsSyscall *os.SyscallError
88+
if !errors.As(err, &eOsSyscall) {
89+
return false
90+
}
91+
var errErrno syscall.Errno // doesn't need a "*" (ptr) because it's already a ptr (uintptr)
92+
if !errors.As(eOsSyscall, &errErrno) {
93+
return false
94+
}
95+
if errErrno == syscall.EADDRINUSE {
96+
return true
97+
}
98+
const WSAEADDRINUSE = 10048
99+
if runtime.GOOS == "windows" && errErrno == WSAEADDRINUSE {
100+
return true
101+
}
102+
return false
103+
}
104+
105+
func incPort(address string) (string, error) {
106+
addrOrig, portOrig, err := net.SplitHostPort(address)
107+
if err != nil {
108+
return address, err
109+
}
110+
111+
// increment port
112+
portNumber, err := strconv.Atoi(portOrig)
113+
if err != nil {
114+
return address, err
115+
}
116+
portNumber++
117+
newPort := strconv.FormatInt(int64(portNumber), 10)
118+
119+
return net.JoinHostPort(addrOrig, newPort), nil
120+
}

0 commit comments

Comments
 (0)