/* * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License 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 Minio.DataModel.Encryption; using Minio.DataModel.ObjectLock; using Minio.Helper; namespace Minio.DataModel.Args; public class CopyObjectArgs : ObjectWriteArgs { public CopyObjectArgs() { RequestMethod = HttpMethod.Put; SourceObject = new CopySourceObjectArgs(); ReplaceTagsDirective = false; ReplaceMetadataDirective = false; ObjectLockSet = false; RetentionUntilDate = default; } internal CopySourceObjectArgs SourceObject { get; set; } internal ObjectStat SourceObjectInfo { get; set; } internal bool ReplaceTagsDirective { get; set; } internal bool ReplaceMetadataDirective { get; set; } internal string StorageClass { get; set; } internal ObjectRetentionMode ObjectLockRetentionMode { get; set; } internal DateTime RetentionUntilDate { get; set; } internal bool ObjectLockSet { get; set; } internal override void Validate() { Utils.ValidateBucketName(BucketName); if (SourceObject is null) throw new InvalidOperationException(nameof(SourceObject) + " has not been assigned. Please use " + nameof(WithCopyObjectSource)); if (SourceObjectInfo is null) throw new InvalidOperationException( "StatObject result for the copy source object needed to continue copy operation. Use " + nameof(WithCopyObjectSourceStats) + " to initialize StatObject result."); if (!string.IsNullOrEmpty(NotMatchETag) && !string.IsNullOrEmpty(MatchETag)) throw new InvalidOperationException("Invalid to set both Etag match conditions " + nameof(NotMatchETag) + " and " + nameof(MatchETag)); if (!ModifiedSince.Equals(default) && !UnModifiedSince.Equals(default)) throw new InvalidOperationException("Invalid to set both modified date match conditions " + nameof(ModifiedSince) + " and " + nameof(UnModifiedSince)); Populate(); } private void Populate() { if (string.IsNullOrEmpty(ObjectName)) ObjectName = SourceObject.ObjectName; if (SSE?.GetEncryptionType().Equals(EncryptionType.SSE_C) == true) { Headers = new Dictionary(StringComparer.Ordinal); SSE.Marshal(Headers); } if (!ReplaceMetadataDirective) { // Check in copy conditions if replace metadata has been set var copyReplaceMeta = SourceObject.CopyOperationConditions?.HasReplaceMetadataDirective() == true; WithReplaceMetadataDirective(copyReplaceMeta); } Headers ??= new Dictionary(StringComparer.Ordinal); if (ReplaceMetadataDirective) { if (Headers is not null) #if NETSTANDARD foreach (var pair in SourceObjectInfo.MetaData.ToList()) #else foreach (var pair in SourceObjectInfo.MetaData) #endif { var comparer = StringComparer.OrdinalIgnoreCase; var newDictionary = new Dictionary(Headers, comparer); SourceObjectInfo.MetaData.Remove(pair.Key); } Headers = Headers .Concat(SourceObjectInfo.MetaData) .GroupBy(item => item.Key, StringComparer.Ordinal) .ToDictionary(item => item.Key, item => item.Last().Value, StringComparer.Ordinal); } if (Headers is not null) { var newKVList = new List>(); #if NETSTANDARD foreach (var item in Headers.ToList()) #else foreach (var item in Headers) #endif { var key = item.Key; if (!OperationsUtil.IsSupportedHeader(item.Key) && !item.Key.StartsWith("x-amz-meta", StringComparison.OrdinalIgnoreCase) && !OperationsUtil.IsSSEHeader(key)) { newKVList.Add(new Tuple("x-amz-meta-" + key.ToLowerInvariant(), item.Value)); Headers.Remove(item.Key); } newKVList.Add(new Tuple(key, item.Value)); } foreach (var item in newKVList) Headers[item.Item1] = item.Item2; } } public CopyObjectArgs WithCopyObjectSource(CopySourceObjectArgs cs) { if (cs is null) throw new InvalidOperationException("The copy source object needed for copy operation is not initialized."); SourceObject.RequestMethod = HttpMethod.Put; SourceObject ??= new CopySourceObjectArgs(); SourceObject.BucketName = cs.BucketName; SourceObject.ObjectName = cs.ObjectName; SourceObject.VersionId = cs.VersionId; SourceObject.SSE = cs.SSE; SourceObject.Headers = cs.Headers; SourceObject.MatchETag = cs.MatchETag; SourceObject.ModifiedSince = cs.ModifiedSince; SourceObject.NotMatchETag = cs.NotMatchETag; SourceObject.UnModifiedSince = cs.UnModifiedSince; SourceObject.CopySourceObjectPath = $"{cs.BucketName}/{Utils.UrlEncode(cs.ObjectName)}"; SourceObject.CopyOperationConditions = cs.CopyOperationConditions?.Clone(); return this; } public CopyObjectArgs WithReplaceTagsDirective(bool replace) { ReplaceTagsDirective = replace; return this; } public CopyObjectArgs WithReplaceMetadataDirective(bool replace) { ReplaceMetadataDirective = replace; return this; } public CopyObjectArgs WithObjectLockMode(ObjectRetentionMode mode) { ObjectLockSet = true; ObjectLockRetentionMode = mode; return this; } public CopyObjectArgs WithObjectLockRetentionDate(DateTime untilDate) { ObjectLockSet = true; RetentionUntilDate = new DateTime(untilDate.Year, untilDate.Month, untilDate.Day, untilDate.Hour, untilDate.Minute, untilDate.Second, untilDate.Kind); return this; } internal CopyObjectArgs WithCopyObjectSourceStats(ObjectStat info) { SourceObjectInfo = info; if (info.MetaData is not null && !ReplaceMetadataDirective) { SourceObject.Headers ??= new Dictionary(StringComparer.Ordinal); SourceObject.Headers = SourceObject.Headers.Concat(info.MetaData) .GroupBy(item => item.Key, StringComparer.Ordinal) .ToDictionary(item => item.Key, item => item.First().Value, StringComparer.Ordinal); } return this; } internal CopyObjectArgs WithStorageClass(string storageClass) { StorageClass = storageClass; return this; } public CopyObjectArgs WithRetentionUntilDate(DateTime date) { ObjectLockSet = true; RetentionUntilDate = date; return this; } internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) { if (!string.IsNullOrEmpty(MatchETag)) requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-copy-source-if-match", MatchETag); if (!string.IsNullOrEmpty(NotMatchETag)) requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-copy-source-if-none-match", NotMatchETag); if (ModifiedSince != default) requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-copy-source-if-unmodified-since", Utils.To8601String(ModifiedSince)); if (UnModifiedSince != default) { using var request = requestMessageBuilder.Request; request.Headers.Add("x-amz-copy-source-if-modified-since", Utils.To8601String(UnModifiedSince)); } if (!string.IsNullOrEmpty(VersionId)) requestMessageBuilder.AddQueryParameter("versionId", VersionId); if (ObjectTags?.TaggingSet?.Tag.Count > 0) { requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging", ObjectTags.GetTagString()); requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging-directive", ReplaceTagsDirective ? "COPY" : "REPLACE"); } if (ReplaceMetadataDirective) requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-metadata-directive", "REPLACE"); if (!string.IsNullOrEmpty(StorageClass)) requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-storage-class", StorageClass); if (LegalHoldEnabled == true) requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-legal-hold", "ON"); if (ObjectLockSet) { if (!RetentionUntilDate.Equals(default)) requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-retain-until-date", Utils.To8601String(RetentionUntilDate)); requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-object-lock-mode", ObjectLockRetentionMode == ObjectRetentionMode.GOVERNANCE ? "GOVERNANCE" : "COMPLIANCE"); } return requestMessageBuilder; } }