/* * 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.ObjectLock; using Minio.DataModel.Result; using Minio.Helper; namespace Minio.DataModel.Args; internal class CopyObjectRequestArgs : ObjectWriteArgs { internal CopyObjectRequestArgs() { RequestMethod = HttpMethod.Put; Headers = new Dictionary(StringComparer.Ordinal); CopyOperationObjectType = typeof(CopyObjectResult); } internal CopySourceObjectArgs SourceObject { get; set; } internal ObjectStat SourceObjectInfo { get; set; } internal Type CopyOperationObjectType { get; set; } internal bool ReplaceTagsDirective { get; set; } internal bool ReplaceMetadataDirective { get; set; } internal string StorageClass { get; set; } internal Dictionary QueryMap { get; set; } internal CopyConditions CopyCondition { get; set; } internal ObjectRetentionMode ObjectLockRetentionMode { get; set; } internal DateTime RetentionUntilDate { get; set; } internal bool ObjectLockSet { get; set; } internal CopyObjectRequestArgs WithQueryMap(IDictionary queryMap) { QueryMap = new Dictionary(queryMap, StringComparer.Ordinal); return this; } internal CopyObjectRequestArgs WithPartCondition(CopyConditions partCondition) { CopyCondition = partCondition.Clone(); Headers ??= new Dictionary(StringComparer.Ordinal); Headers["x-amz-copy-source-range"] = "bytes=" + partCondition.byteRangeStart + "-" + partCondition.byteRangeEnd; return this; } internal CopyObjectRequestArgs WithReplaceMetadataDirective(bool replace) { ReplaceMetadataDirective = replace; return this; } internal CopyObjectRequestArgs WithReplaceTagsDirective(bool replace) { ReplaceTagsDirective = replace; return this; } public CopyObjectRequestArgs WithCopyObjectSource(CopySourceObjectArgs cs) { if (cs is null) throw new InvalidOperationException("The copy source object needed for copy operation is not initialized."); SourceObject ??= new CopySourceObjectArgs(); SourceObject.RequestMethod = HttpMethod.Put; SourceObject.BucketName = cs.BucketName; SourceObject.ObjectName = cs.ObjectName; SourceObject.VersionId = cs.VersionId; SourceObject.SSE = cs.SSE; SourceObject.Headers = new Dictionary(cs.Headers, StringComparer.Ordinal); 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 CopyObjectRequestArgs WithSourceObjectInfo(ObjectStat stat) { SourceObjectInfo = stat; return this; } internal override HttpRequestMessageBuilder BuildRequest(HttpRequestMessageBuilder requestMessageBuilder) { var sourceObjectPath = SourceObject.BucketName + "/" + Utils.UrlEncode(SourceObject.ObjectName); if (!string.IsNullOrEmpty(SourceObject.VersionId)) sourceObjectPath += "?versionId=" + SourceObject.VersionId; // Set the object source requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-copy-source", sourceObjectPath); if (QueryMap is not null) foreach (var query in QueryMap) requestMessageBuilder.AddQueryParameter(query.Key, query.Value); if (SourceObject.CopyOperationConditions is not null) foreach (var item in SourceObject.CopyOperationConditions.Conditions) requestMessageBuilder.AddOrUpdateHeaderParameter(item.Key, item.Value); 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) requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-copy-source-if-modified-since", Utils.To8601String(UnModifiedSince)); if (ObjectTags?.TaggingSet?.Tag.Count > 0) { requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging", ObjectTags.GetTagString()); requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging-directive", ReplaceTagsDirective ? "REPLACE" : "COPY"); if (ReplaceMetadataDirective) requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-tagging-directive", "REPLACE"); } if (ReplaceMetadataDirective) requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-metadata-directive", "REPLACE"); if (!string.IsNullOrEmpty(StorageClass)) requestMessageBuilder.AddOrUpdateHeaderParameter("x-amz-storage-class", StorageClass); 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"); } if (!RequestBody.IsEmpty) requestMessageBuilder.SetBody(RequestBody); return requestMessageBuilder; } internal CopyObjectRequestArgs WithCopyOperationObjectType(Type cp) { CopyOperationObjectType = cp; return this; } public CopyObjectRequestArgs WithObjectLockMode(ObjectRetentionMode mode) { ObjectLockSet = true; ObjectLockRetentionMode = mode; return this; } public CopyObjectRequestArgs 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 override void Validate() { Utils.ValidateBucketName(BucketName); //Object name can be same as that of source. if (SourceObject is null) throw new InvalidOperationException(nameof(SourceObject) + " has not been assigned."); Populate(); } internal void Populate() { ObjectName = string.IsNullOrEmpty(ObjectName) ? SourceObject.ObjectName : ObjectName; // Opting for concat as Headers may have byte range info .etc. if (!ReplaceMetadataDirective && SourceObjectInfo.MetaData is not null) Headers = SourceObjectInfo.MetaData.Concat(Headers).GroupBy(item => item.Key, StringComparer.Ordinal) .ToDictionary(item => item.Key, item => item.First().Value, StringComparer.Ordinal); else if (ReplaceMetadataDirective) Headers ??= new Dictionary(StringComparer.Ordinal); } }