503 lines
No EOL
22 KiB
C#
503 lines
No EOL
22 KiB
C#
// ----------------------------------------------------------------------------------
|
|
// Microsoft Developer & Platform Evangelism
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
|
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
|
|
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
|
|
// ----------------------------------------------------------------------------------
|
|
// The example companies, organizations, products, domain names,
|
|
// e-mail addresses, logos, people, places, and events depicted
|
|
// herein are fictitious. No association with any real company,
|
|
// organization, product, domain name, email address, logo, person,
|
|
// places, or events is intended or should be inferred.
|
|
// ----------------------------------------------------------------------------------
|
|
|
|
//
|
|
// <copyright file="RestHelpers.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Collections.Specialized;
|
|
using System.Web;
|
|
using System.Xml;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Microsoft.Samples.ServiceHosting.StorageClient
|
|
{
|
|
namespace StorageHttpConstants
|
|
{
|
|
|
|
internal static class ConstChars
|
|
{
|
|
internal const string Linefeed = "\n";
|
|
internal const string CarriageReturnLinefeed = "\r\n";
|
|
internal const string Colon = ":";
|
|
internal const string Comma = ",";
|
|
internal const string Slash = "/";
|
|
internal const string BackwardSlash = @"\";
|
|
internal const string Space = " ";
|
|
internal const string Ampersand = "&";
|
|
internal const string QuestionMark = "?";
|
|
internal const string Equal = "=";
|
|
internal const string Bang = "!";
|
|
internal const string Star = "*";
|
|
internal const string Dot = ".";
|
|
}
|
|
|
|
internal static class RequestParams
|
|
{
|
|
internal const string NumOfMessages = "numofmessages";
|
|
internal const string VisibilityTimeout = "visibilitytimeout";
|
|
internal const string PeekOnly = "peekonly";
|
|
internal const string MessageTtl = "messagettl";
|
|
internal const string Messages = "messages";
|
|
internal const string PopReceipt = "popreceipt";
|
|
}
|
|
|
|
internal static class QueryParams
|
|
{
|
|
internal const string SeparatorForParameterAndValue = "=";
|
|
internal const string QueryParamTimeout = "timeout";
|
|
internal const string QueryParamComp = "comp";
|
|
|
|
// Other query string parameter names
|
|
internal const string QueryParamBlockId = "blockid";
|
|
internal const string QueryParamPrefix = "prefix";
|
|
internal const string QueryParamMarker = "marker";
|
|
internal const string QueryParamMaxResults = "maxresults";
|
|
internal const string QueryParamDelimiter = "delimiter";
|
|
internal const string QueryParamModifiedSince = "modifiedsince";
|
|
}
|
|
|
|
internal static class CompConstants
|
|
{
|
|
internal const string Metadata = "metadata";
|
|
internal const string List = "list";
|
|
internal const string BlobList = "bloblist";
|
|
internal const string BlockList = "blocklist";
|
|
internal const string Block = "block";
|
|
internal const string Acl = "acl";
|
|
}
|
|
|
|
internal static class XmlElementNames
|
|
{
|
|
internal const string BlockList = "BlockList";
|
|
internal const string Block = "Block";
|
|
internal const string EnumerationResults = "EnumerationResults";
|
|
internal const string Prefix = "Prefix";
|
|
internal const string Marker = "Marker";
|
|
internal const string MaxResults = "MaxResults";
|
|
internal const string Delimiter = "Delimiter";
|
|
internal const string NextMarker = "NextMarker";
|
|
internal const string Containers = "Containers";
|
|
internal const string Container = "Container";
|
|
internal const string ContainerName = "Name";
|
|
internal const string ContainerNameAttribute = "ContainerName";
|
|
internal const string AccountNameAttribute = "AccountName";
|
|
internal const string LastModified = "LastModified";
|
|
internal const string Etag = "Etag";
|
|
internal const string Url = "Url";
|
|
internal const string CommonPrefixes = "CommonPrefixes";
|
|
internal const string ContentType = "ContentType";
|
|
internal const string ContentEncoding = "ContentEncoding";
|
|
internal const string ContentLanguage = "ContentLanguage";
|
|
internal const string Size = "Size";
|
|
internal const string Blobs = "Blobs";
|
|
internal const string Blob = "Blob";
|
|
internal const string BlobName = "Name";
|
|
internal const string BlobPrefix = "BlobPrefix";
|
|
internal const string BlobPrefixName = "Name";
|
|
internal const string Name = "Name";
|
|
internal const string Queues = "Queues";
|
|
internal const string Queue = "Queue";
|
|
internal const string QueueName = "QueueName";
|
|
internal const string QueueMessagesList = "QueueMessagesList";
|
|
internal const string QueueMessage = "QueueMessage";
|
|
internal const string MessageId = "MessageId";
|
|
internal const string PopReceipt = "PopReceipt";
|
|
internal const string InsertionTime = "InsertionTime";
|
|
internal const string ExpirationTime = "ExpirationTime";
|
|
internal const string TimeNextVisible = "TimeNextVisible";
|
|
internal const string MessageText = "MessageText";
|
|
|
|
// Error specific constants
|
|
internal const string ErrorRootElement = "Error";
|
|
internal const string ErrorCode = "Code";
|
|
internal const string ErrorMessage = "Message";
|
|
internal const string ErrorException = "ExceptionDetails";
|
|
internal const string ErrorExceptionMessage = "ExceptionMessage";
|
|
internal const string ErrorExceptionStackTrace = "StackTrace";
|
|
internal const string AuthenticationErrorDetail = "AuthenticationErrorDetail";
|
|
|
|
//The following are for table error messages
|
|
internal const string DataWebMetadataNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";
|
|
internal const string TableErrorCodeElement = "code";
|
|
internal const string TableErrorMessageElement = "message";
|
|
}
|
|
|
|
internal static class HeaderNames
|
|
{
|
|
internal const string PrefixForStorageProperties = "x-ms-prop-";
|
|
internal const string PrefixForMetadata = "x-ms-meta-";
|
|
internal const string PrefixForStorageHeader = "x-ms-";
|
|
internal const string PrefixForTableContinuation = "x-ms-continuation-";
|
|
|
|
//
|
|
// Standard headers...
|
|
//
|
|
internal const string ContentLanguage = "Content-Language";
|
|
internal const string ContentLength = "Content-Length";
|
|
internal const string ContentType = "Content-Type";
|
|
internal const string ContentEncoding = "Content-Encoding";
|
|
internal const string ContentMD5 = "Content-MD5";
|
|
internal const string ContentRange = "Content-Range";
|
|
internal const string LastModifiedTime = "Last-Modified";
|
|
internal const string Server = "Server";
|
|
internal const string Allow = "Allow";
|
|
internal const string ETag = "ETag";
|
|
internal const string Range = "Range";
|
|
internal const string Date = "Date";
|
|
internal const string Authorization = "Authorization";
|
|
internal const string IfModifiedSince = "If-Modified-Since";
|
|
internal const string IfUnmodifiedSince = "If-Unmodified-Since";
|
|
internal const string IfMatch = "If-Match";
|
|
internal const string IfNoneMatch = "If-None-Match";
|
|
internal const string IfRange = "If-Range";
|
|
internal const string NextPartitionKey = "NextPartitionKey";
|
|
internal const string NextRowKey = "NextRowKey";
|
|
internal const string NextTableName = "NextTableName";
|
|
|
|
//
|
|
// Storage specific custom headers...
|
|
//
|
|
internal const string StorageDateTime = PrefixForStorageHeader + "date";
|
|
internal const string PublicAccess = PrefixForStorageProperties + "publicaccess";
|
|
internal const string StorageRange = PrefixForStorageHeader + "range";
|
|
|
|
internal const string CreationTime = PrefixForStorageProperties + "creation-time";
|
|
internal const string ForceUpdate = PrefixForStorageHeader + "force-update";
|
|
internal const string ApproximateMessagesCount = PrefixForStorageHeader + "approximate-messages-count";
|
|
internal const string Version = PrefixForStorageHeader + "version";
|
|
}
|
|
|
|
internal static class HeaderValues
|
|
{
|
|
internal const string ContentTypeXml = "application/xml";
|
|
|
|
/// <summary>
|
|
/// This is the default content-type xStore uses when no content type is specified
|
|
/// </summary>
|
|
internal const string DefaultContentType = "application/octet-stream";
|
|
|
|
// The Range header value is "bytes=start-end", both start and end can be empty
|
|
internal const string RangeHeaderFormat = "bytes={0}-{1}";
|
|
|
|
}
|
|
|
|
internal static class AuthenticationSchemeNames
|
|
{
|
|
internal const string SharedKeyAuthSchemeName = "SharedKey";
|
|
internal const string SharedKeyLiteAuthSchemeName = "SharedKeyLite";
|
|
}
|
|
|
|
internal static class HttpMethod
|
|
{
|
|
internal const string Get = "GET";
|
|
internal const string Put = "PUT";
|
|
internal const string Post = "POST";
|
|
internal const string Head = "HEAD";
|
|
internal const string Delete = "DELETE";
|
|
internal const string Trace = "TRACE";
|
|
internal const string Options = "OPTIONS";
|
|
internal const string Connect = "CONNECT";
|
|
}
|
|
|
|
internal static class BlobBlockConstants
|
|
{
|
|
internal const int KB = 1024;
|
|
internal const int MB = 1024 * KB;
|
|
/// <summary>
|
|
/// When transmitting a blob that is larger than this constant, this library automatically
|
|
/// transmits the blob as individual blocks. I.e., the blob is (1) partitioned
|
|
/// into separate parts (these parts are called blocks) and then (2) each of the blocks is
|
|
/// transmitted separately.
|
|
/// The maximum size of this constant as supported by the real blob storage service is currently
|
|
/// 64 MB; the development storage tool currently restricts this value to 2 MB.
|
|
/// Setting this constant can have a significant impact on the performance for uploading or
|
|
/// downloading blobs.
|
|
/// As a general guideline: If you run in a reliable environment increase this constant to reduce
|
|
/// the amount of roundtrips. In an unreliable environment keep this constant low to reduce the
|
|
/// amount of data that needs to be retransmitted in case of connection failures.
|
|
/// </summary>
|
|
internal const long MaximumBlobSizeBeforeTransmittingAsBlocks = 2 * MB;
|
|
/// <summary>
|
|
/// The size of a single block when transmitting a blob that is larger than the
|
|
/// MaximumBlobSizeBeforeTransmittingAsBlocks constant (see above).
|
|
/// The maximum size of this constant is currently 4 MB; the development storage
|
|
/// tool currently restricts this value to 1 MB.
|
|
/// Setting this constant can have a significant impact on the performance for uploading or
|
|
/// downloading blobs.
|
|
/// As a general guideline: If you run in a reliable environment increase this constant to reduce
|
|
/// the amount of roundtrips. In an unreliable environment keep this constant low to reduce the
|
|
/// amount of data that needs to be retransmitted in case of connection failures.
|
|
/// </summary>
|
|
internal const long BlockSize = 1 * MB;
|
|
}
|
|
|
|
internal static class ListingConstants
|
|
{
|
|
internal const int MaxContainerListResults = 100;
|
|
internal const int MaxBlobListResults = 100;
|
|
internal const int MaxQueueListResults = 50;
|
|
internal const int MaxTableListResults = 50;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains regular expressions for checking whether container and table names conform
|
|
/// to the rules of the storage REST protocols.
|
|
/// </summary>
|
|
public static class RegularExpressionStrings
|
|
{
|
|
/// <summary>
|
|
/// Container or queue names that match against this regular expression are valid.
|
|
/// </summary>
|
|
public const string ValidContainerNameRegex = @"^([a-z]|\d){1}([a-z]|-|\d){1,61}([a-z]|\d){1}$";
|
|
|
|
/// <summary>
|
|
/// Table names that match against this regular expression are valid.
|
|
/// </summary>
|
|
public const string ValidTableNameRegex = @"^([a-z]|[A-Z]){1}([a-z]|[A-Z]|\d){2,62}$";
|
|
}
|
|
|
|
internal static class StandardPortalEndpoints
|
|
{
|
|
internal const string BlobStorage = "blob";
|
|
internal const string QueueStorage = "queue";
|
|
internal const string TableStorage = "table";
|
|
internal const string StorageHostSuffix = ".core.windows.net";
|
|
internal const string BlobStorageEndpoint = BlobStorage + StorageHostSuffix;
|
|
internal const string QueueStorageEndpoint = QueueStorage + StorageHostSuffix;
|
|
internal const string TableStorageEndpoint = TableStorage + StorageHostSuffix;
|
|
}
|
|
}
|
|
|
|
internal static partial class Utilities
|
|
{
|
|
internal static HttpWebRequest CreateHttpRequest(Uri uri, string httpMethod, TimeSpan timeout)
|
|
{
|
|
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
|
|
request.Timeout = (int)timeout.TotalMilliseconds;
|
|
request.ReadWriteTimeout = (int)timeout.TotalMilliseconds;
|
|
request.Method = httpMethod;
|
|
request.ContentLength = 0;
|
|
request.Headers.Add(StorageHttpConstants.HeaderNames.StorageDateTime,
|
|
Utilities.ConvertDateTimeToHttpString(DateTime.UtcNow));
|
|
return request;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the date time to a valid string form as per HTTP standards
|
|
/// </summary>
|
|
internal static string ConvertDateTimeToHttpString(DateTime dateTime)
|
|
{
|
|
// On the wire everything should be represented in UTC. This assert will catch invalid callers who
|
|
// are violating this rule.
|
|
Debug.Assert(dateTime == DateTime.MaxValue || dateTime == DateTime.MinValue || dateTime.Kind == DateTimeKind.Utc);
|
|
|
|
// 'R' means rfc1123 date which is what our server uses for all dates...
|
|
// It will be in the following format:
|
|
// Sun, 28 Jan 2008 12:11:37 GMT
|
|
return dateTime.ToString("R", CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a string having the date time information in acceptable formats according to HTTP standards
|
|
/// </summary>
|
|
internal static bool TryGetDateTimeFromHttpString(string dateString, out DateTime? result)
|
|
{
|
|
DateTime dateTime;
|
|
result = null;
|
|
|
|
// 'R' means rfc1123 date which is the preferred format used in HTTP
|
|
bool parsed = DateTime.TryParseExact(dateString, "R", null, DateTimeStyles.None, out dateTime);
|
|
if (parsed)
|
|
{
|
|
// For some reason, format string "R" makes the DateTime.Kind as Unspecified while it's actually
|
|
// Utc. Specifying DateTimeStyles.AssumeUniversal also doesn't make the difference. If we also
|
|
// specify AdjustToUniversal it works as expected but we don't really want Parse to adjust
|
|
// things automatically.
|
|
result = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Copies from one stream to another
|
|
/// </summary>
|
|
/// <param name="sourceStream">The stream to copy from</param>
|
|
/// <param name="destinationStream">The stream to copy to</param>
|
|
internal static long CopyStream(Stream sourceStream, Stream destinationStream)
|
|
{
|
|
const int BufferSize = 0x10000;
|
|
byte[] buffer = new byte[BufferSize];
|
|
int n = 0;
|
|
long totalRead = 0;
|
|
do
|
|
{
|
|
n = sourceStream.Read(buffer, 0, BufferSize);
|
|
if (n > 0)
|
|
{
|
|
totalRead += n;
|
|
destinationStream.Write(buffer, 0, n);
|
|
}
|
|
|
|
} while (n > 0);
|
|
return totalRead;
|
|
}
|
|
|
|
internal static void CopyStream(Stream sourceStream, Stream destinationStream, long length)
|
|
{
|
|
const int BufferSize = 0x10000;
|
|
byte[] buffer = new byte[BufferSize];
|
|
int n = 0;
|
|
long amountLeft = length;
|
|
|
|
do
|
|
{
|
|
amountLeft -= n;
|
|
n = sourceStream.Read(buffer, 0, (int)Math.Min(BufferSize, amountLeft));
|
|
if (n > 0)
|
|
{
|
|
destinationStream.Write(buffer, 0, n);
|
|
}
|
|
|
|
} while (n > 0);
|
|
}
|
|
|
|
internal static int CopyStreamToBuffer(Stream sourceStream, byte[] buffer, int bytesToRead)
|
|
{
|
|
int n = 0;
|
|
int amountLeft = bytesToRead;
|
|
do
|
|
{
|
|
n = sourceStream.Read(buffer, bytesToRead - amountLeft, amountLeft);
|
|
amountLeft -= n;
|
|
} while (n > 0);
|
|
return bytesToRead - amountLeft;
|
|
}
|
|
|
|
internal static Uri CreateRequestUri(
|
|
Uri baseUri,
|
|
bool usePathStyleUris,
|
|
string accountName,
|
|
string containerName,
|
|
string blobName,
|
|
TimeSpan Timeout,
|
|
NameValueCollection queryParameters,
|
|
out ResourceUriComponents uriComponents
|
|
)
|
|
{
|
|
uriComponents =
|
|
new ResourceUriComponents(accountName, containerName, blobName);
|
|
Uri uri = HttpRequestAccessor.ConstructResourceUri(baseUri, uriComponents, usePathStyleUris);
|
|
|
|
if (queryParameters != null)
|
|
{
|
|
UriBuilder builder = new UriBuilder(uri);
|
|
|
|
if (queryParameters.Get(StorageHttpConstants.QueryParams.QueryParamTimeout) == null)
|
|
{
|
|
queryParameters.Add(StorageHttpConstants.QueryParams.QueryParamTimeout,
|
|
Timeout.TotalSeconds.ToString(CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
bool firstParam = true;
|
|
foreach (string queryKey in queryParameters.AllKeys)
|
|
{
|
|
if (!firstParam)
|
|
sb.Append("&");
|
|
sb.Append(HttpUtility.UrlEncode(queryKey));
|
|
sb.Append('=');
|
|
sb.Append(HttpUtility.UrlEncode(queryParameters[queryKey]));
|
|
firstParam = false;
|
|
}
|
|
|
|
if (sb.Length > 0)
|
|
{
|
|
builder.Query = sb.ToString();
|
|
}
|
|
return builder.Uri;
|
|
}
|
|
else
|
|
{
|
|
return uri;
|
|
}
|
|
}
|
|
|
|
internal static bool StringIsIPAddress(string address)
|
|
{
|
|
IPAddress outIPAddress;
|
|
|
|
return IPAddress.TryParse(address, out outIPAddress);
|
|
}
|
|
|
|
internal static void AddMetadataHeaders(HttpWebRequest request, NameValueCollection metadata)
|
|
{
|
|
foreach (string key in metadata.Keys)
|
|
{
|
|
request.Headers.Add(
|
|
StorageHttpConstants.HeaderNames.PrefixForMetadata + key,
|
|
metadata[key]
|
|
);
|
|
}
|
|
}
|
|
|
|
internal static bool IsValidTableName(string name)
|
|
{
|
|
if (string.IsNullOrEmpty(name))
|
|
{
|
|
return false;
|
|
}
|
|
Regex reg = new Regex(StorageHttpConstants.RegularExpressionStrings.ValidTableNameRegex);
|
|
if (reg.IsMatch(name))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal static bool IsValidContainerOrQueueName(string name)
|
|
{
|
|
if (string.IsNullOrEmpty(name))
|
|
{
|
|
return false;
|
|
}
|
|
Regex reg = new Regex(StorageHttpConstants.RegularExpressionStrings.ValidContainerNameRegex);
|
|
if (reg.IsMatch(name))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} |