package customizations

import (
	
	
	
	

	
	

	
	awsmiddleware 
	
	
	
	s3arn 
	
)

const (
	s3AccessPoint  = "s3-accesspoint"
	s3ObjectLambda = "s3-object-lambda"
)

// processARNResource is used to process an ARN resource.
type processARNResource struct {

	// UseARNRegion indicates if region parsed from an ARN should be used.
	UseARNRegion bool

	// UseAccelerate indicates if s3 transfer acceleration is enabled
	UseAccelerate bool

	// EndpointResolver used to resolve endpoints. This may be a custom endpoint resolver
	EndpointResolver EndpointResolver

	// EndpointResolverOptions used by endpoint resolver
	EndpointResolverOptions EndpointResolverOptions

	// DisableMultiRegionAccessPoints indicates multi-region access point support is disabled
	DisableMultiRegionAccessPoints bool
}

// ID returns the middleware ID.
func (*processARNResource) () string { return "S3:ProcessARNResource" }

func ( *processARNResource) (
	 context.Context,  middleware.SerializeInput,  middleware.SerializeHandler,
) (
	 middleware.SerializeOutput,  middleware.Metadata,  error,
) {
	// check if arn was provided, if not skip this middleware
	,  := s3shared.GetARNResourceFromContext()
	if ! {
		return .HandleSerialize(, )
	}

	,  := .Request.(*http.Request)
	if ! {
		return , , fmt.Errorf("unknown request type %T", )
	}

	// parse arn into an endpoint arn wrt to service
	,  := s3arn.ParseEndpointARN()
	if  != nil {
		return , , 
	}

	// build a resource request struct
	 := s3shared.ResourceRequest{
		Resource:      ,
		UseARNRegion:  .UseARNRegion,
		UseFIPS:       .EndpointResolverOptions.UseFIPSEndpoint == aws.FIPSEndpointStateEnabled,
		RequestRegion: awsmiddleware.GetRegion(),
		SigningRegion: awsmiddleware.GetSigningRegion(),
		PartitionID:   awsmiddleware.GetPartitionID(),
	}

	// switch to correct endpoint updater
	switch tv := .(type) {
	case arn.AccessPointARN:
		// multi-region arns do not need to validate for cross partition request
		if len(.Region) != 0 {
			// validate resource request
			if  := validateRegionForResourceRequest();  != nil {
				return , , 
			}
		}

		// Special handling for region-less ap-arns.
		if len(.Region) == 0 {
			// check if multi-region arn support is disabled
			if .DisableMultiRegionAccessPoints {
				return , , fmt.Errorf("Invalid configuration, Multi-Region access point ARNs are disabled")
			}

			// Do not allow dual-stack configuration with multi-region arns.
			if .EndpointResolverOptions.UseDualStackEndpoint == aws.DualStackEndpointStateEnabled {
				return , , s3shared.NewClientConfiguredForDualStackError(,
					.PartitionID, .RequestRegion, nil)
			}
		}

		// check if accelerate
		if .UseAccelerate {
			return , , s3shared.NewClientConfiguredForAccelerateError(,
				.PartitionID, .RequestRegion, nil)
		}

		// fetch arn region to resolve request
		 := .Region
		// check if request region is FIPS
		if .UseFIPS && len() == 0 {
			// Do not allow Fips support within multi-region arns.
			return , , s3shared.NewClientConfiguredForFIPSError(
				, .PartitionID, .RequestRegion, nil)
		}

		var  func(context.Context, accesspointOptions) (context.Context, error)
		if len() == 0 {
			 = buildMultiRegionAccessPointsRequest
		} else {
			 = buildAccessPointRequest
		}

		// build request as per accesspoint builder
		,  = (, accesspointOptions{
			processARNResource: *,
			request:            ,
			resource:           ,
			resolveRegion:      ,
			partitionID:        .PartitionID,
			requestRegion:      .RequestRegion,
		})
		if  != nil {
			return , , 
		}

	case arn.S3ObjectLambdaAccessPointARN:
		// validate region for resource request
		if  := validateRegionForResourceRequest();  != nil {
			return , , 
		}

		// check if accelerate
		if .UseAccelerate {
			return , , s3shared.NewClientConfiguredForAccelerateError(,
				.PartitionID, .RequestRegion, nil)
		}

		// check if dualstack
		if .EndpointResolverOptions.UseDualStackEndpoint == aws.DualStackEndpointStateEnabled {
			return , , s3shared.NewClientConfiguredForDualStackError(,
				.PartitionID, .RequestRegion, nil)
		}

		// fetch arn region to resolve request
		 := .Region

		// build access point request
		,  = buildS3ObjectLambdaAccessPointRequest(, accesspointOptions{
			processARNResource: *,
			request:            ,
			resource:           .AccessPointARN,
			resolveRegion:      ,
			partitionID:        .PartitionID,
			requestRegion:      .RequestRegion,
		})
		if  != nil {
			return , , 
		}

	// process outpost accesspoint ARN
	case arn.OutpostAccessPointARN:
		// validate region for resource request
		if  := validateRegionForResourceRequest();  != nil {
			return , , 
		}

		// check if accelerate
		if .UseAccelerate {
			return , , s3shared.NewClientConfiguredForAccelerateError(,
				.PartitionID, .RequestRegion, nil)
		}

		// check if dual stack
		if .EndpointResolverOptions.UseDualStackEndpoint == aws.DualStackEndpointStateEnabled {
			return , , s3shared.NewClientConfiguredForDualStackError(,
				.PartitionID, .RequestRegion, nil)
		}

		// check if request region is FIPS
		if .UseFIPS {
			return , , s3shared.NewFIPSConfigurationError(, .PartitionID,
				.RequestRegion, nil)
		}

		// build outpost access point request
		,  = buildOutpostAccessPointRequest(, outpostAccessPointOptions{
			processARNResource: *,
			resource:           ,
			request:            ,
			partitionID:        .PartitionID,
			requestRegion:      .RequestRegion,
		})
		if  != nil {
			return , , 
		}

	default:
		return , , s3shared.NewInvalidARNError(, nil)
	}

	return .HandleSerialize(, )
}

