/*******************************************************************************
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use
* this file except in compliance with the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file.
* This file 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.
* *****************************************************************************
* __ _ _ ___
* ( )( \/\/ )/ __)
* /__\ \ / \__ \
* (_)(_) \/\/ (___/
*
* AWS SDK for .NET
* API Version: 2006-03-01
*
*/
using Amazon.Runtime;
using Amazon.Runtime.Internal;
using Amazon.Runtime.Internal.Util;
using Amazon.S3.Model;
using Amazon.Util;
using System;
using System.Net;
namespace Amazon.S3.Util
{
///
/// Class to manage and cache the correct region for buckets accessed without an explicit region.
///
public static partial class BucketRegionDetector
{
private const int BucketRegionCacheMaxEntries = 300;
private const string AuthorizationHeaderMalformedErrorCode = "AuthorizationHeaderMalformed";
///
/// A cache of BucketName -> RegionEndpoint pairs.
/// The cache is used to make sure that bucket requests are signed for the correct region,
/// even when requesting them without an explicit region.
///
public static LruCache BucketRegionCache { get; private set; }
static BucketRegionDetector()
{
BucketRegionCache = new LruCache(BucketRegionCacheMaxEntries);
}
///
/// Detect a bucket region mismatch based on the x-amz-bucket-region header, and the status code provided.
///
///
///
///
///
internal static string GetCorrectRegion(AmazonS3Uri requestedBucketUri, HttpStatusCode headBucketStatusCode, string xAmzBucketRegionHeaderValue)
{
if (xAmzBucketRegionHeaderValue != null && headBucketStatusCode == HttpStatusCode.BadRequest)
{
return CheckRegionAndUpdateCache(requestedBucketUri, xAmzBucketRegionHeaderValue);
}
return null;
}
///
/// Detects if the signature is malformed, and the requested bucket is in a Region
/// different from the Region of the request.
///
///
///
/// the correct region if a mismatch was detected, null otherwise
private static string GetCorrectRegion(AmazonS3Uri requestedBucketUri, AmazonServiceException serviceException)
{
string regionFromExceptionBody = null;
string regionFromExceptionHeader = null;
var s3Exception = serviceException as AmazonS3Exception;
if (s3Exception != null)
{
if (string.Equals(s3Exception.ErrorCode, AuthorizationHeaderMalformedErrorCode, StringComparison.Ordinal))
{
regionFromExceptionBody = CheckRegionAndUpdateCache(requestedBucketUri, s3Exception.Region);
}
if (regionFromExceptionBody == null)
{
var innerException = s3Exception.InnerException as HttpErrorResponseException;
if (innerException != null && innerException.Response != null && innerException.Response.IsHeaderPresent(HeaderKeys.XAmzBucketRegion))
{
regionFromExceptionHeader = CheckRegionAndUpdateCache(requestedBucketUri, innerException.Response.GetHeaderValue(HeaderKeys.XAmzBucketRegion));
}
}
}
return regionFromExceptionBody ?? regionFromExceptionHeader;
}
private static string CheckRegionAndUpdateCache(AmazonS3Uri requestedBucketUri, string actualRegion)
{
var requestedRegion = requestedBucketUri.Region == null ? null : requestedBucketUri.Region.SystemName;
if (actualRegion != null && !string.Equals(requestedRegion, actualRegion, StringComparison.Ordinal))
{
BucketRegionCache.AddOrUpdate(requestedBucketUri.Bucket, RegionEndpoint.GetBySystemName(actualRegion));
return actualRegion;
}
return null;
}
private static string GetHeadBucketPreSignedUrl(string bucketName, ImmutableCredentials credentials)
{
// all buckets accessible via USEast1
using (var s3Client = GetUsEast1ClientFromCredentials(credentials))
{
// IMPORTANT:
// This method is called as part of the request pipeline.
// If the pipeline were to be invoked here it would cause
// unwanted recursion.
// As such, the only reason it's OK to use an S3Client here
// is because this code is using a method that doesn't go
// through the request pipeline: GetPreSignedURLInternal
var request = new GetPreSignedUrlRequest
{
BucketName = bucketName,
Expires = s3Client.Config.CorrectedUtcNow.ToLocalTime().AddDays(1),
Verb = HttpVerb.HEAD,
Protocol = Protocol.HTTP
};
return s3Client.GetPreSignedURLInternal(request, false);
}
}
private static AmazonS3Client GetUsEast1ClientFromCredentials(ImmutableCredentials credentials)
{
if (credentials == null)
{
return new AmazonS3Client(new AnonymousAWSCredentials(), RegionEndpoint.USEast1);
}
else if(credentials.UseToken)
{
return new AmazonS3Client(credentials.AccessKey, credentials.SecretKey, credentials.Token, RegionEndpoint.USEast1);
}
else
{
return new AmazonS3Client(credentials.AccessKey, credentials.SecretKey, RegionEndpoint.USEast1);
}
}
}
}