/* * 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; } } }