// validate if s3 resource and request region config is compatible.
func ( s3shared.ResourceRequest) error {
	// check if resourceRequest leads to a cross partition error
	,  := .IsCrossPartition()
	if  != nil {
		return 
	}
	if  {
		// if cross partition
		return s3shared.NewClientPartitionMismatchError(.Resource,
			.PartitionID, .RequestRegion, nil)
	}

	// check if resourceRequest leads to a cross region error
	if !.AllowCrossRegion() && .IsCrossRegion() {
		// if cross region, but not use ARN region is not enabled
		return s3shared.NewClientRegionMismatchError(.Resource,
			.PartitionID, .RequestRegion, nil)
	}

	return nil
}

// === Accesspoint ==========

type accesspointOptions struct {
	processARNResource
	request       *http.Request
	resource      arn.AccessPointARN
	resolveRegion string
	partitionID   string
	requestRegion string
}

func ( context.Context,  accesspointOptions) (context.Context, error) {
	 := .resource
	 := .request
	 := .resolveRegion

	 := .Service

	 := .EndpointResolverOptions
	.Logger = middleware.GetLogger()
	.ResolvedRegion = "" // clear endpoint option's resolved region so that we resolve using the passed in region

	// resolve endpoint
	,  := .EndpointResolver.ResolveEndpoint(, )
	if  != nil {
		return , s3shared.NewFailedToResolveEndpointError(
			,
			.partitionID,
			.requestRegion,
			,
		)
	}

	// assign resolved endpoint url to request url
	.URL,  = url.Parse(.URL)
	if  != nil {
		return , fmt.Errorf("failed to parse endpoint URL: %w", )
	}

	if len(.SigningName) != 0 && .Source == aws.EndpointSourceCustom {
		 = awsmiddleware.SetSigningName(, .SigningName)
	} else {
		// Must sign with s3-object-lambda
		 = awsmiddleware.SetSigningName(, )
	}

	if len(.SigningRegion) != 0 {
		 = awsmiddleware.SetSigningRegion(, .SigningRegion)
	} else {
		 = awsmiddleware.SetSigningRegion(, )
	}

	// update serviceID to "s3-accesspoint"
	 = awsmiddleware.SetServiceID(, s3AccessPoint)

	// disable host prefix behavior
	 = http.DisableEndpointHostPrefix(, true)

	// remove the serialized arn in place of /{Bucket}
	 = setBucketToRemoveOnContext(, .String())

	// skip arn processing, if arn region resolves to a immutable endpoint
	if .HostnameImmutable {
		return , nil
	}

	updateS3HostForS3AccessPoint()

	,  = buildAccessPointHostPrefix(, , )
	if  != nil {
		return , 
	}

	return , nil
}

