diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..4d5617f
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,34 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+
+ # Maintain dependencies for GitHub Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ commit-message:
+ prefix: "chore"
+ include: "scope"
+
+ # Maintain dependencies for go modules
+ - package-ecosystem: "gomod"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ commit-message:
+ prefix: "chore"
+ include: "scope"
+
+ # Maintain dependencies for docker
+ - package-ecosystem: "docker"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ commit-message:
+ prefix: "chore"
+ include: "scope"
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index a157f84..4179b91 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -6,27 +6,18 @@ on:
pull_request:
jobs:
- lint:
- name: golangci-lint
+ golangci-lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Run golangci-lint
- uses: golangci/golangci-lint-action@v2.2.0
+ uses: golangci/golangci-lint-action@v2.4.0
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.31
args: --timeout 5m
- # Optional: working directory, useful for monorepos
- # working-directory: somedir
-
- # Optional: golangci-lint command line arguments.
- # args: --issues-exit-code=0
-
- # Optional: show only new issues if it's a pull request. The default value is `false`.
- # only-new-issues: true
build:
name: Build
runs-on: ubuntu-latest
@@ -41,8 +32,8 @@ jobs:
- name: Test
run: go test .
- working-directory: .
+ working-directory: cmd/simplehttpserver
- name: Build
run: go build .
- working-directory: .
+ working-directory: cmd/simplehttpserver/
diff --git a/.gitignore b/.gitignore
index dabd7a7..4f9c0f3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,2 @@
*.exe
-simplehttpserver
+cmd/simplehttpserver/simplehttpserver
diff --git a/.golangci.yml b/.golangci.yml
index d5e9089..31b66f3 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -41,8 +41,6 @@ linters-settings:
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
# lll:
# line-length: 140
- maligned:
- suggest-new: true
misspell:
locale: US
nolintlint:
@@ -73,14 +71,12 @@ linters:
- gosimple
- govet
- ineffassign
- - interfacer
- - maligned
- misspell
- nakedret
- noctx
- nolintlint
- rowserrcheck
- - scopelint
+ - exportloopref
- staticcheck
- structcheck
- stylecheck
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 50bb74f..f6cb1e2 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -1,6 +1,6 @@
builds:
- binary: simplehttpserver
- main: simplehttpserver.go
+ main: cmd/simplehttpserver/simplehttpserver.go
goos:
- linux
- windows
diff --git a/Dockerfile b/Dockerfile
index a01590c..0919a3c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,8 +1,9 @@
-FROM golang:1.14-alpine AS builder
+FROM golang:1.16-alpine AS builder
RUN apk add --no-cache git
-RUN GO111MODULE=auto go get -u -v github.com/projectdiscovery/simplehttpserver
+RUN GO111MODULE=auto go get -u -v github.com/projectdiscovery/simplehttpserver/cmd/simplehttpserver
FROM alpine:latest
+RUN apk add --no-cache bind-tools ca-certificates
COPY --from=builder /go/bin/simplehttpserver /usr/local/bin/
ENTRYPOINT ["simplehttpserver"]
diff --git a/README.md b/README.md
index f88164f..426fd74 100644
--- a/README.md
+++ b/README.md
@@ -1,65 +1,44 @@
-
-
-
-
+SimpleHTTPserver
+Go alternative of python SimpleHTTPServer
-[](https://opensource.org/licenses/MIT)
-[](https://goreportcard.com/report/github.com/projectdiscovery/simplehttpserver)
-[](https://github.com/projectdiscovery/simplehttpserver/issues)
-[](https://github.com/projectdiscovery/simplehttpserver/releases)
-[](https://twitter.com/pdiscoveryio)
-[](https://hub.docker.com/r/projectdiscovery/simplehttpserver)
-[](https://discord.gg/KECAGdH)
-simplehttpserver is a go enhanced version of the well known python simplehttpserver.
+
+
+
+
+
+
+
+
-# Resources
+
+ Features •
+ Usage •
+ Installation •
+ Run SimpleHTTPserver •
+ Join Discord
+
-- [Features](#features)
-- [Usage](#usage)
-- [Installation Instructions](#installation-instructions)
-- [Running simplehttpserver](#running-simplehttpserver-in-the-current-folder )
-- [Thanks](#thanks)
-
-# Features
-
-
-
-
-
-
- - File server in arbitrary directory
- - Full request/response dump
- - Configurable ip address and listening port
-
-
-# Installation Instructions
+---
+SimpleHTTPserver is a go enhanced version of the well known python simplehttpserver with in addition a fully customizable TCP server, both supporting TLS.
-### From Binary
-The installation is easy. You can download the pre-built binaries for your platform from the [Releases](https://github.com/projectdiscovery/simplehttpserver/releases/) page. Extract them using tar, move it to your `$PATH`and you're ready to go.
+# Features
-```sh
-Download latest binary from https://github.com/projectdiscovery/simplehttpserver/releases
+- HTTPS support
+- File server in arbitrary directory
+- Full request/response dump
+- Configurable ip address and listening port
+- Configurable HTTP/TCP server with customizable response via YAML template
-▶ tar -xvf simplehttpserver-linux-amd64.tar
-▶ mv simplehttpserver-linux-amd64 /usr/local/bin/simplehttpserver
-▶ simplehttpserver -h
-```
-### From Source
+# Installing SimpleHTTPserver
-simplehttpserver requires **go1.14+** to install successfully. Run the following command to get the repo -
+SimpleHTTPserver requires **go1.14+** to install successfully. Run the following command to get the repo -
```sh
-▶ GO111MODULE=on go get -v github.com/projectdiscovery/simplehttpserver
-```
-
-### From Github
-
-```sh
-▶ git clone https://github.com/projectdiscovery/simplehttpserver.git; cd simplehttpserver; go build; mv simplehttpserver /usr/local/bin/; simplehttpserver -h
+▶ GO111MODULE=on go get -v github.com/projectdiscovery/cmd/simplehttpserver
```
# Usage
@@ -70,11 +49,23 @@ simplehttpserver -h
This will display help for the tool. Here are all the switches it supports.
-| Flag | Description | Example |
-| ------ | ---------------------------------------------------- | --------------------------------------- |
-| listen | Configure listening ip:port (default 127.0.0.1:8000) | simplehttpserver -listen 127.0.0.1:8000 |
-| path | Fileserver folder (default current directory) | simplehttpserver -path /var/docs |
-| v | Verbose (dump request/response, default false) | simplehttpserver -v |
+| Flag | Description | Example |
+| ----------- | -------------------------------------------------------------------- | ------------------------------------------------- |
+| listen | Configure listening ip:port (default 127.0.0.1:8000) | simplehttpserver -listen 127.0.0.1:8000 |
+| path | Fileserver folder (default current directory) | simplehttpserver -path /var/docs |
+| verbose | Verbose (dump request/response, default false) | simplehttpserver -v |
+| tcp | TCP server (default 127.0.0.1:8000) | simplehttpserver -tcp 127.0.0.1:8000 |
+| tls | Enable TLS for TCP server | simplehttpserver -tls |
+| rules | File containing yaml rules | simplehttpserver -rules rule.yaml | |
+| upload | Enable file upload in case of http server | simplehttpserver -upload |
+| https | Enable HTTPS in case of http server | simplehttpserver -https |
+| cert | HTTPS/TLS certificate (self generated if not specified) | simplehttpserver -cert cert.pem |
+| key | HTTPS/TLS certificate private key (self generated if not specified) | simplehttpserver -key cert.key |
+| domain | Domain name to use for the self-generated certificate | simplehttpserver -domain projectdiscovery.io |
+| basic-auth | Basic auth (username:password) | simplehttpserver -basic-auth user:password |
+| realm | Basic auth message | simplehttpserver -realm "insert the credentials" |
+| version | Show version | simplehttpserver -version |
+| silent | Show only results | simplehttpserver -silent |
### Running simplehttpserver in the current folder
@@ -87,6 +78,91 @@ This will run the tool exposing the current directory on port 8000
2021/01/11 21:41:15 [::1]:50181 "GET /favicon.ico HTTP/1.1" 404 19
```
+### Running simplehttpserver in the current folder with HTTPS
+
+This will run the tool exposing the current directory on port 8000 over HTTPS with user provided certificate:
+
+```sh
+▶ simplehttpserver -https -cert cert.pen -key cert.key
+2021/01/11 21:40:48 Serving . on http://0.0.0.0:8000/...
+2021/01/11 21:41:15 [::1]:50181 "GET / HTTP/1.1" 200 383
+2021/01/11 21:41:15 [::1]:50181 "GET /favicon.ico HTTP/1.1" 404 19
+```
+
+Instead, to run with self-signed certificate and specific domain name:
+```sh
+▶ simplehttpserver -https -domain localhost
+2021/01/11 21:40:48 Serving . on http://0.0.0.0:8000/...
+2021/01/11 21:41:15 [::1]:50181 "GET / HTTP/1.1" 200 383
+2021/01/11 21:41:15 [::1]:50181 "GET /favicon.ico HTTP/1.1" 404 19
+```
+
+### Running simplehttpserver with basic auth and file upload
+
+This will run the tool and will request the user to enter username and password before authorizing file uploads
+
+```sh
+▶ simplehttpserver -basic-auth root:root -upload
+2021/01/11 21:40:48 Serving . on http://0.0.0.0:8000/...
+```
+
+To upload files use the following curl request with basic auth header:
+```sh
+▶ curl -v --user 'root:root' --upload-file file.txt http://localhost:8000/file.txt
+```
+
+### Running TCP server with custom responses
+
+This will run the tool as TLS TCP server and enable custom responses based on YAML templates:
+
+```sh
+▶ simplehttpserver -rule rules.yaml -tcp -tls -domain localhost
+```
+
+The rules are written as follows:
+```yaml
+rules:
+ - match: regex
+ response: response data
+```
+
+For example to handle two different paths simulating an HTTP server or SMTP commands:
+```yaml
+rules:
+ # HTTP Requests
+ - match: GET /path1
+ response: |
+ HTTP/1.0 200 OK
+ Server: httpd/2.0
+ x-frame-options: SAMEORIGIN
+ x-xss-protection: 1; mode=block
+ Date: Fri, 16 Apr 2021 14:30:32 GMT
+ Content-Type: text/html
+ Connection: close
+
+
+
+ - match: GET /path2
+ response: |
+ HTTP/1.0 404 OK
+ Server: httpd/2.0
+
+ Not found
+ # SMTP Commands
+ - match: "EHLO example.com"
+ response: |
+ 250-localhost Nice to meet you, [127.0.0.1]
+ 250-PIPELINING
+ 250-8BITMIME
+ 250-SMTPUTF8
+ 250-AUTH LOGIN PLAIN
+ 250 STARTTLS
+ - match: "MAIL FROM: "
+ response: 250 Accepted
+ - match: "RCPT TO: "
+ response: 250 Accepted
+```
+
# Thanks
-simplehttpserver is made with 🖤 by the [projectdiscovery](https://projectdiscovery.io) team. Community contributions have made the project what it is. See the **[Thanks.md](https://github.com/projectdiscovery/simplehttpserver/blob/master/THANKS.md)** file for more details.
+SimpleHTTPserver is made with 🖤 by the [projectdiscovery](https://projectdiscovery.io) team.
diff --git a/cmd/simplehttpserver/simplehttpserver.go b/cmd/simplehttpserver/simplehttpserver.go
new file mode 100644
index 0000000..ed82b40
--- /dev/null
+++ b/cmd/simplehttpserver/simplehttpserver.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/simplehttpserver/internal/runner"
+)
+
+func main() {
+ // Parse the command line flags and read config files
+ options := runner.ParseOptions()
+ r, err := runner.New(options)
+ if err != nil {
+ gologger.Fatal().Msgf("Could not create runner: %s\n", err)
+ }
+
+ if err := r.Run(); err != nil {
+ gologger.Info().Msgf("%s\n", err)
+ }
+ defer r.Close() //nolint
+}
diff --git a/go.mod b/go.mod
index 1876095..f4c4a9d 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,10 @@
module github.com/projectdiscovery/simplehttpserver
-go 1.15
+go 1.14
+
+require (
+ github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
+ github.com/projectdiscovery/gologger v1.1.4
+ github.com/projectdiscovery/sslcert v0.0.0-20210416140253-8f56bec1bb5e
+ gopkg.in/yaml.v2 v2.4.0
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..aafe4a2
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,41 @@
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
+github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
+github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/projectdiscovery/gologger v1.1.4 h1:qWxGUq7ukHWT849uGPkagPKF3yBPYAsTtMKunQ8O2VI=
+github.com/projectdiscovery/gologger v1.1.4/go.mod h1:Bhb6Bdx2PV1nMaFLoXNBmHIU85iROS9y1tBuv7T5pMY=
+github.com/projectdiscovery/sslcert v0.0.0-20210416140253-8f56bec1bb5e h1:IZa08TUGbU7I0HUb9QQt/8wuu2fPZqfnMXwWhtMxei8=
+github.com/projectdiscovery/sslcert v0.0.0-20210416140253-8f56bec1bb5e/go.mod h1:jSp8W5zIkNPxAqVdcoFlfv0K5cqogTe65fMinR0Fvuk=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/runner/banner.go b/internal/runner/banner.go
new file mode 100644
index 0000000..f727190
--- /dev/null
+++ b/internal/runner/banner.go
@@ -0,0 +1,24 @@
+package runner
+
+import "github.com/projectdiscovery/gologger"
+
+const banner = `
+ _____ _ __ __ __________________
+ / ___/(_)___ ___ ____ / /__ / / / /_ __/_ __/ __ \________ ______ _____ _____
+ \__ \/ / __ -__ \/ __ \/ / _ \/ /_/ / / / / / / /_/ / ___/ _ \/ ___/ | / / _ \/ ___/
+ ___/ / / / / / / / /_/ / / __/ __ / / / / / / ____(__ ) __/ / | |/ / __/ /
+/____/_/_/ /_/ /_/ .___/_/\___/_/ /_/ /_/ /_/ /_/ /____/\___/_/ |___/\___/_/
+ /_/ - v0.0.2
+`
+
+// Version is the current version
+const Version = `0.0.2`
+
+// showBanner is used to show the banner to the user
+func showBanner() {
+ gologger.Print().Msgf("%s\n", banner)
+ gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n")
+
+ gologger.Print().Msgf("Use with caution. You are responsible for your actions\n")
+ gologger.Print().Msgf("Developers assume no liability and are not responsible for any misuse or damage.\n")
+}
diff --git a/internal/runner/doc.go b/internal/runner/doc.go
new file mode 100644
index 0000000..6d6e364
--- /dev/null
+++ b/internal/runner/doc.go
@@ -0,0 +1,2 @@
+// Package runner contains the internal logic
+package runner
diff --git a/internal/runner/options.go b/internal/runner/options.go
new file mode 100644
index 0000000..bf69db3
--- /dev/null
+++ b/internal/runner/options.go
@@ -0,0 +1,104 @@
+package runner
+
+import (
+ "flag"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/gologger/levels"
+)
+
+// Options of the tool
+type Options struct {
+ ListenAddress string
+ Folder string
+ BasicAuth string
+ username string
+ password string
+ Realm string
+ TLSCertificate string
+ TLSKey string
+ TLSDomain string
+ HTTPS bool
+ Verbose bool
+ EnableUpload bool
+ EnableTCP bool
+ RulesFile string
+ TCPWithTLS bool
+ Version bool
+ Silent bool
+}
+
+// ParseOptions parses the command line options for application
+func ParseOptions() *Options {
+ options := &Options{}
+ flag.StringVar(&options.ListenAddress, "listen", "0.0.0.0:8000", "Address:Port")
+ flag.BoolVar(&options.EnableTCP, "tcp", false, "TCP Server")
+ flag.BoolVar(&options.TCPWithTLS, "tls", false, "Enable TCP TLS")
+ flag.StringVar(&options.RulesFile, "rules", "", "Rules yaml file")
+ flag.StringVar(&options.Folder, "path", ".", "Folder")
+ flag.BoolVar(&options.EnableUpload, "upload", false, "Enable upload via PUT")
+ flag.BoolVar(&options.HTTPS, "https", false, "HTTPS")
+ flag.StringVar(&options.TLSCertificate, "cert", "", "HTTPS Certificate")
+ flag.StringVar(&options.TLSKey, "key", "", "HTTPS Certificate Key")
+ flag.StringVar(&options.TLSDomain, "domain", "local.host", "Domain")
+ flag.BoolVar(&options.Verbose, "verbose", false, "Verbose")
+ flag.StringVar(&options.BasicAuth, "basic-auth", "", "Basic auth (username:password)")
+ flag.StringVar(&options.Realm, "realm", "Please enter username and password", "Realm")
+ flag.BoolVar(&options.Version, "version", false, "Show version of the software")
+ flag.BoolVar(&options.Silent, "silent", false, "Show only results in the output")
+
+ flag.Parse()
+
+ // Read the inputs and configure the logging
+ options.configureOutput()
+
+ showBanner()
+
+ if options.Version {
+ gologger.Info().Msgf("Current Version: %s\n", Version)
+ os.Exit(0)
+ }
+
+ options.validateOptions()
+
+ return options
+}
+
+func (options *Options) validateOptions() {
+ if flag.NArg() > 0 && options.Folder == "." {
+ options.Folder = flag.Args()[0]
+ }
+
+ if options.BasicAuth != "" {
+ baTokens := strings.SplitN(options.BasicAuth, ":", 2)
+ if len(baTokens) > 0 {
+ options.username = baTokens[0]
+ }
+ if len(baTokens) > 1 {
+ options.password = baTokens[1]
+ }
+ }
+}
+
+// configureOutput configures the output on the screen
+func (options *Options) configureOutput() {
+ // If the user desires verbose output, show verbose output
+ if options.Verbose {
+ gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)
+ }
+ if options.Silent {
+ gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)
+ }
+}
+
+// FolderAbsPath of the fileserver folder
+func (options *Options) FolderAbsPath() string {
+ abspath, err := filepath.Abs(options.Folder)
+ if err != nil {
+ return options.Folder
+ }
+ return abspath
+}
diff --git a/internal/runner/runner.go b/internal/runner/runner.go
new file mode 100644
index 0000000..7d69e25
--- /dev/null
+++ b/internal/runner/runner.go
@@ -0,0 +1,98 @@
+package runner
+
+import (
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/simplehttpserver/pkg/binder"
+ "github.com/projectdiscovery/simplehttpserver/pkg/httpserver"
+ "github.com/projectdiscovery/simplehttpserver/pkg/tcpserver"
+)
+
+// Runner is a client for running the enumeration process.
+type Runner struct {
+ options *Options
+ serverTCP *tcpserver.TCPServer
+ httpServer *httpserver.HTTPServer
+}
+
+// New instance of runner
+func New(options *Options) (*Runner, error) {
+ r := Runner{options: options}
+ // Check if the process can listen on the specified ip:port
+ if !binder.CanListenOn(r.options.ListenAddress) {
+ newListenAddress, err := binder.GetRandomListenAddress(r.options.ListenAddress)
+ if err != nil {
+ return nil, err
+ }
+ gologger.Print().Msgf("Can't listen on %s: %s - Using %s\n", r.options.ListenAddress, err, newListenAddress)
+ r.options.ListenAddress = newListenAddress
+ }
+
+ if r.options.EnableTCP {
+ serverTCP, err := tcpserver.New(&tcpserver.Options{
+ Listen: r.options.ListenAddress,
+ TLS: r.options.TCPWithTLS,
+ Domain: "local.host",
+ Verbose: r.options.Verbose,
+ })
+ if err != nil {
+ return nil, err
+ }
+ err = serverTCP.LoadTemplate(r.options.RulesFile)
+ if err != nil {
+ return nil, err
+ }
+ r.serverTCP = serverTCP
+ return &r, nil
+ }
+
+ httpServer, err := httpserver.New(&httpserver.Options{
+ Folder: r.options.Folder,
+ EnableUpload: r.options.EnableUpload,
+ ListenAddress: r.options.ListenAddress,
+ TLS: r.options.HTTPS,
+ Certificate: r.options.TLSCertificate,
+ CertificateKey: r.options.TLSKey,
+ CertificateDomain: r.options.TLSDomain,
+ BasicAuthUsername: r.options.username,
+ BasicAuthPassword: r.options.password,
+ BasicAuthReal: r.options.Realm,
+ Verbose: r.options.Verbose,
+ })
+ if err != nil {
+ return nil, err
+ }
+ r.httpServer = httpServer
+
+ return &r, nil
+}
+
+// Run logic
+func (r *Runner) Run() error {
+ if r.options.EnableTCP {
+ gologger.Print().Msgf("Serving TCP rule based server on tcp://%s", r.options.ListenAddress)
+ return r.serverTCP.ListenAndServe()
+ }
+
+ if r.options.HTTPS {
+ gologger.Print().Msgf("Serving %s on https://%s/", r.options.FolderAbsPath(), r.options.ListenAddress)
+ return r.httpServer.ListenAndServeTLS()
+ }
+
+ gologger.Print().Msgf("Serving %s on http://%s/", r.options.FolderAbsPath(), r.options.ListenAddress)
+ return r.httpServer.ListenAndServe()
+}
+
+// Close the listening services
+func (r *Runner) Close() error {
+ if r.serverTCP != nil {
+ if err := r.serverTCP.Close(); err != nil {
+ return err
+ }
+ }
+ if r.httpServer != nil {
+ if err := r.httpServer.Close(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/pkg/binder/binder.go b/pkg/binder/binder.go
new file mode 100644
index 0000000..549e9d9
--- /dev/null
+++ b/pkg/binder/binder.go
@@ -0,0 +1,36 @@
+package binder
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/phayes/freeport"
+ "github.com/projectdiscovery/gologger"
+)
+
+// CanListenOn the specified address
+func CanListenOn(address string) bool {
+ listener, err := net.Listen("tcp4", address)
+ if err != nil {
+ return false
+ }
+ if err := listener.Close(); err != nil {
+ gologger.Info().Msgf("%s\n", err)
+ }
+ return true
+}
+
+// GetRandomListenAddress from the specified one
+func GetRandomListenAddress(currentAddress string) (string, error) {
+ addrOrig, _, err := net.SplitHostPort(currentAddress)
+ if err != nil {
+ return "", err
+ }
+
+ newPort, err := freeport.GetFreePort()
+ if err != nil {
+ return "", err
+ }
+
+ return net.JoinHostPort(addrOrig, fmt.Sprintf("%d", newPort)), nil
+}
diff --git a/pkg/binder/doc.go b/pkg/binder/doc.go
new file mode 100644
index 0000000..709dc10
--- /dev/null
+++ b/pkg/binder/doc.go
@@ -0,0 +1,2 @@
+// Package binder contains binding helpers
+package binder
diff --git a/pkg/httpserver/authlayer.go b/pkg/httpserver/authlayer.go
new file mode 100644
index 0000000..f2eff4b
--- /dev/null
+++ b/pkg/httpserver/authlayer.go
@@ -0,0 +1,19 @@
+package httpserver
+
+import (
+ "fmt"
+ "net/http"
+)
+
+func (t *HTTPServer) basicauthlayer(handler http.Handler) http.HandlerFunc {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ user, pass, ok := r.BasicAuth()
+ if !ok || user != t.options.BasicAuthUsername || pass != t.options.BasicAuthPassword {
+ w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=\"%s\"", t.options.BasicAuthReal))
+ w.WriteHeader(http.StatusUnauthorized)
+ w.Write([]byte("Unauthorized.\n")) //nolint
+ return
+ }
+ handler.ServeHTTP(w, r)
+ })
+}
diff --git a/pkg/httpserver/doc.go b/pkg/httpserver/doc.go
new file mode 100644
index 0000000..5344c9f
--- /dev/null
+++ b/pkg/httpserver/doc.go
@@ -0,0 +1,2 @@
+// Package httpserver contains the http server logic
+package httpserver
diff --git a/pkg/httpserver/httpserver.go b/pkg/httpserver/httpserver.go
new file mode 100644
index 0000000..1747017
--- /dev/null
+++ b/pkg/httpserver/httpserver.go
@@ -0,0 +1,70 @@
+package httpserver
+
+import (
+ "net/http"
+
+ "github.com/projectdiscovery/sslcert"
+)
+
+// Options of the http server
+type Options struct {
+ Folder string
+ EnableUpload bool
+ ListenAddress string
+ TLS bool
+ Certificate string
+ CertificateKey string
+ CertificateDomain string
+ BasicAuthUsername string
+ BasicAuthPassword string
+ BasicAuthReal string
+ Verbose bool
+}
+
+// HTTPServer instance
+type HTTPServer struct {
+ options *Options
+ layers http.Handler
+}
+
+// New http server instance with options
+func New(options *Options) (*HTTPServer, error) {
+ var h HTTPServer
+ EnableUpload = options.EnableUpload
+ EnableVerbose = options.Verbose
+ layers := h.loglayer(http.FileServer(http.Dir(options.Folder)))
+ if options.BasicAuthUsername != "" || options.BasicAuthPassword != "" {
+ layers = h.loglayer(h.basicauthlayer(http.FileServer(http.Dir(options.Folder))))
+ }
+
+ return &HTTPServer{options: options, layers: layers}, nil
+}
+
+// ListenAndServe requests over http
+func (t *HTTPServer) ListenAndServe() error {
+ return http.ListenAndServe(t.options.ListenAddress, t.layers)
+}
+
+// ListenAndServeTLS requests over https
+func (t *HTTPServer) ListenAndServeTLS() error {
+ if t.options.Certificate == "" || t.options.CertificateKey == "" {
+ tlsOptions := sslcert.DefaultOptions
+ tlsOptions.Host = t.options.CertificateDomain
+ tlsConfig, err := sslcert.NewTLSConfig(tlsOptions)
+ if err != nil {
+ return err
+ }
+ httpServer := &http.Server{
+ Addr: t.options.ListenAddress,
+ TLSConfig: tlsConfig,
+ }
+ httpServer.Handler = t.layers
+ return httpServer.ListenAndServeTLS("", "")
+ }
+ return http.ListenAndServeTLS(t.options.ListenAddress, t.options.Certificate, t.options.CertificateKey, t.layers)
+}
+
+// Close the service
+func (t *HTTPServer) Close() error {
+ return nil
+}
diff --git a/pkg/httpserver/loglayer.go b/pkg/httpserver/loglayer.go
new file mode 100644
index 0000000..1e64b8f
--- /dev/null
+++ b/pkg/httpserver/loglayer.go
@@ -0,0 +1,72 @@
+package httpserver
+
+import (
+ "bytes"
+ "io/ioutil"
+ "net/http"
+ "net/http/httputil"
+ "path"
+
+ "github.com/projectdiscovery/gologger"
+)
+
+// Convenience globals
+var (
+ EnableUpload bool
+ EnableVerbose bool
+)
+
+func (t *HTTPServer) loglayer(handler http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fullRequest, _ := httputil.DumpRequest(r, true)
+ lrw := newLoggingResponseWriter(w)
+ handler.ServeHTTP(lrw, r)
+
+ // Handles file write if enabled
+ if EnableUpload && r.Method == http.MethodPut {
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ gologger.Print().Msgf("%s\n", err)
+ }
+ err = handleUpload(path.Base(r.URL.Path), data)
+ if err != nil {
+ gologger.Print().Msgf("%s\n", err)
+ }
+ }
+
+ if EnableVerbose {
+ headers := new(bytes.Buffer)
+ lrw.Header().Write(headers) //nolint
+ gologger.Print().Msgf("\nRemote Address: %s\n%s\n%s %d %s\n%s\n%s\n", r.RemoteAddr, string(fullRequest), r.Proto, lrw.statusCode, http.StatusText(lrw.statusCode), headers.String(), string(lrw.Data))
+ } else {
+ gologger.Print().Msgf("%s \"%s %s %s\" %d %d", r.RemoteAddr, r.Method, r.URL, r.Proto, lrw.statusCode, len(lrw.Data))
+ }
+ })
+}
+
+type loggingResponseWriter struct {
+ http.ResponseWriter
+ statusCode int
+ Data []byte
+}
+
+func newLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
+ return &loggingResponseWriter{w, http.StatusOK, []byte{}}
+}
+
+// Write the data
+func (lrw *loggingResponseWriter) Write(data []byte) (int, error) {
+ lrw.Data = append(lrw.Data, data...)
+ return lrw.ResponseWriter.Write(data)
+}
+
+// Header of the response
+func (lrw *loggingResponseWriter) Header() http.Header {
+ return lrw.ResponseWriter.Header()
+}
+
+// WriteHeader status code
+func (lrw *loggingResponseWriter) WriteHeader(code int) {
+ lrw.statusCode = code
+ lrw.ResponseWriter.WriteHeader(code)
+}
diff --git a/pkg/httpserver/uploadlayer.go b/pkg/httpserver/uploadlayer.go
new file mode 100644
index 0000000..2663fba
--- /dev/null
+++ b/pkg/httpserver/uploadlayer.go
@@ -0,0 +1,7 @@
+package httpserver
+
+import "io/ioutil"
+
+func handleUpload(file string, data []byte) error {
+ return ioutil.WriteFile(file, data, 0655)
+}
diff --git a/pkg/tcpserver/doc.go b/pkg/tcpserver/doc.go
new file mode 100644
index 0000000..4ab6d69
--- /dev/null
+++ b/pkg/tcpserver/doc.go
@@ -0,0 +1,2 @@
+// Package tcpserver contains the tcp server logic
+package tcpserver
diff --git a/pkg/tcpserver/responseengine.go b/pkg/tcpserver/responseengine.go
new file mode 100644
index 0000000..ec15da0
--- /dev/null
+++ b/pkg/tcpserver/responseengine.go
@@ -0,0 +1,16 @@
+package tcpserver
+
+import (
+ "errors"
+)
+
+// BuildResponse according to rules
+func (t *TCPServer) BuildResponse(data []byte) ([]byte, error) {
+ // Process all the rules
+ for _, rule := range t.options.rules {
+ if rule.matchRegex.Match(data) {
+ return []byte(rule.Response), nil
+ }
+ }
+ return nil, errors.New("no matched rule")
+}
diff --git a/pkg/tcpserver/rule.go b/pkg/tcpserver/rule.go
new file mode 100644
index 0000000..903331b
--- /dev/null
+++ b/pkg/tcpserver/rule.go
@@ -0,0 +1,25 @@
+package tcpserver
+
+import "regexp"
+
+// RulesConfiguration from yaml
+type RulesConfiguration struct {
+ Rules []Rule `yaml:"rules"`
+}
+
+// Rule to apply to various requests
+type Rule struct {
+ Match string `yaml:"match,omitempty"`
+ matchRegex *regexp.Regexp
+ Response string `yaml:"response,omitempty"`
+}
+
+// NewRule from model
+func NewRule(match, response string) (*Rule, error) {
+ regxp, err := regexp.Compile(match)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Rule{Match: match, matchRegex: regxp, Response: response}, nil
+}
diff --git a/pkg/tcpserver/tcpserver.go b/pkg/tcpserver/tcpserver.go
new file mode 100644
index 0000000..876fbb4
--- /dev/null
+++ b/pkg/tcpserver/tcpserver.go
@@ -0,0 +1,145 @@
+package tcpserver
+
+import (
+ "crypto/tls"
+ "io/ioutil"
+ "net"
+ "time"
+
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/sslcert"
+ "gopkg.in/yaml.v2"
+)
+
+const readTimeout = 5
+
+// Options of the tcp server
+type Options struct {
+ Listen string
+ TLS bool
+ Certificate string
+ Key string
+ Domain string
+ rules []Rule
+ Verbose bool
+}
+
+// TCPServer instance
+type TCPServer struct {
+ options *Options
+ listener net.Listener
+}
+
+// New tcp server instance with specified options
+func New(options *Options) (*TCPServer, error) {
+ return &TCPServer{options: options}, nil
+}
+
+// AddRule to the server
+func (t *TCPServer) AddRule(rule Rule) error {
+ t.options.rules = append(t.options.rules, rule)
+ return nil
+}
+
+// ListenAndServe requests
+func (t *TCPServer) ListenAndServe() error {
+ listener, err := net.Listen("tcp4", t.options.Listen)
+ if err != nil {
+ return err
+ }
+ t.listener = listener
+ return t.run()
+}
+
+func (t *TCPServer) handleConnection(conn net.Conn) error {
+ defer conn.Close() //nolint
+
+ buf := make([]byte, 4096)
+ for {
+ if err := conn.SetReadDeadline(time.Now().Add(readTimeout * time.Second)); err != nil {
+ gologger.Info().Msgf("%s\n", err)
+ }
+ _, err := conn.Read(buf)
+ if err != nil {
+ return err
+ }
+
+ gologger.Print().Msgf("%s\n", buf)
+
+ resp, err := t.BuildResponse(buf)
+ if err != nil {
+ return err
+ }
+
+ if _, err := conn.Write(resp); err != nil {
+ gologger.Info().Msgf("%s\n", err)
+ }
+
+ gologger.Print().Msgf("%s\n", resp)
+ }
+}
+
+// ListenAndServeTLS requests over tls
+func (t *TCPServer) ListenAndServeTLS() error {
+ var tlsConfig *tls.Config
+ if t.options.Certificate != "" && t.options.Key != "" {
+ cert, err := tls.LoadX509KeyPair(t.options.Certificate, t.options.Key)
+ if err != nil {
+ return err
+ }
+ tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
+ } else {
+ tlsOptions := sslcert.DefaultOptions
+ tlsOptions.Host = t.options.Domain
+ cfg, err := sslcert.NewTLSConfig(tlsOptions)
+ if err != nil {
+ return err
+ }
+ tlsConfig = cfg
+ }
+
+ listener, err := tls.Listen("tcp", t.options.Listen, tlsConfig)
+ if err != nil {
+ return err
+ }
+ t.listener = listener
+ return t.run()
+}
+
+func (t *TCPServer) run() error {
+ for {
+ c, err := t.listener.Accept()
+ if err != nil {
+ return err
+ }
+ go t.handleConnection(c) //nolint
+ }
+}
+
+// Close the service
+func (t *TCPServer) Close() error {
+ return t.listener.Close()
+}
+
+// LoadTemplate from yaml
+func (t *TCPServer) LoadTemplate(templatePath string) error {
+ var config RulesConfiguration
+ yamlFile, err := ioutil.ReadFile(templatePath)
+ if err != nil {
+ return err
+ }
+ err = yaml.Unmarshal(yamlFile, &config)
+ if err != nil {
+ return err
+ }
+
+ for _, ruleTemplate := range config.Rules {
+ rule, err := NewRule(ruleTemplate.Match, ruleTemplate.Response)
+ if err != nil {
+ return err
+ }
+ t.options.rules = append(t.options.rules, *rule)
+ }
+
+ return nil
+}
diff --git a/simplehttpserver.go b/simplehttpserver.go
deleted file mode 100644
index 2e7ad19..0000000
--- a/simplehttpserver.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package main
-
-import (
- "bytes"
- "flag"
- "fmt"
- "log"
- "net/http"
- "net/http/httputil"
-)
-
-type options struct {
- ListenAddress string
- Folder string
- Verbose bool
-}
-
-var opts options
-
-func main() {
- flag.StringVar(&opts.ListenAddress, "listen", "0.0.0.0:8000", "Address:Port")
- flag.StringVar(&opts.Folder, "path", ".", "Folder")
- flag.BoolVar(&opts.Verbose, "v", false, "Verbose")
- flag.Parse()
-
- if flag.NArg() > 0 && opts.Folder == "." {
- opts.Folder = flag.Args()[0]
- }
-
- log.Printf("Serving %s on http://%s/...", opts.Folder, opts.ListenAddress)
- fmt.Println(http.ListenAndServe(opts.ListenAddress, loglayer(http.FileServer(http.Dir(opts.Folder)))))
-}
-
-func loglayer(handler http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- fullRequest, _ := httputil.DumpRequest(r, true)
- lrw := newLoggingResponseWriter(w)
- handler.ServeHTTP(lrw, r)
-
- if opts.Verbose {
- headers := new(bytes.Buffer)
- lrw.Header().Write(headers) //nolint
- log.Printf("\nRemote Address: %s\n%s\n%s %d %s\n%s\n%s\n", r.RemoteAddr, string(fullRequest), r.Proto, lrw.statusCode, http.StatusText(lrw.statusCode), headers.String(), string(lrw.Data))
- } else {
- log.Printf("%s \"%s %s %s\" %d %d", r.RemoteAddr, r.Method, r.URL, r.Proto, lrw.statusCode, len(lrw.Data))
- }
- })
-}
-
-type loggingResponseWriter struct {
- http.ResponseWriter
- statusCode int
- Data []byte
-}
-
-func newLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
- return &loggingResponseWriter{w, http.StatusOK, []byte{}}
-}
-
-func (lrw *loggingResponseWriter) Write(data []byte) (int, error) {
- lrw.Data = append(lrw.Data, data...)
- return lrw.ResponseWriter.Write(data)
-}
-
-func (lrw *loggingResponseWriter) Header() http.Header {
- return lrw.ResponseWriter.Header()
-}
-
-func (lrw *loggingResponseWriter) WriteHeader(code int) {
- lrw.statusCode = code
- lrw.ResponseWriter.WriteHeader(code)
-}