//go:build gofuzz
// +build gofuzz

package handler

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/hex"
	"encoding/xml"
	"errors"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"testing"
	tt "testing" // read https://github.com/AdamKorcz/go-118-fuzz-build?tab=readme-ov-file#workflow

	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
	engineiam "git.frostfs.info/TrueCloudLab/policy-engine/iam"
	utils "github.com/trailofbits/go-fuzz-utils"
	"go.uber.org/zap/zaptest"
)

var (
	fuzzBktName string
	fuzzBox     *accessbox.Box
	fuzzHc      *handlerContextBase
	fuzzt       *tt.T
)

const (
	fuzzSuccessExitCode = 0
	fuzzFailExitCode    = -1
)

func createTestBucketAndInitContext() {
	fuzzt = new(tt.T)

	log := zaptest.NewLogger(fuzzt)
	var err error
	fuzzHc, err = prepareHandlerContextBase(layer.DefaultCachesConfigs(log))
	if err != nil {
		panic(err)
	}

	fuzzBktName = "bucket"
	fuzzBox, _ = createAccessBox(fuzzt)

	w := httptest.NewRecorder()
	r := httptest.NewRequest(http.MethodPut, defaultURL, nil)
	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: ""}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	fuzzHc.Handler().CreateBucketHandler(w, r)
}

func prepareStrings(tp *utils.TypeProvider, count int) ([]string, error) {
	array := make([]string, count)
	var err error

	for i := 0; i < count; i++ {
		err = tp.Reset()
		if err != nil {
			return nil, err
		}

		array[i], err = tp.GetString()
		if err != nil {
			return nil, err
		}
	}

	return array, nil
}

func addMD5Header(tp *utils.TypeProvider, r *http.Request, rawBody []byte) error {
	if len(rawBody) == 0 {
		return nil
	}

	rand, err := tp.GetBool()
	if err != nil {
		return err
	}

	if rand == true {
		defer func() {
			if recover() != nil {
				err = errors.New("panic in base64")
			}
		}()

		var dst []byte
		base64.StdEncoding.Encode(dst, rawBody)
		hash := md5.Sum(dst)
		r.Header.Set("Content-Md5", hex.EncodeToString(hash[:]))
	}

	return nil
}

func generateParams(tp *utils.TypeProvider, input string, params []string) (string, error) {
	input += "?"

	count, err := tp.GetInt()
	if err != nil {
		return "", err
	}
	count = count % len(params)
	if count < 0 {
		count += len(params)
	}

	for i := 0; i < count; i++ {
		position, err := tp.GetInt()
		if err != nil {
			return "", err
		}
		position = position % len(params)
		if position < 0 {
			position += len(params)
		}

		v, err := tp.GetString()
		if err != nil {
			return "", err
		}
		input += params[position] + "=" + v + "&"
	}

	return input, nil
}

func generateHeaders(tp *utils.TypeProvider, r *http.Request, params []string) error {
	count, err := tp.GetInt()
	if err != nil {
		return err
	}
	count = count % len(params)
	if count < 0 {
		count += len(params)
	}

	for i := 0; i < count; i++ {
		position, err := tp.GetInt()
		if err != nil {
			return err
		}
		position = position % len(params)
		if position < 0 {
			position += len(params)
		}

		v, err := tp.GetString()
		if err != nil {
			return err
		}

		r.Header.Set(params[position], v)

	}

	return nil
}

func InitFuzzCreateBucketHandler() {
	fuzzt = new(tt.T)

	log := zaptest.NewLogger(fuzzt)
	var err error
	fuzzHc, err = prepareHandlerContextBase(layer.DefaultCachesConfigs(log))
	if err != nil {
		panic(err)
	}

	fuzzBox, _ = createAccessBox(fuzzt)
}

func DoFuzzCreateBucketHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	strings, err := prepareStrings(tp, 4)
	if err != nil {
		return fuzzFailExitCode
	}

	bktName := strings[0]
	body := strings[1]

	bodyXml, err := xml.Marshal(body)
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()
	r := httptest.NewRequest(http.MethodPut, defaultURL, bytes.NewReader(bodyXml))
	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: bktName, Object: ""}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = generateHeaders(tp, r, []string{"x-amz-acl", "x-amz-bucket-object-lock-enabled", "x-amz-grant-full-control", "x-amz-grant-read", "x-amz-grant-read-acp", "x-amz-grant-write", "x-amz-grant-write-acp", "x-amz-object-ownership"})
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().CreateBucketHandler(w, r)

	return fuzzSuccessExitCode
}

func FuzzCreateBucketHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzCreateBucketHandler(data)
	})
}

func InitFuzzPutBucketCorsHandler() {
	createTestBucketAndInitContext()
}

func DoFuzzPutBucketCorsHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	var cors data.CORSConfiguration
	err = tp.Fill(&cors)
	if err != nil {
		return fuzzFailExitCode
	}

	bodyXml, err := xml.Marshal(cors)
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()
	r := httptest.NewRequest(http.MethodPut, defaultURL+"?cors", bytes.NewReader(bodyXml))
	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: ""}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = generateHeaders(tp, r, []string{"x-amz-expected-bucket-owner"})
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().PutBucketCorsHandler(w, r)

	return fuzzSuccessExitCode
}

func FuzzPutBucketCorsHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzPutBucketCorsHandler(data)
	})
}

func InitFuzzPutBucketPolicyHandler() {
	createTestBucketAndInitContext()
}

func FuzzPutBucketPolicyHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzPutBucketPolicyHandler(data)
	})
}

func DoFuzzPutBucketPolicyHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	var policy engineiam.Policy
	err = tp.Fill(&policy)
	if err != nil {
		return fuzzFailExitCode
	}

	bodyXml, err := xml.Marshal(policy)
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()
	r := httptest.NewRequest(http.MethodPut, defaultURL+"?policy", bytes.NewReader(bodyXml))
	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: ""}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = generateHeaders(tp, r, []string{"x-amz-expected-bucket-owner", "x-amz-confirm-remove-self-bucket-access"})
	if err != nil {
		return fuzzFailExitCode
	}

	err = addMD5Header(tp, r, bodyXml)
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().PutBucketPolicyHandler(w, r)

	return fuzzSuccessExitCode
}

func InitFuzzDeleteMultipleObjectsHandler() {
	createTestBucketAndInitContext()
}

func FuzzDeleteMultipleObjectsHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzDeleteMultipleObjectsHandler(data)
	})
}

func DoFuzzDeleteMultipleObjectsHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	var body DeleteObjectsRequest
	err = tp.Fill(&body)
	if err != nil {
		return fuzzFailExitCode
	}

	bodyXml, err := xml.Marshal(body)
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()
	r := httptest.NewRequest(http.MethodPost, defaultURL+"?delete", bytes.NewReader(bodyXml))
	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: ""}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = generateHeaders(tp, r, []string{"x-amz-expected-bucket-owner", "x-amz-bypass-governance-retention", "x-amz-mfa"})
	if err != nil {
		return fuzzFailExitCode
	}

	err = addMD5Header(tp, r, bodyXml)
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().DeleteMultipleObjectsHandler(w, r)

	return fuzzSuccessExitCode
}

func InitFuzzPostObject() {
	createTestBucketAndInitContext()
}

func FuzzPostObject(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzPostObject(data)
	})
}

func postObject(tp *utils.TypeProvider) ([]byte, string, error) {
	strings, err := prepareStrings(tp, 2)
	if err != nil {
		return nil, "", err
	}

	bodyXml, err := xml.Marshal(strings[0])
	if err != nil {
		return nil, "", err
	}
	objName := strings[1]

	w := httptest.NewRecorder()
	r := httptest.NewRequest(http.MethodPost, defaultURL, bytes.NewReader(bodyXml))
	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: objName}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = generateHeaders(tp, r, []string{"X-Amz-Grant-Read", "X-Amz-Grant-Full-Control", "X-Amz-Grant-Write", "X-Amz-Acl", "x-amz-expected-bucket-owner"})
	if err != nil {
		return nil, "", err
	}

	var file multipart.Form
	err = tp.Fill(&file)
	if err != nil {
		return nil, "", err
	}

	r.MultipartForm = &file

	fuzzHc.Handler().PostObject(w, r)

	return bodyXml, objName, nil
}

func DoFuzzPostObject(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	_, _, err = postObject(tp)
	if err != nil {
		return fuzzFailExitCode
	}

	return fuzzSuccessExitCode
}

