diff --git a/generator/.DevConfigs/a5cb7525-8e50-4583-ae7b-36180442d7e3.json b/generator/.DevConfigs/a5cb7525-8e50-4583-ae7b-36180442d7e3.json new file mode 100644 index 000000000000..c8bdc2769712 --- /dev/null +++ b/generator/.DevConfigs/a5cb7525-8e50-4583-ae7b-36180442d7e3.json @@ -0,0 +1,11 @@ +{ + "services": [ + { + "serviceName": "S3", + "type": "patch", + "changeLogMessages": [ + "Fixed a bug in DeleteObjects where valid objects would not be deleted when the request included invalid keys" + ] + } + ] + } \ No newline at end of file diff --git a/sdk/src/Services/S3/Custom/Model/DeleteObjectsRequest.Extensions.cs b/sdk/src/Services/S3/Custom/Model/DeleteObjectsRequest.Extensions.cs index 402c2f418a3b..fddfe93c39c8 100644 --- a/sdk/src/Services/S3/Custom/Model/DeleteObjectsRequest.Extensions.cs +++ b/sdk/src/Services/S3/Custom/Model/DeleteObjectsRequest.Extensions.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). @@ -20,6 +20,7 @@ using Amazon.Runtime; using Amazon.Runtime.Internal; +using Amazon.Runtime.Internal.Util; namespace Amazon.S3.Model { @@ -30,13 +31,69 @@ namespace Amazon.S3.Model /// public partial class DeleteObjectsRequest : AmazonWebServiceRequest { + private List validationErrors; + + /// + /// Gets the list of keys that were skipped during client-side validation. + /// + public List ValidationErrors + { + get + { + if (validationErrors == null) + { + validationErrors = new List(); + } + return validationErrors; + } + } + + private static Logger Logger + { + get + { + return Logger.GetLogger(typeof(DeleteObjectsRequest)); + } + } /// /// Add a key to the set of keys of objects to be deleted. /// /// Object key + /// + /// Invalid keys (null, empty, or longer than 1024 characters) are skipped with an INFO-level log message. + /// These validation errors are also tracked in the ValidationErrors collection. + /// This allows the DeleteObjects operation to proceed with valid keys even when some keys are invalid. + /// public void AddKey(string key) { + // Skip invalid keys instead of throwing exceptions + // This allows the DeleteObjects operation to proceed with valid keys + if (string.IsNullOrEmpty(key)) + { + string message = "Skipping null or empty key in DeleteObjects request"; + Logger.InfoFormat(message); + + ValidationErrors.Add(new DeleteError { + Key = key ?? "(null)", + Code = "InvalidKey", + Message = "Key cannot be null or empty" + }); + return; + } + if (key.Length > 1024) + { + string message = string.Format("Skipping key in DeleteObjects request: key length {0} exceeds maximum of 1024 characters", key.Length); + Logger.InfoFormat(message); + + ValidationErrors.Add(new DeleteError { + Key = key.Substring(0, Math.Min(key.Length, 128)) + (key.Length > 128 ? "..." : ""), + Code = "KeyTooLongError", + Message = string.Format("Key length {0} exceeds maximum of 1024 characters", key.Length) + }); + return; + } + AddKey(new KeyVersion { Key = key }); } @@ -45,8 +102,40 @@ public void AddKey(string key) /// /// Key of the object to be deleted. /// Version of the object to be deleted. + /// + /// Invalid keys (null, empty, or longer than 1024 characters) are skipped with an INFO-level log message. + /// These validation errors are also tracked in the ValidationErrors collection. + /// This allows the DeleteObjects operation to proceed with valid keys even when some keys are invalid. + /// public void AddKey(string key, string version) { + // Skip invalid keys instead of throwing exceptions + // This allows the DeleteObjects operation to proceed with valid keys + if (string.IsNullOrEmpty(key)) + { + string message = "Skipping null or empty key in DeleteObjects request"; + Logger.InfoFormat(message); + + ValidationErrors.Add(new DeleteError { + Key = key ?? "(null)", + Code = "InvalidKey", + Message = "Key cannot be null or empty" + }); + return; + } + if (key.Length > 1024) + { + string message = string.Format("Skipping key in DeleteObjects request: key length {0} exceeds maximum of 1024 characters", key.Length); + Logger.InfoFormat(message); + + ValidationErrors.Add(new DeleteError { + Key = key.Substring(0, Math.Min(key.Length, 128)) + (key.Length > 128 ? "..." : ""), + Code = "KeyTooLongError", + Message = string.Format("Key length {0} exceeds maximum of 1024 characters", key.Length) + }); + return; + } + AddKey(new KeyVersion { Key = key, VersionId = version }); } diff --git a/sdk/src/Services/S3/Custom/Model/Internal/MarshallTransformations/DeleteObjectsRequestMarshaller.cs b/sdk/src/Services/S3/Custom/Model/Internal/MarshallTransformations/DeleteObjectsRequestMarshaller.cs index 58570bfca476..d175af0b1c60 100644 --- a/sdk/src/Services/S3/Custom/Model/Internal/MarshallTransformations/DeleteObjectsRequestMarshaller.cs +++ b/sdk/src/Services/S3/Custom/Model/Internal/MarshallTransformations/DeleteObjectsRequestMarshaller.cs @@ -68,36 +68,52 @@ public IRequest Marshall(DeleteObjectsRequest deleteObjectsRequest) xmlWriter.WriteStartElement("Delete", S3Constants.S3RequestXmlNamespace); var deleteDeleteobjectsList = deleteObjectsRequest.Objects; - if (deleteDeleteobjectsList != null && deleteDeleteobjectsList.Count > 0) + if (deleteDeleteobjectsList == null || deleteDeleteobjectsList.Count == 0) { - foreach (var deleteDeleteobjectsListValue in deleteDeleteobjectsList) + if (deleteObjectsRequest.ValidationErrors?.Count > 0) { - xmlWriter.WriteStartElement("Object", ""); - if (deleteDeleteobjectsListValue.IsSetKey()) - { - xmlWriter.WriteElementString("Key", "", S3Transforms.ToXmlStringValue(deleteDeleteobjectsListValue.Key)); - } - if (deleteDeleteobjectsListValue.IsSetVersionId()) - { - xmlWriter.WriteElementString("VersionId", "", S3Transforms.ToXmlStringValue(deleteDeleteobjectsListValue.VersionId)); - } - - if (deleteDeleteobjectsListValue.IsSetETag()) - { - xmlWriter.WriteElementString("ETag", "", S3Transforms.ToXmlStringValue(deleteDeleteobjectsListValue.ETag)); - } - if (deleteDeleteobjectsListValue.IsSetLastModifiedTime()) - { - xmlWriter.WriteElementString("LastModifiedTime", "", S3Transforms.ToXmlStringValue(deleteDeleteobjectsListValue.LastModifiedTime.Value)); - } - if (deleteDeleteobjectsListValue.IsSetSize()) - { - xmlWriter.WriteElementString("Size", "", S3Transforms.ToXmlStringValue(deleteDeleteobjectsListValue.Size.Value)); - } - - xmlWriter.WriteEndElement(); + // All keys were invalid - provide clear error with validation details + throw new AmazonS3Exception( + $"DeleteObjects operation failed: All {deleteObjectsRequest.ValidationErrors.Count} keys were invalid. See ValidationErrors for details." + ); } + else + { + // No keys were provided + throw new AmazonS3Exception( + "DeleteObjects operation requires at least one valid key, but none were provided." + ); + } + } + + foreach (var deleteDeleteobjectsListValue in deleteDeleteobjectsList) + { + xmlWriter.WriteStartElement("Object", ""); + if (deleteDeleteobjectsListValue.IsSetKey()) + { + xmlWriter.WriteElementString("Key", "", S3Transforms.ToXmlStringValue(deleteDeleteobjectsListValue.Key)); + } + if (deleteDeleteobjectsListValue.IsSetVersionId()) + { + xmlWriter.WriteElementString("VersionId", "", S3Transforms.ToXmlStringValue(deleteDeleteobjectsListValue.VersionId)); + } + + if (deleteDeleteobjectsListValue.IsSetETag()) + { + xmlWriter.WriteElementString("ETag", "", S3Transforms.ToXmlStringValue(deleteDeleteobjectsListValue.ETag)); + } + if (deleteDeleteobjectsListValue.IsSetLastModifiedTime()) + { + xmlWriter.WriteElementString("LastModifiedTime", "", S3Transforms.ToXmlStringValue(deleteDeleteobjectsListValue.LastModifiedTime.Value)); + } + if (deleteDeleteobjectsListValue.IsSetSize()) + { + xmlWriter.WriteElementString("Size", "", S3Transforms.ToXmlStringValue(deleteDeleteobjectsListValue.Size.Value)); + } + + xmlWriter.WriteEndElement(); } + if (deleteObjectsRequest.IsSetQuiet()) { xmlWriter.WriteElementString("Quiet", "", S3Transforms.ToXmlStringValue(deleteObjectsRequest.Quiet.Value));