/**
 * Copyright Microsoft Corporation
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License 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.
 */
/**
 * 
 */
package com.microsoft.azure.storage.core;

import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.StorageException;

RESERVED FOR INTERNAL USE. This is a Version 2 Canonicalization strategy conforming to the PDC 2009-09-19 specification
/** * RESERVED FOR INTERNAL USE. This is a Version 2 Canonicalization strategy conforming to the PDC 2009-09-19 * specification */
abstract class Canonicalizer {
The expected length for the canonicalized string when SharedKeyFull is used to sign requests.
/** * The expected length for the canonicalized string when SharedKeyFull is used to sign requests. */
private static final int ExpectedBlobQueueCanonicalizedStringLength = 300;
The expected length for the canonicalized string when SharedKeyFull is used to sign table requests.
/** * The expected length for the canonicalized string when SharedKeyFull is used to sign table requests. */
private static final int ExpectedTableCanonicalizedStringLength = 200; private static final Pattern CRLF = Pattern.compile("\r\n", Pattern.LITERAL);
Add x-ms- prefixed headers in a fixed order.
Params:
  • conn – the HttpURLConnection for the operation
  • canonicalizedString – the canonicalized string to add the canonicalized headerst to.
/** * Add x-ms- prefixed headers in a fixed order. * * @param conn * the HttpURLConnection for the operation * @param canonicalizedString * the canonicalized string to add the canonicalized headerst to. */
private static void addCanonicalizedHeaders(final HttpURLConnection conn, final StringBuilder canonicalizedString) { // Look for header names that start with // HeaderNames.PrefixForStorageHeader // Then sort them in case-insensitive manner. final Map<String, List<String>> headers = conn.getRequestProperties(); final ArrayList<String> httpStorageHeaderNameArray = new ArrayList<String>(); for (final String key : headers.keySet()) { if (key.toLowerCase(Utility.LOCALE_US).startsWith(Constants.PREFIX_FOR_STORAGE_HEADER)) { httpStorageHeaderNameArray.add(key.toLowerCase(Utility.LOCALE_US)); } } Collections.sort(httpStorageHeaderNameArray); // Now go through each header's values in the sorted order and append // them to the canonicalized string. for (final String key : httpStorageHeaderNameArray) { final StringBuilder canonicalizedElement = new StringBuilder(key); String delimiter = ":"; final ArrayList<String> values = getHeaderValues(headers, key); boolean appendCanonicalizedElement = false; // Go through values, unfold them, and then append them to the // canonicalized element string. for (final String value : values) { if (value != null) { appendCanonicalizedElement = true; } // Unfolding is simply removal of CRLF. final String unfoldedValue = CRLF.matcher(value) .replaceAll(Matcher.quoteReplacement(Constants.EMPTY_STRING)); // Append it to the canonicalized element string. canonicalizedElement.append(delimiter); canonicalizedElement.append(unfoldedValue); delimiter = ","; } // Now, add this canonicalized element to the canonicalized header // string. if (appendCanonicalizedElement) { appendCanonicalizedElement(canonicalizedString, canonicalizedElement.toString()); } } }
Append a string to a string builder with a newline constant
Params:
  • builder – the StringBuilder object
  • element – the string to append.
/** * Append a string to a string builder with a newline constant * * @param builder * the StringBuilder object * @param element * the string to append. */
protected static void appendCanonicalizedElement(final StringBuilder builder, final String element) { builder.append("\n"); builder.append(element); }
Constructs a canonicalized string from the request's headers that will be used to construct the signature string for signing a Blob or Queue service request under the Shared Key Full authentication scheme.
Params:
  • address – the request URI
  • accountName – the account name associated with the request
  • method – the verb to be used for the HTTP request.
  • contentType – the content type of the HTTP request.
  • contentLength – the length of the content written to the outputstream in bytes, -1 if unknown
  • date – the date/time specification for the HTTP request
  • conn – the HttpURLConnection for the operation.
Throws:
Returns:A canonicalized string.
/** * Constructs a canonicalized string from the request's headers that will be used to construct the signature string * for signing a Blob or Queue service request under the Shared Key Full authentication scheme. * * @param address * the request URI * @param accountName * the account name associated with the request * @param method * the verb to be used for the HTTP request. * @param contentType * the content type of the HTTP request. * @param contentLength * the length of the content written to the outputstream in bytes, -1 if unknown * @param date * the date/time specification for the HTTP request * @param conn * the HttpURLConnection for the operation. * @return A canonicalized string. * @throws StorageException */
protected static String canonicalizeHttpRequest(final java.net.URL address, final String accountName, final String method, final String contentType, final long contentLength, final String date, final HttpURLConnection conn) throws StorageException { // The first element should be the Method of the request. // I.e. GET, POST, PUT, or HEAD. final StringBuilder canonicalizedString = new StringBuilder(ExpectedBlobQueueCanonicalizedStringLength); canonicalizedString.append(conn.getRequestMethod()); // The next elements are // If any element is missing it may be empty. appendCanonicalizedElement(canonicalizedString, Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.CONTENT_ENCODING)); appendCanonicalizedElement(canonicalizedString, Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.CONTENT_LANGUAGE)); appendCanonicalizedElement(canonicalizedString, contentLength <= 0 ? Constants.EMPTY_STRING : String.valueOf(contentLength)); appendCanonicalizedElement(canonicalizedString, Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.CONTENT_MD5)); appendCanonicalizedElement(canonicalizedString, contentType != null ? contentType : Constants.EMPTY_STRING); final String dateString = Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.DATE); // If x-ms-date header exists, Date should be empty string appendCanonicalizedElement(canonicalizedString, dateString.equals(Constants.EMPTY_STRING) ? date : Constants.EMPTY_STRING); appendCanonicalizedElement(canonicalizedString, Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.IF_MODIFIED_SINCE)); appendCanonicalizedElement(canonicalizedString, Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.IF_MATCH)); appendCanonicalizedElement(canonicalizedString, Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.IF_NONE_MATCH)); appendCanonicalizedElement(canonicalizedString, Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.IF_UNMODIFIED_SINCE)); appendCanonicalizedElement(canonicalizedString, Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.RANGE)); addCanonicalizedHeaders(conn, canonicalizedString); appendCanonicalizedElement(canonicalizedString, getCanonicalizedResource(address, accountName)); return canonicalizedString.toString(); }
Constructs a canonicalized string that will be used to construct the signature string for signing a Table service request under the Shared Key authentication scheme.
Params:
  • address – the request URI
  • accountName – the account name associated with the request
  • method – the verb to be used for the HTTP request.
  • contentType – the content type of the HTTP request.
  • contentLength – the length of the content written to the outputstream in bytes, -1 if unknown
  • date – the date/time specification for the HTTP request
  • conn – the HttpURLConnection for the operation.
Throws:
Returns:A canonicalized string.
/** * Constructs a canonicalized string that will be used to construct the signature string * for signing a Table service request under the Shared Key authentication scheme. * * @param address * the request URI * @param accountName * the account name associated with the request * @param method * the verb to be used for the HTTP request. * @param contentType * the content type of the HTTP request. * @param contentLength * the length of the content written to the outputstream in bytes, -1 if unknown * @param date * the date/time specification for the HTTP request * @param conn * the HttpURLConnection for the operation. * @return A canonicalized string. * @throws StorageException */
protected static String canonicalizeTableHttpRequest(final java.net.URL address, final String accountName, final String method, final String contentType, final long contentLength, final String date, final HttpURLConnection conn) throws StorageException { // The first element should be the Method of the request. // I.e. GET, POST, PUT, or HEAD. final StringBuilder canonicalizedString = new StringBuilder(ExpectedTableCanonicalizedStringLength); canonicalizedString.append(conn.getRequestMethod()); // The second element should be the MD5 value. // This is optional and may be empty. final String httpContentMD5Value = Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.CONTENT_MD5); appendCanonicalizedElement(canonicalizedString, httpContentMD5Value); // The third element should be the content type. appendCanonicalizedElement(canonicalizedString, contentType); // The fourth element should be the request date. // See if there's an storage date header. // If there's one, then don't use the date header. final String dateString = Utility.getStandardHeaderValue(conn, Constants.HeaderConstants.DATE); // If x-ms-date header exists, Date should be that value. appendCanonicalizedElement(canonicalizedString, dateString.equals(Constants.EMPTY_STRING) ? date : dateString); appendCanonicalizedElement(canonicalizedString, getCanonicalizedResourceLite(address, accountName)); return canonicalizedString.toString(); }
Gets the canonicalized resource string for a Blob or Queue service request under the Shared Key Lite authentication scheme.
Params:
  • address – the resource URI.
  • accountName – the account name for the request.
Throws:
Returns:the canonicalized resource string.
/** * Gets the canonicalized resource string for a Blob or Queue service request under the Shared Key Lite * authentication scheme. * * @param address * the resource URI. * @param accountName * the account name for the request. * @return the canonicalized resource string. * @throws StorageException */
protected static String getCanonicalizedResource(final java.net.URL address, final String accountName) throws StorageException { // Resource path final StringBuilder resourcepath = new StringBuilder("/"); resourcepath.append(accountName); // Note that AbsolutePath starts with a '/'. resourcepath.append(address.getPath()); final StringBuilder canonicalizedResource = new StringBuilder(resourcepath.toString()); // query parameters if (address.getQuery() == null || !address.getQuery().contains("=")) { //no query params. return canonicalizedResource.toString(); } final Map<String, String[]> queryVariables = PathUtility.parseQueryString(address.getQuery()); final Map<String, String> lowercasedKeyNameValue = new HashMap<String, String>(); for (final Entry<String, String[]> entry : queryVariables.entrySet()) { // sort the value and organize it as comma separated values final List<String> sortedValues = Arrays.asList(entry.getValue()); Collections.sort(sortedValues); final StringBuilder stringValue = new StringBuilder(); for (final String value : sortedValues) { if (stringValue.length() > 0) { stringValue.append(","); } stringValue.append(value); } // key turns out to be null for ?a&b&c&d lowercasedKeyNameValue.put((entry.getKey()) == null ? null : entry.getKey().toLowerCase(Utility.LOCALE_US), stringValue.toString()); } final ArrayList<String> sortedKeys = new ArrayList<String>(lowercasedKeyNameValue.keySet()); Collections.sort(sortedKeys); for (final String key : sortedKeys) { final StringBuilder queryParamString = new StringBuilder(); queryParamString.append(key); queryParamString.append(":"); queryParamString.append(lowercasedKeyNameValue.get(key)); appendCanonicalizedElement(canonicalizedResource, queryParamString.toString()); } return canonicalizedResource.toString(); }
Gets the canonicalized resource string for a Blob or Queue service request under the Shared Key Lite authentication scheme.
Params:
  • address – the resource URI.
  • accountName – the account name for the request.
Throws:
Returns:the canonicalized resource string.
/** * Gets the canonicalized resource string for a Blob or Queue service request under the Shared Key Lite * authentication scheme. * * @param address * the resource URI. * @param accountName * the account name for the request. * @return the canonicalized resource string. * @throws StorageException */
protected static String getCanonicalizedResourceLite(final java.net.URL address, final String accountName) throws StorageException { // Resource path final StringBuilder resourcepath = new StringBuilder("/"); resourcepath.append(accountName); // Note that AbsolutePath starts with a '/'. resourcepath.append(address.getPath()); final StringBuilder canonicalizedResource = new StringBuilder(resourcepath.toString()); // query parameters final Map<String, String[]> queryVariables = PathUtility.parseQueryString(address.getQuery()); final String[] compVals = queryVariables.get("comp"); if (compVals != null) { final List<String> sortedValues = Arrays.asList(compVals); Collections.sort(sortedValues); canonicalizedResource.append("?comp="); final StringBuilder stringValue = new StringBuilder(); for (final String value : sortedValues) { if (stringValue.length() > 0) { stringValue.append(","); } stringValue.append(value); } canonicalizedResource.append(stringValue); } return canonicalizedResource.toString(); }
Gets all the values for the given header in the one to many map, performs a trimStart() on each return value
Params:
  • headers – a one to many map of key / values representing the header values for the connection.
  • headerName – the name of the header to lookup
Returns:an ArrayList of all trimmed values corresponding to the requested headerName. This may be empty if the header is not found.
/** * Gets all the values for the given header in the one to many map, performs a trimStart() on each return value * * @param headers * a one to many map of key / values representing the header values for the connection. * @param headerName * the name of the header to lookup * @return an ArrayList<String> of all trimmed values corresponding to the requested headerName. This may be empty * if the header is not found. */
private static ArrayList<String> getHeaderValues(final Map<String, List<String>> headers, final String headerName) { final ArrayList<String> arrayOfValues = new ArrayList<String>(); List<String> values = null; for (final Entry<String, List<String>> entry : headers.entrySet()) { if (entry.getKey().toLowerCase(Utility.LOCALE_US).equals(headerName)) { values = entry.getValue(); break; } } if (values != null) { for (final String value : values) { // canonicalization formula requires the string to be left // trimmed. arrayOfValues.add(Utility.trimStart(value)); } } return arrayOfValues; }
Constructs a canonicalized string for signing a request.
Params:
  • conn – the HttpURLConnection to canonicalize
  • accountName – the account name associated with the request
  • contentLength – the length of the content written to the outputstream in bytes, -1 if unknown
Returns:a canonicalized string.
/** * Constructs a canonicalized string for signing a request. * * @param conn * the HttpURLConnection to canonicalize * @param accountName * the account name associated with the request * @param contentLength * the length of the content written to the outputstream in bytes, -1 if unknown * @return a canonicalized string. */
protected abstract String canonicalize(HttpURLConnection conn, String accountName, Long contentLength) throws StorageException; }