func ( context.Context,  accesspointOptions) (context.Context, error) {
	 := .resource
	 := .request
	 := .resolveRegion

	 := .Service

	 := .EndpointResolverOptions
	.Logger = middleware.GetLogger()
	.ResolvedRegion = "" // clear endpoint options resolved region so we resolve the passed in region

	// resolve endpoint
	,  := .EndpointResolver.ResolveEndpoint(, )
	if  != nil {
		return , s3shared.NewFailedToResolveEndpointError(
			,
			.partitionID,
			.requestRegion,
			,
		)
	}

	// assign resolved endpoint url to request url
	.URL,  = url.Parse(.URL)
	if  != nil {
		return , fmt.Errorf("failed to parse endpoint URL: %w", )
	}

	if len(.SigningName) != 0 && .Source == aws.EndpointSourceCustom {
		 = awsmiddleware.SetSigningName(, .SigningName)
	} else {
		// Must sign with s3-object-lambda
		 = awsmiddleware.SetSigningName(, )
	}

	if len(.SigningRegion) != 0 {
		 = awsmiddleware.SetSigningRegion(, .SigningRegion)
	} else {
		 = awsmiddleware.SetSigningRegion(, )
	}

	// update serviceID to "s3-object-lambda"
	 = awsmiddleware.SetServiceID(, s3ObjectLambda)

	// disable host prefix behavior
	 = http.DisableEndpointHostPrefix(, true)

	// remove the serialized arn in place of /{Bucket}
	 = setBucketToRemoveOnContext(, .String())

	// skip arn processing, if arn region resolves to a immutable endpoint
	if .HostnameImmutable {
		return , nil
	}

	if .Source == aws.EndpointSourceServiceMetadata {
		updateS3HostForS3ObjectLambda()
	}

	,  = buildAccessPointHostPrefix(, , )
	if  != nil {
		return , 
	}

	return , nil
}

func ( context.Context,  accesspointOptions) (context.Context, error) {
	const  = "s3-global."
	const  = "accesspoint."

	 := .resource
	 := .request
	 := .Service
	 := .requestRegion
	 := .Partition

	// resolve endpoint
	 := .EndpointResolverOptions
	.Logger = middleware.GetLogger()

	,  := .EndpointResolver.ResolveEndpoint(, )
	if  != nil {
		return , s3shared.NewFailedToResolveEndpointError(
			,
			.partitionID,
			.requestRegion,
			,
		)
	}

	// set signing region and version for MRAP
	.SigningRegion = "*"
	 = awsmiddleware.SetSigningRegion(, .SigningRegion)
	 = SetSignerVersion(, v4a.Version)

	if len(.SigningName) != 0 {
		 = awsmiddleware.SetSigningName(, .SigningName)
	} else {
		 = awsmiddleware.SetSigningName(, )
	}

	// skip arn processing, if arn region resolves to a immutable endpoint
	if .HostnameImmutable {
		return , nil
	}

	// modify endpoint host to use s3-global host prefix
	 := strings.SplitN(.URL, "://", 2)
	,  := endpoints.GetDNSSuffix(, )
	if  != nil {
		return , fmt.Errorf("Error determining dns suffix from arn partition, %w", )
	}
	// set url as per partition
	.URL = [0] + "://" +  + 

	// assign resolved endpoint url to request url
	.URL,  = url.Parse(.URL)
	if  != nil {
		return , fmt.Errorf("failed to parse endpoint URL: %w", )
	}

	// build access point host prefix
	 := .AccessPointName + "." + 

	// add host prefix to url
	.URL.Host =  + .URL.Host
	if len(.Host) > 0 {
		.Host =  + .Host
	}

	// validate the endpoint host
	if  := http.ValidateEndpointHost(.URL.Host);  != nil {
		return , fmt.Errorf("endpoint validation error: %w, when using arn %v", , )
	}

	// disable host prefix behavior
	 = http.DisableEndpointHostPrefix(, true)

	// remove the serialized arn in place of /{Bucket}
	 = setBucketToRemoveOnContext(, .String())

	return , nil
}

