Ejemplo n.º 1
0
        public async Task <TResp> SendRequestAsync <TReq, TResp>(TReq request, CancellationToken cancellationToken = default) where TResp : IResponse, new() where TReq : IRequest
        {
            cancellationToken.ThrowIfCancellationRequested();

            request.Timestamp = DateTimeOffset.UtcNow;
            request.RequestId = Guid.NewGuid();

            _logger.LogTrace("Sending {RequestType} with request id {RequestId}", typeof(TReq).Name, request.RequestId);

            S3Config config = _options.Value;

            Stream requestStream = _marshaller.MarshalRequest(request, config);

            _validator.ValidateAndThrow(request);

            string bucketName = null;

            if (request is IHasBucketName bn)
            {
                bucketName = bn.BucketName;
            }

            string objectKey = null;

            if (request is IHasObjectKey ok)
            {
                objectKey = ok.ObjectKey;
            }

            //Ensure that the object key is encoded
            string encodedResource = objectKey != null?UrlHelper.UrlPathEncode(objectKey) : null;

            if (config.Endpoint == null || config.NamingMode == NamingMode.PathStyle)
            {
                if (bucketName != null)
                {
                    objectKey = bucketName + '/' + encodedResource;
                }
                else
                {
                    objectKey = encodedResource;
                }
            }
            else
            {
                objectKey = encodedResource;
            }

            StringBuilder sb = StringBuilderPool.Shared.Rent(100);

            Uri endpoint = config.Endpoint;

            if (endpoint != null)
            {
                sb.Append(endpoint.Host);

                if (!endpoint.IsDefaultPort)
                {
                    sb.Append(':').Append(endpoint.Port);
                }
            }
            else
            {
                if (config.NamingMode == NamingMode.VirtualHost)
                {
                    if (bucketName != null)
                    {
                        sb.Append(bucketName).Append(".s3.").Append(ValueHelper.EnumToString(config.Region)).Append(".amazonaws.com");
                    }
                    else
                    {
                        sb.Append("s3.").Append(ValueHelper.EnumToString(config.Region)).Append(".amazonaws.com");
                    }
                }
                else
                {
                    sb.Append("s3.").Append(ValueHelper.EnumToString(config.Region)).Append(".amazonaws.com");
                }
            }

            request.SetHeader(HttpHeaders.Host, sb.ToString());
            request.SetHeader(AmzHeaders.XAmzDate, request.Timestamp, DateTimeFormat.Iso8601DateTime);

            if (requestStream != null)
            {
                foreach (IRequestStreamWrapper wrapper in _requestStreamWrappers)
                {
                    if (wrapper.IsSupported(request))
                    {
                        requestStream = wrapper.Wrap(requestStream, request);
                    }
                }
            }

            if (!request.Headers.TryGetValue(AmzHeaders.XAmzContentSha256, out string contentHash))
            {
                if (config.PayloadSignatureMode == SignatureMode.Unsigned)
                {
                    contentHash = "UNSIGNED-PAYLOAD";
                }
                else
                {
                    contentHash = requestStream == null ? "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" : CryptoHelper.Sha256Hash(requestStream, true).HexEncode();
                }

                request.SetHeader(AmzHeaders.XAmzContentSha256, contentHash);
            }

            _logger.LogDebug("ContentSha256 is {ContentSha256}", contentHash);

            //We add the authorization header here because we need ALL other headers to be present when we do
            request.SetHeader(HttpHeaders.Authorization, _authBuilder.BuildAuthorization(request));

            sb.Append('/').Append(objectKey);

            //Map all the parameters on to the url
            if (request.QueryParameters.Count > 0)
            {
                sb.Append('?').Append(UrlHelper.CreateQueryString(request.QueryParameters));
            }

            string scheme  = endpoint == null ? config.UseTLS ? "https" : "http" : endpoint.Scheme;
            string fullUrl = scheme + "://" + sb;

            StringBuilderPool.Shared.Return(sb);

            _logger.LogDebug("Building request for {Url}", fullUrl);

            (int statusCode, IDictionary <string, string> headers, Stream responseStream) = await _networkDriver.SendRequestAsync(request.Method, fullUrl, request.Headers, requestStream, cancellationToken).ConfigureAwait(false);

            //Clear sensitive material from the request
            if (request is IContainSensitiveMaterial sensitive)
            {
                sensitive.ClearSensitiveMaterial();
            }

            TResp response = new TResp();

            response.StatusCode       = statusCode;
            response.ContentLength    = headers.GetHeaderLong(HttpHeaders.ContentLength);
            response.ConnectionClosed = "closed".Equals(headers.GetHeader(HttpHeaders.Connection), StringComparison.OrdinalIgnoreCase);
            response.Date             = headers.GetHeaderDate(HttpHeaders.Date, DateTimeFormat.Rfc1123);
            response.Server           = headers.GetHeader(HttpHeaders.Server);
            response.ResponseId       = headers.GetHeader(AmzHeaders.XAmzId2);
            response.RequestId        = headers.GetHeader(AmzHeaders.XAmzRequestId);

            // https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
            response.IsSuccess = !(statusCode == 403 || //Forbidden
                                   statusCode == 400 || //BadRequest
                                   statusCode == 500 || //InternalServerError
                                   statusCode == 416 || //RequestedRangeNotSatisfiable
                                   statusCode == 405 || //MethodNotAllowed
                                   statusCode == 411 || //LengthRequired
                                   statusCode == 404 || //NotFound
                                   statusCode == 501 || //NotImplemented
                                   statusCode == 504 || //GatewayTimeout
                                   statusCode == 301 || //MovedPermanently
                                   statusCode == 412 || //PreconditionFailed
                                   statusCode == 307 || //TemporaryRedirect
                                   statusCode == 409 || //Conflict
                                   statusCode == 503);    //ServiceUnavailable

            //Only marshal successful responses
            if (response.IsSuccess)
            {
                _marshaller.MarshalResponse(config, request, response, headers, responseStream);
            }
            else
            {
                MemoryStream ms = new MemoryStream();
                responseStream.CopyTo(ms);

                if (ms.Length > 0)
                {
                    ms.Seek(0, SeekOrigin.Begin);

                    using (responseStream)
                        response.Error = ErrorHandler.Create(ms);

                    _logger.LogError("Received error: '{Message}'. Details: '{Details}'", response.Error.Message, response.Error.GetExtraData());
                }
            }

            return(response);
        }
