Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move some request parsing into hook package #477

Merged
merged 1 commit into from
Nov 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions internal/hook/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"log"
"math"
"net"
"net/http"
"net/textproto"
"os"
"reflect"
Expand Down Expand Up @@ -48,30 +47,6 @@ const (
EnvNamespace string = "HOOK_"
)

// Request represents a webhook request.
type Request struct {
// The request ID set by the RequestID middleware.
ID string

// The Content-Type of the request.
ContentType string

// The raw request body.
Body []byte

// Headers is a map of the parsed headers.
Headers map[string]interface{}

// Query is a map of the parsed URL query values.
Query map[string]interface{}

// Payload is a map of the parsed payload.
Payload map[string]interface{}

// The underlying HTTP request.
RawRequest *http.Request
}

// ParameterNodeError describes an error walking a parameter node.
type ParameterNodeError struct {
key string
Expand Down
116 changes: 116 additions & 0 deletions internal/hook/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package hook

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"unicode"

"github.com/clbanning/mxj"
)

// Request represents a webhook request.
type Request struct {
// The request ID set by the RequestID middleware.
ID string

// The Content-Type of the request.
ContentType string

// The raw request body.
Body []byte

// Headers is a map of the parsed headers.
Headers map[string]interface{}

// Query is a map of the parsed URL query values.
Query map[string]interface{}

// Payload is a map of the parsed payload.
Payload map[string]interface{}

// The underlying HTTP request.
RawRequest *http.Request
}

func (r *Request) ParseJSONPayload() error {
decoder := json.NewDecoder(bytes.NewReader(r.Body))
decoder.UseNumber()

var firstChar byte
for i := 0; i < len(r.Body); i++ {
if unicode.IsSpace(rune(r.Body[i])) {
continue
}
firstChar = r.Body[i]
break
}

if firstChar == byte('[') {
var arrayPayload interface{}
err := decoder.Decode(&arrayPayload)
if err != nil {
return fmt.Errorf("error parsing JSON array payload %+v", err)
}

r.Payload = make(map[string]interface{}, 1)
r.Payload["root"] = arrayPayload
} else {
err := decoder.Decode(&r.Payload)
if err != nil {
return fmt.Errorf("error parsing JSON payload %+v", err)
}
}

return nil
}

func (r *Request) ParseHeaders(headers map[string][]string) {
r.Headers = make(map[string]interface{}, len(headers))

for k, v := range headers {
if len(v) > 0 {
r.Headers[k] = v[0]
}
}
}

func (r *Request) ParseQuery(query map[string][]string) {
r.Query = make(map[string]interface{}, len(query))

for k, v := range query {
if len(v) > 0 {
r.Query[k] = v[0]
}
}
}

func (r *Request) ParseFormPayload() error {
fd, err := url.ParseQuery(string(r.Body))
if err != nil {
return fmt.Errorf("error parsing form payload %+v", err)
}

r.Payload = make(map[string]interface{}, len(fd))

for k, v := range fd {
if len(v) > 0 {
r.Payload[k] = v[0]
}
}

return nil
}

func (r *Request) ParseXMLPayload() error {
var err error

r.Payload, err = mxj.NewMapXmlReader(bytes.NewReader(r.Body))
if err != nil {
return fmt.Errorf("error parsing XML payload: %+v", err)
}

return nil
}
53 changes: 9 additions & 44 deletions webhook.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"bytes"
"crypto/tls"
"encoding/json"
"flag"
Expand All @@ -10,19 +9,16 @@ import (
"log"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"unicode"

"github.com/adnanh/webhook/internal/hook"
"github.com/adnanh/webhook/internal/middleware"
"github.com/adnanh/webhook/internal/pidfile"

"github.com/clbanning/mxj"
chimiddleware "github.com/go-chi/chi/middleware"
"github.com/gorilla/mux"
fsnotify "gopkg.in/fsnotify.v1"
Expand Down Expand Up @@ -373,57 +369,26 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
}
}

// parse headers
req.Headers = valuesToMap(r.Header)

// parse query variables
req.Query = valuesToMap(r.URL.Query())

// parse body
// var payload map[string]interface{}
req.ParseHeaders(r.Header)
req.ParseQuery(r.URL.Query())

switch {
case strings.Contains(req.ContentType, "json"):
decoder := json.NewDecoder(bytes.NewReader(req.Body))
decoder.UseNumber()

var firstChar byte
for i := 0; i < len(req.Body); i++ {
if unicode.IsSpace(rune(req.Body[i])) {
continue
}
firstChar = req.Body[i]
break
}

if firstChar == byte('[') {
var arrayPayload interface{}
err := decoder.Decode(&arrayPayload)
if err != nil {
log.Printf("[%s] error parsing JSON array payload %+v\n", req.ID, err)
}

req.Payload = make(map[string]interface{}, 1)
req.Payload["root"] = arrayPayload
} else {
err := decoder.Decode(&req.Payload)
if err != nil {
log.Printf("[%s] error parsing JSON payload %+v\n", req.ID, err)
}
err = req.ParseJSONPayload()
if err != nil {
log.Printf("[%s] %s", req.ID, err)
}

case strings.Contains(req.ContentType, "x-www-form-urlencoded"):
fd, err := url.ParseQuery(string(req.Body))
err = req.ParseFormPayload()
if err != nil {
log.Printf("[%s] error parsing form payload %+v\n", req.ID, err)
} else {
req.Payload = valuesToMap(fd)
log.Printf("[%s] %s", req.ID, err)
}

case strings.Contains(req.ContentType, "xml"):
req.Payload, err = mxj.NewMapXmlReader(bytes.NewReader(req.Body))
err = req.ParseXMLPayload()
if err != nil {
log.Printf("[%s] error parsing XML payload: %+v\n", req.ID, err)
log.Printf("[%s] %s", req.ID, err)
}

case isMultipart:
Expand Down