/* * 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.Xml.Serialization; using System.Text; using Amazon.Runtime; using Amazon.S3.Util; using Amazon.Util; using System.Globalization; using Amazon.S3.Model.Internal.MarshallTransformations; using Amazon.S3; using Amazon.Runtime.Internal; namespace Amazon.S3.Model { /// /// Returns information about the GetObject response and response metadata. /// public partial class GetObjectResponse : StreamResponse { private string deleteMarker; private string acceptRanges; private string contentRange; private Expiration expiration; private DateTime? restoreExpiration; private bool restoreInProgress; private DateTime? lastModified; private string eTag; private int? missingMeta; private string versionId; private DateTime? expires; private ObjectLockLegalHoldStatus objectLockLegalHoldStatus; private ObjectLockMode objectLockMode; private DateTime? objectLockRetainUntilDate; private string websiteRedirectLocation; private ServerSideEncryptionMethod serverSideEncryption; private ServerSideEncryptionCustomerMethod serverSideEncryptionCustomerMethod; private string serverSideEncryptionKeyManagementServiceKeyId; private HeadersCollection headersCollection = new HeadersCollection(); private MetadataCollection metadataCollection = new MetadataCollection(); private ReplicationStatus replicationStatus; private int? partsCount; private S3StorageClass storageClass; private RequestCharged requestCharged; private int? tagCount; private string bucketName; private string key; private bool? bucketKeyEnabled; private string _checksumCRC32; private string _checksumCRC32C; private string _checksumSHA1; private string _checksumSHA256; /// /// Flag which returns true if the Expires property has been unmarshalled /// from the raw value or set by user code. /// private bool isExpiresUnmarshalled; internal string RawExpires { get; set; } /// /// Gets and sets the BucketName property. /// public string BucketName { get { return this.bucketName; } set { this.bucketName = value; } } /// /// Gets and sets the Key property. /// public string Key { get { return this.key; } set { this.key = value; } } /// /// Specifies whether the object retrieved was (true) or was not (false) a Delete Marker. If false, this response header does not appear in the /// response. /// /// public string DeleteMarker { get { return this.deleteMarker; } set { this.deleteMarker = value; } } // Check to see if DeleteMarker property is set internal bool IsSetDeleteMarker() { return this.deleteMarker != null; } /// /// The collection of headers for the request. /// public HeadersCollection Headers { get { if (this.headersCollection == null) this.headersCollection = new HeadersCollection(); return this.headersCollection; } } /// /// The collection of meta data for the request. /// public MetadataCollection Metadata { get { if (this.metadataCollection == null) this.metadataCollection = new MetadataCollection(); return this.metadataCollection; } } /// /// Gets and sets the AcceptRanges. /// public string AcceptRanges { get { return this.acceptRanges; } set { this.acceptRanges = value; } } // Check to see if AcceptRanges property is set internal bool IsSetAcceptRanges() { return this.acceptRanges != null; } /// /// Gets and sets the ContentRange. /// public string ContentRange { get { return this.contentRange; } set { this.contentRange = value; } } // Check to see if ContentRange property is set internal bool IsSetContentRange() { return this.contentRange != null; } /// /// Gets and sets the Expiration property. /// Specifies the expiration date for the object and the /// rule governing the expiration. /// Is null if expiration is not applicable. /// public Expiration Expiration { get { return this.expiration; } set { this.expiration = value; } } // Check to see if Expiration property is set internal bool IsSetExpiration() { return this.expiration != null; } /// /// Gets and sets the RestoreExpiration property. /// RestoreExpiration will be set for objects that have been restored from Amazon Glacier. /// It indiciates for those objects how long the restored object will exist. /// public DateTime? RestoreExpiration { get { return this.restoreExpiration; } set { this.restoreExpiration = value; } } /// /// Gets and sets the RestoreInProgress /// Will be true when the object is in the process of being restored from Amazon Glacier. /// public bool RestoreInProgress { get { return this.restoreInProgress; } set { this.restoreInProgress = value; } } /// /// Last modified date of the object /// /// public DateTime LastModified { get { return this.lastModified ?? default(DateTime); } set { this.lastModified = value; } } // Check to see if LastModified property is set internal bool IsSetLastModified() { return this.lastModified.HasValue; } /// /// An ETag is an opaque identifier assigned by a web server to a specific version of a resource found at a URL /// /// public string ETag { get { return this.eTag; } set { this.eTag = value; } } // Check to see if ETag property is set internal bool IsSetETag() { return this.eTag != null; } /// /// This is set to the number of metadata entries not returned in x-amz-meta headers. This can happen if you create metadata using an API like /// SOAP that supports more flexible metadata than the REST API. For example, using SOAP, you can create metadata whose values are not legal /// HTTP headers. /// /// public int MissingMeta { get { return this.missingMeta ?? default(int); } set { this.missingMeta = value; } } // Check to see if MissingMeta property is set internal bool IsSetMissingMeta() { return this.missingMeta.HasValue; } /// /// Version of the object. /// /// public string VersionId { get { return this.versionId; } set { this.versionId = value; } } // Check to see if VersionId property is set internal bool IsSetVersionId() { return this.versionId != null; } /// /// The date and time at which the object is no longer cacheable. /// /// public DateTime Expires { get { if (this.isExpiresUnmarshalled) { return this.expires.Value; } else { this.expires = AmazonS3Util.ParseExpiresHeader(this.RawExpires, this.ResponseMetadata.RequestId); this.isExpiresUnmarshalled = true; return this.expires.Value; } } set { this.expires = value; this.isExpiresUnmarshalled = true; } } // Check to see if Expires property is set internal bool IsSetExpires() { return this.expires.HasValue; } /// /// Gets and sets the property ObjectLockLegalHoldStatus. /// public ObjectLockLegalHoldStatus ObjectLockLegalHoldStatus { get { return this.objectLockLegalHoldStatus; } set { this.objectLockLegalHoldStatus = value; } } // Check to see if ObjectLockLegalHoldStatus property is set internal bool IsSetObjectLockLegalHoldStatus() { return this.objectLockLegalHoldStatus != null; } /// /// Gets and sets the property ObjectLockMode. /// /// The Object Lock mode currently in place for this object. /// /// public ObjectLockMode ObjectLockMode { get { return this.objectLockMode; } set { this.objectLockMode = value; } } // Check to see if ObjectLockMode property is set internal bool IsSetObjectLockMode() { return this.objectLockMode != null; } /// /// Gets and sets the property ObjectLockRetainUntilDate. /// /// The date and time when this object's Object Lock will expire. /// /// public DateTime ObjectLockRetainUntilDate { get { return this.objectLockRetainUntilDate.GetValueOrDefault(); } set { this.objectLockRetainUntilDate = value; } } // Check to see if ObjectLockRetainUntilDate property is set internal bool IsSetObjectLockRetainUntilDate() { return this.objectLockRetainUntilDate.HasValue; } /// /// If the bucket is configured as a website, redirects requests for this object to another object in the same bucket or to an external URL. /// Amazon S3 stores the value of this header in the object metadata. /// /// public string WebsiteRedirectLocation { get { return this.websiteRedirectLocation; } set { this.websiteRedirectLocation = value; } } // Check to see if WebsiteRedirectLocation property is set internal bool IsSetWebsiteRedirectLocation() { return this.websiteRedirectLocation != null; } /// /// /// The server-side encryption algorithm used when storing this object in Amazon S3 (for /// example, AES256, aws:kms). /// /// public ServerSideEncryptionMethod ServerSideEncryptionMethod { get { return this.serverSideEncryption; } set { this.serverSideEncryption = value; } } // Check to see if ServerSideEncryptionMethod property is set internal bool IsSetServerSideEncryptionMethod() { return this.serverSideEncryption != null; } /// /// The class of storage used to store the object. /// /// public S3StorageClass StorageClass { get { return this.storageClass; } set { this.storageClass = value; } } // Check to see if StorageClass property is set internal bool IsSetStorageClass() { return this.storageClass != null; } /// /// The id of the AWS Key Management Service key that Amazon S3 uses to encrypt and decrypt the object. /// /// If present, specifies the ID of the Amazon Web Services Key Management Service (Amazon /// Web Services KMS) symmetric encryption customer managed key that was used for the /// object. /// /// [AWSProperty(Sensitive=true)] public string ServerSideEncryptionKeyManagementServiceKeyId { get { return this.serverSideEncryptionKeyManagementServiceKeyId; } set { this.serverSideEncryptionKeyManagementServiceKeyId = value; } } /// /// Checks if ServerSideEncryptionKeyManagementServiceKeyId property is set. /// /// true if ServerSideEncryptionKeyManagementServiceKeyId property is set. internal bool IsSetServerSideEncryptionKeyManagementServiceKeyId() { return !System.String.IsNullOrEmpty(this.serverSideEncryptionKeyManagementServiceKeyId); } /// /// The status of the replication job associated with this source object. /// public ReplicationStatus ReplicationStatus { get { return this.replicationStatus; } set { this.replicationStatus = value; } } /// /// Checks if ReplicationStatus property is set. /// /// true if ReplicationStatus property is set. internal bool IsSetReplicationStatus() { return ReplicationStatus != null; } /// /// The number of parts this oject has. /// public int? PartsCount { get { return this.partsCount; } set { this.partsCount = value; } } /// /// Checks if PartsCount is set. /// /// true if PartsCount property is set. internal bool IsSetPartsCount() { return this.partsCount.HasValue; } /// /// The Server-side encryption algorithm to be used with the customer provided key. /// /// public ServerSideEncryptionCustomerMethod ServerSideEncryptionCustomerMethod { get { if (this.serverSideEncryptionCustomerMethod == null) return ServerSideEncryptionCustomerMethod.None; return this.serverSideEncryptionCustomerMethod; } set { this.serverSideEncryptionCustomerMethod = value; } } /// /// If present, indicates that the requester was successfully charged for the request. /// public RequestCharged RequestCharged { get { return this.requestCharged; } set { this.requestCharged = value; } } /// /// Checks to see if RequestCharged is set. /// /// true, if RequestCharged property is set. internal bool IsSetRequestCharged() { return requestCharged != null; } /// /// The number of tags, if any, on the object. /// public int TagCount { get { return this.tagCount ?? 0; } set { this.tagCount = value; } } /// /// Gets and sets the property BucketKeyEnabled. /// /// Indicates whether the object uses an S3 Bucket Key for server-side encryption with /// Amazon Web Services KMS (SSE-KMS). /// /// public bool BucketKeyEnabled { get { return this.bucketKeyEnabled.GetValueOrDefault(); } set { this.bucketKeyEnabled = value; } } internal bool IsSetBucketKeyEnabled() { return bucketKeyEnabled.HasValue; } /// /// Gets and sets the property ChecksumCRC32. /// /// The base64-encoded, 32-bit CRC32 checksum of the object. /// /// public string ChecksumCRC32 { get { return this._checksumCRC32; } set { this._checksumCRC32 = value; } } // Check to see if ChecksumCRC32 property is set internal bool IsSetChecksumCRC32() { return this._checksumCRC32 != null; } /// /// Gets and sets the property ChecksumCRC32C. /// /// The base64-encoded, 32-bit CRC32C checksum of the object. /// /// public string ChecksumCRC32C { get { return this._checksumCRC32C; } set { this._checksumCRC32C = value; } } // Check to see if ChecksumCRC32C property is set internal bool IsSetChecksumCRC32C() { return this._checksumCRC32C != null; } /// /// Gets and sets the property ChecksumSHA1. /// /// The base64-encoded, 160-bit SHA-1 digest of the object. /// /// public string ChecksumSHA1 { get { return this._checksumSHA1; } set { this._checksumSHA1 = value; } } // Check to see if ChecksumSHA1 property is set internal bool IsSetChecksumSHA1() { return this._checksumSHA1 != null; } /// /// Gets and sets the property ChecksumSHA256. /// /// The base64-encoded, 256-bit SHA-256 digest of the object. /// /// public string ChecksumSHA256 { get { return this._checksumSHA256; } set { this._checksumSHA256 = value; } } // Check to see if ChecksumSHA256 property is set internal bool IsSetChecksumSHA256() { return this._checksumSHA256 != null; } #if BCL /// /// Writes the content of the ResponseStream a file indicated by the filePath argument. /// /// The location where to write the ResponseStream public void WriteResponseStreamToFile(string filePath) { WriteResponseStreamToFile(filePath, false); } /// /// Writes the content of the ResponseStream a file indicated by the filePath argument. /// /// The location where to write the ResponseStream /// Whether or not to append to the file if it exists public void WriteResponseStreamToFile(string filePath, bool append) { CreateDirectory(filePath); Stream downloadStream = CreateDownloadStream(filePath, append); using (downloadStream) { long current = 0; byte[] buffer = new byte[S3Constants.DefaultBufferSize]; int bytesRead = 0; long totalIncrementTransferred = 0; while ((bytesRead = this.ResponseStream.Read(buffer, 0, buffer.Length)) > 0) { downloadStream.Write(buffer, 0, bytesRead); current += bytesRead; totalIncrementTransferred += bytesRead; if (totalIncrementTransferred >= AWSSDKUtils.DefaultProgressUpdateInterval) { this.OnRaiseProgressEvent(filePath, totalIncrementTransferred, current, this.ContentLength, completed:false); totalIncrementTransferred = 0; } } ValidateWrittenStreamSize(current); // Encrypted objects may have size smaller than the total amount of data transfered due to padding. // Instead of changing the file size or the total downloaded size, pass a flag that indicate that the transfer is complete. this.OnRaiseProgressEvent(filePath, totalIncrementTransferred, current, this.ContentLength, completed:true); } } private static Stream CreateDownloadStream(string filePath, bool append) { Stream downloadStream; if (append && File.Exists(filePath)) downloadStream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.Read, S3Constants.DefaultBufferSize); else downloadStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read, S3Constants.DefaultBufferSize); return downloadStream; } private static void CreateDirectory(string filePath) { // Make sure the directory exists to write too. FileInfo fi = new FileInfo(filePath); Directory.CreateDirectory(fi.DirectoryName); } #endif #region Progress Event /// /// The event for Write Object progress notifications. All /// subscribers will be notified when a new progress /// event is raised. /// /// /// Subscribe to this event if you want to receive /// put object progress notifications. Here is how:
/// 1. Define a method with a signature similar to this one: /// /// private void displayProgress(object sender, WriteObjectProgressArgs args) /// { /// Console.WriteLine(args); /// } /// /// 2. Add this method to the Put Object Progress Event delegate's invocation list /// /// GetObjectResponse response = s3Client.GetObject(request); /// response.WriteObjectProgressEvent += displayProgress; /// ///
public event EventHandler WriteObjectProgressEvent; #endregion /// /// This method is called by a producer of write object progress /// notifications. When called, all the subscribers in the /// invocation list will be called sequentially. /// /// The file being written. /// The number of bytes transferred since last event /// The number of bytes transferred /// The total number of bytes to be transferred /// True if transfer is complete internal void OnRaiseProgressEvent(string file, long incrementTransferred, long transferred, long total, bool completed) { AWSSDKUtils.InvokeInBackground(WriteObjectProgressEvent, new WriteObjectProgressArgs(this.BucketName, this.Key, file, this.VersionId, incrementTransferred, transferred, total, completed), this); } private void ValidateWrittenStreamSize(long bytesWritten) { // Check if response stream or it's base stream is a AESDecryptionStream var stream = Runtime.Internal.Util.WrapperStream.SearchWrappedStream(this.ResponseStream, (s => s is Runtime.Internal.Util.DecryptStream)); // Don't validate length if response is an encrypted object. if (stream!=null) return; if (bytesWritten != this.ContentLength) { string amzId2; if(!this.ResponseMetadata.Metadata.TryGetValue(HeaderKeys.XAmzId2Header, out amzId2)) amzId2 = string.Empty; string amzCfId; if(!this.ResponseMetadata.Metadata.TryGetValue(HeaderKeys.XAmzCloudFrontIdHeader, out amzCfId)) amzCfId = string.Empty; string message = null; if (string.IsNullOrEmpty(amzCfId)) { message = string.Format(CultureInfo.InvariantCulture, "The total bytes read {0} from response stream is not equal to the Content-Length {1} for the object {2} in bucket {3}." + " Request ID = {4} , AmzId2 = {5}.", bytesWritten, this.ContentLength, this.Key, this.BucketName, this.ResponseMetadata.RequestId, amzId2); } else { message = string.Format(CultureInfo.InvariantCulture, "The total bytes read {0} from response stream is not equal to the Content-Length {1} for the object {2} in bucket {3}." + " Request ID = {4} , AmzId2 = {5} , AmzCfId = {6}.", bytesWritten, this.ContentLength, this.Key, this.BucketName, this.ResponseMetadata.RequestId, amzId2, amzCfId); } throw new StreamSizeMismatchException(message, this.ContentLength, bytesWritten, this.ResponseMetadata.RequestId, amzId2, amzCfId); } } #if BCL45 || NETSTANDARD /// /// Writes the content of the ResponseStream a file indicated by the filePath argument. /// /// The location where to write the ResponseStream /// Whether or not to append to the file if it exists /// Cancellation token which can be used to cancel this operation. public async System.Threading.Tasks.Task WriteResponseStreamToFileAsync(string filePath, bool append, System.Threading.CancellationToken cancellationToken) { // Make sure the directory exists to write too. FileInfo fi = new FileInfo(filePath); Directory.CreateDirectory(fi.DirectoryName); Stream downloadStream; if (append && File.Exists(filePath)) downloadStream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.Read, S3Constants.DefaultBufferSize); else downloadStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read, S3Constants.DefaultBufferSize); try { long current = 0; #if NETSTANDARD Stream stream = this.ResponseStream; #else Stream stream = new BufferedStream(this.ResponseStream); #endif byte[] buffer = new byte[S3Constants.DefaultBufferSize]; int bytesRead = 0; long totalIncrementTransferred = 0; while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false)) > 0) { cancellationToken.ThrowIfCancellationRequested(); await downloadStream.WriteAsync(buffer, 0, bytesRead, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); current += bytesRead; totalIncrementTransferred += bytesRead; if (totalIncrementTransferred >= AWSSDKUtils.DefaultProgressUpdateInterval) { this.OnRaiseProgressEvent(filePath, totalIncrementTransferred, current, this.ContentLength, completed:false); totalIncrementTransferred = 0; } } ValidateWrittenStreamSize(current); // Encrypted objects may have size smaller than the total amount of data trasnfered due to padding. // Instead of changing the file size or the total downloaded size, pass a flag that indicate that the transfer is complete. this.OnRaiseProgressEvent(filePath, totalIncrementTransferred, current, this.ContentLength, completed:true); } finally { downloadStream.Dispose(); } } #endif } /// /// Encapsulates the information needed to provide /// download progress for the Write Object Event. /// public class WriteObjectProgressArgs : TransferProgressArgs { /// /// The constructor takes the number of /// currently transferred bytes and the /// total number of bytes to be transferred /// /// The bucket name for the S3 object being written. /// The object key for the S3 object being written. /// The version-id of the S3 object. /// The number of bytes transferred since last event /// The number of bytes transferred /// The total number of bytes to be transferred /// True if finished writing internal WriteObjectProgressArgs(string bucketName, string key, string versionId, long incrementTransferred, long transferred, long total, bool completed) : base(incrementTransferred, transferred, total) { this.BucketName = bucketName; this.Key = key; this.VersionId = versionId; this.IsCompleted = completed; } /// /// The constructor takes the number of /// currently transferred bytes and the /// total number of bytes to be transferred /// /// The bucket name for the S3 object being written. /// The object key for the S3 object being written. /// The file for the S3 object being written. /// The version-id of the S3 object. /// The number of bytes transferred since last event /// The number of bytes transferred /// The total number of bytes to be transferred /// True if finished writing internal WriteObjectProgressArgs(string bucketName, string key, string filePath, string versionId, long incrementTransferred, long transferred, long total, bool completed) : base(incrementTransferred, transferred, total) { this.BucketName = bucketName; this.Key = key; this.VersionId = versionId; this.FilePath = filePath; this.IsCompleted = completed; } /// /// Gets the bucket name for the S3 object being written. /// public string BucketName { get; private set; } /// /// Gets the object key for the S3 object being written. /// public string Key { get; private set; } /// /// Gets the version-id of the S3 object. /// public string VersionId { get; private set; } /// /// The file for the S3 object being written. /// public string FilePath { get; private set; } /// /// True if writing is complete /// public bool IsCompleted { get; private set; } } }