From 7980b8ff91d45cc5e3f2d2941a1d1a184ddbbf7c Mon Sep 17 00:00:00 2001 From: manuelnelson Date: Mon, 28 May 2012 18:39:10 -0400 Subject: [PATCH 1/3] Added Cors functionality See https://developers.google.com/storage/docs/cross-origin for more details on Cors and Google Storage --- SharpGs/Cors/ICors.cs | 45 +++ SharpGs/Cors/IHttpMethods.cs | 8 + SharpGs/Cors/IOrigin.cs | 8 + SharpGs/Cors/IResponseHeader.cs | 8 + SharpGs/Cors/Internal/Cors.cs | 61 ++++ SharpGs/Cors/Internal/HttpMethods.cs | 31 ++ SharpGs/Cors/Internal/Origin.cs | 32 +++ SharpGs/Cors/Internal/ResponseHeader.cs | 27 ++ SharpGs/ISharpGs.cs | 104 +++---- SharpGs/Internal/SharpGsClient.cs | 366 ++++++++++++------------ SharpGs/RestApi/RestApiClient.cs | 272 +++++++++--------- SharpGs/SharpGs.csproj | 8 + SharpGsDemo/Program.cs | 156 +++++----- 13 files changed, 677 insertions(+), 449 deletions(-) create mode 100644 SharpGs/Cors/ICors.cs create mode 100644 SharpGs/Cors/IHttpMethods.cs create mode 100644 SharpGs/Cors/IOrigin.cs create mode 100644 SharpGs/Cors/IResponseHeader.cs create mode 100644 SharpGs/Cors/Internal/Cors.cs create mode 100644 SharpGs/Cors/Internal/HttpMethods.cs create mode 100644 SharpGs/Cors/Internal/Origin.cs create mode 100644 SharpGs/Cors/Internal/ResponseHeader.cs diff --git a/SharpGs/Cors/ICors.cs b/SharpGs/Cors/ICors.cs new file mode 100644 index 0000000..6c88b8f --- /dev/null +++ b/SharpGs/Cors/ICors.cs @@ -0,0 +1,45 @@ +using SharpGs.RestApi; + +namespace SharpGs.Cors +{ + /// + /// Setup Cross Origin + /// + public interface ICors + { + + /// + /// Adds an origin to the origin collection. + /// + /// An Origin permitted for cross origin resource sharing with this Google Cloud Storage bucket. + /// For example, http://origin1.example.com. You can use wildcards ("*"). However, if the host part of the Origin begins with a *, + /// then any origin that ends with the same suffix will be considered a match. If you supply a value that consists of only + /// the wildcard (*), this gives access to ALL origins. + /// + /// The origin to add + void AddOrigin(string origin); + + /// + /// Adds a request method to the collection of methods supported in this configuration. Valid values are GET, HEAD, PUT, POST, and DELETE. + /// + /// + /// + void AddMethod(string method); + + /// + /// Adds a response header/s that the user agent is permitted to share across origins. + /// + void AddHeader(string responseHeader); + + /// + /// This value is used to respond to preflight requests, indicating the number of seconds that the client (browser) is allowed to make + /// requests before the client must repeat the preflight request. (Indicates cache expiry time.) Preflight requests are required if + /// the request method contains non-simple headers or if the request method is not POST, GET, or HEAD. The value is returned in the + /// Access-Control-Max-Age header in responses to preflight requests. + /// + int MaxAge { get; set; } + + + string ToXmlString(); + } +} diff --git a/SharpGs/Cors/IHttpMethods.cs b/SharpGs/Cors/IHttpMethods.cs new file mode 100644 index 0000000..7b21dba --- /dev/null +++ b/SharpGs/Cors/IHttpMethods.cs @@ -0,0 +1,8 @@ +namespace SharpGs.Cors +{ + public interface IHttpMethods + { + void AddMethod(string method); + string ToXmlString(); + } +} diff --git a/SharpGs/Cors/IOrigin.cs b/SharpGs/Cors/IOrigin.cs new file mode 100644 index 0000000..cce75de --- /dev/null +++ b/SharpGs/Cors/IOrigin.cs @@ -0,0 +1,8 @@ +namespace SharpGs.Cors +{ + public interface IOrigin + { + void AddOrigin(string origin); + string ToXmlString(); + } +} diff --git a/SharpGs/Cors/IResponseHeader.cs b/SharpGs/Cors/IResponseHeader.cs new file mode 100644 index 0000000..4d0281c --- /dev/null +++ b/SharpGs/Cors/IResponseHeader.cs @@ -0,0 +1,8 @@ +namespace SharpGs.Cors +{ + public interface IResponseHeader + { + void AddResponseHeader(string responseHeader); + string ToXmlString(); + } +} diff --git a/SharpGs/Cors/Internal/Cors.cs b/SharpGs/Cors/Internal/Cors.cs new file mode 100644 index 0000000..9726160 --- /dev/null +++ b/SharpGs/Cors/Internal/Cors.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using SharpGs.RestApi; + +namespace SharpGs.Cors.Internal +{ + public class Cors : ICors + { + private IOrigin Origins = new Origin(); + private IHttpMethods Methods = new HttpMethods(); + private IResponseHeader ResponseHeaders = new ResponseHeader(); + public int MaxAge { get; set; } + public Cors() + { + MaxAge = 1800; + } + + public Cors(XDocument document) + { + foreach (var origin in document.Descendants("Origin")) + Origins.AddOrigin(origin.Value); + foreach (var method in document.Descendants("Method")) + Methods.AddMethod(method.Value.ToUpper()); + foreach (var response in document.Descendants("ResponseHeader")) + ResponseHeaders.AddResponseHeader(response.Value); + var firstOrDefault = document.Descendants("MaxAgeSec").FirstOrDefault(); + if (firstOrDefault != null) + MaxAge = Convert.ToInt16(firstOrDefault.Value); + } + public void AddOrigin(string origin) + { + Origins.AddOrigin(origin); + } + + public void AddMethod(string method) + { + Methods.AddMethod(method); + } + + public void AddHeader(string responseHeader) + { + ResponseHeaders.AddResponseHeader(responseHeader); + } + + public string ToXmlString() + { + var sb = new StringBuilder("").AppendLine(); + sb.AppendLine(""); + sb.AppendLine("\t"); + sb.Append(Origins.ToXmlString()); + sb.Append(Methods.ToXmlString()); + sb.Append(ResponseHeaders.ToXmlString()); + sb.AppendLine("\t\t" + MaxAge + ""); + sb.AppendLine("\t"); + sb.AppendLine(""); + return sb.ToString(); + } + } +} diff --git a/SharpGs/Cors/Internal/HttpMethods.cs b/SharpGs/Cors/Internal/HttpMethods.cs new file mode 100644 index 0000000..8d81e35 --- /dev/null +++ b/SharpGs/Cors/Internal/HttpMethods.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Text; + +namespace SharpGs.Cors.Internal +{ + internal class HttpMethods : IHttpMethods + { + public List RequestMethods; + + public HttpMethods() + { + RequestMethods = new List(); + } + + public void AddMethod(string method) + { + RequestMethods.Add(method); + } + + public string ToXmlString() + { + var sb = new StringBuilder(); + sb.AppendLine("\t\t"); + foreach (var requestMethod in RequestMethods) + { + sb.AppendLine(string.Format("\t\t\t{0}", requestMethod)); + } + return sb.AppendLine("\t\t").ToString(); + } + } +} diff --git a/SharpGs/Cors/Internal/Origin.cs b/SharpGs/Cors/Internal/Origin.cs new file mode 100644 index 0000000..5beca84 --- /dev/null +++ b/SharpGs/Cors/Internal/Origin.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharpGs.Cors.Internal +{ + internal class Origin : IOrigin + { + public List Origins; + public Origin() + { + Origins = new List(); + } + + public void AddOrigin(string origin) + { + Origins.Add(origin); + } + + public string ToXmlString() + { + var sb = new StringBuilder(); + sb.AppendLine("\t\t"); + foreach (var origin in Origins) + { + sb.AppendLine(String.Format("\t\t\t{0}", origin)); + } + return sb.AppendLine("\t\t").ToString(); + } + + } +} diff --git a/SharpGs/Cors/Internal/ResponseHeader.cs b/SharpGs/Cors/Internal/ResponseHeader.cs new file mode 100644 index 0000000..95c5ac7 --- /dev/null +++ b/SharpGs/Cors/Internal/ResponseHeader.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Text; + +namespace SharpGs.Cors.Internal +{ + internal class ResponseHeader: IResponseHeader + { + public List ResponseHeaders = new List(); + public void AddResponseHeader(string responseHeader) + { + ResponseHeaders.Add(responseHeader); + } + + public string ToXmlString() + { + if (ResponseHeaders.Count == 0) + return null; + var sb = new StringBuilder(); + sb.AppendLine("\t\t"); + foreach (var responseHeader in ResponseHeaders) + { + sb.AppendLine(string.Format("\t\t\t{0}", responseHeader)); + } + return sb.AppendLine("\t\t").ToString(); + } + } +} diff --git a/SharpGs/ISharpGs.cs b/SharpGs/ISharpGs.cs index 70ce065..2bfa5dd 100644 --- a/SharpGs/ISharpGs.cs +++ b/SharpGs/ISharpGs.cs @@ -1,52 +1,52 @@ -using System; -using System.Collections.Generic; -using System.Net; - -namespace SharpGs -{ - /// - /// Google Storage service connector - /// - public interface ISharpGs : IDisposable - { - /// - /// Get the AuthKey of GoogleStorage account - /// - string AuthKey { get; } - - /// - /// Get the AuthSecret of GoogleStorage account - /// - string AuthSecret { get; } - - /// - /// If true, uses https secured protocol. True by default - /// - bool SecuredConnection { get; set; } - - /// - /// If is not null, connection will be done through proxy - /// - IWebProxy WebProxy { get; set; } - - /// - /// Request server for list of buckets - /// https://code.google.com/apis/storage/docs/reference-methods.html#getservice - /// - IEnumerable Buckets { get; } - - /// - /// Create new bucket. If bucket with same name already exists in GS, exception will be thrown. - /// https://code.google.com/apis/storage/docs/reference-methods.html#putbucket - /// - /// unique name of the bucket - void CreateBucket(string name); - - /// - /// Get the bucket info - /// - /// name of the bucket - /// bucket information - IBucket GetBucket(string name); - } -} +using System; +using System.Collections.Generic; +using System.Net; + +namespace SharpGs +{ + /// + /// Google Storage service connector + /// + public interface ISharpGs : IDisposable + { + /// + /// Get the AuthKey of GoogleStorage account + /// + string AuthKey { get; } + + /// + /// Get the AuthSecret of GoogleStorage account + /// + string AuthSecret { get; } + + /// + /// If true, uses https secured protocol. True by default + /// + bool SecuredConnection { get; set; } + + /// + /// If is not null, connection will be done through proxy + /// + IWebProxy WebProxy { get; set; } + + /// + /// Request server for list of buckets + /// https://code.google.com/apis/storage/docs/reference-methods.html#getservice + /// + IEnumerable Buckets { get; } + + /// + /// Create new bucket. If bucket with same name already exists in GS, exception will be thrown. + /// https://code.google.com/apis/storage/docs/reference-methods.html#putbucket + /// + /// unique name of the bucket + void CreateBucket(string name); + + /// + /// Get the bucket info + /// + /// name of the bucket + /// bucket information + IBucket GetBucket(string name); + } +} diff --git a/SharpGs/Internal/SharpGsClient.cs b/SharpGs/Internal/SharpGsClient.cs index d9123d7..6b5999c 100644 --- a/SharpGs/Internal/SharpGsClient.cs +++ b/SharpGs/Internal/SharpGsClient.cs @@ -1,183 +1,183 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Xml.Linq; -using SharpGs.RestApi; -using System.Net; - -namespace SharpGs.Internal -{ - internal class SharpGsClient : ISharpGs - { - private const string DefaultGoogleHost = @"commondatastorage.googleapis.com"; - - protected string GoogleStorageHost - { - get; set; - } - - public string AuthKey - { - get; private set; - } - - public string AuthSecret - { - get; private set; - } - - public bool SecuredConnection - { - get; set; - } - - public IWebProxy WebProxy - { - get; set; - } - - private Uri ConnectionUrl(RequestMethod requestMethod, string bucketName = null, string path = null, string parameters = null) - { - var bucket = bucketName == null ? String.Empty : bucketName + '.'; - return - new Uri(String.Format("{0}://{1}{2}/{3}{4}", SecuredConnection ? Uri.UriSchemeHttps : Uri.UriSchemeHttp, - bucket, GoogleStorageHost, path, GetAdditionalParameters(requestMethod, parameters))); - } - - private static string GetAdditionalParameters(RequestMethod requestMethod, string parameters) - { - if (requestMethod == RequestMethod.ACL_GET || requestMethod == RequestMethod.ACL_SET) - return "?acl"; - if (parameters != null) - return "?" + parameters; - return String.Empty; - } - - public SharpGsClient(string key, string secret) - { - AuthKey = key; - AuthSecret = secret; - SecuredConnection = true; - GoogleStorageHost = DefaultGoogleHost; - } - - #region Request Generation - - private static string SyndicateCanonicalHeaders(RequestMethod requestMethod, string contentMd5, string contentType, string date) - { - return String.Format("{0}\n{1}\n{2}\n{3}\n", RestApiClient.PureRequestMethod(requestMethod), contentMd5, contentType, date); - } - - private static string SyndicateCanonicalResource(RequestMethod requestMethod, string bucket, string path) - { - var sb = new StringBuilder("/"); - if (bucket != null) - { - sb.Append(bucket); - sb.Append("/"); - if (path != null) - sb.Append(path); - if (requestMethod == RequestMethod.ACL_GET || requestMethod == RequestMethod.ACL_SET) - sb.Append("?acl"); - } - return sb.ToString(); - } - - private static string SyndicateAuthValue(string key, string signature) - { - return String.Format(@"GOOG1 {0}:{1}", key, signature); - } - - internal XDocument Request(RequestMethod requestMethod = RequestMethod.GET, string bucket = null, string path = null, byte[] content = null, string contentType = null, Bucket.ObjectHead objectHead = null, bool withData = false, string parameters = null) - { - var stream = content == null ? null : new MemoryStream(content); - try - { - return RequestStream(requestMethod, bucket, path, stream, contentType, objectHead, withData, parameters); - } - finally - { - if (stream != null) - stream.Close(); - } - } - - internal XDocument RequestStream(RequestMethod requestMethod = RequestMethod.GET, string bucket = null, string path = null, Stream content = null, string contentType = null, Bucket.ObjectHead objectHead = null, bool withData = false, string parameters = null) - { - var contentTypeFixed = contentType ?? @"application/xml"; - var dateO = DateTime.UtcNow; - var date = dateO.ToString(@"ddd, dd MMM yyyy HH':'mm':'ss 'GMT'", CultureInfo.GetCultureInfo("EN-US")); - - var canonicalHeaders = SyndicateCanonicalHeaders(requestMethod, - content == null - ? null - : Convert.ToBase64String( - MD5.Create().ComputeHash(content)), - contentTypeFixed, - date); - var canonicalResource = SyndicateCanonicalResource(requestMethod, bucket, path); - - var signatureOrigin = String.Format("{0}{1}", canonicalHeaders, canonicalResource); - var signature = - Convert.ToBase64String( - new HMACSHA1(Encoding.UTF8.GetBytes(AuthSecret)).ComputeHash(Encoding.UTF8.GetBytes(signatureOrigin))); - - var api = new RestApiClient(ConnectionUrl(requestMethod, bucket, path, parameters), requestMethod, WebProxy); - if (objectHead != null) - objectHead.Key = path; - var result = api.Request(SyndicateAuthValue(AuthKey, signature), dateO, content, contentTypeFixed, - objectHead, withData); - if (String.IsNullOrEmpty(result)) - return null; - var responce = XDocument.Parse(FilterResponse(result)); - var error = responce.Descendants(@"Error").FirstOrDefault(); - if (error == null) - return responce; - throw error.FindException(); - } - - private static string FilterResponse(string response) - { - var p = response; - while (true) - { - var ptr = p.IndexOf(" xmlns="); - if (ptr < 0) - break; - var ptrE = p.IndexOf(p[ptr + 7], ptr + 8); - if (ptrE <= ptr) - break; - p = p.Substring(0, ptr) + p.Substring(ptrE + 1); - } - return p; - } - - #endregion Request Generation - - public IEnumerable Buckets - { - get - { - return Request().Descendants(@"Bucket").Select(bucket => Bucket.FromXml(bucket, this)); - } - } - - public IBucket GetBucket(string name) - { - return Buckets.FirstOrDefault(b => b.Name.Equals(name)); - } - - public void CreateBucket(string name) - { - Request(RequestMethod.PUT, name); - } - - public void Dispose() - { - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Xml.Linq; +using SharpGs.RestApi; +using System.Net; + +namespace SharpGs.Internal +{ + internal class SharpGsClient : ISharpGs + { + private const string DefaultGoogleHost = @"commondatastorage.googleapis.com"; + + protected string GoogleStorageHost + { + get; set; + } + + public string AuthKey + { + get; private set; + } + + public string AuthSecret + { + get; private set; + } + + public bool SecuredConnection + { + get; set; + } + + public IWebProxy WebProxy + { + get; set; + } + + private Uri ConnectionUrl(RequestMethod requestMethod, string bucketName = null, string path = null, string parameters = null) + { + var bucket = bucketName == null ? String.Empty : bucketName + '.'; + return + new Uri(String.Format("{0}://{1}{2}/{3}{4}", SecuredConnection ? Uri.UriSchemeHttps : Uri.UriSchemeHttp, + bucket, GoogleStorageHost, path, GetAdditionalParameters(requestMethod, parameters))); + } + + private static string GetAdditionalParameters(RequestMethod requestMethod, string parameters) + { + if (requestMethod == RequestMethod.ACL_GET || requestMethod == RequestMethod.ACL_SET) + return "?acl"; + if (parameters != null) + return "?" + parameters; + return String.Empty; + } + + public SharpGsClient(string key, string secret) + { + AuthKey = key; + AuthSecret = secret; + SecuredConnection = true; + GoogleStorageHost = DefaultGoogleHost; + } + + #region Request Generation + + private static string SyndicateCanonicalHeaders(RequestMethod requestMethod, string contentMd5, string contentType, string date) + { + return String.Format("{0}\n{1}\n{2}\n{3}\n", RestApiClient.PureRequestMethod(requestMethod), contentMd5, contentType, date); + } + + private static string SyndicateCanonicalResource(RequestMethod requestMethod, string bucket, string path) + { + var sb = new StringBuilder("/"); + if (bucket != null) + { + sb.Append(bucket); + sb.Append("/"); + if (path != null) + sb.Append(path); + if (requestMethod == RequestMethod.ACL_GET || requestMethod == RequestMethod.ACL_SET) + sb.Append("?acl"); + } + return sb.ToString(); + } + + private static string SyndicateAuthValue(string key, string signature) + { + return String.Format(@"GOOG1 {0}:{1}", key, signature); + } + + internal XDocument Request(RequestMethod requestMethod = RequestMethod.GET, string bucket = null, string path = null, byte[] content = null, string contentType = null, Bucket.ObjectHead objectHead = null, bool withData = false, string parameters = null) + { + var stream = content == null ? null : new MemoryStream(content); + try + { + return RequestStream(requestMethod, bucket, path, stream, contentType, objectHead, withData, parameters); + } + finally + { + if (stream != null) + stream.Close(); + } + } + + internal XDocument RequestStream(RequestMethod requestMethod = RequestMethod.GET, string bucket = null, string path = null, Stream content = null, string contentType = null, Bucket.ObjectHead objectHead = null, bool withData = false, string parameters = null) + { + var contentTypeFixed = contentType ?? @"application/xml"; + var dateO = DateTime.UtcNow; + var date = dateO.ToString(@"ddd, dd MMM yyyy HH':'mm':'ss 'GMT'", CultureInfo.GetCultureInfo("EN-US")); + + var canonicalHeaders = SyndicateCanonicalHeaders(requestMethod, + content == null + ? null + : Convert.ToBase64String( + MD5.Create().ComputeHash(content)), + contentTypeFixed, + date); + var canonicalResource = SyndicateCanonicalResource(requestMethod, bucket, path); + + var signatureOrigin = String.Format("{0}{1}", canonicalHeaders, canonicalResource); + var signature = + Convert.ToBase64String( + new HMACSHA1(Encoding.UTF8.GetBytes(AuthSecret)).ComputeHash(Encoding.UTF8.GetBytes(signatureOrigin))); + + var api = new RestApiClient(ConnectionUrl(requestMethod, bucket, path, parameters), requestMethod, WebProxy); + if (objectHead != null) + objectHead.Key = path; + var result = api.Request(SyndicateAuthValue(AuthKey, signature), dateO, content, contentTypeFixed, + objectHead, withData); + if (String.IsNullOrEmpty(result)) + return null; + var responce = XDocument.Parse(FilterResponse(result)); + var error = responce.Descendants(@"Error").FirstOrDefault(); + if (error == null) + return responce; + throw error.FindException(); + } + + private static string FilterResponse(string response) + { + var p = response; + while (true) + { + var ptr = p.IndexOf(" xmlns="); + if (ptr < 0) + break; + var ptrE = p.IndexOf(p[ptr + 7], ptr + 8); + if (ptrE <= ptr) + break; + p = p.Substring(0, ptr) + p.Substring(ptrE + 1); + } + return p; + } + + #endregion Request Generation + + public IEnumerable Buckets + { + get + { + return Request().Descendants(@"Bucket").Select(bucket => Bucket.FromXml(bucket, this)); + } + } + + public IBucket GetBucket(string name) + { + return Buckets.FirstOrDefault(b => b.Name.Equals(name)); + } + + public void CreateBucket(string name) + { + Request(RequestMethod.PUT, name); + } + + public void Dispose() + { + } + } +} diff --git a/SharpGs/RestApi/RestApiClient.cs b/SharpGs/RestApi/RestApiClient.cs index 15cf5d7..dbff6a6 100644 --- a/SharpGs/RestApi/RestApiClient.cs +++ b/SharpGs/RestApi/RestApiClient.cs @@ -1,136 +1,136 @@ -using System; -using System.IO; -using System.Net; -using System.Security.Cryptography; -using SharpGs.Internal; - -namespace SharpGs.RestApi -{ - internal class RestApiClient - { - private readonly Uri _uri; - private readonly RequestMethod _method; - private readonly IWebProxy _webProxy; - - public RestApiClient(Uri uri, RequestMethod method, IWebProxy proxy = null) - { - _uri = uri; - _method = method; - _webProxy = proxy; - } - - internal static RequestMethod PureRequestMethod(RequestMethod method) - { - switch (method) - { - case RequestMethod.ACL_GET: - return RequestMethod.GET; - case RequestMethod.ACL_SET: - return RequestMethod.PUT; - default: - return method; - } - } - - public static void CopyStream(Stream input, Stream output) - { - var buffer = new byte[32768]; - int read; - while ((read = input.Read(buffer, 0, buffer.Length)) > 0) - { - output.Write(buffer, 0, read); - } - } - - private HttpWebRequest CreateRequest(string authValue, DateTime date, Stream content, string contentType) - { - var request = (HttpWebRequest)WebRequest.Create(_uri); - request.Method = PureRequestMethod(_method).ToString(); - - if (_webProxy != null) - request.Proxy = _webProxy; - - request.Headers.Add(@"Authorization", authValue); - if (content != null) - { - content.Seek(0, SeekOrigin.Begin); - request.Headers.Add(@"Content-MD5", Convert.ToBase64String(MD5.Create().ComputeHash(content))); - } - request.Date = date; - request.ContentLength = content == null ? 0 : content.Length; - request.ContentType = contentType; - request.KeepAlive = false; - if (content != null) - { - var resultedStream = request.GetRequestStream(); - content.Seek(0, SeekOrigin.Begin); - CopyStream(content, resultedStream); - } - return request; - } - - private static bool FetchDataFromResponse(HttpWebResponse response, Bucket.ObjectHead objectHead, bool withData) - { - if (objectHead == null) - return false; - objectHead.Size = response.ContentLength; - objectHead.ContentType = response.ContentType; - objectHead.ETag = response.Headers["ETag"]; - objectHead.LastModified = response.LastModified; - if (withData) - { - var stream = response.GetResponseStream(); - if (objectHead.TargetStream == null) - { - objectHead.Content = new byte[objectHead.Size]; - var read = (long) 0; - if (stream != null) - { - while (read < objectHead.Size) - { - var toread = (int) ((objectHead.Size - read)%4048); - read += stream.Read(objectHead.Content, 0, toread); - } - return true; - } - } - else - { - if (stream != null) - { - CopyStream(stream, objectHead.TargetStream); - return true; - } - } - } - return false; - } - - private static string StreamToString(Stream stream) - { - using (var reader = new StreamReader(stream)) - { - return reader.ReadToEnd(); - } - } - - public string Request(string authValue, DateTime date, Stream content, string contentType, Bucket.ObjectHead objectHead, bool withData) - { - try - { - var request = CreateRequest(authValue, date, content, contentType); - - using (var response = (HttpWebResponse)request.GetResponse()) - { - if (response.StatusCode == HttpStatusCode.OK && FetchDataFromResponse(response, objectHead, withData)) - return String.Empty; - return StreamToString(response.GetResponseStream()); - } - } - catch (WebException exception) - { - return StreamToString(exception.Response.GetResponseStream()); - } - } - } -} +using System; +using System.IO; +using System.Net; +using System.Security.Cryptography; +using SharpGs.Internal; + +namespace SharpGs.RestApi +{ + internal class RestApiClient + { + private readonly Uri _uri; + private readonly RequestMethod _method; + private readonly IWebProxy _webProxy; + + public RestApiClient(Uri uri, RequestMethod method, IWebProxy proxy = null) + { + _uri = uri; + _method = method; + _webProxy = proxy; + } + + internal static RequestMethod PureRequestMethod(RequestMethod method) + { + switch (method) + { + case RequestMethod.ACL_GET: + return RequestMethod.GET; + case RequestMethod.ACL_SET: + return RequestMethod.PUT; + default: + return method; + } + } + + public static void CopyStream(Stream input, Stream output) + { + var buffer = new byte[32768]; + int read; + while ((read = input.Read(buffer, 0, buffer.Length)) > 0) + { + output.Write(buffer, 0, read); + } + } + + private HttpWebRequest CreateRequest(string authValue, DateTime date, Stream content, string contentType) + { + var request = (HttpWebRequest)WebRequest.Create(_uri); + request.Method = PureRequestMethod(_method).ToString(); + + if (_webProxy != null) + request.Proxy = _webProxy; + + request.Headers.Add(@"Authorization", authValue); + if (content != null) + { + content.Seek(0, SeekOrigin.Begin); + request.Headers.Add(@"Content-MD5", Convert.ToBase64String(MD5.Create().ComputeHash(content))); + } + request.Date = date; + request.ContentLength = content == null ? 0 : content.Length; + request.ContentType = contentType; + request.KeepAlive = false; + if (content != null) + { + var resultedStream = request.GetRequestStream(); + content.Seek(0, SeekOrigin.Begin); + CopyStream(content, resultedStream); + } + return request; + } + + private static bool FetchDataFromResponse(HttpWebResponse response, Bucket.ObjectHead objectHead, bool withData) + { + if (objectHead == null) + return false; + objectHead.Size = response.ContentLength; + objectHead.ContentType = response.ContentType; + objectHead.ETag = response.Headers["ETag"]; + objectHead.LastModified = response.LastModified; + if (withData) + { + var stream = response.GetResponseStream(); + if (objectHead.TargetStream == null) + { + objectHead.Content = new byte[objectHead.Size]; + var read = (long) 0; + if (stream != null) + { + while (read < objectHead.Size) + { + var toread = (int) ((objectHead.Size - read)%4048); + read += stream.Read(objectHead.Content, 0, toread); + } + return true; + } + } + else + { + if (stream != null) + { + CopyStream(stream, objectHead.TargetStream); + return true; + } + } + } + return false; + } + + private static string StreamToString(Stream stream) + { + using (var reader = new StreamReader(stream)) + { + return reader.ReadToEnd(); + } + } + + public string Request(string authValue, DateTime date, Stream content, string contentType, Bucket.ObjectHead objectHead, bool withData) + { + try + { + var request = CreateRequest(authValue, date, content, contentType); + + using (var response = (HttpWebResponse)request.GetResponse()) + { + if (response.StatusCode == HttpStatusCode.OK && FetchDataFromResponse(response, objectHead, withData)) + return String.Empty; + return StreamToString(response.GetResponseStream()); + } + } + catch (WebException exception) + { + return StreamToString(exception.Response.GetResponseStream()); + } + } + } +} diff --git a/SharpGs/SharpGs.csproj b/SharpGs/SharpGs.csproj index 5731b2c..6026fa4 100644 --- a/SharpGs/SharpGs.csproj +++ b/SharpGs/SharpGs.csproj @@ -53,6 +53,14 @@ + + + + + + + + diff --git a/SharpGsDemo/Program.cs b/SharpGsDemo/Program.cs index d044b40..3b6dc49 100644 --- a/SharpGsDemo/Program.cs +++ b/SharpGsDemo/Program.cs @@ -1,78 +1,78 @@ -using System; -using System.IO; -using System.Text; -using SharpGs; - -namespace SharpGsDemo -{ - class Program - { - // Google keys pair - key & secret - private const string AuthKey = @"put yours here"; - private const string AuthSecret = @"put yours here"; - - static void Main() - { - var client = GoogleStorageFactory.Create(AuthKey, AuthSecret); - - //--- Proxy usage (implemented by community user Fabrizio) - //IWebProxy proxy = HttpWebRequest.DefaultWebProxy; - //proxy.Credentials = CredentialCache.DefaultCredentials; - //client.WebProxy = proxy; - - for (var i = 0; i < 2; i++) - { - // Create a bucket - var name = "temp-bucket-" + new Random().Next(); - client.CreateBucket(name); - } - - // Fetching all buckets of user - foreach (var bucket in client.Buckets) - { - Console.WriteLine("{0} - {1}", bucket.Name, bucket.CreationDate); - - // Simple buffer content - bucket.AddObject("someobj/on" + new Random().Next(), Encoding.UTF8.GetBytes("Simple text"), "text/plain"); - // Streamed content (stream will be closed at the end) - bucket.AddObject("someobj/stream-on" + new Random().Next(), GetStreamedString("Stream me!!!"), "text/plain", true); - - foreach (var o in bucket.Objects) - { - Console.WriteLine(" {0} - {1}", o.Key, o.Size); - if (o.Key.StartsWith("someobj/stream-on")) - { - // Get streamed content - using (var ms = new MemoryStream()) - { - o.Retrieve(ms); - ms.Seek(0, SeekOrigin.Begin); - using (var reader = new StreamReader(ms)) - { - Console.WriteLine(" {0}", reader.ReadToEnd()); - } - } - } - else - { - // Get simple content - Console.WriteLine(" {0}", Encoding.UTF8.GetString(o.Retrieve().Content)); - } - o.Delete(); - } - - // Delete bucket - if (bucket.Name.StartsWith("temp-bucket-")) - bucket.Delete(); - } - - Console.WriteLine("Finished"); - Console.ReadKey(); - } - - static Stream GetStreamedString(string data) - { - return new MemoryStream(Encoding.UTF8.GetBytes(data)); - } - } -} +using System; +using System.IO; +using System.Text; +using SharpGs; + +namespace SharpGsDemo +{ + class Program + { + // Google keys pair - key & secret + private const string AuthKey = @"put yours here"; + private const string AuthSecret = @"put yours here"; + + static void Main() + { + var client = GoogleStorageFactory.Create(AuthKey, AuthSecret); + + //--- Proxy usage (implemented by community user Fabrizio) + //IWebProxy proxy = HttpWebRequest.DefaultWebProxy; + //proxy.Credentials = CredentialCache.DefaultCredentials; + //client.WebProxy = proxy; + + for (var i = 0; i < 2; i++) + { + // Create a bucket + var name = "temp-bucket-" + new Random().Next(); + client.CreateBucket(name); + } + + // Fetching all buckets of user + foreach (var bucket in client.Buckets) + { + Console.WriteLine("{0} - {1}", bucket.Name, bucket.CreationDate); + + // Simple buffer content + bucket.AddObject("someobj/on" + new Random().Next(), Encoding.UTF8.GetBytes("Simple text"), "text/plain"); + // Streamed content (stream will be closed at the end) + bucket.AddObject("someobj/stream-on" + new Random().Next(), GetStreamedString("Stream me!!!"), "text/plain", true); + + foreach (var o in bucket.Objects) + { + Console.WriteLine(" {0} - {1}", o.Key, o.Size); + if (o.Key.StartsWith("someobj/stream-on")) + { + // Get streamed content + using (var ms = new MemoryStream()) + { + o.Retrieve(ms); + ms.Seek(0, SeekOrigin.Begin); + using (var reader = new StreamReader(ms)) + { + Console.WriteLine(" {0}", reader.ReadToEnd()); + } + } + } + else + { + // Get simple content + Console.WriteLine(" {0}", Encoding.UTF8.GetString(o.Retrieve().Content)); + } + o.Delete(); + } + + // Delete bucket + if (bucket.Name.StartsWith("temp-bucket-")) + bucket.Delete(); + } + + Console.WriteLine("Finished"); + Console.ReadKey(); + } + + static Stream GetStreamedString(string data) + { + return new MemoryStream(Encoding.UTF8.GetBytes(data)); + } + } +} From aea7c98dc3a0ac77bbd38812e48d4fbebdcf4536 Mon Sep 17 00:00:00 2001 From: manuelnelson Date: Wed, 30 May 2012 15:11:02 -0400 Subject: [PATCH 2/3] Moved Cors out of internal, updated bucket to support Cors --- SharpGs/Cors/{Internal => }/Cors.cs | 4 ++-- SharpGs/IBucket.cs | 13 +++++++++++++ SharpGs/Internal/Bucket.cs | 22 ++++++++++++++++++++++ SharpGs/RestApi/RequestMethod.cs | 4 +++- SharpGs/SharpGs.csproj | 2 +- 5 files changed, 41 insertions(+), 4 deletions(-) rename SharpGs/Cors/{Internal => }/Cors.cs (97%) diff --git a/SharpGs/Cors/Internal/Cors.cs b/SharpGs/Cors/Cors.cs similarity index 97% rename from SharpGs/Cors/Internal/Cors.cs rename to SharpGs/Cors/Cors.cs index 9726160..34b11f7 100644 --- a/SharpGs/Cors/Internal/Cors.cs +++ b/SharpGs/Cors/Cors.cs @@ -2,9 +2,9 @@ using System.Linq; using System.Text; using System.Xml.Linq; -using SharpGs.RestApi; +using SharpGs.Cors.Internal; -namespace SharpGs.Cors.Internal +namespace SharpGs.Cors { public class Cors : ICors { diff --git a/SharpGs/IBucket.cs b/SharpGs/IBucket.cs index 88406f4..f940d00 100644 --- a/SharpGs/IBucket.cs +++ b/SharpGs/IBucket.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using SharpGs.Cors; namespace SharpGs { @@ -51,5 +52,17 @@ public interface IBucket : IAclSetup /// Delete current bucket /// void Delete(); + + /// + /// Retrieve Cross origin resource sharing object for the bucket + /// + ICors Cors { get; } + + /// + /// Save a new resource sharing object for the bucket + /// + /// + void CorsSave(ICors cors); + } } diff --git a/SharpGs/Internal/Bucket.cs b/SharpGs/Internal/Bucket.cs index 7b55d2d..396440d 100644 --- a/SharpGs/Internal/Bucket.cs +++ b/SharpGs/Internal/Bucket.cs @@ -5,6 +5,7 @@ using System.Xml.Linq; using SharpGs.Acl; using SharpGs.Acl.Internal; +using SharpGs.Cors; using SharpGs.RestApi; namespace SharpGs.Internal @@ -82,6 +83,27 @@ public void Delete() _connector.Request(RequestMethod.DELETE, Name); } + /// + /// Retrieve Cross origin resource sharing object for the bucket + /// + public ICors Cors + { + get + { + var result = _connector.Request(RequestMethod.CORS_GET, Name); + return new Cors.Cors(result); + } + } + + /// + /// Save a new resource sharing object for the bucket + /// + /// + public void CorsSave(ICors cors) + { + _connector.Request(RequestMethod.CORS_SET, Name, null, Encoding.UTF8.GetBytes(cors.ToXmlString()), "application/xml"); + } + public IAccessControlList Acl { get diff --git a/SharpGs/RestApi/RequestMethod.cs b/SharpGs/RestApi/RequestMethod.cs index 2e14883..5680166 100644 --- a/SharpGs/RestApi/RequestMethod.cs +++ b/SharpGs/RestApi/RequestMethod.cs @@ -8,6 +8,8 @@ internal enum RequestMethod DELETE, HEAD, ACL_GET, - ACL_SET + ACL_SET, + CORS_GET, + CORS_SET } } diff --git a/SharpGs/SharpGs.csproj b/SharpGs/SharpGs.csproj index 6026fa4..b747526 100644 --- a/SharpGs/SharpGs.csproj +++ b/SharpGs/SharpGs.csproj @@ -55,7 +55,7 @@ - + From c1c8e011078625b2bbdf0a308bbce1500fa28bc2 Mon Sep 17 00:00:00 2001 From: manuelnelson Date: Sun, 3 Jun 2012 16:10:12 -0400 Subject: [PATCH 3/3] Added Cors Functionality to Google Storage --- SharpGs/Internal/SharpGsClient.cs | 4 ++++ SharpGs/RestApi/RestApiClient.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/SharpGs/Internal/SharpGsClient.cs b/SharpGs/Internal/SharpGsClient.cs index 6b5999c..94d5012 100644 --- a/SharpGs/Internal/SharpGsClient.cs +++ b/SharpGs/Internal/SharpGsClient.cs @@ -52,6 +52,8 @@ private static string GetAdditionalParameters(RequestMethod requestMethod, strin { if (requestMethod == RequestMethod.ACL_GET || requestMethod == RequestMethod.ACL_SET) return "?acl"; + if (requestMethod == RequestMethod.CORS_GET || requestMethod == RequestMethod.CORS_SET) + return "?cors"; if (parameters != null) return "?" + parameters; return String.Empty; @@ -83,6 +85,8 @@ private static string SyndicateCanonicalResource(RequestMethod requestMethod, st sb.Append(path); if (requestMethod == RequestMethod.ACL_GET || requestMethod == RequestMethod.ACL_SET) sb.Append("?acl"); + if (requestMethod == RequestMethod.CORS_GET || requestMethod == RequestMethod.CORS_SET) + sb.Append("?cors"); } return sb.ToString(); } diff --git a/SharpGs/RestApi/RestApiClient.cs b/SharpGs/RestApi/RestApiClient.cs index dbff6a6..4642d1e 100644 --- a/SharpGs/RestApi/RestApiClient.cs +++ b/SharpGs/RestApi/RestApiClient.cs @@ -27,6 +27,10 @@ internal static RequestMethod PureRequestMethod(RequestMethod method) return RequestMethod.GET; case RequestMethod.ACL_SET: return RequestMethod.PUT; + case RequestMethod.CORS_GET: + return RequestMethod.GET; + case RequestMethod.CORS_SET: + return RequestMethod.PUT; default: return method; }