using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.Globalization; namespace Windows.Azure.Storage { public class StorageRequest { private ConnectionDetails connectionDetails; private HttpWebRequest request; private byte[] content = null; private Dictionary functionHeaders; public StorageRequest(ConnectionDetails connectionDetails) { this.connectionDetails = connectionDetails; } public void InitializeRequest(string path, Dictionary queryParams) { this.request = (HttpWebRequest)WebRequest.Create(GetUri(path, queryParams)); content = null; functionHeaders = null; } public void SetContent(byte[] content) { this.content = content; } private void SetDateHeader() { this.request.Headers.Add("x-ms-date", DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture)); } public void SetFunctionHeaders(Dictionary headers) { functionHeaders = headers; } public void SetMethod(string method) { this.request.Method = method; } private void SetContentLength() { if (this.content == null) { this.request.ContentLength = 0; } else { this.request.ContentLength = this.content.Length; } } private void SetBodyContent(byte[] blobContents) { this.request.GetRequestStream().Write(blobContents, 0, blobContents.Length); } private string GetUri(string path, Dictionary queryParams) { StringBuilder uri = new StringBuilder(); uri.Append(this.connectionDetails.GetConnectionURI()); if (path != null) { uri.AppendFormat("{0}", path); } if (queryParams != null && queryParams.Count > 0) { uri.Append("?"); for (int i = 0; i < queryParams.Count; i++) { uri.AppendFormat("{0}={1}", queryParams.ElementAt(i).Key, queryParams.ElementAt(i).Value); if (i < queryParams.Count - 1) { uri.Append("&"); } } } return uri.ToString(); } public StorageResponse DispatchRequest() { try { SetContentLength(); SetDateHeader(); SetFunctionHeaders(); SetAuthorizationHeader(); if (this.content != null) { SetBodyContent(this.content); } using (HttpWebResponse response = (HttpWebResponse)this.request.GetResponse()) { return new StorageResponse(response); } } catch (WebException ex) { using (HttpWebResponse errorResponse = (HttpWebResponse)ex.Response) { return new StorageResponse(errorResponse); } } } private void SetFunctionHeaders() { if (this.functionHeaders != null) { foreach (KeyValuePair header in this.functionHeaders) { this.request.Headers.Add(header.Key, header.Value); } } } private void SetAuthorizationHeader() { // Now sign the request // For a blob, you need to use this Canonical form: // VERB + "\n" + // Content - MD5 + "\n" + // Content - Type + "\n" + // Date + "\n" + // CanonicalizedHeaders + // CanonicalizedResource; StringBuilder signature = new StringBuilder(); // Verb signature.Append(String.Format("{0}{1}", request.Method, "\n")); // Content-MD5 Header signature.Append("\n"); // Content-Type Header signature.Append("\n"); // Then Date, if we have already added the x-ms-date header, leave this null signature.Append("\n"); // Now for CanonicalizedHeaders // TODO: Replace with LINQ statement foreach (string header in request.Headers) { if (header.StartsWith("x-ms")) { signature.Append(String.Format("{0}:{1}\n", header, request.Headers[header])); } } // Now for CanonicalizedResource // Format is /{0}/{1} where 0 is name of the account and 1 is resources URI path // Also make sure any comp query params are included signature.Append(String.Format("/{0}{1}", connectionDetails.Account, GetCanonicalizedResourceURI())); // Next, we need to encode our signature using the HMAC-SHA256 algorithm byte[] signatureByteForm = System.Text.Encoding.UTF8.GetBytes(signature.ToString()); System.Security.Cryptography.HMACSHA256 hasher = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(connectionDetails.Key)); // Now build the Authorization header String authHeader = String.Format(CultureInfo.InvariantCulture, "{0} {1}:{2}", "SharedKey", connectionDetails.Account, System.Convert.ToBase64String(hasher.ComputeHash(signatureByteForm) )); // And add the Authorization header to the request request.Headers.Add("Authorization", authHeader); } private string GetCanonicalizedResourceURI() { StringBuilder sb = new StringBuilder(); sb.Append(request.RequestUri.AbsolutePath); // If there are any comp query params, they needed to be added, but not anything else if (request.RequestUri.Query.Contains("comp")) { sb.Append(request.RequestUri.Query); } return sb.ToString(); } } }