mirror of
https://github.com/projectdiscovery/httpx
synced 2026-06-08 16:50:17 +00:00
422 lines
13 KiB
Go
422 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/julienschmidt/httprouter"
|
|
"github.com/projectdiscovery/httpx/internal/testutils"
|
|
fileutil "github.com/projectdiscovery/utils/file"
|
|
)
|
|
|
|
var httpTestcases = map[string]testutils.TestCase{
|
|
"Standard HTTP GET Request": &standardHttpGet{},
|
|
"Standard HTTPS GET Request": &standardHttpGet{tls: true},
|
|
"Raw HTTP GET Request": &standardHttpGet{unsafe: true},
|
|
"Raw request with non standard rfc path via stdin": &standardHttpGet{unsafe: true, stdinPath: "/%invalid"},
|
|
"Raw request with non standard rfc path via cli flag": &standardHttpGet{unsafe: true, path: "/%invalid"},
|
|
"Regression test for: https://github.com/projectdiscovery/httpx/issues/363": &issue363{}, // infinite redirect
|
|
"Regression test for: https://github.com/projectdiscovery/httpx/issues/276": &issue276{}, // full path with port in output
|
|
"Regression test for: https://github.com/projectdiscovery/httpx/issues/277": &issue277{}, // scheme://host:port via stdin
|
|
"Regression test for: https://github.com/projectdiscovery/httpx/issues/303": &issue303{}, // misconfigured gzip header with uncompressed body
|
|
"Regression test for: https://github.com/projectdiscovery/httpx/issues/400": &issue400{}, // post operation with body
|
|
"Regression test for: https://github.com/projectdiscovery/httpx/issues/414": &issue414{}, // stream mode with path
|
|
"Regression test for unwanted chars": &titleUnwantedChars{}, // new line scanning with title flag, Regression test for: https://github.com/projectdiscovery/httpx/issues/433
|
|
"Request URI to existing file - https://github.com/projectdiscovery/httpx/issues/480": &issue480{}, // request uri pointing to existing file
|
|
"Standard HTTP GET Request with match response time": &standardHttpGet{mrt: true, inputValue: "\"<10s\""},
|
|
"Standard HTTP GET Request with filter response time": &standardHttpGet{frt: true, inputValue: "\">3ms\""},
|
|
"Multiple Custom Header": &customHeader{inputData: []string{"-debug-req", "-H", "'user-agent: test'", "-H", "'foo: bar'"}, expectedOutput: []string{"User-Agent: test", "Foo: bar"}},
|
|
"Output Match Condition": &outputMatchCondition{inputData: []string{"-silent", "-mdc", "\"status_code == 200\""}},
|
|
"Output Filter Condition": &outputFilterCondition{inputData: []string{"-silent", "-fdc", "\"status_code == 400\""}},
|
|
"Output All": &outputAll{},
|
|
}
|
|
|
|
type standardHttpGet struct {
|
|
tls bool
|
|
unsafe bool
|
|
mrt bool
|
|
frt bool
|
|
inputValue string
|
|
stdinPath string
|
|
path string
|
|
expectedOutput string
|
|
}
|
|
|
|
func (h *standardHttpGet) Execute() error {
|
|
router := httprouter.New()
|
|
router.GET("/", httprouter.Handle(func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
_, _ = fmt.Fprintf(w, "This is a test")
|
|
r.Close = true
|
|
}))
|
|
var ts *httptest.Server
|
|
if h.tls {
|
|
ts = httptest.NewTLSServer(router)
|
|
} else {
|
|
ts = httptest.NewServer(router)
|
|
}
|
|
defer ts.Close()
|
|
var extra []string
|
|
if h.unsafe {
|
|
extra = append(extra, "-unsafe")
|
|
}
|
|
if h.path != "" {
|
|
extra = append(extra, "-path", "\""+h.path+"\"")
|
|
}
|
|
if h.mrt {
|
|
extra = append(extra, "-mrt", h.inputValue)
|
|
}
|
|
if h.frt {
|
|
extra = append(extra, "-frt", h.inputValue)
|
|
}
|
|
URL := ts.URL
|
|
if h.stdinPath != "" {
|
|
URL += h.stdinPath
|
|
}
|
|
|
|
results, err := testutils.RunHttpxAndGetResults(URL, debug, extra...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(results) != 1 {
|
|
return errIncorrectResultsCount(results)
|
|
}
|
|
|
|
if h.expectedOutput != "" && !strings.EqualFold(results[0], h.expectedOutput) {
|
|
return errIncorrectResult(h.expectedOutput, results[0])
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type issue276 struct{}
|
|
|
|
func (h *issue276) Execute() error {
|
|
var ts *httptest.Server
|
|
router := httprouter.New()
|
|
router.GET("/redirect", httprouter.Handle(func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
w.Header().Add("Location", ts.URL+"/redirect")
|
|
w.WriteHeader(302)
|
|
_, _ = fmt.Fprintf(w, "<html><body><title>Object moved</title></body></html>")
|
|
}))
|
|
ts = httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunHttpxAndGetResults(ts.URL+"/redirect", debug, "-status-code", "-title", "-no-color")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(results) != 1 {
|
|
return errIncorrectResultsCount(results)
|
|
}
|
|
// check if we have all the items on the cli
|
|
// full url with port
|
|
// status code
|
|
// title
|
|
expected := ts.URL + "/redirect" + " [302] [Object moved]"
|
|
if !strings.EqualFold(results[0], expected) {
|
|
return errIncorrectResult(expected, results[0])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type issue277 struct{}
|
|
|
|
func (h *issue277) Execute() error {
|
|
var ts *httptest.Server
|
|
router := httprouter.New()
|
|
router.GET("/hpp", httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
if p.ByName("pp") == `%22%3E%3Ch1%3Easdasd%3C%2Fh1%3E` {
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
}))
|
|
ts = httptest.NewServer(router)
|
|
defer ts.Close()
|
|
uripath := "/hpp/?pp=%22%3E%3Ch1%3Easdasd%3C%2Fh1%3E"
|
|
results, err := testutils.RunHttpxAndGetResults(ts.URL+uripath, debug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(results) != 1 {
|
|
return errIncorrectResultsCount(results)
|
|
}
|
|
// check if we have all the items on the cli
|
|
// full url with port
|
|
// status code
|
|
// title
|
|
expected := ts.URL + uripath
|
|
if !strings.EqualFold(results[0], expected) {
|
|
return errIncorrectResult(expected, results[0])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type issue303 struct{}
|
|
|
|
func (h *issue303) Execute() error {
|
|
var ts *httptest.Server
|
|
router := httprouter.New()
|
|
router.GET("/hpp", httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
// mimic a misconfigured web server behavior declaring gzip body
|
|
w.Header().Add("Content-Encoding", "gzip")
|
|
// but sending it uncompressed
|
|
_, _ = fmt.Fprint(w, "<html><body>This is a test</body></html>")
|
|
}))
|
|
ts = httptest.NewServer(router)
|
|
defer ts.Close()
|
|
results, err := testutils.RunHttpxAndGetResults(ts.URL, debug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(results) != 1 {
|
|
return errIncorrectResultsCount(results)
|
|
}
|
|
// check if we have all the items on the cli
|
|
// full url with port
|
|
expected := ts.URL
|
|
if !strings.EqualFold(results[0], expected) {
|
|
return errIncorrectResult(expected, results[0])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type issue363 struct{}
|
|
|
|
func (h *issue363) Execute() error {
|
|
var ts *httptest.Server
|
|
router := httprouter.New()
|
|
router.GET("/redirect", httprouter.Handle(func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
w.Header().Add("Location", ts.URL+"/redirect")
|
|
w.WriteHeader(302)
|
|
_, _ = fmt.Fprintf(w, "<html><body><title>Object moved</title></body></html>")
|
|
}))
|
|
ts = httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunHttpxAndGetResults(ts.URL+"/redirect", debug, "-no-color", "-follow-redirects")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(results) != 1 {
|
|
return errIncorrectResultsCount(results)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type issue400 struct{}
|
|
|
|
func (h *issue400) Execute() error {
|
|
var ts *httptest.Server
|
|
router := httprouter.New()
|
|
router.POST("/receive", httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
w.Header().Add("Content-Type", "application/json")
|
|
data, _ := io.ReadAll(r.Body)
|
|
_, _ = fmt.Fprintf(w, "data received %s", data)
|
|
}))
|
|
ts = httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunHttpxAndGetResults(ts.URL+"/receive", debug, "-body 'a=b'", "-x POST", "-status-code")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(results) != 1 {
|
|
return errIncorrectResultsCount(results)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type issue414 struct{}
|
|
|
|
func (h *issue414) Execute() error {
|
|
var ts *httptest.Server
|
|
uripath := "/path"
|
|
router := httprouter.New()
|
|
router.POST(uripath, httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
w.Header().Add("Content-Type", "application/json")
|
|
data, _ := io.ReadAll(r.Body)
|
|
_, _ = fmt.Fprintf(w, "data received %s", data)
|
|
}))
|
|
ts = httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunHttpxAndGetResults(ts.URL, debug, fmt.Sprintf("-path '%s'", uripath))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(results) != 1 {
|
|
return errIncorrectResultsCount(results)
|
|
}
|
|
expected := ts.URL + uripath
|
|
if !strings.EqualFold(results[0], expected) {
|
|
return errIncorrectResult(expected, results[0])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type titleUnwantedChars struct{}
|
|
|
|
func (h *titleUnwantedChars) Execute() error {
|
|
var ts *httptest.Server
|
|
router := httprouter.New()
|
|
uriPath := "/index"
|
|
router.GET(uriPath, httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
htmlResponse := "<html><head><title>\v\fProject\n\r Discovery\n - Httpx\t></title></head><body>test data</body></html>"
|
|
_, _ = fmt.Fprint(w, htmlResponse)
|
|
}))
|
|
ts = httptest.NewServer(router)
|
|
defer ts.Close()
|
|
results, err := testutils.RunHttpxAndGetResults(fmt.Sprint(ts.URL, uriPath), debug, "-title", "-no-color")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.Contains(results[0], "\n") {
|
|
return errIncorrectResultsCount(results)
|
|
}
|
|
if strings.Contains(results[0], "\r") {
|
|
return errIncorrectResultsCount(results)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type issue480 struct{}
|
|
|
|
func (h *issue480) Execute() error {
|
|
var ts *httptest.Server
|
|
router := httprouter.New()
|
|
uriPath := "////////////////../../../../../../../../etc/passwd"
|
|
router.GET(uriPath, httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
htmlResponse := "<html><body>ok from uri</body></html>"
|
|
_, _ = fmt.Fprint(w, htmlResponse)
|
|
}))
|
|
ts = httptest.NewServer(router)
|
|
defer ts.Close()
|
|
results, err := testutils.RunHttpxAndGetResults(ts.URL, debug, "-path", "////////////////../../../../../../../../etc/passwd")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !strings.Contains(results[0], uriPath) {
|
|
return errIncorrectResultsCount(results)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type customHeader struct {
|
|
inputData []string
|
|
expectedOutput []string
|
|
}
|
|
|
|
func (h *customHeader) Execute() error {
|
|
var ts *httptest.Server
|
|
router := httprouter.New()
|
|
router.GET("/", httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
w.Header().Add("Content-Type", "application/json")
|
|
_, _ = fmt.Fprint(w, `{"status": "ok"}`)
|
|
}))
|
|
ts = httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunHttpxAndGetCombinedResults(ts.URL, true, h.inputData...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, expected := range h.expectedOutput {
|
|
if !strings.Contains(results, expected) {
|
|
return errIncorrectResult(expected, results)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type outputMatchCondition struct {
|
|
inputData []string
|
|
}
|
|
|
|
func (h *outputMatchCondition) Execute() error {
|
|
var ts *httptest.Server
|
|
router := httprouter.New()
|
|
router.GET("/", httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
w.Header().Add("Content-Type", "application/json")
|
|
w.WriteHeader(200)
|
|
_, _ = fmt.Fprint(w, `{"status": "ok"}`)
|
|
}))
|
|
ts = httptest.NewServer(router)
|
|
defer ts.Close()
|
|
results, err := testutils.RunHttpxAndGetResults(ts.URL, false, h.inputData...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(results) != 1 {
|
|
return errIncorrectResultsCount(results)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type outputFilterCondition struct {
|
|
inputData []string
|
|
}
|
|
|
|
func (h *outputFilterCondition) Execute() error {
|
|
var ts *httptest.Server
|
|
router := httprouter.New()
|
|
router.GET("/", httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
w.Header().Add("Content-Type", "application/json")
|
|
w.WriteHeader(200)
|
|
_, _ = fmt.Fprint(w, `{"status": "ok"}`)
|
|
}))
|
|
ts = httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunHttpxAndGetResults(ts.URL, false, h.inputData...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(results) != 1 {
|
|
return errIncorrectResultsCount(results)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type outputAll struct {
|
|
}
|
|
|
|
func (h *outputAll) Execute() error {
|
|
var ts *httptest.Server
|
|
router := httprouter.New()
|
|
router.GET("/", httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
w.Header().Add("Content-Type", "application/json")
|
|
w.WriteHeader(200)
|
|
_, _ = fmt.Fprint(w, `{"status": "ok"}`)
|
|
}))
|
|
ts = httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
fileName := "test_output_all"
|
|
_, hErr := testutils.RunHttpxAndGetResults(ts.URL, false, []string{"-o", fileName, "-oa"}...)
|
|
if hErr != nil {
|
|
return hErr
|
|
}
|
|
|
|
expectedFiles := []string{fileName, fileName + ".json", fileName + ".csv"}
|
|
var actualFiles []string
|
|
|
|
for _, file := range expectedFiles {
|
|
if fileutil.FileExists(file) {
|
|
actualFiles = append(actualFiles, file)
|
|
}
|
|
}
|
|
if len(actualFiles) != 3 {
|
|
return errIncorrectResultsCount(actualFiles)
|
|
}
|
|
|
|
for _, file := range actualFiles {
|
|
_ = os.Remove(file)
|
|
}
|
|
|
|
return nil
|
|
}
|