Ejemplo n.º 2
0
        public async Task <TResp> HandleResponse <TReq, TResp>(TReq request, string url, Stream?requestStream, CancellationToken token) where TResp : IResponse, new() where TReq : IRequest
        {
            _logger.LogDebug("Sending request to {Url}", url);

            (int statusCode, IDictionary <string, string> headers, Stream? responseStream) = await _networkDriver.SendRequestAsync(request.Method, url, request.Headers, requestStream, token).ConfigureAwait(false);

            //Clear sensitive material from the request
            if (request is IContainSensitiveMaterial sensitive)
            {
                sensitive.ClearSensitiveMaterial();
            }

            TResp response = new TResp();

            response.StatusCode       = statusCode;
            response.ContentLength    = headers.GetHeaderLong(HttpHeaders.ContentLength);
            response.ConnectionClosed = "closed".Equals(headers.GetHeader(HttpHeaders.Connection), StringComparison.OrdinalIgnoreCase);
            response.Date             = headers.GetHeaderDate(HttpHeaders.Date, DateTimeFormat.Rfc1123);
            response.Server           = headers.GetHeader(HttpHeaders.Server);
            response.ResponseId       = headers.GetHeader(AmzHeaders.XAmzId2);
            response.RequestId        = headers.GetHeader(AmzHeaders.XAmzRequestId);

            // https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
            response.IsSuccess = !(statusCode == 403 || //Forbidden
                                   statusCode == 400 || //BadRequest
                                   statusCode == 500 || //InternalServerError
                                   statusCode == 416 || //RequestedRangeNotSatisfiable
                                   statusCode == 405 || //MethodNotAllowed
                                   statusCode == 411 || //LengthRequired
                                   statusCode == 404 || //NotFound
                                   statusCode == 501 || //NotImplemented
                                   statusCode == 504 || //GatewayTimeout
                                   statusCode == 301 || //MovedPermanently
                                   statusCode == 412 || //PreconditionFailed
                                   statusCode == 307 || //TemporaryRedirect
                                   statusCode == 409 || //Conflict
                                   statusCode == 503);    //ServiceUnavailable

            //Only marshal successful responses
            if (response.IsSuccess)
            {
                _marshaller.MarshalResponse(_options.Value, response, headers, responseStream ?? Stream.Null);
            }
            else if (responseStream != null)
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    await responseStream.CopyToAsync(ms, 81920, token).ConfigureAwait(false);

                    if (ms.Length > 0)
                    {
                        ms.Seek(0, SeekOrigin.Begin);

                        using (responseStream)
                            response.Error = ErrorHandler.Create(ms);

                        _logger.LogError("Received error: '{Message}'. Details: '{Details}'", response.Error.Message, response.Error.GetErrorDetails());
                    }
                }
            }

            //We always map even if the request is not successful
            _postMapper.PostMap(_options.Value, request, response);
            return(response);
        }
Ejemplo n.º 3
0
        public async Task <TResp> SendRequestAsync <TReq, TResp>(TReq request, CancellationToken cancellationToken) where TResp : IResponse, new() where TReq : IRequest
        {
            _logger.LogTrace($"Sending {typeof(TReq)} to bucket '{request.BucketName}' as resource '{request.Resource}'");

            Stream requestStream = _marshaller.MarshalRequest(request);

            _validator.ValidateAndThrow(request);

            //Ensure that the resource is encoded
            string encodedResource = UrlHelper.UrlPathEncode(request.Resource);

            if (_options.Value.Endpoint == null || _options.Value.NamingType == NamingType.PathStyle)
            {
                if (!string.IsNullOrEmpty(request.BucketName))
                {
                    request.Resource = request.BucketName + '/' + encodedResource;
                }
                else
                {
                    request.Resource = encodedResource;
                }
            }
            else
            {
                request.Resource = encodedResource;
            }

            StringBuilder sb = new StringBuilder(512);

            if (_options.Value.Endpoint != null)
            {
                sb.Append(_options.Value.Endpoint.Host);
            }
            else
            {
                if (_options.Value.NamingType == NamingType.VirtualHost)
                {
                    if (!string.IsNullOrEmpty(request.BucketName))
                    {
                        sb.Append(request.BucketName).Append(".s3.").Append(ValueHelper.EnumToString(_options.Value.Region)).Append(".amazonaws.com");
                    }
                    else
                    {
                        sb.Append("s3.").Append(ValueHelper.EnumToString(_options.Value.Region)).Append(".amazonaws.com");
                    }
                }
                else
                {
                    sb.Append("s3.").Append(ValueHelper.EnumToString(_options.Value.Region)).Append(".amazonaws.com");
                }
            }

            request.AddHeader(HttpHeaders.Host, sb.ToString());
            request.AddHeader(AmzHeaders.XAmzDate, request.Date, DateTimeFormat.Iso8601DateTime);

            if (requestStream != null && _requestStreamWrappers != null)
            {
                foreach (IRequestStreamWrapper wrapper in _requestStreamWrappers)
                {
                    if (wrapper.IsSupported(request))
                    {
                        requestStream = wrapper.Wrap(requestStream, request);
                    }
                }
            }

            //We check if it was already added here because it might have been due to streaming support
            if (!request.Headers.ContainsKey(AmzHeaders.XAmzContentSha256))
            {
                string contentHash;

                if (!_options.Value.EnablePayloadSigning)
                {
                    contentHash = "UNSIGNED-PAYLOAD";
                }
                else
                {
                    contentHash = requestStream == null ? "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" : CryptoHelper.Sha256Hash(requestStream, true).HexEncode();
                }

                _logger.LogDebug("ContentSha256 is {ContentSha256}", contentHash);

                request.AddHeader(AmzHeaders.XAmzContentSha256, contentHash);
            }

            //We add the authorization header here because we need ALL other headers to be present when we do
            request.AddHeader(HttpHeaders.Authorization, _authBuilder.BuildAuthorization(request));

            sb.Append('/').Append(request.Resource);

            //Map all the parameters on to the url
            if (request.QueryParameters.Count > 0)
            {
                sb.Append('?').Append(UrlHelper.CreateQueryString(request.QueryParameters));
            }

            string fullUrl = "https://" + sb;

            _logger.LogDebug("Building request for {Url}", fullUrl);

            (int statusCode, IDictionary <string, string> headers, Stream responseStream) = await _networkDriver.SendRequestAsync(request.Method, fullUrl, request.Headers, requestStream, cancellationToken).ConfigureAwait(false);

            //Clear sensitive material from the request
            if (request is IContainSensitiveMaterial sensitive)
            {
                sensitive.ClearSensitiveMaterial();
            }

            TResp response = new TResp();

            response.StatusCode       = statusCode;
            response.ContentLength    = headers.GetHeaderLong(HttpHeaders.ContentLength);
            response.ConnectionClosed = "closed".Equals(headers.GetHeader(HttpHeaders.Connection), StringComparison.OrdinalIgnoreCase);
            response.Date             = headers.GetHeaderDate(HttpHeaders.Date, DateTimeFormat.Rfc1123);
            response.Server           = headers.GetHeader(HttpHeaders.Server);
            response.ResponseId       = headers.GetHeader(AmzHeaders.XAmzId2);
            response.RequestId        = headers.GetHeader(AmzHeaders.XAmzRequestId);

            // https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
            response.IsSuccess = !(statusCode == 403 || //Forbidden
                                   statusCode == 400 || //BadRequest
                                   statusCode == 500 || //InternalServerError
                                   statusCode == 416 || //RequestedRangeNotSatisfiable
                                   statusCode == 405 || //MethodNotAllowed
                                   statusCode == 411 || //LengthRequired
                                   statusCode == 404 || //NotFound
                                   statusCode == 501 || //NotImplemented
                                   statusCode == 504 || //GatewayTimeout
                                   statusCode == 301 || //MovedPermanently
                                   statusCode == 412 || //PreconditionFailed
                                   statusCode == 307 || //TemporaryRedirect
                                   statusCode == 409 || //Conflict
                                   statusCode == 503);    //ServiceUnavailable

            //Don't know why, but ContentLength seems to be 0 sometimes, even though it is in the headers
            if (!response.IsSuccess && (response.ContentLength > 0 || responseStream.Length > 0))
            {
                using (responseStream)
                    response.Error = ErrorHandler.Create(responseStream);

                _logger.LogError("Received error: '{Message}'. Details: '{Details}'", response.Error.Message, response.Error.GetExtraData());
            }

            //Only marshal successful responses
            if (response.IsSuccess)
            {
                _marshaller.MarshalResponse(request, response, headers, responseStream);
            }

            return(response);
        }