/**
* 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;
}