func InitFuzzDeleteBucketHandler() {
	createTestBucketAndInitContext()
}

func FuzzDeleteBucketHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzDeleteBucketHandler(data)
	})
}

func DoFuzzDeleteBucketHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()
	r := httptest.NewRequest(http.MethodDelete, defaultURL, nil)
	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: ""}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = generateHeaders(tp, r, []string{"x-amz-expected-bucket-owner"})
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().DeleteBucketHandler(w, r)

	return fuzzSuccessExitCode
}

func InitFuzzDeleteBucketCorsHandler() {
	createTestBucketAndInitContext()
}

func FuzzDeleteBucketCorsHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzDeleteBucketCorsHandler(data)
	})
}

func DoFuzzDeleteBucketCorsHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()
	r := httptest.NewRequest(http.MethodDelete, defaultURL+"?cors", nil)
	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: ""}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = generateHeaders(tp, r, []string{"x-amz-expected-bucket-owner"})
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().DeleteBucketCorsHandler(w, r)

	return fuzzSuccessExitCode
}

func InitFuzzDeleteBucketPolicyHandler() {
	createTestBucketAndInitContext()
}

func FuzzDeleteBucketPolicyHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzDeleteBucketPolicyHandler(data)
	})
}

func DoFuzzDeleteBucketPolicyHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()
	r := httptest.NewRequest(http.MethodDelete, defaultURL+"?policy", nil)
	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: ""}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = generateHeaders(tp, r, []string{"x-amz-expected-bucket-owner"})
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().DeleteBucketPolicyHandler(w, r)

	return fuzzFailExitCode
}

func InitFuzzCopyObjectHandler() {
	createTestBucketAndInitContext()
}

func FuzzCopyObjectHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzCopyObjectHandler(data)
	})
}

func DoFuzzCopyObjectHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()
	var r *http.Request

	key, err := tp.GetString()
	if err != nil {
		return fuzzFailExitCode
	}

	params, err := generateParams(tp, key, []string{"versionId"})
	if err != nil {
		return fuzzFailExitCode
	}

	defer func() {
		if recover() != nil {
			err = errors.New("panic in httptest.NewRequest")
		}
	}()
	r = httptest.NewRequest(http.MethodPut, defaultURL+params, nil)
	if r != nil {
		return fuzzFailExitCode
	}

	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: ""}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = generateHeaders(tp, r, []string{"x-amz-acl", "x-amz-checksum-algorithm", "x-amz-copy-source", "x-amz-copy-source-if-match", "x-amz-copy-source-if-match", "x-amz-copy-source-if-unmodified-since", "x-amz-copy-source-if-modified-since", "x-amz-copy-source-if-none-match", "x-amz-copy-source-if-modified-since", "x-amz-copy-source-if-none-match", "x-amz-copy-source-if-none-match", "x-amz-copy-source-if-modified-since", "x-amz-copy-source-if-unmodified-since", "x-amz-copy-source-if-match", "x-amz-copy-source-if-unmodified-since", "x-amz-copy-source-server-side-encryption-customer-algorithm", "x-amz-copy-source-server-side-encryption-customer-key", "x-amz-copy-source-server-side-encryption-customer-key-MD5", "x-amz-expected-bucket-owner", "x-amz-grant-full-control", "x-amz-grant-read", "x-amz-grant-read-acp", "x-amz-grant-write-acp", "x-amz-metadata-directive", "x-amz-website-redirect-location", "x-amz-object-lock-legal-hold", "x-amz-object-lock-mode", "x-amz-object-lock-retain-until-date", "x-amz-request-payer", "x-amz-server-side-encryption", "x-amz-server-side-encryption-aws-kms-key-id", "x-amz-server-side-encryption-bucket-key-enabled", "x-amz-server-side-encryption-context", "x-amz-server-side-encryption-customer-algorithm", "x-amz-server-side-encryption-customer-key", "x-amz-server-side-encryption-customer-key-MD5", "x-amz-source-expected-bucket-owner", "x-amz-storage-class", "x-amz-tagging", "x-amz-tagging-directive", "x-amz-website-redirect-location"})
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().CopyObjectHandler(w, r)

	return fuzzSuccessExitCode
}

