Skip to content

Commit e4371de

Browse files
committed
fix: upload directory content type
1 parent 5d5b50a commit e4371de

File tree

3 files changed

+118
-1
lines changed

3 files changed

+118
-1
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"services": [
3+
{
4+
"serviceName": "S3",
5+
"type": "patch",
6+
"changeLogMessages": [
7+
"Fix an issue with Transfer Utility's UploadDirectory where the content type of 1 file was persisting for all the files in the directory."
8+
]
9+
}
10+
]
11+
}

sdk/src/Services/S3/Custom/Transfer/Internal/SimpleUploadCommand.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
using Amazon.S3.Model;
3131
using Amazon.S3.Util;
3232
using Amazon.Runtime;
33+
using Amazon.Util;
3334

3435
namespace Amazon.S3.Transfer.Internal
3536
{
@@ -54,7 +55,6 @@ internal PutObjectRequest ConstructRequest()
5455
{
5556
PutObjectRequest putRequest = new PutObjectRequest()
5657
{
57-
Headers = this._fileTransporterRequest.Headers,
5858
BucketName = this._fileTransporterRequest.BucketName,
5959
Key = this._fileTransporterRequest.Key,
6060
CannedACL = this._fileTransporterRequest.CannedACL,
@@ -85,6 +85,25 @@ internal PutObjectRequest ConstructRequest()
8585
ServerSideEncryptionKeyManagementServiceEncryptionContext = this._fileTransporterRequest.SSEKMSEncryptionContext,
8686
WebsiteRedirectLocation = this._fileTransporterRequest.WebsiteRedirectLocation,
8787
};
88+
89+
// We are iterating over the Headers to avoid setting the Header from the Transfer utility upload request
90+
// to the PutObjectRequest since that will cause issues down the line.
91+
// The AmazonS3PreMarshallHandler modifies the content type on the headers collection
92+
// which would impact other requests in a directory upload if the Headers were referenced.
93+
if (this._fileTransporterRequest.Headers != null && this._fileTransporterRequest.Headers.Count > 0)
94+
{
95+
foreach (var headerKey in this._fileTransporterRequest.Headers.Keys)
96+
{
97+
if (string.Equals(headerKey, HeaderKeys.ContentTypeHeader) && this._fileTransporterRequest.IsSetContentType())
98+
{
99+
continue;
100+
}
101+
else
102+
{
103+
putRequest.Headers[headerKey] = this._fileTransporterRequest.Headers[headerKey];
104+
}
105+
}
106+
}
88107

89108
// Avoid setting ContentType to null, as that may clear
90109
// out an existing value in Headers collection

sdk/test/Services/S3/IntegrationTests/TransferUtilityTests.cs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,93 @@ public async System.Threading.Tasks.Task UploadAsyncCancellationTest()
11181118
}
11191119
#endif
11201120

1121+
[TestMethod]
1122+
[TestCategory("S3")]
1123+
public void UploadDirectoryWithMixedFileTypesContentTypeTest()
1124+
{
1125+
var directory = CreateMixedFileTypeTestDirectory();
1126+
var keyPrefix = directory.Name;
1127+
1128+
// Upload directory without setting explicit ContentType
1129+
var transferUtility = new TransferUtility(Client);
1130+
var request = new TransferUtilityUploadDirectoryRequest
1131+
{
1132+
BucketName = bucketName,
1133+
Directory = directory.FullName,
1134+
KeyPrefix = keyPrefix,
1135+
SearchPattern = "*",
1136+
SearchOption = SearchOption.AllDirectories
1137+
// Note: No ContentType set - should auto-detect per file
1138+
};
1139+
1140+
transferUtility.UploadDirectory(request);
1141+
1142+
// Validate each file got correct content type based on extension
1143+
ValidateDirectoryContentTypes(Client, bucketName, keyPrefix, directory);
1144+
}
1145+
1146+
public static DirectoryInfo CreateMixedFileTypeTestDirectory()
1147+
{
1148+
var directoryPath = GenerateDirectoryPath("MixedFileTypeTest");
1149+
1150+
var testFiles = new Dictionary<string, string>
1151+
{
1152+
{ "test.html", "<html><body>Test HTML</body></html>" },
1153+
{ "test.css", "body { color: red; }" },
1154+
{ "test.js", "console.log('test');" },
1155+
{ "test.json", "{ \"test\": \"value\" }" },
1156+
{ "test.txt", "Plain text content" },
1157+
{ "test.xml", "<?xml version=\"1.0\"?><root>test</root>" },
1158+
{ "test.pdf", "PDF content placeholder" },
1159+
{ "test.svg", "<svg><rect width=\"100\" height=\"100\"/></svg>" }
1160+
};
1161+
1162+
foreach (var file in testFiles)
1163+
{
1164+
var filePath = Path.Combine(directoryPath, file.Key);
1165+
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
1166+
File.WriteAllText(filePath, file.Value);
1167+
}
1168+
1169+
return new DirectoryInfo(directoryPath);
1170+
}
1171+
1172+
private static void ValidateDirectoryContentTypes(IAmazonS3 s3client, string bucketName, string keyPrefix, DirectoryInfo directory)
1173+
{
1174+
var expectedContentTypes = new Dictionary<string, string>
1175+
{
1176+
{ ".html", "text/html" },
1177+
{ ".css", "text/css" },
1178+
{ ".js", "application/x-javascript" },
1179+
{ ".json", "application/json" },
1180+
{ ".txt", "text/plain" },
1181+
{ ".xml", "text/xml" },
1182+
{ ".pdf", "application/pdf" },
1183+
{ ".svg", "image/svg+xml" }
1184+
};
1185+
1186+
var files = directory.GetFiles("*", SearchOption.AllDirectories);
1187+
foreach (var file in files)
1188+
{
1189+
var filePath = file.FullName;
1190+
var relativePath = filePath.Substring(directory.FullName.Length + 1);
1191+
var key = (!string.IsNullOrEmpty(keyPrefix) ? keyPrefix + "/" : string.Empty) + relativePath.Replace("\\", "/");
1192+
1193+
var metadata = s3client.GetObjectMetadata(new GetObjectMetadataRequest
1194+
{
1195+
BucketName = bucketName,
1196+
Key = key
1197+
});
1198+
1199+
var extension = Path.GetExtension(file.Name).ToLowerInvariant();
1200+
var expectedContentType = expectedContentTypes[extension];
1201+
1202+
Assert.AreEqual(expectedContentType, metadata.Headers.ContentType,
1203+
$"File {file.Name} should have content type {expectedContentType} but got {metadata.Headers.ContentType}");
1204+
}
1205+
}
1206+
1207+
11211208
public static void ConfigureProgressValidator(DirectoryProgressValidator<DownloadDirectoryProgressArgs> progressValidator)
11221209
{
11231210
progressValidator.Validate = (progress, lastProgress) =>

0 commit comments

Comments
 (0)