/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Net;
using Amazon.Runtime;
using Amazon.Runtime.Internal.Util;
using Amazon.Runtime.Internal.Auth;
using Amazon.Util;
namespace Amazon.Runtime.Internal
{
///
/// Default implementation of the IRequest interface.
///
/// This class is only intended for internal use inside the AWS client libraries.
/// Callers shouldn't ever interact directly with objects of this class.
///
///
public class DefaultRequest : IRequest
{
readonly ParameterCollection parametersCollection;
readonly IDictionary parametersFacade;
readonly IDictionary headers = new Dictionary(StringComparer.OrdinalIgnoreCase);
readonly IDictionary trailingHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase);
readonly IDictionary subResources = new Dictionary(StringComparer.OrdinalIgnoreCase);
readonly IDictionary pathResources = new Dictionary(StringComparer.Ordinal);
Uri endpoint;
string resourcePath;
string serviceName;
readonly AmazonWebServiceRequest originalRequest;
byte[] content;
Stream contentStream;
string contentStreamHash;
string httpMethod = "POST";
bool useQueryString = false;
string requestName;
string canonicalResource;
RegionEndpoint alternateRegion;
long originalStreamLength;
int marshallerVersion = 2; //2 is the default version and must be used whenever a version is not specified in the marshaller.
///
/// Constructs a new DefaultRequest with the specified service name and the
/// original, user facing request object.
///
/// The orignal request that is being wrapped
/// The service name
public DefaultRequest(AmazonWebServiceRequest request, String serviceName)
{
if (request == null) throw new ArgumentNullException("request");
if (string.IsNullOrEmpty(serviceName)) throw new ArgumentNullException("serviceName");
this.serviceName = serviceName;
this.originalRequest = request;
this.requestName = this.originalRequest.GetType().Name;
this.UseSigV4 = ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)this.originalRequest).UseSigV4;
this.SignatureVersion = ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)this.originalRequest).SignatureVersion;
this.HostPrefix = string.Empty;
parametersCollection = new ParameterCollection();
parametersFacade = new ParametersDictionaryFacade(parametersCollection);
}
///
/// The name of the request
///
public string RequestName
{
get { return this.requestName; }
}
///
/// Gets and sets the type of http request to make, whether it should be POST,GET or DELETE
///
public string HttpMethod
{
get
{
return this.httpMethod;
}
set
{
this.httpMethod = value;
}
}
///
/// Gets and sets a flag that indicates whether the request is sent as a query string instead of the request body.
///
public bool UseQueryString
{
get
{
if (this.HttpMethod == "GET")
return true;
return this.useQueryString;
}
set
{
this.useQueryString = value;
}
}
///
/// Returns the original, user facing request object which this internal
/// request object is representing.
///
public AmazonWebServiceRequest OriginalRequest
{
get
{
return originalRequest;
}
}
///
/// Returns a dictionary of the headers included in this request.
///
public IDictionary Headers
{
get
{
return this.headers;
}
}
///
/// Returns a dictionary of the parameters included in this request.
///
public IDictionary Parameters
{
get
{
return this.parametersFacade;
}
}
///
/// Collection of parameters included in this request.
///
public ParameterCollection ParameterCollection
{
get
{
return this.parametersCollection;
}
}
///
/// Returns the subresources that should be appended to the resource path.
/// This is used primarily for Amazon S3, where object keys can contain '?'
/// characters, making string-splitting of a resource path potentially
/// hazardous.
///
public IDictionary SubResources
{
get
{
return this.subResources;
}
}
///
/// Adds a new null entry to the SubResources collection for the request
///
/// The name of the subresource
public void AddSubResource(string subResource)
{
AddSubResource(subResource, null);
}
///
/// Adds a new entry to the SubResources collection for the request
///
/// The name of the subresource
/// Value of the entry
public void AddSubResource(string subResource, string value)
{
SubResources.Add(subResource, value);
}
///
/// Gets and Sets the endpoint for this request.
///
public Uri Endpoint
{
get
{
return this.endpoint;
}
set
{
this.endpoint = value;
}
}
///
/// Gets and Sets the resource path added on to the endpoint.
///
public string ResourcePath
{
get
{
return this.resourcePath;
}
set
{
this.resourcePath = value;
}
}
///
/// Returns the path resources that should be used within the resource path.
/// This is used for services where path keys can contain '/'
/// characters, making string-splitting of a resource path potentially
/// hazardous.
///
public IDictionary PathResources
{
get
{
return this.pathResources;
}
}
///
/// Adds a new entry to the PathResources collection for the request
///
/// The name of the pathresource with potential greedy syntax: {key+}
/// Value of the entry
public void AddPathResource(string key, string value)
{
PathResources.Add(key, value);
}
///
/// Gets and Sets the version number for the marshaller used to create this request. The version number
/// is used to support backward compatible changes that would otherwise be breaking changes when a
/// newer core is used with an older service assembly.
/// Versions:
/// 1 - Legacy version (no longer supported)
/// 2 - Default version. Support for path segments
///
public int MarshallerVersion
{
get
{
return this.marshallerVersion;
}
set
{
this.marshallerVersion = value;
}
}
public string CanonicalResource
{
get
{
return this.canonicalResource;
}
set
{
this.canonicalResource = value;
}
}
///
/// Gets and Sets the content for this request.
///
public byte[] Content
{
get
{
return this.content;
}
set
{
this.content = value;
}
}
///
/// Flag that signals that Content was and should be set
/// from the Parameters collection.
///
public bool SetContentFromParameters { get; set; }
///
/// Gets and sets the content stream.
///
public Stream ContentStream
{
get { return this.contentStream; }
set
{
this.contentStream = value;
OriginalStreamPosition = -1;
if (this.contentStream != null)
{
Stream baseStream = HashStream.GetNonWrapperBaseStream(this.contentStream);
if (baseStream.CanSeek)
OriginalStreamPosition = baseStream.Position;
}
}
}
///
/// Gets and sets the original stream position.
/// If ContentStream is null or does not support seek, this propery
/// should be equal to -1.
///
public long OriginalStreamPosition
{
get { return this.originalStreamLength; }
set { this.originalStreamLength = value; }
}
///
/// Computes the SHA 256 hash of the content stream. If the stream is not
/// seekable, it searches the parent stream hierarchy to find a seekable
/// stream prior to computation. Once computed, the hash is cached for future
/// use. If a suitable stream cannot be found to use, null is returned.
///
public string ComputeContentStreamHash()
{
if (this.contentStream == null)
return null;
if (this.contentStreamHash == null)
{
var seekableStream = WrapperStream.SearchWrappedStream(this.contentStream, s => s.CanSeek);
if (seekableStream != null)
{
var position = seekableStream.Position;
byte[] payloadHashBytes = CryptoUtilFactory.CryptoInstance.ComputeSHA256Hash(seekableStream);
this.contentStreamHash = AWSSDKUtils.ToHex(payloadHashBytes, true);
seekableStream.Seek(position, SeekOrigin.Begin);
}
}
return this.contentStreamHash;
}
///
/// The name of the service to which this request is being sent.
///
public string ServiceName
{
get
{
return this.serviceName;
}
}
///
/// Alternate endpoint to use for this request, if any.
///
public RegionEndpoint AlternateEndpoint
{
get
{
return this.alternateRegion;
}
set
{
this.alternateRegion = value;
}
}
///
/// Host prefix value to prepend to the endpoint for this request, if any.
///
public string HostPrefix { get; set; }
///
/// Gets and sets the Suppress404Exceptions property. If true then 404s return back from AWS will not cause an exception and
/// an empty response object will be returned.
///
public bool Suppress404Exceptions
{
get;
set;
}
///
/// If using AWS4 signing protocol, contains the resultant parts of the
/// signature that we may need to make use of if we elect to do a chunked
/// encoding upload.
///
public AWS4SigningResult AWS4SignerResult { get; set; }
///
/// WARNING: Setting DisablePayloadSigning to true disables the SigV4 payload signing
/// data integrity check on this request.
/// If using SigV4, the DisablePayloadSigning flag controls if the payload should be
/// signed on a request by request basis. By default this flag is null which will use the
/// default client behavior. The default client behavior is to sign the payload. When
/// DisablePayloadSigning is true, the request will be signed with an UNSIGNED-PAYLOAD value.
/// Setting DisablePayloadSigning to true requires that the request is sent over a HTTPS
/// connection.
/// Under certain circumstances, such as uploading to S3 while using MD5 hashing, it may
/// be desireable to use UNSIGNED-PAYLOAD to decrease signing CPU usage. This flag only applies
/// to Amazon S3 PutObject and UploadPart requests.
/// MD5Stream, SigV4 payload signing, and HTTPS each provide some data integrity
/// verification. If DisableMD5Stream is true and DisablePayloadSigning is true, then the
/// possibility of data corruption is completely dependant on HTTPS being the only remaining
/// source of data integrity verification.
///
public bool? DisablePayloadSigning { get; set; }
///
/// If using SigV4a signing protocol, contains the resultant parts of the
/// signature that we may need to make use of if we elect to do a chunked
/// encoding upload.
///
public AWS4aSigningResult AWS4aSignerResult { get; set; }
///
/// Determine whether to use a chunked encoding upload for the request
/// (applies to Amazon S3 PutObject and UploadPart requests only). If
/// DisablePayloadSigning is true, UseChunkEncoding will be automatically
/// set to false.
///
public bool UseChunkEncoding { get; set; }
///
/// Determine whether to use double encoding for request's signer.
/// Propagated from the endpoint's authSchemes.
/// Default value is "true".
/// Currently only S3 and S3 Control will disable double encoding.
///
public bool UseDoubleEncoding { get; set; } = true;
///
/// Used for Amazon S3 requests where the bucket name is removed from
/// the marshalled resource path into the host header. To comply with
/// AWS2 signature calculation, we need to recover the bucket name
/// and include it in the resource canonicalization, which we do using
/// this field.
///
public string CanonicalResourcePrefix
{
get;
set;
}
///
/// This flag specifies if SigV4 is required for the current request.
/// Returns true if the request will use SigV4.
/// Setting it to false will use SigV2.
///
[Obsolete("UseSigV4 is deprecated. Use SignatureVersion directly instead.")]
public bool UseSigV4
{
get { return SignatureVersion == SignatureVersion.SigV4; }
set { this.SignatureVersion = value ? SignatureVersion.SigV4 : SignatureVersion.SigV2; }
}
///
/// Specifies which signature version shall be used for the current request.
///
public SignatureVersion SignatureVersion { get; set; }
///
/// The authentication region to use for the request.
/// Set from Config.AuthenticationRegion.
///
public string AuthenticationRegion { get; set; }
///
/// The region in which the service request was signed.
///
public string DeterminedSigningRegion { get ; set; }
///
/// If the request needs to be signed with a different service name
/// than the client config AuthenticationServiceName, set it here to override
/// the result of DetermineService in AWS4Signer
///
public string OverrideSigningServiceName { get; set; }
///
/// The checksum algorithm that was selected to validate this request's integrity
///
public CoreChecksumAlgorithm SelectedChecksum { get; set; }
///
/// Returns a dictionary of the trailing headers included
/// after this request's content.
///
public IDictionary TrailingHeaders => this.trailingHeaders;
///
/// Checks if the request stream can be rewinded.
///
/// Returns true if the request stream can be rewinded ,
/// else false.
public bool IsRequestStreamRewindable()
{
var stream = this.ContentStream;
// Retries may not be possible with a stream
if (stream != null)
{
// Pull out the underlying non-wrapper stream
stream = WrapperStream.GetNonWrapperBaseStream(stream);
// Retry is possible if stream is seekable
return stream.CanSeek;
}
return true;
}
///
/// Returns true if the request can contain a request body, else false.
///
/// Returns true if the currect request can contain a request body, else false.
public bool MayContainRequestBody()
{
return
(this.HttpMethod == "POST" ||
this.HttpMethod == "PUT" ||
this.HttpMethod == "PATCH");
}
///
/// Returns true if the request has a body, else false.
///
/// Returns true if the request has a body, else false.
public bool HasRequestBody()
{
var isPutPost = (this.HttpMethod == "POST" || this.HttpMethod == "PUT" || this.HttpMethod == "PATCH");
var hasContent = this.HasRequestData();
return (isPutPost && hasContent);
}
public string GetHeaderValue(string headerName)
{
string headerValue;
if (headers.TryGetValue(headerName, out headerValue))
return headerValue;
return string.Empty;
}
}
}