/* * Copyright 2010-2014 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. */ using Amazon.Runtime; using Amazon.Runtime.Internal; using Amazon.Runtime.Internal.Auth; using Amazon.Runtime.Internal.Transform; using Amazon.S3.Model; using Amazon.S3.Model.Internal.MarshallTransformations; using Amazon.S3.Util; using Amazon.Util; using Amazon.Util.Internal; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; using System.Text; using System.Threading; namespace Amazon.S3 { public partial class AmazonS3Client { #region GetPreSignedURL /// /// Create a signed URL allowing access to a resource that would /// usually require authentication. /// /// /// /// When using query string authentication you create a query, /// specify an expiration time for the query, sign it with your /// signature, place the data in an HTTP request, and distribute /// the request to a user or embed the request in a web page. /// /// /// A PreSigned URL can be generated for GET, PUT, DELETE and HEAD /// operations on your bucketName, keys, and versions. /// /// /// The GetPreSignedUrlRequest that defines the /// An Action delegate that is invoked when the operation completes. /// A user-defined state object that is passed to the callback procedure. Retrieve this object from within the callback /// procedure using the AsyncState property. /// public void GetPreSignedURLAsync(GetPreSignedUrlRequest request, AmazonServiceCallback callback, AsyncOptions options = null) { options = options ?? new AsyncOptions(); if (callback == null) { throw new ArgumentNullException("callback"); } ThreadPool.QueueUserWorkItem(new WaitCallback(delegate { // Provide a default policy if user doesn't set it. try { callback(new AmazonServiceResult(request, new GetPreSignedUrlResponse(GetPreSignedURLInternal(request)), null, options.State)); } catch (Exception e) { callback(new AmazonServiceResult(request, null, e, options.State)); } })); } #endregion #region Post Object /// /// Upload data to Amazon S3 using HTTP POST. /// /// /// For more information, /// /// Request object which describes the data to POST /// An Action delegate that is invoked when the operation completes. /// A user-defined state object that is passed to the callback procedure. Retrieve this object from within the callback /// procedure using the AsyncState property. public void PostObjectAsync(PostObjectRequest request, AmazonServiceCallback callback, AsyncOptions options = null) { options = options ?? new AsyncOptions(); Action callbackHelper = (AmazonWebServiceRequest req, AmazonWebServiceResponse res, Exception ex, AsyncOptions ao) => { AmazonServiceResult responseObject = new AmazonServiceResult((PostObjectRequest)req, (PostObjectResponse)res, ex, ao.State); if (callback != null) callback(responseObject); }; ThreadPool.QueueUserWorkItem(new WaitCallback(delegate { // Provide a default policy if user doesn't set it. try { InferContentType(request); if (request.SignedPolicy == null) { CreateSignedPolicy(request); } PostObject(request, options, callbackHelper); } catch (Exception e) { callback(new AmazonServiceResult(request, null, e, options.State)); } })); } private void InferContentType(PostObjectRequest request) { if (String.IsNullOrEmpty(request.Headers.ContentType)) { if (request.Key.IndexOf('.') > -1) request.Headers.ContentType = AmazonS3Util.MimeTypeFromExtension(request.Key.Substring(request.Key.LastIndexOf('.'))); else if (!String.IsNullOrEmpty(request.Path) && request.Path.IndexOf('.') > -1) request.Headers.ContentType = AmazonS3Util.MimeTypeFromExtension(request.Key.Substring(request.Path.LastIndexOf('.'))); else request.Headers.ContentType = "application/octet-stream"; } } private void CreateSignedPolicy(PostObjectRequest request) { StringBuilder metadataPolicy = new StringBuilder(); foreach (var kvp in request.Metadata) { var metakey = kvp.Key.StartsWith(S3Constants.PostFormDataXAmzPrefix, StringComparison.Ordinal) ? kvp.Key : S3Constants.PostFormDataMetaPrefix + kvp.Key; metadataPolicy.Append(string.Format(",{{\"{0}\": \"{1}\"}}", metakey, kvp.Value)); } StringBuilder headersPolicy = new StringBuilder(); foreach (var key in request.Headers.Keys) { headersPolicy.Append(string.Format(",{{\"{0}\": \"{1}\"}}", key, request.Headers[key])); } string policyString = null; int position = request.Key.LastIndexOf('/'); if (position == -1) { policyString = "{\"expiration\": \"" + AWSSDKUtils.CorrectedUtcNow.AddHours(24).ToString("yyyy-MM-ddTHH:mm:ssZ") + "\",\"conditions\": [{\"bucket\": \"" + request.Bucket + "\"},[\"starts-with\", \"$key\", \"" + "\"],{\"acl\": \"" + request.CannedACL.Value + "\"},[\"eq\", \"$Content-Type\", " + "\"" + request.Headers.ContentType + "\"" + "]" + metadataPolicy.ToString() + headersPolicy.ToString() + "]}"; } else { policyString = "{\"expiration\": \"" + AWSSDKUtils.CorrectedUtcNow.AddHours(24).ToString("yyyy-MM-ddTHH:mm:ssZ") + "\",\"conditions\": [{\"bucket\": \"" + request.Bucket + "\"},[\"starts-with\", \"$key\", \"" + request.Key.Substring(0, position) + "/\"],{\"acl\": \"" + request.CannedACL.Value + "\"},[\"eq\", \"$Content-Type\", " + "\"" + request.Headers.ContentType + "\"" + "]" + metadataPolicy.ToString() + headersPolicy.ToString() + "]}"; } if (Config.SignatureVersion == "2") { request.SignedPolicy = S3PostUploadSignedPolicy.GetSignedPolicy(policyString, base.Credentials); } else { request.SignedPolicy = S3PostUploadSignedPolicy.GetSignedPolicyV4(policyString, base.Credentials, request.Region); } } private void PostObject(PostObjectRequest request, AsyncOptions options, Action callbackHelper) { string url; string subdomain = request.Region.Equals(RegionEndpoint.USEast1) ? "s3" : "s3-" + request.Region.SystemName; IDictionary headers = new Dictionary(); if (request.Bucket.IndexOf('.') > -1) url = string.Format(CultureInfo.InvariantCulture, "https://{0}.amazonaws.com/{1}/", subdomain, request.Bucket); else url = string.Format(CultureInfo.InvariantCulture, "https://{0}.{1}.amazonaws.com", request.Bucket, subdomain); Uri uri = new Uri(url); IHttpRequest webRequest = null; if (AWSConfigs.HttpClient == AWSConfigs.HttpClientOption.UnityWWW) webRequest = new UnityWwwRequest(uri); else webRequest = new UnityRequest(uri); var boundary = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace('=', 'z'); headers[HeaderKeys.ContentTypeHeader] = string.Format(CultureInfo.InvariantCulture, "multipart/form-data; boundary={0}", boundary); headers[HeaderKeys.UserAgentHeader] = AWSSDKUtils.UserAgentHeader; webRequest.Method = "POST"; using (var reqStream = new MemoryStream()) { request.WriteFormData(boundary, reqStream); byte[] boundaryBytes = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "--{0}\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n", boundary)); reqStream.Write(boundaryBytes, 0, boundaryBytes.Length); using (var inputStream = null == request.Path ? request.InputStream : File.OpenRead(request.Path)) { byte[] buf = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.Read(buf, 0, 1024)) > 0) { reqStream.Write(buf, 0, bytesRead); } } byte[] endBoundaryBytes = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "\r\n--{0}--", boundary)); reqStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length); webRequest.WriteToRequestBody(null, reqStream.ToArray(), headers); var callback = ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)request).StreamUploadProgressCallback; if (callback != null) webRequest.SetupProgressListeners(reqStream, 0, request, callback); } var executionContext = new AsyncExecutionContext( new AsyncRequestContext(this.Config.LogMetrics, new NullSigner()) { ClientConfig = this.Config, OriginalRequest = request, Action = callbackHelper, AsyncOptions = options, IsAsync = true }, new AsyncResponseContext() ); webRequest.SetRequestHeaders(headers); executionContext.RuntimeState = webRequest; executionContext.ResponseContext.AsyncResult = new RuntimeAsyncResult(executionContext.RequestContext.Callback, executionContext.RequestContext.State); executionContext.ResponseContext.AsyncResult.AsyncOptions = executionContext.RequestContext.AsyncOptions; executionContext.ResponseContext.AsyncResult.Action = executionContext.RequestContext.Action; executionContext.ResponseContext.AsyncResult.Request = executionContext.RequestContext.OriginalRequest; webRequest.BeginGetResponse(new AsyncCallback(ProcessPostResponse), executionContext); } private void ProcessPostResponse(IAsyncResult result) { IAsyncExecutionContext executionContext = null; IHttpRequest httpRequest = null; try { executionContext = result.AsyncState as IAsyncExecutionContext; httpRequest = executionContext.RuntimeState as IHttpRequest; var httpResponse = httpRequest.EndGetResponse(result); executionContext.ResponseContext.HttpResponse = httpResponse; } catch (Exception exception) { // Capture the exception and invoke outer handlers to // process the exception. executionContext.ResponseContext.AsyncResult.Exception = exception; } finally { httpRequest.Dispose(); } PostResponseHelper(result); } private void PostResponseHelper(IAsyncResult result) { IAsyncExecutionContext executionContext = result.AsyncState as IAsyncExecutionContext; IWebResponseData response = executionContext.ResponseContext.HttpResponse; RuntimeAsyncResult asyncResult = executionContext.ResponseContext.AsyncResult as RuntimeAsyncResult; if (executionContext.ResponseContext.AsyncResult.Exception == null) { PostObjectResponse postResponse = new PostObjectResponse(); postResponse.HttpStatusCode = response.StatusCode; postResponse.ContentLength = response.ContentLength; if (response.IsHeaderPresent(HeaderKeys.XAmzRequestIdHeader)) postResponse.RequestId = response.GetHeaderValue(HeaderKeys.XAmzRequestIdHeader); if (response.IsHeaderPresent(HeaderKeys.XAmzId2Header)) postResponse.HostId = response.GetHeaderValue(HeaderKeys.XAmzId2Header); if (response.IsHeaderPresent(HeaderKeys.XAmzVersionIdHeader)) postResponse.VersionId = response.GetHeaderValue(HeaderKeys.XAmzVersionIdHeader); PostObjectRequest request = executionContext.RequestContext.OriginalRequest as PostObjectRequest; asyncResult.Request = request; asyncResult.Response = postResponse; } asyncResult.Exception = executionContext.ResponseContext.AsyncResult.Exception; asyncResult.Action = executionContext.RequestContext.Action; asyncResult.InvokeCallback(); } #endregion } }