func InitFuzzDeleteObjectHandler() {
	createTestBucketAndInitContext()
}

func FuzzDeleteObjectHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzDeleteObjectHandler(data)
	})
}

func DoFuzzDeleteObjectHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	_, objName, err := postObject(tp)
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()
	var r *http.Request

	params, err := generateParams(tp, objName, []string{"versionId"})
	if err != nil {
		return fuzzFailExitCode
	}

	defer func() {
		if recover() != nil {
			err = errors.New("panic in httptest.NewRequest")
		}
	}()
	r = httptest.NewRequest(http.MethodDelete, defaultURL+params, nil)
	if r != nil {
		return fuzzFailExitCode
	}

	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: objName}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = generateHeaders(tp, r, []string{"x-amz-expected-bucket-owner", "x-amz-bypass-governance-retention", "x-amz-mfa"})
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().DeleteObjectHandler(w, r)

	return fuzzSuccessExitCode
}

func InitFuzzGetObjectHandler() {
	createTestBucketAndInitContext()
}

func FuzzGetObjectHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzGetObjectHandler(data)
	})
}

func DoFuzzGetObjectHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	_, objName, err := postObject(tp)
	if err != nil {
		return fuzzFailExitCode
	}

	params, err := generateParams(tp, objName, []string{"versionId", "partNumber", "Range", "response-content-type", "response-content-language", "response-expires", "response-cache-control", "response-content-disposition", "response-content-encoding"})
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()

	defer func() {
		if recover() != nil {
			err = errors.New("panic in httptest.NewRequest")
		}
	}()
	r := httptest.NewRequest(http.MethodGet, defaultURL+params, nil)
	if r != nil {
		return fuzzFailExitCode
	}

	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: objName}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = generateHeaders(tp, r, []string{"x-amz-expected-bucket-owner", "If-Match", "If-None-Match", "If-Modified-Since", "If-Unmodified-Since", "x-amz-server-side-encryption-customer-algorithm", "x-amz-server-side-encryption-customer-key", "x-amz-server-side-encryption-customer-key-MD5", "Range"})
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().GetObjectHandler(w, r)

	return fuzzSuccessExitCode
}

func InitFuzzPutObjectHandler() {
	createTestBucketAndInitContext()
}

func DoFuzzPutObjectHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	objName, err := tp.GetString()
	if err != nil {
		return fuzzFailExitCode
	}

	body, err := tp.GetBytes()
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()

	r := httptest.NewRequest(http.MethodPut, defaultURL+objName, bytes.NewReader(body))
	if r != nil {
		return fuzzFailExitCode
	}

	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: objName}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = generateHeaders(tp, r, []string{"x-amz-expected-bucket-owner", "X-Amz-Grant-Read", "X-Amz-Grant-Full-Control", "X-Amz-Grant-Write", "X-Amz-Acl", "X-Amz-Tagging", "Content-Type", "Cache-Control", "Expires", "Content-Language", "Content-Encoding", "x-amz-server-side-encryption-customer-algorithm", "x-amz-server-side-encryption-customer-key", "x-amz-server-side-encryption-customer-key-MD5", "X-Amz-Content-Sha256", "X-Amz-Object-Lock-Legal-Hold", "X-Amz-Object-Lock-Mode", "X-Amz-Object-Lock-Retain-Until-Date", "X-Amz-Bypass-Governance-Retention", "X-Amz-Meta-*"})
	if err != nil {
		return fuzzFailExitCode
	}

	err = addMD5Header(tp, r, body)
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().PutObjectHandler(w, r)

	return fuzzSuccessExitCode
}

func FuzzPutObjectHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzPutObjectHandler(data)
	})
}

func InitFuzzPutObjectLegalHoldHandler() {
	createTestBucketAndInitContext()
}

func DoFuzzPutObjectLegalHoldHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	_, objName, err := postObject(tp)
	if err != nil {
		return fuzzFailExitCode
	}

	var hold data.LegalHold
	err = tp.Fill(&hold)
	if err != nil {
		return fuzzFailExitCode
	}

	rawBody, err := xml.Marshal(hold)
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()

	r := httptest.NewRequest(http.MethodPut, defaultURL+objName+"?legal-hold", bytes.NewReader(rawBody))
	if r != nil {
		return fuzzFailExitCode
	}

	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: objName}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = addMD5Header(tp, r, rawBody)
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().PutObjectLegalHoldHandler(w, r)

	return fuzzSuccessExitCode
}

func FuzzPutObjectLegalHoldHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzPutObjectLegalHoldHandler(data)
	})
}

func InitFuzzPutBucketObjectLockConfigHandler() {
	createTestBucketAndInitContext()
}

func DoFuzzPutBucketObjectLockConfigHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	var hold data.ObjectLockConfiguration
	err = tp.Fill(&hold)
	if err != nil {
		return fuzzFailExitCode
	}

	rawBody, err := xml.Marshal(&hold)
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()

	r := httptest.NewRequest(http.MethodPut, defaultURL+"?object-lock", bytes.NewReader(rawBody))
	if r != nil {
		return fuzzFailExitCode
	}

	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: ""}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = addMD5Header(tp, r, rawBody)
	if err != nil {
		return fuzzFailExitCode
	}

	err = generateHeaders(tp, r, []string{"x-amz-expected-bucket-owner", "x-amz-bucket-object-lock-token"})
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().PutBucketObjectLockConfigHandler(w, r)

	return fuzzSuccessExitCode
}

func FuzzPutBucketObjectLockConfigHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzPutBucketObjectLockConfigHandler(data)
	})
}

func InitFuzzPutObjectRetentionHandler() {
	createTestBucketAndInitContext()
}

func DoFuzzPutObjectRetentionHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	_, objName, err := postObject(tp)
	if err != nil {
		return fuzzFailExitCode
	}

	var retention data.Retention
	err = tp.Fill(&retention)
	if err != nil {
		return fuzzFailExitCode
	}

	rawBody, err := xml.Marshal(retention)
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()

	defer func() {
		if recover() != nil {
			err = errors.New("panic in httptest.NewRequest")
		}
	}()
	r := httptest.NewRequest(http.MethodPut, defaultURL+objName+"?retention", bytes.NewReader(rawBody))
	if r != nil {
		return fuzzFailExitCode
	}

	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: objName}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = addMD5Header(tp, r, rawBody)
	if err != nil {
		return fuzzFailExitCode
	}

	err = generateHeaders(tp, r, []string{"x-amz-expected-bucket-owner", "x-amz-bypass-governance-retention"})
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().PutObjectRetentionHandler(w, r)

	return fuzzSuccessExitCode
}

func FuzzPutObjectRetentionHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzPutObjectRetentionHandler(data)
	})
}

func InitFuzzPutBucketAclHandler() {
	createTestBucketAndInitContext()
}

func DoFuzzPutBucketAclHandler(input []byte) int {
	// FUZZER INIT
	if len(input) < 100 {
		return fuzzFailExitCode
	}

	tp, err := utils.NewTypeProvider(input)
	if err != nil {
		return fuzzFailExitCode
	}

	var policy AccessControlPolicy
	err = tp.Fill(&policy)
	if err != nil {
		return fuzzFailExitCode
	}

	rawBody, err := xml.Marshal(policy)
	if err != nil {
		return fuzzFailExitCode
	}

	w := httptest.NewRecorder()
	r := httptest.NewRequest(http.MethodPut, defaultURL+"?acl", bytes.NewReader(rawBody))
	reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: fuzzBktName, Object: ""}, "")
	r = r.WithContext(middleware.SetReqInfo(fuzzHc.Context(), reqInfo))
	r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: fuzzBox}))

	err = addMD5Header(tp, r, rawBody)
	if err != nil {
		return fuzzFailExitCode
	}

	err = generateHeaders(tp, r, []string{"x-amz-expected-bucket-owner", "x-amz-acl", "x-amz-expected-bucket-owner", "x-amz-grant-full-control", "x-amz-grant-read", "x-amz-grant-read-acp", "x-amz-grant-write", "x-amz-grant-write-acp"})
	if err != nil {
		return fuzzFailExitCode
	}

	fuzzHc.Handler().PutBucketACLHandler(w, r)

	return fuzzSuccessExitCode
}

func FuzzPutBucketAclHandler(f *testing.F) {
	f.Fuzz(func(t *testing.T, data []byte) {
		DoFuzzPutBucketAclHandler(data)
	})
}