Skip to content

Commit

Permalink
Add 'json_body' as config req variable
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcel Ludwig committed Sep 30, 2020
1 parent f2e979c commit 41fcb71
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 24 deletions.
77 changes: 55 additions & 22 deletions eval/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package eval
import (
"bytes"
"context"
"encoding/json"
"io"
"io/ioutil"
"mime"
"net/http"
"net/url"
"os"
Expand Down Expand Up @@ -48,7 +50,7 @@ func NewENVContext(src []byte) *hcl.EvalContext {
}
}

func NewHTTPContext(baseCtx *hcl.EvalContext, req, bereq *http.Request, beresp *http.Response) *hcl.EvalContext {
func NewHTTPContext(baseCtx *hcl.EvalContext, buffer bool, req, bereq *http.Request, beresp *http.Response) *hcl.EvalContext {
if req == nil {
return baseCtx
}
Expand All @@ -65,13 +67,21 @@ func NewHTTPContext(baseCtx *hcl.EvalContext, req, bereq *http.Request, beresp *
id = uid
}

if buffer {
switch req.Method {
case http.MethodPost, http.MethodPut, http.MethodPatch:
setGetBody(req)
}
}

evalCtx.Variables["req"] = cty.ObjectVal(reqCtxMap.Merge(ContextMap{
"id": cty.StringVal(id),
"method": cty.StringVal(req.Method),
"path": cty.StringVal(req.URL.Path),
"url": cty.StringVal(newRawURL(req.URL).String()),
"query": seetie.ValuesMapToValue(req.URL.Query()),
"post": seetie.ValuesMapToValue(parseForm(req).PostForm),
"id": cty.StringVal(id),
"method": cty.StringVal(req.Method),
"path": cty.StringVal(req.URL.Path),
"url": cty.StringVal(newRawURL(req.URL).String()),
"query": seetie.ValuesMapToValue(req.URL.Query()),
"post": seetie.ValuesMapToValue(parseForm(req).PostForm),
"json_body": seetie.MapToValue(parseJSON(req)),
}.Merge(newVariable(httpCtx, req.Cookies(), req.Header))))

if beresp != nil {
Expand Down Expand Up @@ -105,33 +115,56 @@ func (rc readCloser) Close() error {
return rc.closer.Close()
}

func setGetBody(r *http.Request) *http.Request {
if r.Body != nil && r.GetBody == nil {
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
r.GetBody = func() (io.ReadCloser, error) {
return newReadCloser(bytes.NewBuffer(bodyBytes), r.Body), nil
}
// reset
r.Body, _ = r.GetBody()
}
return r
}

// parseForm populates the request PostForm field.
// As Proxy we should not consume the request body.
// Create a copy, buffer and reset via GetBody method.
// Rewind body via GetBody method.
func parseForm(r *http.Request) *http.Request {
if r.Body == nil {
if r.GetBody == nil {
return r
}

switch r.Method {
case http.MethodPut, http.MethodPatch, http.MethodPost:
if r.GetBody == nil {
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
r.GetBody = func() (io.ReadCloser, error) {
return newReadCloser(bytes.NewBuffer(bodyBytes), r.Body), nil
}
}

r.Body, _ = r.GetBody()
_ = r.ParseMultipartForm(defaultMaxMemory)
r.Body, _ = r.GetBody()
r.Body, _ = r.GetBody() // reset
}
return r
}

func parseJSON(r *http.Request) map[string]interface{} {
if r.GetBody == nil {
return nil
}

m, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
if m != "application/json" {
return nil
}

var result map[string]interface{}
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil
}
_ = json.Unmarshal(b, &result)
r.Body, _ = r.GetBody() // reset
return result
}

func newRawURL(u *url.URL) *url.URL {
rawURL := *u
rawURL.RawQuery = ""
Expand Down
2 changes: 1 addition & 1 deletion eval/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func TestNewHTTPContext(t *testing.T) {
bereq := req.Clone(context.Background())
beresp := newBeresp(bereq)

got := NewHTTPContext(tt.baseCtx, req, bereq, beresp)
got := NewHTTPContext(tt.baseCtx, true, req, bereq, beresp)
got.Functions = nil // we are not interested in a functions test

var hclResult EvalTestContext
Expand Down
31 changes: 30 additions & 1 deletion handler/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net/http"
"net/url"
"path"
"reflect"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -42,6 +43,7 @@ var (
type Proxy struct {
evalContext *hcl.EvalContext
log *logrus.Entry
mustBuffer bool
options *ProxyOptions
originURL *url.URL
transport *http.Transport
Expand Down Expand Up @@ -117,6 +119,7 @@ func NewProxy(options *ProxyOptions, log *logrus.Entry, evalCtx *hcl.EvalContext
proxy := &Proxy{
evalContext: evalCtx,
log: log,
mustBuffer: mustBuffer(options),
options: options,
originURL: originURL,
upstreamLog: logging.NewAccessLog(&logConf, log.Logger),
Expand Down Expand Up @@ -307,7 +310,7 @@ func (p *Proxy) setRoundtripContext(req *http.Request, beresp *http.Response) {
headerCtx = req.Header
}

evalCtx := eval.NewHTTPContext(p.evalContext, req, bereq, beresp)
evalCtx := eval.NewHTTPContext(p.evalContext, p.mustBuffer, req, bereq, beresp)

// Remove blacklisted headers after evaluation to be accessible within our context configuration.
if attrCtx == attrReqHeaders {
Expand All @@ -329,6 +332,32 @@ func (p *Proxy) setRoundtripContext(req *http.Request, beresp *http.Response) {
}
}

// mustBuffer determines if any of the hcl.bodies makes use of 'post' or 'json_body'.
func mustBuffer(opts *ProxyOptions) bool {
for _, body := range opts.Context {
attrs, err := body.JustAttributes()
if err != nil {
return false
}
for _, attr := range attrs {
for _, traversal := range attr.Expr.Variables() {
if traversal.RootName() != "req" {
continue
}
for _, step := range traversal[1:] {
nameField := reflect.ValueOf(step).FieldByName("Name")
name := nameField.String()
switch name {
case "json_body", "post":
return true
}
}
}
}
}
return false
}

func isCorsRequest(req *http.Request) bool {
return req.Header.Get("Origin") != ""
}
Expand Down

0 comments on commit 41fcb71

Please sign in to comment.