diff --git a/generator/.DevConfigs/c2e4913e-b19a-4bca-b372-322bffb0662b.json b/generator/.DevConfigs/c2e4913e-b19a-4bca-b372-322bffb0662b.json
new file mode 100644
index 000000000000..084297338565
--- /dev/null
+++ b/generator/.DevConfigs/c2e4913e-b19a-4bca-b372-322bffb0662b.json
@@ -0,0 +1,11 @@
+{
+ "services": [
+ {
+ "serviceName": "S3",
+ "type": "patch",
+ "changeLogMessages": [
+ "Move S3PostUploadSignedPolicy out of _bcl folder"
+ ]
+ }
+ ]
+}
diff --git a/sdk/src/Services/S3/Custom/Util/_bcl/S3PostUploadSignedPolicy.cs b/sdk/src/Services/S3/Custom/Util/S3PostUploadSignedPolicy.cs
similarity index 54%
rename from sdk/src/Services/S3/Custom/Util/_bcl/S3PostUploadSignedPolicy.cs
rename to sdk/src/Services/S3/Custom/Util/S3PostUploadSignedPolicy.cs
index 0a40a93c755c..456711bc0fa4 100644
--- a/sdk/src/Services/S3/Custom/Util/_bcl/S3PostUploadSignedPolicy.cs
+++ b/sdk/src/Services/S3/Custom/Util/S3PostUploadSignedPolicy.cs
@@ -19,17 +19,18 @@
*
*/
-using System;
-using System.IO;
-using System.Text;
-using System.Xml.Serialization;
-
using Amazon.Runtime;
using Amazon.Util;
-using System.Globalization;
+using Amazon.Util.Internal;
+using System;
using System.Collections.Generic;
-using System.Text.Json.Nodes;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
using System.Text.Json;
+using System.Xml.Serialization;
+using System.Diagnostics.CodeAnalysis;
namespace Amazon.S3.Util
{
@@ -47,14 +48,59 @@ namespace Amazon.S3.Util
public class S3PostUploadSignedPolicy
{
private static JsonDocumentOptions options = new JsonDocumentOptions { AllowTrailingCommas = true };
- ///
- /// Given a policy and AWS credentials, produce a S3PostUploadSignedPolicy.
- ///
- /// JSON string representing the policy to sign
- /// Credentials to sign the policy with
- /// Service region endpoint.
- /// A signed policy object for use with an S3PostUploadRequest.
- public static S3PostUploadSignedPolicy GetSignedPolicy(string policy, AWSCredentials credentials, RegionEndpoint region)
+
+ private static string
+ KEY_POLICY = "policy",
+ KEY_SIGNATURE = "signature",
+ KEY_ACCESSKEY = "access_key";
+
+ ///
+ /// The policy document which governs what uploads can be done.
+ ///
+ public string Policy { get; set; }
+
+ ///
+ /// The signature for the policy.
+ ///
+ public string Signature { get; set; }
+
+ ///
+ /// The AWS Access Key Id for the credential pair that produced the signature.
+ ///
+ public string AccessKeyId { get; set; }
+
+ ///
+ /// The security token from session or instance credentials.
+ ///
+ public string SecurityToken { get; set; }
+
+ ///
+ /// The signing algorithm used. Required as a field in the post Amazon
+ /// S3 can re-calculate the signature.
+ ///
+ public string Algorithm { get; set; }
+
+ ///
+ /// The date value in ISO8601 format. It is the same date used in
+ /// creating the signing key.
+ ///
+ public string Date { get; set; }
+
+ ///
+ /// In addition to the access key ID, this provides scope information
+ /// used in calculating the signing key for signature calculation.
+ ///
+ public string Credential { get; set; }
+
+
+ ///
+ /// Given a policy and AWS credentials, produce a S3PostUploadSignedPolicy.
+ ///
+ /// JSON string representing the policy to sign
+ /// Credentials to sign the policy with
+ /// Service region endpoint.
+ /// A signed policy object for use with an S3PostUploadRequest.
+ public static S3PostUploadSignedPolicy GetSignedPolicy(string policy, AWSCredentials credentials, RegionEndpoint region)
{
var signedAt = AWSSDKUtils.CorrectedUtcNow;
@@ -71,7 +117,7 @@ public static S3PostUploadSignedPolicy GetSignedPolicy(string policy, AWSCredent
};
if (iCreds.UseToken) { extraConditions[S3Constants.PostFormDataSecurityToken] = iCreds.Token; }
- var policyBytes = addConditionsToPolicy(policy, extraConditions);
+ var policyBytes = AddConditionsToPolicy(policy, extraConditions);
var base64Policy = Convert.ToBase64String(policyBytes);
@@ -91,75 +137,126 @@ public static S3PostUploadSignedPolicy GetSignedPolicy(string policy, AWSCredent
};
}
- private static byte[] addConditionsToPolicy(string policy, Dictionary newConditions)
+ ///
+ /// Adds or updates conditions in an S3 POST policy document while preserving other elements.
+ /// This method creates a new policy document that includes all original policy properties,
+ /// updates any existing conditions that match the new conditions, and adds any new conditions.
+ ///
+ /// Original policy document as JSON string
+ /// Dictionary of new conditions to add or update
+ /// UTF-8 bytes of the modified policy document
+ private static byte[] AddConditionsToPolicy(string policy, Dictionary newConditions)
{
- var json = JsonNode.Parse(policy, null, options) as JsonObject;
- var jsonConditions = json["conditions"] as JsonArray;
-
- if (jsonConditions != null)
+ using (JsonDocument policyDoc = JsonDocument.Parse(policy, options))
{
- foreach (var newCond in newConditions)
+ using (var ms = new MemoryStream())
{
- bool found = false;
- for (int i = 0; i < jsonConditions.Count; i++)
+ using (var writer = new Utf8JsonWriter(ms))
{
- if (jsonConditions[i] is JsonObject obj && obj.ContainsKey(newCond.Key))
+ // Step 1: Start writing the new policy document object
+ writer.WriteStartObject();
+
+ // Step 2: Copy all properties except "conditions" from the original policy
+ // (We'll handle the conditions array separately)
+ foreach (var property in policyDoc.RootElement.EnumerateObject())
{
- obj[newCond.Key] = newCond.Value;
- found = true;
+ if (property.Name != "conditions")
+ {
+ property.WriteTo(writer);
+ }
}
- }
- if (!found)
- {
- var newCondition = new JsonObject
+ // Step 3: Start writing the new "conditions" array
+ writer.WritePropertyName("conditions");
+ writer.WriteStartArray();
+
+ // Step 4: Check if the original policy has a conditions array
+ bool foundConditions = policyDoc.RootElement.TryGetProperty("conditions", out JsonElement conditionsElement);
+
+ if (foundConditions && conditionsElement.ValueKind == JsonValueKind.Array)
{
- [newCond.Key] = newCond.Value
- };
- jsonConditions.Add(newCondition);
- }
- }
- }
- return Encoding.UTF8.GetBytes(json.ToJsonString().Trim());
- }
-
- ///
- /// The policy document which governs what uploads can be done.
- ///
- public string Policy { get; set; }
+ // Step 5: Process each existing condition in the original policy
+ foreach (JsonElement condition in conditionsElement.EnumerateArray())
+ {
+ bool shouldCopyCondition = true; // Default: copy the original condition as-is
+ string matchedKey = null; // Track if we found a key to update
- ///
- /// The signature for the policy.
- ///
- public string Signature { get; set; }
+ // Step 5a: If this condition is an object, check if it contains any keys
+ // that match our new conditions (meaning we need to update this condition)
+ if (condition.ValueKind == JsonValueKind.Object)
+ {
+ // Optimized: Directly iterate over dictionary entries to avoid ToList allocation
+ foreach (var newCond in newConditions)
+ {
+ if (condition.TryGetProperty(newCond.Key, out _))
+ {
+ // This condition contains a key we want to update
+ matchedKey = newCond.Key;
+ shouldCopyCondition = false; // Don't copy as-is, we'll update it
+ break;
+ }
+ }
+ }
- ///
- /// The AWS Access Key Id for the credential pair that produced the signature.
- ///
- public string AccessKeyId { get; set; }
+ // Step 5b: Handle the condition based on whether we need to update it
+ if (shouldCopyCondition)
+ {
+ // Case 1: This condition doesn't need updating, copy it as-is
+ condition.WriteTo(writer);
+ }
+ else if (matchedKey != null)
+ {
+ // Case 2: This condition needs updating because it contains a key
+ // that matches one of our new conditions
+
+ // Start a new object for the updated condition
+ writer.WriteStartObject();
+
+ // Process each property in the original condition
+ foreach (var property in condition.EnumerateObject())
+ {
+ if (property.Name == matchedKey)
+ {
+ // This is the property we want to update with the new value
+ writer.WriteString(matchedKey, newConditions[matchedKey]);
- ///
- /// The security token from session or instance credentials.
- ///
- public string SecurityToken { get; set; }
-
- ///
- /// The signing algorithm used. Required as a field in the post Amazon
- /// S3 can re-calculate the signature.
- ///
- public string Algorithm { get; set; }
+ // Remove this key from newConditions since we've now handled it
+ // (It won't need to be added as a new condition at the end)
+ newConditions.Remove(matchedKey);
+ }
+ else
+ {
+ // This is a property we want to preserve (not update)
+ // Without this else clause, properties other than the one being updated would be lost
+ property.WriteTo(writer);
+ }
+ }
+
+ // End the updated condition object
+ writer.WriteEndObject();
+ }
+ }
+ }
- ///
- /// The date value in ISO8601 format. It is the same date used in
- /// creating the signing key.
- ///
- public string Date { get; set; }
+ // Step 6: Add any remaining new conditions that weren't updates to existing ones
+ foreach (var newCond in newConditions)
+ {
+ writer.WriteStartObject();
+ writer.WriteString(newCond.Key, newCond.Value);
+ writer.WriteEndObject();
+ }
- ///
- /// In addition to the access key ID, this provides scope information
- /// used in calculating the signing key for signature calculation.
- ///
- public string Credential { get; set; }
+ // Step 7: Close the conditions array and the overall policy object
+ writer.WriteEndArray();
+ writer.WriteEndObject();
+ writer.Flush();
+
+ // Step 8: Return the complete modified policy document as bytes
+ return ms.ToArray();
+ }
+ }
+ }
+ }
///
/// Get the policy document as a human readable string.
@@ -170,31 +267,34 @@ public string GetReadablePolicy()
return Encoding.UTF8.GetString(Convert.FromBase64String(this.Policy));
}
- private static string
- KEY_POLICY = "policy",
- KEY_SIGNATURE = "signature",
- KEY_ACCESSKEY = "access_key";
-
+
///
/// JSON representation of this object
///
/// JSON string
public string ToJson()
{
- var json = new JsonObject
+ var obj = new Dictionary
{
[KEY_POLICY] = this.Policy,
[KEY_SIGNATURE] = this.Signature,
[KEY_ACCESSKEY] = this.AccessKeyId
};
- return json.ToJsonString();
+ return JsonSerializerHelper.Serialize>(
+ obj,
+ DictionaryStringStringJsonSerializerContexts.Default
+ );
}
///
/// XML Representation of this object
///
/// XML String
+#if NET8_0_OR_GREATER
+ [RequiresUnreferencedCode("ToXml is not supported for Native AOT.")]
+
+#endif
public string ToXml()
{
StringBuilder xml = new StringBuilder(1024);
@@ -206,6 +306,40 @@ public string ToXml()
return xml.ToString();
}
+ ///
+ /// Create an instance of this class from an XML string.
+ ///
+ /// XML string generated by ToXml()
+ /// Instance of S3PostUploadSignedPolicy
+#if NET8_0_OR_GREATER
+ [RequiresUnreferencedCode("GetSignedPolicyFromXml is not supported for Native AOT.")]
+
+#endif
+ public static S3PostUploadSignedPolicy GetSignedPolicyFromXml(string policyXml)
+ {
+ var reader = new StringReader(policyXml);
+ XmlSerializer serializer = new XmlSerializer(typeof(S3PostUploadSignedPolicy));
+
+ S3PostUploadSignedPolicy policy;
+ try
+ {
+ policy = serializer.Deserialize(reader) as S3PostUploadSignedPolicy;
+ }
+ catch (Exception e)
+ {
+ throw new ArgumentException("Could not parse XML", e);
+ }
+
+ if (String.IsNullOrEmpty(policy.AccessKeyId))
+ throw new ArgumentException("XML Document requries 'AccessKeyId' field");
+ if (String.IsNullOrEmpty(policy.Policy))
+ throw new ArgumentException("XML Document requries 'Policy' field");
+ if (String.IsNullOrEmpty(policy.Signature))
+ throw new ArgumentException("XML Document requries 'Signature' field");
+
+ return policy;
+ }
+
///
/// Create an instance of this class from a JSON string.
///
@@ -240,35 +374,5 @@ public static S3PostUploadSignedPolicy GetSignedPolicyFromJson(string policyJson
throw new ArgumentException("Invalid JSON document", e);
}
}
-
- ///
- /// Create an instance of this class from an XML string.
- ///
- /// XML string generated by ToXml()
- /// Instance of S3PostUploadSignedPolicy
- public static S3PostUploadSignedPolicy GetSignedPolicyFromXml(string policyXml)
- {
- var reader = new StringReader(policyXml);
- XmlSerializer serializer = new XmlSerializer(typeof(S3PostUploadSignedPolicy));
-
- S3PostUploadSignedPolicy policy;
- try
- {
- policy = serializer.Deserialize(reader) as S3PostUploadSignedPolicy;
- }
- catch (Exception e)
- {
- throw new ArgumentException("Could not parse XML", e);
- }
-
- if (String.IsNullOrEmpty(policy.AccessKeyId))
- throw new ArgumentException("XML Document requries 'AccessKeyId' field");
- if (String.IsNullOrEmpty(policy.Policy))
- throw new ArgumentException("XML Document requries 'Policy' field");
- if (String.IsNullOrEmpty(policy.Signature))
- throw new ArgumentException("XML Document requries 'Signature' field");
-
- return policy;
- }
}
}