diff --git a/README.mkd b/README.mkd index 0a8bf16..60fe190 100644 --- a/README.mkd +++ b/README.mkd @@ -244,3 +244,17 @@ out/example.com/61ac5fbb9d3dd054006ae82630b045ba730d8618:3:> TRACE /robots.txt H out/example.com/bd8d9f4c470ffa0e6ec8cfa8ba1c51d62289b6dd:3:> TRACE /.well-known/security.txt HTTP/1.1 ... ``` + +### JSON Output + +Instead of the usual meg output it is also possible to output the results +as a JSON string with the `-j` or `--jsonOut` option: + +``` +▶ meg --jsonOut +{"request":{"url":"","hostname":"","method":"","path":"","host":"","headers":[{"headerkey":"","headervalue":""},{"headerkey":"","headervalue":""}],"body":"","follow":false},"response":{"statuscode":,"status":"","headers":[{"headerkey":"","headervalue":""}],"body":""}} +``` + +### No headers + +With --no-headers you suppress the output of HTTP headers. diff --git a/args.go b/args.go index 90cca52..694e0e0 100644 --- a/args.go +++ b/args.go @@ -50,6 +50,7 @@ type config struct { saveStatus saveStatusArgs timeout int verbose bool + jsonOut bool paths string hosts string @@ -106,6 +107,11 @@ func processArgs() config { flag.BoolVar(&rawHTTP, "rawhttp", false, "") flag.BoolVar(&rawHTTP, "r", false, "") + // Json Output param + jsonOut := false + flag.BoolVar(&jsonOut, "jsonOut", false, "") + flag.BoolVar(&jsonOut, "j", false, "") + // no headers noHeaders := false flag.BoolVar(&noHeaders, "no-headers", false, "") @@ -156,6 +162,7 @@ func processArgs() config { hosts: hosts, output: output, noHeaders: noHeaders, + jsonOut: jsonOut, } } @@ -176,7 +183,9 @@ func init() { h += " -s, --savestatus Save only responses with specific status code\n" h += " -t, --timeout Set the HTTP timeout (default: 10000)\n" h += " -v, --verbose Verbose mode\n" - h += " -X, --method HTTP method (default: GET)\n\n" + h += " -X, --method HTTP method (default: GET)\n" + h += " --no-headers Don't output headers\n" + h += " -j, --jsonOut Output in JSON\n\n" h += "Defaults:\n" h += " pathsFile: ./paths\n" diff --git a/main.go b/main.go index f2a73a2..f1d9e3f 100644 --- a/main.go +++ b/main.go @@ -93,7 +93,7 @@ func main() { continue } - path, err := res.save(c.output, c.noHeaders) + path, err := res.save(c.output, c.noHeaders, c.jsonOut) if err != nil { fmt.Fprintf(os.Stderr, "failed to save file: %s\n", err) } diff --git a/response.go b/response.go index 1ca0d0a..1590168 100644 --- a/response.go +++ b/response.go @@ -3,10 +3,12 @@ package main import ( "bytes" "crypto/sha1" + "encoding/json" "fmt" "io/ioutil" "os" "path" + "strings" ) // a response is a wrapper around an HTTP response; @@ -21,8 +23,100 @@ type response struct { err error } -// String returns a string representation of the request and response -func (r response) String() string { +// jsonString returns a JSON string representation of the request and response +func (r response) jsonString(noHeaders bool) string { + // Building the structs for the various JSON elements (root element, headers, body, ...) + + // Struct for Headers (Key, Value) + type JsonHeader struct { + JsonHeaderKey string `json:"headerkey"` + JsonHeaderValue string `json:"headervalue"` + } + // Struct representing the request (URL, Hostname, HTTP Method, Path, Host, Headers (struct), Body, Follow (-L)) + type JsonRequest struct { + JsonRequestUrl string `json:"url"` + JsonRequestHostname string `json:"hostname"` + JsonRequestMethod string `json:"method"` + JsonRequestPath string `json:"path"` + JsonRequestHost string `json:"host"` + JsonRequestHeaders []JsonHeader `json:"headers"` + JsonRequestBody string `json:"body"` + JsonRequestFollow bool `json:"follow"` + } + // Struct representing the response (HTTP status code, status text, headers(struct), body) + type JsonResponse struct { + JsonResponseStatusCode int `json:"statuscode"` + JsonResponseStatus string `json:"status"` + JsonResponseHeader []JsonHeader `json:"headers"` + JsonBody string `json:"body"` + } + // Struct representing the root element (Request, Response) + type JsonOut struct { + JsonRequest JsonRequest `json:"request"` + JsonResponse JsonResponse `json:"response"` + } + + // Prepare the Header structs which will be inserted into the JSON element + var jsonRequestHeaders []JsonHeader + var jsonResponseHeaders []JsonHeader + // If we specify --no-headers we will simply receive "headers":null in both the request and response + // Otherwise we will extract the headers from the request/response and fill the header elements + if !noHeaders { + // Fill the request header struct + for _, h := range r.request.headers { + // We use SplitN because then we only split on the first colon occurrence + x := strings.SplitN(h, ":", 2) + jsonRequestHeaders = append(jsonRequestHeaders, JsonHeader{x[0], strings.TrimSpace(x[1])}) + } + // Fill the response header struct + for _, h := range r.headers { + x := strings.SplitN(h, ":", 2) + jsonResponseHeaders = append(jsonResponseHeaders, JsonHeader{x[0], strings.TrimSpace(x[1])}) + } + } + // Create the JSON Body element + jsonBody := &bytes.Buffer{} + jsonBody.Write(r.body) + + // Create the JSON element + // Root Element + resp := JsonOut{ + // Request Element + JsonRequest{ + r.request.URL(), + r.request.Hostname(), + r.request.method, + r.request.path, + r.request.host, + jsonRequestHeaders, + r.request.body, + r.request.followLocation, + }, + // Response Element + JsonResponse{ + r.statusCode, + r.status, + jsonResponseHeaders, + jsonBody.String(), + }, + } + // Marshal the JSON struct + ba, err := json.Marshal(resp) + if err != nil { + fmt.Println("error:", err) + } + + return string(ba) +} + +// megString returns a string representation of the request and response in a "traditional" meg format +func (r response) megString(noHeaders bool) string { + if noHeaders { + b := &bytes.Buffer{} + b.Write(r.body) + return b.String() + } + b := &bytes.Buffer{} b.WriteString(r.request.URL()) @@ -51,20 +145,15 @@ func (r response) String() string { return b.String() } -func (r response) StringNoHeaders() string { - b := &bytes.Buffer{} - - b.Write(r.body) - - return b.String() -} - // save write a request and response output to disk -func (r response) save(pathPrefix string, noHeaders bool) (string, error) { - - content := []byte(r.String()) - if noHeaders { - content = []byte(r.StringNoHeaders()) +func (r response) save(pathPrefix string, noHeaders bool, json bool) (string, error) { + var content []byte + + // Depending if we want to save a json or meg string we call the corresponding String method + if json { + content = []byte(r.jsonString(noHeaders)) + } else { + content = []byte(r.megString(noHeaders)) } checksum := sha1.Sum(content)