mirror of
https://github.com/BishopFox/sj.git
synced 2026-11-12 18:16:39 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bbf0453a5 | ||
|
|
a39028b463 | ||
|
|
e22c11c118 | ||
|
|
528a771c87 | ||
|
|
c85512dfe0 | ||
|
|
6a01afb367 | ||
|
|
7ac01dc2a1 | ||
|
|
9b48aab4fb |
No files matched your search
@@ -11,6 +11,11 @@ builds:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w -X main.build={{.Version}}
|
||||
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
|
||||
@@ -13,6 +13,9 @@ import (
|
||||
|
||||
var getAccessibleEndpoints bool
|
||||
var outputFormat string
|
||||
var responsePreviewLength int
|
||||
var verbose bool
|
||||
|
||||
var automateCmd = &cobra.Command{
|
||||
Use: "automate",
|
||||
Short: "Sends a series of automated requests to the discovered endpoints.",
|
||||
@@ -24,6 +27,8 @@ responds in an abnormal way, manual testing should be conducted (prepare manual
|
||||
if outfile != "" && strings.ToLower(outputFormat) != "" {
|
||||
if !strings.HasSuffix(strings.ToLower(outfile), "json") && strings.ToLower(outputFormat) != "json" {
|
||||
log.Fatal("Only the JSON output format is supported at the moment.")
|
||||
} else if strings.HasSuffix(strings.ToLower(outfile), "json") && strings.ToLower(outputFormat) == "console" {
|
||||
outputFormat = "json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,4 +58,7 @@ responds in an abnormal way, manual testing should be conducted (prepare manual
|
||||
func init() {
|
||||
automateCmd.PersistentFlags().StringVarP(&outputFormat, "output-format", "F", "console", "The output format. Only 'console' (default) and 'json' are supported at the moment.")
|
||||
automateCmd.PersistentFlags().BoolVar(&getAccessibleEndpoints, "get-accessible-endpoints", false, "Only output the accessible endpoints (those that return a 200 status code).")
|
||||
automateCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "enable verbose mode, which shows a preview of each response.")
|
||||
automateCmd.PersistentFlags().IntVar(&responsePreviewLength, "response-preview-length", 50, "sets the response preview length when using verbose output.")
|
||||
|
||||
}
|
||||
+119
-14
@@ -1,27 +1,81 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func writeLog(sc int, target, method string, errorMsg string) {
|
||||
if sc != 200 {
|
||||
if sc == 401 || sc == 403 {
|
||||
logUnauth(sc, target, method, errorMsg)
|
||||
} else if sc == 301 || sc == 302 {
|
||||
logRedirect(sc, target, method)
|
||||
} else if sc == 0 {
|
||||
logBad(sc, target, method)
|
||||
} else if sc == 404 {
|
||||
logNotFound(sc, target, method, errorMsg)
|
||||
} else if sc == 1 {
|
||||
logDangerous(target, method)
|
||||
func writeLog(sc int, target, method, errorMsg, response string) {
|
||||
|
||||
tempResponsePreviewLength := responsePreviewLength
|
||||
|
||||
if len(response) < responsePreviewLength {
|
||||
responsePreviewLength = len(response)
|
||||
}
|
||||
|
||||
if outfile != "" {
|
||||
file, err := os.OpenFile(outfile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
fmt.Println("Output file does not exist or cannot be created")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
log.SetOutput(file)
|
||||
}
|
||||
|
||||
if outputFormat == "console" {
|
||||
if strings.Contains(response, "\"") {
|
||||
log.SetFormatter(&log.TextFormatter{DisableQuote: true, DisableTimestamp: true})
|
||||
} else {
|
||||
logManual(sc, target, method, errorMsg)
|
||||
log.SetFormatter(&log.TextFormatter{DisableTimestamp: true})
|
||||
}
|
||||
} else {
|
||||
logAccessible(sc, target, method)
|
||||
log.SetFormatter(&log.JSONFormatter{DisableHTMLEscape: true, DisableTimestamp: true})
|
||||
}
|
||||
|
||||
if verbose {
|
||||
if sc != 200 {
|
||||
if sc == 401 || sc == 403 {
|
||||
logVerboseUnauth(sc, target, method, errorMsg, response)
|
||||
} else if sc == 301 || sc == 302 {
|
||||
logVerboseRedirect(sc, target, method, response)
|
||||
} else if sc == 0 {
|
||||
logVerboseBad(sc, target, method, response)
|
||||
} else if sc == 404 {
|
||||
logNotFound(sc, target, method, errorMsg)
|
||||
} else if sc == 1 {
|
||||
logDangerous(target, method)
|
||||
} else {
|
||||
logVerboseManual(sc, target, method, errorMsg, response)
|
||||
}
|
||||
} else {
|
||||
logVerboseAccessible(sc, target, method, response)
|
||||
}
|
||||
} else {
|
||||
if sc != 200 {
|
||||
if sc == 401 || sc == 403 {
|
||||
logUnauth(sc, target, method, errorMsg)
|
||||
} else if sc == 301 || sc == 302 {
|
||||
logRedirect(sc, target, method)
|
||||
} else if sc == 0 {
|
||||
logBad(sc, target, method)
|
||||
} else if sc == 404 {
|
||||
logNotFound(sc, target, method, errorMsg)
|
||||
} else if sc == 1 {
|
||||
logDangerous(target, method)
|
||||
} else {
|
||||
logManual(sc, target, method, errorMsg)
|
||||
}
|
||||
} else {
|
||||
logAccessible(sc, target, method)
|
||||
}
|
||||
}
|
||||
responsePreviewLength = tempResponsePreviewLength
|
||||
}
|
||||
|
||||
func logAccessible(status int, target, method string) {
|
||||
@@ -32,6 +86,15 @@ func logAccessible(status int, target, method string) {
|
||||
}).Print("Endpoint accessible!")
|
||||
}
|
||||
|
||||
func logVerboseAccessible(status int, target, method, response string) {
|
||||
log.WithFields(log.Fields{
|
||||
"Status": status,
|
||||
"Target": target,
|
||||
"Method": method,
|
||||
"Preview": response[:responsePreviewLength],
|
||||
}).Print("Endpoint accessible!")
|
||||
}
|
||||
|
||||
func logDangerous(target, method string) {
|
||||
log.WithFields(log.Fields{
|
||||
"Status": "skipped",
|
||||
@@ -51,6 +114,18 @@ func logManual(status int, target, method, errorMsg string) {
|
||||
}).Warn(errorMsg)
|
||||
}
|
||||
|
||||
func logVerboseManual(status int, target, method, errorMsg, response string) {
|
||||
if errorMsg == "" {
|
||||
errorMsg = "Manual testing may be required."
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"Status": status,
|
||||
"Target": target,
|
||||
"Method": method,
|
||||
"Preview": response[:responsePreviewLength],
|
||||
}).Warn(errorMsg)
|
||||
}
|
||||
|
||||
func logNotFound(status int, target, method, errorMsg string) {
|
||||
if errorMsg == "" {
|
||||
errorMsg = "Endpoint not found."
|
||||
@@ -70,6 +145,15 @@ func logRedirect(status int, target, method string) {
|
||||
}).Error("Redirect detected. This likely requires authentication.")
|
||||
}
|
||||
|
||||
func logVerboseRedirect(status int, target, method, response string) {
|
||||
log.WithFields(log.Fields{
|
||||
"Status": status,
|
||||
"Target": target,
|
||||
"Method": method,
|
||||
"Preview": response[0:responsePreviewLength],
|
||||
}).Error("Redirect detected. This likely requires authentication.")
|
||||
}
|
||||
|
||||
func logBad(status int, target, method string) {
|
||||
log.WithFields(log.Fields{
|
||||
"Status": "N/A",
|
||||
@@ -78,6 +162,15 @@ func logBad(status int, target, method string) {
|
||||
}).Warn("Bad request (could not reach the target).")
|
||||
}
|
||||
|
||||
func logVerboseBad(status int, target, method, response string) {
|
||||
log.WithFields(log.Fields{
|
||||
"Status": "N/A",
|
||||
"Target": target,
|
||||
"Method": method,
|
||||
"Preview": response[:responsePreviewLength],
|
||||
}).Warn("Bad request (could not reach the target).")
|
||||
}
|
||||
|
||||
func logUnauth(status int, target, method, errorMsg string) {
|
||||
if errorMsg == "" {
|
||||
errorMsg = "Unauthorized."
|
||||
@@ -88,3 +181,15 @@ func logUnauth(status int, target, method, errorMsg string) {
|
||||
"Method": method,
|
||||
}).Error(errorMsg)
|
||||
}
|
||||
|
||||
func logVerboseUnauth(status int, target, method, errorMsg, response string) {
|
||||
if errorMsg == "" {
|
||||
errorMsg = "Unauthorized."
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"Status": status,
|
||||
"Target": target,
|
||||
"Method": method,
|
||||
"Preview": response[:responsePreviewLength],
|
||||
}).Error(errorMsg)
|
||||
}
|
||||
+1
-1
@@ -104,7 +104,7 @@ func MakeRequest(client http.Client, method, target string, timeout int64, reqDa
|
||||
}
|
||||
req.Header.Set(h[0], h[1])
|
||||
} else {
|
||||
log.Fatal("Custom header provided cannot be used.")
|
||||
log.Warnf("Header provided (%s) cannot be used. Headers must be in 'Key: Value' format (this may be caused by a header declared within the definition file).\n", Headers[i])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ $ sj brute -u https://petstore.swagger.io`,
|
||||
log.Error("Command not specified. See the --help flag for usage.")
|
||||
}
|
||||
},
|
||||
Version: "1.6.2",
|
||||
Version: "1.7.2",
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type SchemaReference struct {
|
||||
Required []string
|
||||
Type string
|
||||
Properties struct {
|
||||
Property struct {
|
||||
Type string
|
||||
Format string
|
||||
Example string
|
||||
Reference string
|
||||
XML struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Prefix string
|
||||
Attribute bool
|
||||
Wrapped bool
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HandleSchemaReference(schema *openapi3.SchemaRef) {
|
||||
|
||||
}
|
||||
|
||||
func (s SwaggerRequest) SetParametersFromSchema(param *openapi3.ParameterRef, location, schemaRef string, req *openapi3.RequestBodyRef, counter int) SwaggerRequest {
|
||||
if param != nil {
|
||||
name := strings.TrimPrefix(schemaRef, "#/components/schemas/")
|
||||
if s.Def.Components.Schemas[name] != nil {
|
||||
schema := s.Def.Components.Schemas[name]
|
||||
if schema.Value.Properties != nil {
|
||||
for property := range schema.Value.Properties {
|
||||
if schema.Value.Properties[property].Ref != "" {
|
||||
if counter < 3 {
|
||||
s = s.SetParametersFromSchema(param, location, schema.Value.Properties[property].Ref, req, counter+1)
|
||||
} else {
|
||||
log.Warnf("Nested reference encountered for %s (Property: %s). Test this endpoint manually.\n", s.URL.Scheme+"://"+s.URL.Host+s.URL.Path, property)
|
||||
break
|
||||
}
|
||||
} else if location == "path" {
|
||||
if schema.Value.Properties[property].Value.Type == "string" {
|
||||
if strings.Contains(s.URL.Path, param.Value.Name) {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", "test")
|
||||
} else {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+strings.ToLower(param.Value.Name)+"}", "test")
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(s.URL.Path, param.Value.Name) {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", "1")
|
||||
} else {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+strings.ToLower(param.Value.Name)+"}", "1")
|
||||
}
|
||||
}
|
||||
} else if location == "query" {
|
||||
if schema.Value.Properties[property].Value.Type == "string" {
|
||||
s.Query.Add(param.Value.Name, "test")
|
||||
} else {
|
||||
s.Query.Add(param.Value.Name, "1")
|
||||
}
|
||||
} else if location == "body" {
|
||||
if schema.Value.Properties[property].Value.Type == "string" {
|
||||
s.Body[param.Value.Name] = "test"
|
||||
} else {
|
||||
s.Body[param.Value.Name] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if schema.Value.Enum != nil {
|
||||
if location == "path" {
|
||||
value := schema.Value.Enum[rand.Intn(len(schema.Value.Enum))]
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", fmt.Sprint(value))
|
||||
} else if location == "query" {
|
||||
value := schema.Value.Enum[rand.Intn(len(schema.Value.Enum))]
|
||||
s.Query.Add(param.Value.Name, fmt.Sprint(value))
|
||||
} else if location == "body" {
|
||||
value := schema.Value.Enum[rand.Intn(len(schema.Value.Enum))]
|
||||
s.Body[param.Value.Name] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
name := strings.TrimPrefix(schemaRef, "#/components/schemas/")
|
||||
if s.Def.Components.Schemas[name] != nil {
|
||||
schema := s.Def.Components.Schemas[name]
|
||||
if schema.Value.Properties != nil {
|
||||
for property := range schema.Value.Properties {
|
||||
if schema.Value.Properties[property].Ref != "" {
|
||||
if counter < 3 {
|
||||
s = s.SetParametersFromSchema(param, location, schema.Value.Properties[property].Ref, req, counter+1)
|
||||
} else {
|
||||
log.Warnf("Nested reference encountered for %s (Property: %s). Test this endpoint manually.\n", s.URL.Scheme+"://"+s.URL.Host+s.URL.Path, property)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if schema.Value.Properties[property].Value.Type == "string" {
|
||||
s.Body[property] = "test"
|
||||
} else {
|
||||
s.Body[property] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if schema.Value.Enum != nil {
|
||||
value := schema.Value.Enum[rand.Intn(len(schema.Value.Enum))]
|
||||
s.Body[name] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
+230
-282
@@ -1,17 +1,15 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi2"
|
||||
"github.com/getkin/kin-openapi/openapi2conv"
|
||||
@@ -37,7 +35,6 @@ type SwaggerRequest struct {
|
||||
}
|
||||
|
||||
var accessibleEndpoints []string
|
||||
var specJSON string
|
||||
|
||||
func GenerateRequests(bodyBytes []byte, client http.Client) []string {
|
||||
|
||||
@@ -52,10 +49,6 @@ func GenerateRequests(bodyBytes []byte, client http.Client) []string {
|
||||
PrintSpecInfo(*s.Def.Info)
|
||||
}
|
||||
|
||||
if os.Args[1] == "automate" && outfile != "" {
|
||||
WriteJSONFile(`"results":[`, false)
|
||||
}
|
||||
|
||||
if s.Def.Paths == nil {
|
||||
log.Fatalf("Could not find any defined operations. Review the file manually.")
|
||||
}
|
||||
@@ -70,7 +63,7 @@ func GenerateRequests(bodyBytes []byte, client http.Client) []string {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
if len(s.Def.Servers) > 1 && apiTarget == "" {
|
||||
if apiTarget == "" {
|
||||
for _, server := range s.Def.Servers {
|
||||
if outputFormat == "console" && os.Args[1] == "automate" {
|
||||
log.Warnf("Results for %s:\n", server.URL)
|
||||
@@ -79,6 +72,11 @@ func GenerateRequests(bodyBytes []byte, client http.Client) []string {
|
||||
if server.URL == "/" {
|
||||
s.Path = ""
|
||||
}
|
||||
|
||||
if strings.Contains(server.URL, "localhost") || strings.Contains(server.URL, "127.0.0.1") || strings.Contains(server.URL, "::1") {
|
||||
log.Warn("The server(s) documented in the definition file contain(s) a local host value and may result in errors. Supply a target manually using the '-T' flag.")
|
||||
}
|
||||
|
||||
u, _ := url.Parse(server.URL)
|
||||
s.URL = *u
|
||||
s = s.IterateOverPaths(client)
|
||||
@@ -94,6 +92,13 @@ func GenerateRequests(bodyBytes []byte, client http.Client) []string {
|
||||
} else {
|
||||
if apiTarget != "" {
|
||||
u, _ = url.Parse(apiTarget)
|
||||
} else {
|
||||
if swaggerURL == "" {
|
||||
u, _ = url.Parse(s.Def.Servers[0].URL)
|
||||
if strings.Contains(s.Def.Servers[0].URL, "localhost") || strings.Contains(s.Def.Servers[0].URL, "127.0.0.1") || strings.Contains(s.Def.Servers[0].URL, "::1") {
|
||||
log.Warn("The server documented in the definition file contains a local host value and may result in errors. Supply a target manually using the '-T' flag.")
|
||||
}
|
||||
}
|
||||
}
|
||||
s.URL = *u
|
||||
s = s.IterateOverPaths(client)
|
||||
@@ -118,16 +123,6 @@ func GenerateRequests(bodyBytes []byte, client http.Client) []string {
|
||||
isDuplicateEndpoint = false
|
||||
}
|
||||
}
|
||||
} else if outfile == "" && strings.ToLower(outputFormat) == "json" {
|
||||
results := strings.Join(s.ResultsJSON, ",")
|
||||
fmt.Printf(`%s"results":[%s]}%s`, specJSON, results, "\n")
|
||||
} else if outfile != "" {
|
||||
if accessibleEndpoints == nil {
|
||||
WriteJSONFile("]}\n", false)
|
||||
} else {
|
||||
WriteJSONFile(",", true)
|
||||
}
|
||||
log.Infof("Results written to %s", outfile)
|
||||
}
|
||||
}
|
||||
return s.Paths
|
||||
@@ -186,74 +181,25 @@ func (s SwaggerRequest) BuildDefinedRequests(client http.Client, method string,
|
||||
|
||||
s.URL.RawQuery = s.Query.Encode()
|
||||
if os.Args[1] == "automate" {
|
||||
_, _, sc := MakeRequest(client, method, s.URL.String(), timeout, bytes.NewReader(s.BodyData))
|
||||
_, resp, sc := MakeRequest(client, method, s.URL.String(), timeout, bytes.NewReader(s.BodyData))
|
||||
if sc == 200 {
|
||||
accessibleEndpointFound = true
|
||||
accessibleEndpoints = append(accessibleEndpoints, s.URL.String())
|
||||
}
|
||||
if outfile != "" {
|
||||
if strings.ToLower(outputFormat) != "console" {
|
||||
if getAccessibleEndpoints {
|
||||
if sc == 200 {
|
||||
result := fmt.Sprintf(`{"status_code":"%d","url":"%s","method":"%s","details":"%s"},`, sc, s.URL.String(), method, errorDescriptions[fmt.Sprint(sc)])
|
||||
WriteJSONFile(result, false)
|
||||
writeLog(sc, s.URL.String(), method, errorDescriptions[fmt.Sprint(sc)], resp)
|
||||
}
|
||||
} else {
|
||||
result := fmt.Sprintf(`{"status_code":"%d","url":"%s","method":"%s","details":"%s"},`, sc, s.URL.String(), method, errorDescriptions[fmt.Sprint(sc)])
|
||||
WriteJSONFile(result, false)
|
||||
writeLog(sc, s.URL.String(), method, errorDescriptions[fmt.Sprint(sc)], resp)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
} else if outfile == "" {
|
||||
if strings.ToLower(outputFormat) != "console" {
|
||||
if getAccessibleEndpoints {
|
||||
if sc == 200 {
|
||||
s.ResultsJSON = append(s.ResultsJSON, fmt.Sprintf(`{"status_code":"%d","url":"%s","method":"%s","details":"%s"}`, sc, s.URL.String(), method, errorDescriptions[fmt.Sprint(sc)]))
|
||||
}
|
||||
} else {
|
||||
if sc == 1 {
|
||||
s.ResultsJSON = append(s.ResultsJSON, fmt.Sprintf(`{"status_code":"%d","url":"%s","method":"%s","details":"Skipped due to dangerous keyword in request"}`, sc, s.URL.String(), method))
|
||||
} else {
|
||||
s.ResultsJSON = append(s.ResultsJSON, fmt.Sprintf(`{"status_code":"%d","url":"%s","method":"%s","details":"%s"}`, sc, s.URL.String(), method, errorDescriptions[fmt.Sprint(sc)]))
|
||||
}
|
||||
}
|
||||
} else if !getAccessibleEndpoints && strings.ToLower(outputFormat) == "console" {
|
||||
writeLog(sc, s.URL.String(), method, errorDescriptions[fmt.Sprint(sc)])
|
||||
}
|
||||
}
|
||||
} else if os.Args[1] == "prepare" {
|
||||
if strings.ToLower(prepareFor) == "curl" {
|
||||
if s.BodyData == nil {
|
||||
if len(Headers) == 0 {
|
||||
fmt.Printf("curl -sk -X %s '%s'\n", method, s.URL.String())
|
||||
} else {
|
||||
fmt.Printf("curl -sk -X %s '%s' -H '%s'\n", method, s.URL.String(), strings.Join(Headers, "' -H '"))
|
||||
}
|
||||
} else {
|
||||
if len(Headers) == 0 {
|
||||
fmt.Printf("curl -sk -X %s '%s' -d '%s'\n", method, s.URL.String(), s.BodyData)
|
||||
} else {
|
||||
fmt.Printf("curl -sk -X %s '%s' -d '%s' -H '%s'\n", method, s.URL.String(), s.BodyData, strings.Join(Headers, "' -H '"))
|
||||
}
|
||||
}
|
||||
} else if strings.ToLower(prepareFor) == "sqlmap" {
|
||||
if s.BodyData == nil {
|
||||
if len(Headers) == 0 {
|
||||
fmt.Printf("sqlmap -u %s\n", s.URL.String())
|
||||
} else {
|
||||
fmt.Printf("sqlmap -u %s -H '%s'\n", s.URL.String(), strings.Join(Headers, "' -H '"))
|
||||
}
|
||||
} else {
|
||||
if len(Headers) == 0 {
|
||||
fmt.Printf("sqlmap -u %s --data='%s'\n", s.URL.String(), s.BodyData)
|
||||
} else {
|
||||
fmt.Printf("sqlmap -u %s --data='%s' -H '%s'\n", s.URL.String(), s.BodyData, strings.Join(Headers, "' -H '"))
|
||||
}
|
||||
}
|
||||
} else if strings.ToLower(prepareFor) == "ffuf" {
|
||||
// TODO
|
||||
} else {
|
||||
log.Fatal("External tool not supported. Only 'curl' and 'sqlmap' are supported options for the '-e' flag at this time.")
|
||||
writeLog(sc, s.URL.String(), method, errorDescriptions[fmt.Sprint(sc)], resp)
|
||||
}
|
||||
|
||||
//}
|
||||
} else if os.Args[1] == "prepare" {
|
||||
s.PrintPreparedCommands(method)
|
||||
} else if os.Args[1] == "endpoints" {
|
||||
|
||||
for k := range s.Def.Paths {
|
||||
@@ -272,36 +218,49 @@ func (s SwaggerRequest) AddParametersToRequest(op *openapi3.Operation) SwaggerRe
|
||||
if param.Value == nil && param.Value.Schema.Ref == "" {
|
||||
continue
|
||||
} else if param.Value.In == "path" {
|
||||
if param.Value.Schema.Ref != "" {
|
||||
s.SetParametersFromSchema(param, "path", param.Value.Schema.Ref, nil)
|
||||
} else if param.Value.Schema.Value.Type != "" && param.Value.Schema.Value.Type == "string" {
|
||||
if param.Value.Schema != nil {
|
||||
if param.Value.Schema.Ref != "" {
|
||||
s = s.SetParametersFromSchema(param, "path", param.Value.Schema.Ref, nil, 0)
|
||||
} else if param.Value.Schema.Value.Type != "" && param.Value.Schema.Value.Type == "string" {
|
||||
if strings.Contains(s.URL.Path, param.Value.Name) {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", "test")
|
||||
} else {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+strings.ToLower(param.Value.Name)+"}", "test")
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(s.URL.Path, param.Value.Name) {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", "1")
|
||||
} else {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+strings.ToLower(param.Value.Name)+"}", "1")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(s.URL.Path, param.Value.Name) {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", "test")
|
||||
} else {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+strings.ToLower(param.Value.Name)+"}", "test")
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(s.URL.Path, param.Value.Name) {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", "1")
|
||||
} else {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+strings.ToLower(param.Value.Name)+"}", "1")
|
||||
}
|
||||
}
|
||||
} else if param.Value.In == "query" {
|
||||
if param.Value.Schema.Ref != "" {
|
||||
s.SetParametersFromSchema(param, "query", param.Value.Schema.Ref, nil)
|
||||
} else {
|
||||
if param.Value.Schema.Value.Type != "" && param.Value.Schema.Value.Type == "string" {
|
||||
s.Query.Add(param.Value.Name, "test")
|
||||
if param.Value.Schema != nil {
|
||||
if param.Value.Schema.Ref != "" {
|
||||
s = s.SetParametersFromSchema(param, "query", param.Value.Schema.Ref, nil, 0)
|
||||
} else {
|
||||
s.Query.Add(param.Value.Name, "1")
|
||||
if param.Value.Schema.Value.Type != "" && param.Value.Schema.Value.Type == "string" {
|
||||
s.Query.Add(param.Value.Name, "test")
|
||||
} else {
|
||||
s.Query.Add(param.Value.Name, "1")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.Query.Add(param.Value.Name, "test")
|
||||
}
|
||||
} else if param.Value.In == "header" && param.Value.Required {
|
||||
|
||||
} else if param.Value.In == "header" && param.Value.Required && strings.ToLower(param.Value.Name) != "content-type" {
|
||||
Headers = append(Headers, fmt.Sprintf("%s: %s", param.Value.Name, "1"))
|
||||
} else if param.Value.In == "body" {
|
||||
if param.Value.Schema.Ref != "" {
|
||||
s.SetParametersFromSchema(param, "body", param.Value.Schema.Ref, nil)
|
||||
s = s.SetParametersFromSchema(param, "body", param.Value.Schema.Ref, nil, 0)
|
||||
}
|
||||
if param.Value.Schema.Value.Type == "string" {
|
||||
s.Body[param.Value.Name] = "test"
|
||||
@@ -321,80 +280,116 @@ func (s SwaggerRequest) AddParametersToRequest(op *openapi3.Operation) SwaggerRe
|
||||
if op.RequestBody != nil {
|
||||
if op.RequestBody.Value.Content != nil {
|
||||
for i := range op.RequestBody.Value.Content {
|
||||
if contentType == "" && Headers != nil && strings.Contains(strings.ToLower(strings.Join(Headers, ",")), "content-type") {
|
||||
headerString := strings.Join(Headers, ",")
|
||||
Headers = nil
|
||||
ctIndex := strings.Index(strings.ToLower(headerString), "content-type:") + 14
|
||||
headerString = headerString[ctIndex:]
|
||||
if strings.Contains(headerString, ",") {
|
||||
headerString = strings.TrimPrefix(headerString, ",")
|
||||
ctEndIndex := strings.Index(headerString[ctIndex:], ",") + 1
|
||||
headerString = headerString[:ctEndIndex]
|
||||
} else if !strings.Contains(strings.ToLower(headerString), "content-type") && (strings.Contains(strings.ToLower(headerString), "application/x-www-form-urlencoded") || strings.Contains(strings.ToLower(headerString), "application/json")) {
|
||||
headerString = ""
|
||||
}
|
||||
if headerString != "" {
|
||||
Headers = append(Headers, strings.Split(headerString, ",")...)
|
||||
}
|
||||
|
||||
}
|
||||
if op.RequestBody.Value.Content.Get(i).Schema.Value == nil {
|
||||
s.SetParametersFromSchema(nil, "body", op.RequestBody.Value.Content.Get(i).Schema.Ref, op.RequestBody)
|
||||
if strings.Contains(i, "json") {
|
||||
s.BodyData, _ = json.Marshal(s.Body)
|
||||
}
|
||||
} else if i == "application/x-www-form-urlencoded" {
|
||||
if contentType == "" && !strings.Contains(strings.ToLower(strings.Join(Headers, "")), "content-type") {
|
||||
Headers = append(Headers, "Content-Type: application/x-www-form-urlencoded")
|
||||
}
|
||||
var formData []string
|
||||
for j := range op.RequestBody.Value.Content.Get(i).Schema.Value.Properties {
|
||||
if op.RequestBody.Value.Content.Get(i).Schema.Value.Properties[j].Value != nil {
|
||||
var valueType string = op.RequestBody.Value.Content.Get(i).Schema.Value.Properties[j].Value.Type
|
||||
if valueType == "string" {
|
||||
s.Body[j] = "test"
|
||||
} else if valueType == "boolean" {
|
||||
s.Body[j] = false
|
||||
} else if valueType == "integer" || valueType == "number" {
|
||||
s.Body[j] = 1
|
||||
} else {
|
||||
s.Body[j] = "unknown_type_populate_manually"
|
||||
}
|
||||
formData = append(formData, fmt.Sprintf("%s=%s", j, fmt.Sprint(s.Body[j])))
|
||||
}
|
||||
}
|
||||
s.BodyData = []byte(strings.Join(formData, "&"))
|
||||
} else if strings.Contains(i, "json") {
|
||||
if contentType == "" && !strings.Contains(strings.ToLower(strings.Join(Headers, "")), "content-type") {
|
||||
Headers = append(Headers, "Content-Type: application/json")
|
||||
}
|
||||
for j := range op.RequestBody.Value.Content.Get(i).Schema.Value.Properties {
|
||||
if op.RequestBody.Value.Content.Get(i).Schema.Value.Properties[j].Value != nil {
|
||||
var valueType string = op.RequestBody.Value.Content.Get(i).Schema.Value.Properties[j].Value.Type
|
||||
if valueType == "string" {
|
||||
s.Body[j] = "test"
|
||||
} else if valueType == "boolean" {
|
||||
s.Body[j] = false
|
||||
} else if valueType == "integer" || valueType == "number" {
|
||||
s.Body[j] = 1
|
||||
} else {
|
||||
s.Body[j] = "unknown_type_populate_manually"
|
||||
}
|
||||
}
|
||||
}
|
||||
s.BodyData, _ = json.Marshal(s.Body)
|
||||
} else if strings.Contains(i, "multipart/form-data") {
|
||||
//TODO
|
||||
s.Body["MULTIPART_FORM_DATA"] = "TEST_MANUALLY"
|
||||
s.BodyData = []byte("MULTIPART_FORM_DATA=TEST_MANUALLY")
|
||||
} else if strings.Contains(i, "xml") {
|
||||
//TODO
|
||||
s.Body["XML"] = "TEST_MANUALLY"
|
||||
s.BodyData = []byte("XML=TEST_MANUALLY")
|
||||
if contentType == "" {
|
||||
EnforceSingleContentType(i)
|
||||
} else {
|
||||
fmt.Println(i)
|
||||
s.Body["test"] = "test"
|
||||
s.BodyData = []byte("test=test")
|
||||
EnforceSingleContentType(contentType)
|
||||
}
|
||||
if op.RequestBody.Value.Content.Get(i).Schema != nil {
|
||||
if op.RequestBody.Value.Content.Get(i).Schema.Value == nil {
|
||||
s = s.SetParametersFromSchema(nil, "body", op.RequestBody.Value.Content.Get(i).Schema.Ref, op.RequestBody, 0)
|
||||
if strings.Contains(i, "json") {
|
||||
s.BodyData, _ = json.Marshal(s.Body)
|
||||
} else if strings.Contains(i, "x-www-form-urlencoded") {
|
||||
var formData []string
|
||||
for j := range s.Body {
|
||||
formData = append(formData, fmt.Sprintf("%s=%s", j, fmt.Sprint(s.Body[j])))
|
||||
}
|
||||
s.BodyData = []byte(strings.Join(formData, "&"))
|
||||
} else if strings.Contains(i, "xml") {
|
||||
type Element struct {
|
||||
XMLName xml.Name
|
||||
Content any `xml:",chardata"`
|
||||
}
|
||||
|
||||
type Root struct {
|
||||
XMLName xml.Name `xml:"root"`
|
||||
Elements []Element `xml:",any"`
|
||||
}
|
||||
|
||||
var elements []Element
|
||||
for key, value := range s.Body {
|
||||
elements = append(elements, Element{
|
||||
XMLName: xml.Name{Local: key},
|
||||
Content: value,
|
||||
})
|
||||
}
|
||||
|
||||
root := Root{
|
||||
Elements: elements,
|
||||
}
|
||||
|
||||
xmlData, err := xml.Marshal(root)
|
||||
if err != nil {
|
||||
log.Warn("Error marshalling XML data.")
|
||||
}
|
||||
s.BodyData = xmlData
|
||||
} else {
|
||||
log.Warnf("Content type not supported. Test this path manually: %s (Content type: %s)\n", s.URL.Path, i)
|
||||
}
|
||||
} else {
|
||||
var formData []string
|
||||
|
||||
for j := range op.RequestBody.Value.Content.Get(i).Schema.Value.Properties {
|
||||
if op.RequestBody.Value.Content.Get(i).Schema.Value.Properties[j].Ref != "" {
|
||||
s = s.SetParametersFromSchema(nil, "body", op.RequestBody.Value.Content.Get(i).Schema.Value.Properties[j].Ref, op.RequestBody, 0)
|
||||
} else {
|
||||
var valueType string = op.RequestBody.Value.Content.Get(i).Schema.Value.Properties[j].Value.Type
|
||||
if op.RequestBody.Value.Content.Get(i).Schema.Value.Properties[j].Value != nil {
|
||||
if valueType == "string" {
|
||||
s.Body[j] = "test"
|
||||
} else if valueType == "boolean" {
|
||||
s.Body[j] = false
|
||||
} else if valueType == "integer" || valueType == "number" {
|
||||
s.Body[j] = 1
|
||||
} else {
|
||||
s.Body[j] = "unknown_type_populate_manually"
|
||||
}
|
||||
if i == "application/x-www-form-urlencoded" {
|
||||
formData = append(formData, fmt.Sprintf("%s=%s", j, fmt.Sprint(s.Body[j])))
|
||||
}
|
||||
}
|
||||
|
||||
if i == "application/x-www-form-urlencoded" {
|
||||
s.BodyData = []byte(strings.Join(formData, "&"))
|
||||
} else if strings.Contains(i, "json") || i == "*/*" {
|
||||
s.BodyData, _ = json.Marshal(s.Body)
|
||||
} else if strings.Contains(i, "xml") {
|
||||
//
|
||||
type Element struct {
|
||||
XMLName xml.Name
|
||||
Content any `xml:",chardata"`
|
||||
}
|
||||
|
||||
type Root struct {
|
||||
XMLName xml.Name `xml:"root"`
|
||||
Elements []Element `xml:",any"`
|
||||
}
|
||||
|
||||
var elements []Element
|
||||
for key, value := range s.Body {
|
||||
elements = append(elements, Element{
|
||||
XMLName: xml.Name{Local: key},
|
||||
Content: value,
|
||||
})
|
||||
}
|
||||
|
||||
root := Root{
|
||||
Elements: elements,
|
||||
}
|
||||
|
||||
xmlData, err := xml.Marshal(root)
|
||||
if err != nil {
|
||||
log.Warn("Error marshalling XML data.")
|
||||
}
|
||||
s.BodyData = xmlData
|
||||
} else {
|
||||
s.Body["test"] = "test"
|
||||
s.BodyData = []byte("test=test")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -406,76 +401,31 @@ func (s SwaggerRequest) AddParametersToRequest(op *openapi3.Operation) SwaggerRe
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SwaggerRequest) SetParametersFromSchema(param *openapi3.ParameterRef, location, schemaRef string, req *openapi3.RequestBodyRef) {
|
||||
if param != nil {
|
||||
name := strings.TrimPrefix(schemaRef, "#/components/schemas/")
|
||||
if s.Def.Components.Schemas[name] != nil {
|
||||
schema := s.Def.Components.Schemas[name]
|
||||
if schema.Value.Properties != nil {
|
||||
for property := range schema.Value.Properties {
|
||||
if schema.Value.Properties[property].Value.Type == "string" {
|
||||
if location == "path" {
|
||||
if strings.Contains(s.URL.Path, param.Value.Name) {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", "test")
|
||||
} else {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+strings.ToLower(param.Value.Name)+"}", "test")
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(s.URL.Path, param.Value.Name) {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", "1")
|
||||
} else {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+strings.ToLower(param.Value.Name)+"}", "1")
|
||||
}
|
||||
}
|
||||
} else if location == "query" {
|
||||
if schema.Value.Properties[property].Value.Type == "string" {
|
||||
s.Query.Add(param.Value.Name, "test")
|
||||
} else {
|
||||
s.Query.Add(param.Value.Name, "1")
|
||||
}
|
||||
} else if location == "body" {
|
||||
if schema.Value.Properties[property].Value.Type == "string" {
|
||||
s.Body[param.Value.Name] = "test"
|
||||
} else {
|
||||
s.Body[param.Value.Name] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if schema.Value.Enum != nil {
|
||||
if location == "path" {
|
||||
value := schema.Value.Enum[rand.Intn(len(schema.Value.Enum))]
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", fmt.Sprint(value))
|
||||
} else if location == "query" {
|
||||
value := schema.Value.Enum[rand.Intn(len(schema.Value.Enum))]
|
||||
s.Query.Add(param.Value.Name, fmt.Sprint(value))
|
||||
} else if location == "body" {
|
||||
value := schema.Value.Enum[rand.Intn(len(schema.Value.Enum))]
|
||||
s.Body[param.Value.Name] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
func (s SwaggerRequest) PrintPreparedCommands(method string) {
|
||||
var preparedCommand string
|
||||
if strings.ToLower(prepareFor) == "curl" {
|
||||
preparedCommand = fmt.Sprintf("curl -sk -X %s '%s'", method, s.URL.String())
|
||||
} else if strings.ToLower(prepareFor) == "sqlmap" {
|
||||
preparedCommand = fmt.Sprintf("sqlmap --method=%s -u %s", method, s.URL.String())
|
||||
} else if strings.ToLower(prepareFor) == "ffuf" {
|
||||
log.Fatal("ffuf is not supported yet :(")
|
||||
// TODO
|
||||
} else {
|
||||
name := strings.TrimPrefix(schemaRef, "#/components/schemas/")
|
||||
if s.Def.Components.Schemas[name] != nil {
|
||||
schema := s.Def.Components.Schemas[name]
|
||||
if schema.Value.Properties != nil {
|
||||
for property := range schema.Value.Properties {
|
||||
if schema.Value.Properties[property].Ref != "" {
|
||||
s.SetParametersFromSchema(nil, "body", schema.Value.Properties[property].Ref, req)
|
||||
} else {
|
||||
if schema.Value.Properties[property].Value.Type == "string" {
|
||||
s.Body[property] = "test"
|
||||
} else {
|
||||
s.Body[property] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if schema.Value.Enum != nil {
|
||||
value := schema.Value.Enum[rand.Intn(len(schema.Value.Enum))]
|
||||
s.Body[schema.Value.Title] = value
|
||||
}
|
||||
log.Fatal("External tool not supported. Only 'curl' and 'sqlmap' are supported options for the '-e' flag at this time.")
|
||||
}
|
||||
|
||||
if s.BodyData != nil {
|
||||
if strings.ToLower(prepareFor) == "sqlmap" {
|
||||
preparedCommand = preparedCommand + fmt.Sprintf(" --data='%s'", s.BodyData)
|
||||
} else {
|
||||
preparedCommand = preparedCommand + fmt.Sprintf(" -d '%s'", s.BodyData)
|
||||
}
|
||||
}
|
||||
if len(Headers) != 0 {
|
||||
preparedCommand = preparedCommand + fmt.Sprintf(" -H '%s'", strings.Join(Headers, "' -H '"))
|
||||
}
|
||||
|
||||
fmt.Println(preparedCommand)
|
||||
}
|
||||
|
||||
func (s SwaggerRequest) GetBasePath() string {
|
||||
@@ -519,10 +469,7 @@ func (s SwaggerRequest) GetBasePath() string {
|
||||
}
|
||||
|
||||
func PrintSpecInfo(i openapi3.Info) {
|
||||
specJSON = fmt.Sprintf(`{"title":"%s","description":"%s",`, i.Title, i.Description)
|
||||
if outfile != "" {
|
||||
WriteJSONFile(specJSON, false)
|
||||
} else if strings.ToLower(outputFormat) == "console" {
|
||||
if strings.ToLower(outputFormat) == "console" {
|
||||
if i.Title != "" {
|
||||
fmt.Println("Title:", i.Title)
|
||||
}
|
||||
@@ -534,6 +481,30 @@ func PrintSpecInfo(i openapi3.Info) {
|
||||
if i.Title == "" && i.Description == "" {
|
||||
log.Warnf("Detected possible error in parsing the definition file. Title and description values are empty.\n\n")
|
||||
}
|
||||
} else if strings.ToLower(outputFormat) == "json" {
|
||||
titleDescriptionLogger := log.New()
|
||||
titleDescriptionLogger.SetFormatter(&log.JSONFormatter{DisableTimestamp: true, DisableHTMLEscape: true})
|
||||
if outfile != "" {
|
||||
file, err := os.OpenFile(outfile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
fmt.Println("Output file does not exist or cannot be created")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
titleDescriptionLogger.SetOutput(file)
|
||||
}
|
||||
|
||||
if i.Title != "" {
|
||||
titleDescriptionLogger.WithField("Title:", i.Title).Print("N/A")
|
||||
}
|
||||
if i.Description != "" {
|
||||
titleDescriptionLogger.WithField("Description:", i.Description).Print("N/A")
|
||||
}
|
||||
if i.Title == "" && i.Description == "" {
|
||||
log.Warn("Detected possible error in parsing the definition file. Title and description values are empty.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,51 +570,6 @@ func UnmarshalSpec(bodyBytes []byte) (newDoc *openapi3.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func WriteJSONFile(result string, end bool) {
|
||||
if end {
|
||||
file, err := os.OpenFile(outfile, os.O_RDWR, 0644)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("File does not exist or cannot be created")
|
||||
os.Exit(1)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
w := bufio.NewWriter(file)
|
||||
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
log.Error("Failed to get file info:", err)
|
||||
}
|
||||
fileSize := fileInfo.Size()
|
||||
if fileSize != 0 {
|
||||
lastChar := fileSize - 1
|
||||
|
||||
_, err = file.Seek(lastChar, 0)
|
||||
if err != nil {
|
||||
log.Error("Error seeking to last character", err)
|
||||
}
|
||||
fmt.Fprintf(w, "]}\n")
|
||||
|
||||
}
|
||||
w.Flush()
|
||||
} else {
|
||||
file, err := os.OpenFile(outfile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("File does not exist or cannot be created")
|
||||
os.Exit(1)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
w := bufio.NewWriter(file)
|
||||
|
||||
fmt.Fprintf(w, "%s", result)
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ExtractSpecFromJS(bodyBytes []byte) []byte {
|
||||
var openApiIndex int
|
||||
var specClose int
|
||||
@@ -714,3 +640,25 @@ func CheckAndConfigureProxy() (client http.Client) {
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func EnforceSingleContentType(newContentType string) {
|
||||
newContentType = strings.TrimSpace(newContentType)
|
||||
if Headers != nil {
|
||||
headerString := strings.Join(Headers, ",")
|
||||
Headers = nil
|
||||
ctIndex := strings.Index(strings.ToLower(headerString), "content-type:") + 14
|
||||
headerString = headerString[ctIndex:]
|
||||
if strings.Contains(headerString, ",") {
|
||||
headerString = strings.TrimPrefix(headerString, ",")
|
||||
ctEndIndex := strings.Index(headerString[ctIndex:], ",") + 1
|
||||
headerString = headerString[:ctEndIndex]
|
||||
} else if !strings.Contains(headerString, ":") {
|
||||
headerString = ""
|
||||
}
|
||||
if headerString != "" {
|
||||
Headers = append(Headers, strings.Split(headerString, ",")...)
|
||||
}
|
||||
}
|
||||
|
||||
Headers = append(Headers, "Content-Type: "+newContentType)
|
||||
}
|
||||
Reference in new issue
Block a user