All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.azure.storage.core.Canonicalizer Maven / Gradle / Ivy

/**
 * 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
 */
abstract class Canonicalizer {

    /**
     * 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.
     */
    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.
     * 
     * @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> headers = conn.getRequestProperties();
        final ArrayList httpStorageHeaderNameArray = new ArrayList();

        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 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
     * 
     * @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.
     * 
     * @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.
     * 
     * @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.
     * 
     * @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 queryVariables = PathUtility.parseQueryString(address.getQuery());

        final Map lowercasedKeyNameValue = new HashMap();

        for (final Entry entry : queryVariables.entrySet()) {
            // sort the value and organize it as comma separated values
            final List 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 sortedKeys = new ArrayList(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.
     * 
     * @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 queryVariables = PathUtility.parseQueryString(address.getQuery());

        final String[] compVals = queryVariables.get("comp");

        if (compVals != null) {

            final List 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
     * 
     * @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 of all trimmed values corresponding to the requested headerName. This may be empty
     *         if the header is not found.
     */
    private static ArrayList getHeaderValues(final Map> headers, final String headerName) {

        final ArrayList arrayOfValues = new ArrayList();
        List values = null;

        for (final Entry> 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.
     * 
     * @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;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy