Compare commits

...
Author SHA1 Message Date
Mzack9999 6a1e040363 Prefer structured 2026-10-26 16:36:22 +01:00
Mzack9999 105b1ba7d0 Merge dev 2026-10-26 13:48:09 +01:00
Mzack9999 5ae0145cc9 Fix iteration 2026-10-26 08:05:59 +01:00
Mzack9999 c96b397aa0 Merge dev 2026-10-26 07:57:38 +01:00
Mzack9999 8b3caf4d49 Merge origin 2026-10-26 07:57:37 +01:00
Mzack9999 cc93090527 bound records 2026-09-24 00:50:53 +02:00
Mzack9999 6ed56ce6af fix parser 2026-09-23 21:58:12 +02:00
Mzack9999 e752d0d098 merge dev 2026-09-23 20:23:20 +02:00
Mzack9999 9ea561f066 merge dev 2026-09-14 22:32:12 +02:00
Mzack9999 e1a30dcb61 katana input 2026-07-15 18:40:13 +04:00
7 changed files with 588 additions and 2 deletions

No files matched your search

+14
View File
@@ -6,6 +6,7 @@ Currently the following formats are implemented -
- Burp Suite XML Request/Response file
- Proxify JSONL output file
- Katana JSONL crawl output file
- OpenAPI Specification file
- Postman Collection file
- Swagger Specification file
@@ -106,3 +107,16 @@ Swagger specification file is converted from OpenAPI 2.0 format to OpenAPI 3.0 f
## Burp XML / Proxify JSONL
These modules are generic and parse raw requests from these respective tools.
## Katana JSONL
This module ingests the JSONL crawl output of the [katana](https://github.com/projectdiscovery/katana) crawler (`katana -jsonl -o crawl.jsonl`), bridging crawling and DAST so a live crawl of a target can directly drive fuzzing (`nuclei -im katana -l crawl.jsonl -dast ...`).
For each crawled endpoint it produces a fuzzable request:
- When katana captured the raw request (the common case, including authenticated and non-GET requests), the raw request is parsed as-is, preserving method, headers, body, and all parameters.
- When only discrete fields are present (method, endpoint, headers, body), a raw request is synthesized from them and parsed back, so the result has the same shape as every other input format.
The parser is tolerant of mixed input: blank and malformed lines are skipped with a warning, and a bare URL line (katana's default non-JSONL output) is treated as a `GET` request.
A single record may be up to 32MB, which is well above a realistic worst case crawl record. Larger records, including a malformed one that never terminates, are drained and skipped with a warning rather than buffered, so one bad line cannot exhaust memory and end the scan.
+2 -1
View File
@@ -12,7 +12,8 @@ import (
"gopkg.in/yaml.v3"
)
// ParseReqRespCallback is a callback function for discovered raw requests
// ParseReqRespCallback is a callback function for discovered raw requests.
// It returns false to ask the format to stop parsing.
type ParseReqRespCallback func(rr *types.RequestResponse) bool
// InputFormatOptions contains options for the input
+283
View File
@@ -0,0 +1,283 @@
// Package katana implements an input format that ingests the JSONL output of
// the katana crawler (`katana -jsonl`) and turns each crawled endpoint into a
// fuzzable nuclei request. This bridges crawling and DAST: instead of fuzzing
// only a proxy-fed list of requests, nuclei can consume a crawl of the target
// directly, preserving method, headers, body and parameters discovered on the
// wire (including non-GET requests, which a bare URL list cannot express).
package katana
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strings"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)
const (
// MaxRecordSize is the maximum size of a single katana JSONL record. Crawled
// requests can carry large bodies and raw dumps, so the limit sits well above
// the 10MB of a realistic worst case record, while still bounding the memory a
// single record can consume. Records above it are skipped, not buffered.
MaxRecordSize = 32 * 1024 * 1024
// readBufferSize is the size of the chunks a record is read in. An oversized or
// unterminated record is drained through this buffer, so the memory used while
// skipping it stays constant regardless of how long the record is.
readBufferSize = 64 * 1024
)
// KatanaFormat is a parser for katana JSONL crawl output.
type KatanaFormat struct {
opts formats.InputFormatOptions
}
// New creates a new katana JSONL format parser.
func New() *KatanaFormat {
return &KatanaFormat{}
}
var _ formats.Format = &KatanaFormat{}
// katanaResult mirrors the relevant subset of katana's output.Result JSON.
type katanaResult struct {
Request *katanaRequest `json:"request"`
Error string `json:"error"`
}
// katanaRequest mirrors the relevant subset of katana's navigation.Request JSON.
type katanaRequest struct {
Method string `json:"method"`
Endpoint string `json:"endpoint"`
Body string `json:"body"`
Headers map[string]string `json:"headers"`
Raw string `json:"raw"`
}
// Name returns the name of the format.
func (k *KatanaFormat) Name() string {
return "katana"
}
// SetOptions sets the options for the input format.
func (k *KatanaFormat) SetOptions(options formats.InputFormatOptions) {
k.opts = options
}
// Parse parses katana JSONL output and calls the provided callback for each
// request it discovers. It is tolerant of mixed input: blank lines are skipped,
// and a bare URL line (katana's default non-JSONL output) is treated as a GET.
// Records above MaxRecordSize are dropped with a warning so that a malformed or
// unterminated record cannot exhaust memory.
func (k *KatanaFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error {
reader := bufio.NewReaderSize(input, readBufferSize)
for {
record, oversized, err := readRecord(reader)
if err != nil && !errors.Is(err, io.EOF) {
return fmt.Errorf("could not read katana jsonl input: %w", err)
}
if oversized {
gologger.Warning().Msgf("katana: skipping record larger than %d bytes\n", MaxRecordSize)
} else if len(record) > 0 && !k.handleRecord(record, resultsCb) {
return nil
}
if errors.Is(err, io.EOF) {
return nil
}
}
}
// readRecord reads a single newline delimited record, keeping at most
// MaxRecordSize bytes in memory. A record above the limit is drained chunk by
// chunk and reported as oversized so the caller can skip it, instead of being
// accumulated in full.
func readRecord(reader *bufio.Reader) (record []byte, oversized bool, err error) {
for {
chunk, err := reader.ReadSlice('\n')
if len(chunk) > 0 {
// The delimiter is trimmed off the record, so it does not count
// towards the limit.
size := len(chunk)
if chunk[size-1] == '\n' {
size--
}
if oversized || len(record)+size > MaxRecordSize {
// Release what was read so far and keep consuming until the record
// ends: only the reader buffer stays allocated while draining.
oversized, record = true, nil
} else {
record = append(record, chunk...)
}
}
// A full buffer without a delimiter means the record continues.
if errors.Is(err, bufio.ErrBufferFull) {
continue
}
return bytes.TrimSpace(record), oversized, err
}
}
// handleRecord parses a single record and reports whether parsing should
// continue. A skipped record continues; the callback returns false to stop.
func (k *KatanaFormat) handleRecord(record []byte, resultsCb formats.ParseReqRespCallback) bool {
if record[0] != '{' {
// Bare URL line (katana default output without -jsonl): treat as GET.
line := string(record)
if !isAbsoluteURL(line) {
gologger.Warning().Msg("katana: could not parse line as a URL or JSON record\n")
return true
}
rr, err := k.buildFromComponents(http.MethodGet, line, nil, "")
if err != nil {
gologger.Warning().Msgf("katana: could not parse url %s: %s\n", line, err)
return true
}
return resultsCb(rr)
}
var result katanaResult
if err := json.Unmarshal(record, &result); err != nil {
gologger.Warning().Msgf("katana: could not decode jsonl line: %s\n", err)
return true
}
if result.Request == nil || result.Request.Endpoint == "" {
gologger.Warning().Msg("katana: invalid record with missing request or endpoint\n")
return true
}
if !isAbsoluteURL(result.Request.Endpoint) {
gologger.Warning().Msgf("katana: skipping non http(s) endpoint %s\n", result.Request.Endpoint)
return true
}
rr, err := k.toRequestResponse(result.Request)
if err != nil {
gologger.Warning().Msgf("katana: could not parse request %s: %s\n", result.Request.Endpoint, err)
return true
}
return resultsCb(rr)
}
// toRequestResponse converts a katana request into nuclei's standard
// RequestResponse. It prefers the captured raw request when available and falls
// back to synthesizing one from the discovered components otherwise.
func (k *KatanaFormat) toRequestResponse(req *katanaRequest) (*types.RequestResponse, error) {
method := req.Method
if method == "" {
method = http.MethodGet
}
if strings.TrimSpace(req.Raw) == "" {
return k.buildFromComponents(method, req.Endpoint, req.Headers, req.Body)
}
rr, err := types.ParseRawRequestWithURL(req.Raw, req.Endpoint)
if err != nil || !rawConflicts(req, rr) {
return rr, err
}
// Some katana versions record a form submission as a POST with a body but
// keep a raw GET without it (katana issue 876). Rebuild the request from the
// structured fields so the method and body survive, and keep the headers the
// raw request captured, such as cookies.
return k.buildFromComponents(method, req.Endpoint, mergeRawHeaders(rr, req.Headers), req.Body)
}
// rawConflicts reports whether the parsed raw request disagrees with the
// structured method or body of the katana request.
func rawConflicts(req *katanaRequest, rr *types.RequestResponse) bool {
if rr == nil || rr.Request == nil {
return false
}
if req.Method != "" && !strings.EqualFold(req.Method, rr.Request.Method) {
return true
}
return req.Body != "" && rr.Request.Body == ""
}
// mergeRawHeaders returns the headers of the raw request overridden by the
// structured headers. Framing headers are dropped, because they describe the
// raw body and not the structured one.
func mergeRawHeaders(rr *types.RequestResponse, structured map[string]string) map[string]string {
headers := make(map[string]string)
rr.Request.Headers.Iterate(func(key, value string) bool {
if !strings.EqualFold(key, "Content-Length") && !strings.EqualFold(key, "Transfer-Encoding") {
headers[http.CanonicalHeaderKey(key)] = value
}
return true
})
for key, value := range structured {
headers[http.CanonicalHeaderKey(key)] = value
}
return headers
}
// buildFromComponents synthesizes a raw HTTP request from discrete fields and
// parses it back into a RequestResponse, reusing the well-tested raw parser so
// the resulting object is identical in shape to the other input formats.
func (k *KatanaFormat) buildFromComponents(method, endpoint string, headers map[string]string, body string) (*types.RequestResponse, error) {
parsed, err := url.Parse(endpoint)
if err != nil {
return nil, fmt.Errorf("invalid endpoint: %w", err)
}
if parsed.Host == "" {
return nil, fmt.Errorf("endpoint %q has no host", endpoint)
}
requestURI := parsed.RequestURI()
if requestURI == "" {
requestURI = "/"
}
var sb strings.Builder
sb.WriteString(method)
sb.WriteString(" ")
sb.WriteString(requestURI)
sb.WriteString(" HTTP/1.1\r\n")
sb.WriteString("Host: ")
sb.WriteString(parsed.Host)
sb.WriteString("\r\n")
// Emit headers in a deterministic order, skipping Host (already written)
// and any header with a line break, which would split the raw request.
for _, key := range sortedHeaderKeys(headers) {
if strings.EqualFold(key, "Host") || strings.ContainsAny(key+headers[key], "\r\n") {
continue
}
sb.WriteString(key)
sb.WriteString(": ")
sb.WriteString(headers[key])
sb.WriteString("\r\n")
}
sb.WriteString("\r\n")
if body != "" {
sb.WriteString(body)
}
return types.ParseRawRequestWithURL(sb.String(), endpoint)
}
// sortedHeaderKeys returns the header keys in deterministic (sorted) order.
func sortedHeaderKeys(headers map[string]string) []string {
if len(headers) == 0 {
return nil
}
keys := make([]string, 0, len(headers))
for k := range headers {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
// isAbsoluteURL reports whether the line is an absolute http(s) URL.
func isAbsoluteURL(line string) bool {
u, err := url.Parse(line)
return err == nil && u.IsAbs() && u.Host != "" && (u.Scheme == "http" || u.Scheme == "https")
}
+245
View File
@@ -0,0 +1,245 @@
package katana
import (
"io"
"os"
"runtime"
"strings"
"testing"
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
"github.com/stretchr/testify/require"
)
func TestKatanaFormatName(t *testing.T) {
require.Equal(t, "katana", New().Name())
}
func TestKatanaFormatParse(t *testing.T) {
inputFile := "../testdata/katana.jsonl"
file, err := os.Open(inputFile)
require.Nilf(t, err, "error opening katana input file: %v", err)
defer func() { _ = file.Close() }()
var got []*types.RequestResponse
err = New().Parse(file, func(rr *types.RequestResponse) bool {
got = append(got, rr)
return true
}, inputFile)
require.NoError(t, err)
// 3 JSONL records + 1 bare URL line; malformed and blank lines skipped.
require.Len(t, got, 4)
urls := make([]string, 0, len(got))
for _, rr := range got {
urls = append(urls, rr.URL.String())
}
require.ElementsMatch(t, []string{
"https://ginandjuice.shop/catalog/product?productId=1",
"https://ginandjuice.shop/catalog/subscribe",
"https://ginandjuice.shop/login",
"https://ginandjuice.shop/about",
}, urls)
}
func TestKatanaFormatRawRequestPreserved(t *testing.T) {
// A POST with a captured raw request must preserve method and body.
rr := parseSingle(t, `{"request":{"method":"POST","endpoint":"https://example.com/sub","raw":"POST /sub HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nemail=a%40b.com"}}`)
require.Equal(t, "POST", rr.Request.Method)
require.Equal(t, "email=a%40b.com", rr.Request.Body)
require.Equal(t, "https://example.com/sub", rr.URL.String())
}
func TestKatanaFormatRawConflictUsesStructuredRequest(t *testing.T) {
// katana issue 876: a POST with a body whose raw request is a GET without it.
rr := parseSingle(t, `{"request":{"method":"POST","endpoint":"https://example.com/sub","body":"user=a&pass=b","headers":{"Content-Type":"application/x-www-form-urlencoded"},"raw":"GET /sub HTTP/1.1\r\nHost: example.com\r\nCookie: s=1\r\nContent-Length: 0\r\n\r\n"}}`)
require.Equal(t, "POST", rr.Request.Method)
require.Equal(t, "user=a&pass=b", rr.Request.Body)
require.Equal(t, "https://example.com/sub", rr.URL.String())
built, err := rr.BuildRequest()
require.NoError(t, err)
require.Equal(t, "s=1", built.Header.Get("Cookie"))
require.Equal(t, "application/x-www-form-urlencoded", built.Header.Get("Content-Type"))
require.Equal(t, int64(len("user=a&pass=b")), built.ContentLength)
}
func TestKatanaFormatComponentSynthesis(t *testing.T) {
// No raw request: it must be synthesized from method/endpoint/headers/body.
rr := parseSingle(t, `{"request":{"method":"POST","endpoint":"https://example.com/login","headers":{"Content-Type":"application/json"},"body":"{\"u\":\"admin\"}"}}`)
require.Equal(t, "POST", rr.Request.Method)
require.Equal(t, `{"u":"admin"}`, rr.Request.Body)
require.Equal(t, "https://example.com/login", rr.URL.String())
built, err := rr.BuildRequest()
require.NoError(t, err)
require.Equal(t, "application/json", built.Header.Get("Content-Type"))
require.Equal(t, "example.com", built.Host)
}
func TestKatanaFormatQueryParamsRetained(t *testing.T) {
// Parameter variants must survive so the fuzzer can target them.
rr := parseSingle(t, `{"request":{"method":"GET","endpoint":"https://example.com/p?id=1&q=2"}}`)
require.Equal(t, "https://example.com/p?id=1&q=2", rr.URL.String())
require.Equal(t, "GET", rr.Request.Method)
}
func TestKatanaFormatGracefulSkips(t *testing.T) {
input := strings.Join([]string{
"",
"garbage",
`{"request":null}`,
`{"request":{"endpoint":""}}`,
`{"request":{"method":"GET","endpoint":"ftp://example.com/file"}}`,
`{"request":{"method":"GET","endpoint":"/relative/only"}}`,
`{"request":{"method":"GET","endpoint":"https://example.com/ok"}}`,
}, "\n")
var got []*types.RequestResponse
err := New().Parse(strings.NewReader(input), func(rr *types.RequestResponse) bool {
got = append(got, rr)
return true
}, "test")
require.NoError(t, err)
require.Len(t, got, 1)
require.Equal(t, "https://example.com/ok", got[0].URL.String())
}
func TestKatanaFormatCallbackStops(t *testing.T) {
input := strings.Join([]string{
`{"request":{"method":"GET","endpoint":"https://example.com/1"}}`,
`{"request":{"method":"GET","endpoint":"https://example.com/2"}}`,
}, "\n")
var count int
err := New().Parse(strings.NewReader(input), func(_ *types.RequestResponse) bool {
count++
return false // request to stop after the first
}, "test")
require.NoError(t, err)
require.Equal(t, 1, count)
}
func TestKatanaFormatLargeRecord(t *testing.T) {
largeBody := strings.Repeat("a", 10*1024*1024+1)
input := `{"request":{"method":"POST","endpoint":"https://example.com/upload","body":"` + largeBody + `"}}`
rr := parseSingle(t, input)
require.Equal(t, largeBody, rr.Request.Body)
}
func TestKatanaFormatRecordSizeLimit(t *testing.T) {
const (
prefix = `{"request":{"method":"POST","endpoint":"https://example.com/upload","body":"`
suffix = `"}}`
)
record := func(size int) string {
return prefix + strings.Repeat("a", size-len(prefix)-len(suffix)) + suffix
}
// A record right at the limit is parsed, one byte over it is dropped.
rr := parseSingle(t, record(MaxRecordSize))
require.Len(t, rr.Request.Body, MaxRecordSize-len(prefix)-len(suffix))
var got []*types.RequestResponse
err := New().Parse(strings.NewReader(record(MaxRecordSize+1)), func(rr *types.RequestResponse) bool {
got = append(got, rr)
return true
}, "test")
require.NoError(t, err)
require.Empty(t, got)
}
func TestKatanaFormatOversizedRecordSkipped(t *testing.T) {
// A record above the limit must be dropped while the rest of the file is
// still parsed.
input := io.MultiReader(
strings.NewReader(`{"request":{"method":"POST","endpoint":"https://example.com/upload","body":"`),
io.LimitReader(filler{}, MaxRecordSize),
strings.NewReader("\"}}\n"+`{"request":{"method":"GET","endpoint":"https://example.com/ok"}}`),
)
var got []*types.RequestResponse
err := New().Parse(input, func(rr *types.RequestResponse) bool {
got = append(got, rr)
return true
}, "test")
require.NoError(t, err)
require.Len(t, got, 1)
require.Equal(t, "https://example.com/ok", got[0].URL.String())
}
func TestKatanaFormatUnterminatedRecordIsBounded(t *testing.T) {
// An unterminated record is drained rather than buffered, so what parsing
// costs must not grow with how long the record is: a record sixteen times
// over the limit may not cost measurably more than one just over it.
small := allocsForUnterminatedRecord(t, 2*MaxRecordSize)
large := allocsForUnterminatedRecord(t, 16*MaxRecordSize)
require.LessOrEqual(t, large, small+MaxRecordSize,
"draining a record 16x over the limit allocated %d bytes against %d for 2x", large, small)
}
// allocsForUnterminatedRecord reports the bytes allocated while parsing a
// record of the given size that never terminates with a newline.
func allocsForUnterminatedRecord(t *testing.T, size int64) uint64 {
t.Helper()
input := io.MultiReader(
strings.NewReader(`{"request":{"method":"POST","endpoint":"https://example.com/upload","body":"`),
io.LimitReader(filler{}, size),
)
var before, after runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&before)
var got []*types.RequestResponse
err := New().Parse(input, func(rr *types.RequestResponse) bool {
got = append(got, rr)
return true
}, "test")
runtime.ReadMemStats(&after)
require.NoError(t, err)
require.Empty(t, got)
return after.TotalAlloc - before.TotalAlloc
}
// filler is an endless source of record payload bytes, used to feed oversized
// records without materializing them in the test.
type filler struct{}
func (filler) Read(p []byte) (int, error) {
for i := range p {
p[i] = 'a'
}
return len(p), nil
}
func parseSingle(t *testing.T, line string) *types.RequestResponse {
t.Helper()
var got *types.RequestResponse
err := New().Parse(strings.NewReader(line), func(rr *types.RequestResponse) bool {
got = rr
return true
}, "test")
require.NoError(t, err)
require.NotNil(t, got)
return got
}
func TestKatanaFormatHeaderLineBreaksDropped(t *testing.T) {
// A crawled header value with a line break must not inject extra headers.
rr := parseSingle(t, `{"request":{"method":"GET","endpoint":"https://example.com/x","headers":{"X-Ok":"1","X-Bad":"a\r\nX-Injected: 1"}}}`)
value, ok := rr.Request.Headers.Get("X-Ok")
require.True(t, ok)
require.Equal(t, "1", value)
_, ok = rr.Request.Headers.Get("X-Injected")
require.False(t, ok)
_, ok = rr.Request.Headers.Get("X-Bad")
require.False(t, ok)
}
+6
View File
@@ -0,0 +1,6 @@
{"timestamp":"2024-01-01T00:00:00Z","request":{"method":"GET","endpoint":"https://ginandjuice.shop/catalog/product?productId=1","tag":"a","source":"https://ginandjuice.shop/","raw":"GET /catalog/product?productId=1 HTTP/1.1\r\nHost: ginandjuice.shop\r\nUser-Agent: katana\r\n\r\n"}}
{"timestamp":"2024-01-01T00:00:01Z","request":{"method":"POST","endpoint":"https://ginandjuice.shop/catalog/subscribe","body":"email=test%40test.com","raw":"POST /catalog/subscribe HTTP/1.1\r\nHost: ginandjuice.shop\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 21\r\n\r\nemail=test%40test.com"}}
{"timestamp":"2024-01-01T00:00:02Z","request":{"method":"POST","endpoint":"https://ginandjuice.shop/login","headers":{"Content-Type":"application/json"},"body":"{\"username\":\"admin\",\"password\":\"x\"}"}}
not-json-should-be-skipped
https://ginandjuice.shop/about
+35
View File
@@ -0,0 +1,35 @@
package http
import (
"testing"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/stretchr/testify/require"
)
func TestHttpInputProviderIteratesAllKatanaRecords(t *testing.T) {
input := `{"request":{"method":"GET","endpoint":"https://example.com/a?id=1"}}` + "\n" +
`{"request":{"method":"POST","endpoint":"https://example.com/login","body":"user=admin"}}` + "\n" +
"https://example.com/bare?q=1\n"
provider, err := NewHttpInputProvider(&HttpMultiFormatOptions{
InputMode: "katana",
InputContents: input,
})
require.NoError(t, err)
require.Equal(t, int64(3), provider.Count())
var got []string
provider.Iterate(func(value *contextargs.MetaInput) bool {
got = append(got, value.Input)
return true
})
require.Equal(t, []string{"https://example.com/a?id=1", "https://example.com/login", "https://example.com/bare?q=1"}, got)
var first []string
provider.Iterate(func(value *contextargs.MetaInput) bool {
first = append(first, value.Input)
return false
})
require.Len(t, first, 1)
}
+3 -1
View File
@@ -11,6 +11,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/burp"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/json"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/katana"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/openapi"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/raw"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/swagger"
@@ -88,7 +89,7 @@ func NewHttpInputProvider(opts *HttpMultiFormatOptions) (*HttpInputProvider, err
parseErr := format.Parse(bytes.NewReader(data), func(request *types.RequestResponse) bool {
count++
return false
return true
}, opts.InputFile)
if parseErr != nil {
return nil, errors.Wrap(parseErr, "could not parse input file")
@@ -147,6 +148,7 @@ var providersList = []formats.Format{
openapi.New(),
swagger.New(),
raw.New(),
katana.New(),
}
// SupportedFormats returns the list of supported formats in comma-separated