public static string MakeCanonicalString(string verb, string bucketName, string key, SortedList queryParams, SortedList headers, string expires)
        {
            StringBuilder buf = new StringBuilder();
            buf.Append(verb);
            buf.Append("\n");

            ThreeSharpStringComparer comparer = new ThreeSharpStringComparer(CompareInfo.GetCompareInfo(""), CompareOptions.StringSort);
            SortedList interestingHeaders = new SortedList(comparer);

            if (headers != null)
            {
                foreach (string header in headers.Keys)
                {
                    string lk = header.ToLower();
                    if (lk.Equals("content-type") ||
                         lk.Equals("content-md5") ||
                         lk.Equals("date") ||
                         lk.StartsWith(AMAZON_HEADER_PREFIX))
                    {
                        interestingHeaders.Add(lk, headers[header]);
                    }
                }
            }
            if (interestingHeaders[ALTERNATIVE_DATE_HEADER] != null)
            {
                interestingHeaders.Add("date", "");
            }

            // if the expires is non-null, use that for the date field.  this
            // trumps the x-amz-date behavior.
            if (expires != null)
            {
                interestingHeaders.Add("date", expires);
            }

            // these headers require that we still put a new line after them,
            // even if they don't exist.
            {
                string[] newlineHeaders = { "content-type", "content-md5" };
                foreach (string header in newlineHeaders)
                {
                    if (interestingHeaders.IndexOfKey(header) == -1)
                    {
                        interestingHeaders.Add(header, "");
                    }
                }
            }

            // Finally, add all the interesting headers (i.e.: all that startwith x-amz- ;-))
            foreach (string header in interestingHeaders.Keys)
            {
                if (header.StartsWith(AMAZON_HEADER_PREFIX))
                {
                    buf.Append(header).Append(":").Append((interestingHeaders[header] as string).Trim());
                }
                else
                {
                    buf.Append(interestingHeaders[header]);
                }
                buf.Append("\n");
            }

            // Build the path using the bucket and key
            buf.Append("/");
            if (bucketName != null && !bucketName.Equals(""))
            {
                buf.Append(bucketName);
                buf.Append("/");
            }

            // Append the key (it may be an empty string)
            if (key != null && key.Length != 0)
            {
                buf.Append(key);
            }

            // if there is an acl, logging, or torrent parameter, add them to the string.
            if (queryParams != null)
            {
                if (queryParams.IndexOfKey("acl") != -1)
                {
                    buf.Append("?acl");
                }
                else if (queryParams.IndexOfKey("torrent") != -1)
                {
                    buf.Append("?torrent");
                }
                else if (queryParams.IndexOfKey("logging") != -1)
                {
                    buf.Append("?logging");
                }
            }

            return buf.ToString();
        }
        public static string MakeCanonicalString(string verb, string bucketName, string key, SortedList queryParams, SortedList headers, string expires)
        {
            StringBuilder buf = new StringBuilder();

            buf.Append(verb);
            buf.Append("\n");

            ThreeSharpStringComparer comparer = new ThreeSharpStringComparer(CompareInfo.GetCompareInfo(""), CompareOptions.StringSort);
            SortedList interestingHeaders     = new SortedList(comparer);

            if (headers != null)
            {
                foreach (string header in headers.Keys)
                {
                    string lk = header.ToLower();
                    if (lk.Equals("content-type") ||
                        lk.Equals("content-md5") ||
                        lk.Equals("date") ||
                        lk.StartsWith(AMAZON_HEADER_PREFIX))
                    {
                        interestingHeaders.Add(lk, headers[header]);
                    }
                }
            }
            if (interestingHeaders[ALTERNATIVE_DATE_HEADER] != null)
            {
                interestingHeaders.Add("date", "");
            }

            // if the expires is non-null, use that for the date field.  this
            // trumps the x-amz-date behavior.
            if (expires != null)
            {
                interestingHeaders.Add("date", expires);
            }

            // these headers require that we still put a new line after them,
            // even if they don't exist.
            {
                string[] newlineHeaders = { "content-type", "content-md5" };
                foreach (string header in newlineHeaders)
                {
                    if (interestingHeaders.IndexOfKey(header) == -1)
                    {
                        interestingHeaders.Add(header, "");
                    }
                }
            }

            // Finally, add all the interesting headers (i.e.: all that startwith x-amz- ;-))
            foreach (string header in interestingHeaders.Keys)
            {
                if (header.StartsWith(AMAZON_HEADER_PREFIX))
                {
                    buf.Append(header).Append(":").Append((interestingHeaders[header] as string).Trim());
                }
                else
                {
                    buf.Append(interestingHeaders[header]);
                }
                buf.Append("\n");
            }

            // Build the path using the bucket and key
            buf.Append("/");
            if (bucketName != null && !bucketName.Equals(""))
            {
                buf.Append(bucketName);
                buf.Append("/");
            }

            // Append the key (it may be an empty string)
            if (key != null && key.Length != 0)
            {
                buf.Append(key);
            }

            // if there is an acl, logging, or torrent parameter, add them to the string.
            if (queryParams != null)
            {
                if (queryParams.IndexOfKey("acl") != -1)
                {
                    buf.Append("?acl");
                }
                else if (queryParams.IndexOfKey("torrent") != -1)
                {
                    buf.Append("?torrent");
                }
                else if (queryParams.IndexOfKey("logging") != -1)
                {
                    buf.Append("?logging");
                }
            }

            return(buf.ToString());
        }