Skip to content

Commit

Permalink
fix: add constant to fix code smell
Browse files Browse the repository at this point in the history
Signed-off-by: Felipe Zipitria <[email protected]>
  • Loading branch information
fzipi committed Jan 25, 2022
1 parent 01eaefd commit 24d5a48
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 78 deletions.
2 changes: 1 addition & 1 deletion http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (c *Client) Do(req Request) (*Response, error) {

// GetRoundTripTime returns the time taken from the initial send till receiving the full response
func (c *Client) GetRoundTripTime() *RoundTripTime {
return c.Transport.GetRoundTripTime()
return c.Transport.GetTrackedTime()
}

// StartTrackingTime sets the timer to start transactions. This will be the starting time in logs.
Expand Down
153 changes: 153 additions & 0 deletions http/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package http

import "testing"

func TestNewClient(t *testing.T) {
c := NewClient()

if c.Jar == nil {
t.Logf("Error creating Client")
}
}

func TestConnectDestinationHTTPS(t *testing.T) {
d := &Destination{
DestAddr: "example.com",
Port: 443,
Protocol: "https",
}

c := NewClient()

err := c.NewConnection(*d)
if err != nil {
t.Logf("This should not error")
}

if c.Transport.protocol != "https" {
t.Logf("Error connecting to example.com using https")
}
}

func TestDoRequest(t *testing.T) {
d := &Destination{
DestAddr: "httpbin.org",
Port: 443,
Protocol: "https",
}

c := NewClient()

req := generateBaseRequestForTesting()

err := c.NewConnection(*d)
if err != nil {
t.Logf("This should not error")
}

_, err = c.Do(*req)

if err == nil {
t.Logf("This should return error")
}
// if resp.Parsed.Status != 301 {

// }
}

func TestGetTrackedTime(t *testing.T) {
d := &Destination{
DestAddr: "httpbin.org",
Port: 443,
Protocol: "https",
}

c := NewClient()

rl := &RequestLine{
Method: "POST",
URI: "/post",
Version: "HTTP/1.1",
}

h := Header{"Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost"}

data := []byte(`test=me&one=two`)
req := NewRequest(rl, h, data, true)

err := c.NewConnection(*d)
if err != nil {
t.Logf("This should not error")
}

c.StartTrackingTime()

resp, err := c.Do(*req)

c.StopTrackingTime()

if err != nil {
t.Logf("This should not error")
}

if resp.Parsed.StatusCode != 200 {
t.Logf("Error in calling website")
}

rtt := c.GetRoundTripTime()

if rtt.RoundTripDuration() < 0 {
t.Logf("Error getting RTT")
}
}

func TestClientMultipartFormDataRequest(t *testing.T) {
d := &Destination{
DestAddr: "httpbin.org",
Port: 443,
Protocol: "https",
}

c := NewClient()

rl := &RequestLine{
Method: "POST",
URI: "/post",
Version: "HTTP/1.1",
}

h := Header{
"Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost",
"Content-Type": "multipart/form-data; boundary=--------397236876",
}

data := []byte(`----------397236876
Content-Disposition: form-data; name="fileRap"; filename="test.txt"
Content-Type: text/plain
Some-file-test-here
----------397236876--`)

req := NewRequest(rl, h, data, true)

err := c.NewConnection(*d)

if err != nil {
t.Logf("This should not error")
}

c.StartTrackingTime()

resp, err := c.Do(*req)

c.StopTrackingTime()

if err != nil {
t.Logf("This should not error")
}

if resp.Parsed.StatusCode != 200 {
t.Logf("Error in calling website")
}

}
49 changes: 47 additions & 2 deletions http/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
package http

import (
"bufio"
"bytes"
"errors"
"io"
"net"
"net/http"
"net/url"
"strconv"
"time"
Expand Down Expand Up @@ -37,8 +40,8 @@ func (c *Connection) StopTrackingTime() {
c.duration.StopTracking()
}

// GetRoundTripTime will return the time since the request started and the response was parsed
func (c *Connection) GetRoundTripTime() *RoundTripTime {
// GetTrackedTime will return the time since the request started and the response was parsed
func (c *Connection) GetTrackedTime() *RoundTripTime {
return c.duration
}

Expand Down Expand Up @@ -83,3 +86,45 @@ func (c *Connection) receive() ([]byte, error) {

return buf, err
}

// Request will use all the inputs and send a raw http request to the destination
func (c *Connection) Request(request *Request) error {
// Build request first, then connect and send, so timers are accurate
data, err := buildRequest(request)
if err != nil {
log.Fatal().Msgf("ftw/http: fatal error building request: %s", err.Error())
}

log.Debug().Msgf("ftw/http: sending data:\n%s\n", data)

_, err = c.send(data)

if err != nil {
log.Error().Msgf("ftw/http: error writing data: %s", err.Error())
}

return err
}

// Response reads the response sent by the WAF and return the corresponding struct
// It leverages the go stdlib for reading and parsing the response
func (c *Connection) Response() (*Response, error) {
data, err := c.receive()

if err != nil {
return nil, err
}

r := bytes.NewReader(data)
reader := *bufio.NewReader(r)

httpResponse, err := http.ReadResponse(&reader, nil)
if err != nil {
return nil, err
}
response := Response{
RAW: data,
Parsed: *httpResponse,
}
return &response, err
}
24 changes: 24 additions & 0 deletions http/connection_test.go
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
package http

import "testing"

func TestDestinationFromString(t *testing.T) {

}
func TestMultipleRequestTypes(t *testing.T) {
var req *Request

rl := &RequestLine{
Method: "POST",
URI: "/path",
Version: "HTTP/1.1",
}

h := Header{"Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost"}

data := []byte(`test=me&one=two`)
req = NewRequest(rl, h, data, true)

if !req.WithAutoCompleteHeaders() {
t.Error("Set Autocomplete headers error ")
}
}
5 changes: 5 additions & 0 deletions http/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import (
"strconv"
)

const (
// ContentTypeHeader gives you the string for content type
ContentTypeHeader string = "Content-Type"
)

// Based on https://golang.org/src/net/http/header.go

// Header is a simplified version of headers, where there is only one header per key.
Expand Down
9 changes: 8 additions & 1 deletion http/header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package http

import (
"bytes"
"io"
"testing"
)

Expand Down Expand Up @@ -47,7 +48,7 @@ var headerWriteTests = []struct {
},
}

func TestHeaderWrite(t *testing.T) {
func TestHeaderWriteBytes(t *testing.T) {
var buf bytes.Buffer
for i, test := range headerWriteTests {
_ = test.h.WriteBytes(&buf)
Expand All @@ -58,6 +59,12 @@ func TestHeaderWrite(t *testing.T) {
}
}

func TestHeaderWrite(t *testing.T) {
for _, test := range headerWriteTests {
_ = test.h.Write(io.Discard)
}
}

func TestHeaderSetGet(t *testing.T) {
h := Header{
"Custom": "Value",
Expand Down
52 changes: 4 additions & 48 deletions http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"errors"
"fmt"
"net/http"
"net/url"
"strings"

Expand All @@ -13,36 +12,11 @@ import (
"github.com/rs/zerolog/log"
)

// Destination is the host, port and protocol to be used when connecting to a remote host
type Destination struct {
DestAddr string `default:"localhost"`
Port int `default:"80"`
Protocol string `default:"http"`
}

// RequestLine is the first line in the HTTP request dialog
type RequestLine struct {
Method string `default:"GET"`
Version string `default:"HTTP/1.1"`
URI string `default:"/"`
}

// ToString converts the request line to string for sending it in the wire
func (rl RequestLine) ToString() string {
return fmt.Sprintf("%s %s %s\r\n", rl.Method, rl.URI, rl.Version)
}

// Request represents a request
// No Defaults represents the previous "stop_magic" behavior
type Request struct {
requestLine *RequestLine
headers Header
cookies http.CookieJar
data []byte
raw []byte
autoCompleteHeaders bool
}

// NewRequest creates a new request, an initial request line, and headers
func NewRequest(reqLine *RequestLine, h Header, data []byte, b bool) *Request {
r := &Request{
Expand Down Expand Up @@ -133,25 +107,6 @@ func (r *Request) AddStandardHeaders(size int) {
r.headers.AddStandard(size)
}

// Request will use all the inputs and send a raw http request to the destination
func (c *Connection) Request(request *Request) error {
// Build request first, then connect and send, so timers are accurate
data, err := buildRequest(request)
if err != nil {
log.Fatal().Msgf("ftw/http: fatal error building request: %s", err.Error())
}

log.Debug().Msgf("ftw/http: sending data:\n%s\n", data)

_, err = c.send(data)

if err != nil {
log.Error().Msgf("ftw/http: error writing data: %s", err.Error())
}

return err
}

// isRaw is a helper that returns true if raw or encoded data
func (r Request) isRaw() bool {
return utils.IsNotEmpty(r.raw)
Expand All @@ -176,15 +131,16 @@ func buildRequest(r *Request) ([]byte, error) {
// We need to add the remaining headers, unless "NoDefaults"
if utils.IsNotEmpty(r.data) && r.WithAutoCompleteHeaders() {
// If there is no Content-Type, then we add one
r.AddHeader("Content-Type", "application/x-www-form-urlencoded")
r.AddHeader(ContentTypeHeader, "application/x-www-form-urlencoded")
err := r.SetData(encodeDataParameters(r.headers, r.data))
if err != nil {
log.Info().Msgf("ftw/http: cannot set data to: %q", r.data)
return nil, err
}
}

// Multipart form data needs to end in \r\n, per RFC (and modsecurity make a scene if not)
if ct := r.headers.Value("Content-Type"); strings.HasPrefix(ct, "multipart/form-data") {
if ct := r.headers.Value(ContentTypeHeader); strings.HasPrefix(ct, "multipart/form-data;") {
crlf := []byte("\r\n")
lf := []byte("\n")
log.Debug().Msgf("ftw/http: with LF only - %d bytes:\n%x\n", len(r.data), r.data)
Expand Down Expand Up @@ -238,7 +194,7 @@ func emptyQueryValues(values url.Values) bool {

// encodeDataParameters url encode parameters in data
func encodeDataParameters(h Header, data []byte) []byte {
if h.Get("Content-Type") == "application/x-www-form-urlencoded" {
if h.Get(ContentTypeHeader) == "application/x-www-form-urlencoded" {
if escapedData, _ := url.QueryUnescape(string(data)); escapedData == string(data) {
log.Trace().Msgf("ftw/http: parsing data: %q", data)
queryString, err := url.ParseQuery(string(data))
Expand Down
Loading

0 comments on commit 24d5a48

Please sign in to comment.