func ( context.Context,  *http.Request,  arn.AccessPointARN) (context.Context, error) {
	// add host prefix for access point
	 := .AccessPointName + "-" + .AccountID + "."
	.URL.Host =  + .URL.Host
	if len(.Host) > 0 {
		.Host =  + .Host
	}

	// validate the endpoint host
	if  := http.ValidateEndpointHost(.URL.Host);  != nil {
		return , s3shared.NewInvalidARNError(, )
	}

	return , nil
}

// ====== Outpost Accesspoint ========

type outpostAccessPointOptions struct {
	processARNResource
	request       *http.Request
	resource      arn.OutpostAccessPointARN
	partitionID   string
	requestRegion string
}

func ( context.Context,  outpostAccessPointOptions) (context.Context, error) {
	 := .resource
	 := .request

	 := .Region
	 := .Service
	 := 
	if strings.EqualFold(, "s3-outposts") {
		// assign endpoints ID as "S3"
		 = "s3"
	}

	 := .EndpointResolverOptions
	.Logger = middleware.GetLogger()
	.ResolvedRegion = ""

	// resolve regional endpoint for resolved region.
	,  := .EndpointResolver.ResolveEndpoint(, )
	if  != nil {
		return , s3shared.NewFailedToResolveEndpointError(
			,
			.partitionID,
			.requestRegion,
			,
		)
	}

	// assign resolved endpoint url to request url
	.URL,  = url.Parse(.URL)
	if  != nil {
		return , fmt.Errorf("failed to parse endpoint URL: %w", )
	}

	// assign resolved service from arn as signing name
	if len(.SigningName) != 0 && .Source == aws.EndpointSourceCustom {
		 = awsmiddleware.SetSigningName(, .SigningName)
	} else {
		 = awsmiddleware.SetSigningName(, )
	}

	if len(.SigningRegion) != 0 {
		// redirect signer to use resolved endpoint signing name and region
		 = awsmiddleware.SetSigningRegion(, .SigningRegion)
	} else {
		 = awsmiddleware.SetSigningRegion(, )
	}

	// update serviceID to resolved service id
	 = awsmiddleware.SetServiceID(, )

	// disable host prefix behavior
	 = http.DisableEndpointHostPrefix(, true)

	// remove the serialized arn in place of /{Bucket}
	 = setBucketToRemoveOnContext(, .String())

	// skip further customizations, if arn region resolves to a immutable endpoint
	if .HostnameImmutable {
		return , nil
	}

	updateHostPrefix(, , )

	// add host prefix for s3-outposts
	 := .AccessPointName + "-" + .AccountID + "." + .OutpostID + "."
	.URL.Host =  + .URL.Host
	if len(.Host) > 0 {
		.Host =  + .Host
	}

	// validate the endpoint host
	if  := http.ValidateEndpointHost(.URL.Host);  != nil {
		return , s3shared.NewInvalidARNError(, )
	}

	return , nil
}