/* * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017-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 System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Text.RegularExpressions; using Minio.DataModel.ObjectLock; using Minio.Helper; namespace Minio.DataModel; public class ObjectStat { private ObjectStat() { MetaData = new Dictionary(StringComparer.OrdinalIgnoreCase); ExtraHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); } public string ObjectName { get; private set; } public long Size { get; private set; } public DateTime LastModified { get; private set; } public string ETag { get; private set; } public string ContentType { get; private set; } [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", Justification = "Needs to be concrete type for XML deserialization")] public Dictionary MetaData { get; } public string VersionId { get; private set; } public bool DeleteMarker { get; private set; } [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation", Justification = "Needs to be concrete type for XML deserialization")] public Dictionary ExtraHeaders { get; } public uint? TaggingCount { get; private set; } public string ArchiveStatus { get; private set; } public DateTime? Expires { get; private set; } public string ReplicationStatus { get; } public ObjectRetentionMode? ObjectLockMode { get; private set; } public DateTime? ObjectLockRetainUntilDate { get; private set; } public bool? LegalHoldEnabled { get; private set; } public static ObjectStat FromResponseHeaders(string objectName, IDictionary responseHeaders) { if (string.IsNullOrEmpty(objectName)) throw new ArgumentNullException(nameof(objectName), "Name of an object cannot be empty"); if (responseHeaders is null) throw new ArgumentNullException(nameof(responseHeaders)); var objInfo = new ObjectStat { ObjectName = objectName }; foreach (var paramName in responseHeaders.Keys) { var paramValue = responseHeaders[paramName]; switch (paramName.ToLowerInvariant()) { case "content-length": objInfo.Size = long.Parse(paramValue, NumberStyles.Number, CultureInfo.InvariantCulture); break; case "last-modified": objInfo.LastModified = DateTime.Parse(paramValue, CultureInfo.InvariantCulture); break; case "etag": objInfo.ETag = paramValue.Replace("\"", string.Empty, StringComparison.OrdinalIgnoreCase); break; case "content-type": objInfo.ContentType = paramValue; objInfo.MetaData["Content-Type"] = objInfo.ContentType; break; case "x-amz-version-id": objInfo.VersionId = paramValue; break; case "x-amz-delete-marker": objInfo.DeleteMarker = paramValue.Equals("true", StringComparison.OrdinalIgnoreCase); break; case "x-amz-archive-status": objInfo.ArchiveStatus = paramValue; break; case "x-amz-tagging-count": if (int.TryParse(paramValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var tagCount) && tagCount >= 0) objInfo.TaggingCount = (uint)tagCount; break; case "x-amz-expiration": // x-amz-expiration header includes the expiration date and the corresponding rule id. var expirationResponse = paramValue.Trim(); var expiryDatePattern = @"(Sun|Mon|Tue|Wed|Thu|Fri|Sat), \d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} [A-Z]+"; var expiryMatch = Regex.Match(expirationResponse, expiryDatePattern, RegexOptions.None, TimeSpan.FromHours(1)); if (expiryMatch.Success) objInfo.Expires = DateTime.Parse(expiryMatch.Value, CultureInfo.CurrentCulture); break; case "x-amz-object-lock-mode": if (!string.IsNullOrWhiteSpace(paramValue)) objInfo.ObjectLockMode = paramValue.Equals("governance", StringComparison.OrdinalIgnoreCase) ? ObjectRetentionMode.GOVERNANCE : ObjectRetentionMode.COMPLIANCE; break; case "x-amz-object-lock-retain-until-date": var lockUntilDate = paramValue; if (!string.IsNullOrWhiteSpace(lockUntilDate)) objInfo.ObjectLockRetainUntilDate = DateTime.Parse(lockUntilDate, CultureInfo.CurrentCulture); break; case "x-amz-object-lock-legal-hold": var legalHoldON = paramValue.Trim(); if (!string.IsNullOrWhiteSpace(legalHoldON)) objInfo.LegalHoldEnabled = legalHoldON.Equals("on", StringComparison.OrdinalIgnoreCase); break; default: if (OperationsUtil.IsSupportedHeader(paramName)) objInfo.MetaData[paramName] = paramValue; else if (paramName.StartsWith("x-amz-meta-", StringComparison.OrdinalIgnoreCase)) objInfo.MetaData[paramName["x-amz-meta-".Length..]] = paramValue; else objInfo.ExtraHeaders[paramName] = paramValue; break; } } return objInfo; } public override string ToString() { var versionInfo = "VersionId(None)"; var legalHold = "LegalHold(None)"; var taggingCount = "Tagging-Count(0)"; var expires = "Expiry(None)"; var objectLockInfo = "ObjectLock(None)"; var archiveStatus = "Archive Status(None)"; var replicationStatus = "Replication Status(None)"; if (!string.IsNullOrWhiteSpace(VersionId)) { versionInfo = $"Version ID({VersionId})"; if (DeleteMarker) versionInfo = $"Version ID({VersionId}, deleted)"; } if (Expires is not null) expires = "Expiry(" + Utils.To8601String(Expires.Value) + ")"; if (ObjectLockMode is not null) { objectLockInfo = "ObjectLock Mode(" + (ObjectLockMode == ObjectRetentionMode.GOVERNANCE ? "GOVERNANCE" : "COMPLIANCE") + ")"; objectLockInfo += " Retain Until Date(" + Utils.To8601String(ObjectLockRetainUntilDate.Value) + ")"; } if (TaggingCount is not null) taggingCount = "Tagging-Count(" + TaggingCount.Value + ")"; if (LegalHoldEnabled is not null) legalHold = "LegalHold(" + (LegalHoldEnabled.Value ? "Enabled" : "Disabled") + ")"; if (!string.IsNullOrWhiteSpace(ReplicationStatus)) replicationStatus = "Replication Status(" + ReplicationStatus + ")"; if (!string.IsNullOrWhiteSpace(ArchiveStatus)) archiveStatus = "Archive Status(" + ArchiveStatus + ")"; var lineTwo = $"{expires} {objectLockInfo} {legalHold} {taggingCount} {archiveStatus} {replicationStatus}"; return $"{ObjectName} : {versionInfo} Size({Size}) LastModified({LastModified}) ETag({ETag}) Content-Type({ContentType})" + (string.IsNullOrWhiteSpace(lineTwo) ? "" : "\n" + lineTwo); } }