// // typica - A client library for Amazon Web Services // Copyright (C) 2007 Xerox 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.xerox.amazonws.common; import java.io.InputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.SortedMap; import java.util.TimeZone; import java.util.TreeMap; /** * This class provides an interface with the Amazon SQS service. It provides high level * methods for listing and creating message queues. * * Http authentication code borrowed from Amazon S3 AWSAuthConnection code * (see amazon copyright below). * * @author D. Kavanagh * @author developer@dotech.com */ public class AWSAuthConnection extends AWSConnection { /** * Initializes the queue service with your AWS login information. * * @param awsAccessId The your user key into AWS * @param awsSecretKey The secret string used to generate signatures for authentication. * @param isSecure True if the data should be encrypted on the wire on the way to or from SQS. * @param server Which host to connect to. Usually, this will be s3.amazonaws.com * @param port Which port to use. */ public AWSAuthConnection(String awsAccessId, String awsSecretKey, boolean isSecure, String server, int port) { super(awsAccessId, awsSecretKey, isSecure, server, port); } /** * Make a new HttpURLConnection. * @param method The HTTP method to use (GET, PUT, DELETE) * @param resource The resource name (bucketName + "/" + key). * @param headers A Map of String to List of Strings representing the http * headers to pass (can be null). */ protected HttpURLConnection makeRequest(String method, String resource, Map headers) throws MalformedURLException, IOException { URL url = makeURL(resource); HttpURLConnection connection = (HttpURLConnection)url.openConnection(); connection.setRequestMethod(method); addHeaders(connection, headers); addAuthHeader(connection, method, resource); return connection; } /** * Add the given headers to the HttpURLConnection. * @param connection The HttpURLConnection to which the headers will be added. * @param headers A Map of String to List of Strings representing the http * headers to pass (can be null). */ private void addHeaders(HttpURLConnection connection, Map headers) { addHeaders(connection, headers, ""); } /** * Add the given metadata fields to the HttpURLConnection. * @param connection The HttpURLConnection to which the headers will be added. * @param metadata A Map of String to List of Strings representing the s3 * metadata for this resource. */ private void addMetadataHeaders(HttpURLConnection connection, Map metadata) { addHeaders(connection, metadata, "x-amz-meta-"); } /** * Add the given headers to the HttpURLConnection with a prefix before the keys. * @param connection The HttpURLConnection to which the headers will be added. * @param headers A Map of String to List of Strings representing the http * headers to pass (can be null). * @param prefix The string to prepend to each key before adding it to the connection. */ private void addHeaders(HttpURLConnection connection, Map headers, String prefix) { if (headers != null) { for (Iterator i = headers.keySet().iterator(); i.hasNext(); ) { String key = (String)i.next(); for (Iterator j = ((List)headers.get(key)).iterator(); j.hasNext(); ) { String value = (String)j.next(); connection.addRequestProperty(prefix + key, value); } } } } /** * Add the appropriate Authorization header to the HttpURLConnection. * @param connection The HttpURLConnection to which the header will be added. * @param method The HTTP method to use (GET, PUT, DELETE) * @param resource The resource name (bucketName + "/" + key). */ private void addAuthHeader(HttpURLConnection connection, String method, String resource) { if (connection.getRequestProperty("Date") == null) { connection.setRequestProperty("Date", httpDate()); } if (connection.getRequestProperty("Content-Type") == null) { connection.setRequestProperty("Content-Type", ""); } String canonicalString = makeCanonicalString(method, resource, connection.getRequestProperties()); String encodedCanonical = encode(getSecretAccessKey(), canonicalString, false); connection.setRequestProperty("Authorization", "AWS " + getAwsAccessKeyId() + ":" + encodedCanonical); } private String makeCanonicalString(String method, String resource, Map headers) { return makeCanonicalString(method, resource, headers, null); } /** * Calculate the canonical string. When expires is non-null, it will be * used instead of the Date header. */ private String makeCanonicalString(String method, String resource, Map headers, String expires) { StringBuffer buf = new StringBuffer(); buf.append(method + "\n"); // Add all interesting headers to a list, then sort them. "Interesting" // is defined as Content-MD5, Content-Type, Date, and x-amz- SortedMap interestingHeaders = new TreeMap(); if (headers != null) { for (Iterator i = headers.keySet().iterator(); i.hasNext(); ) { String key = (String)i.next(); if (key == null) continue; String lk = key.toLowerCase(); // Ignore any headers that are not particularly interesting. if (lk.equals("content-type") || lk.equals("content-md5") || lk.equals("date") || lk.startsWith("x-amz-")) { List s = (List)headers.get(key); interestingHeaders.put(lk, concatenateList(s)); } } } if (interestingHeaders.containsKey("x-amz-date")) { interestingHeaders.put("date", ""); } // if the expires is non-null, use that for the date field. this // trumps the x-amz-date behavior. if (expires != null) { interestingHeaders.put("date", expires); } // these headers require that we still put a new line in after them, // even if they don't exist. if (! interestingHeaders.containsKey("content-type")) { interestingHeaders.put("content-type", ""); } if (! interestingHeaders.containsKey("content-md5")) { interestingHeaders.put("content-md5", ""); } // Finally, add all the interesting headers (i.e.: all that startwith x-amz- ;-)) for (Iterator i = interestingHeaders.keySet().iterator(); i.hasNext(); ) { String key = (String)i.next(); if (key.startsWith("x-amz-")) { buf.append(key).append(':').append(interestingHeaders.get(key)); } else { buf.append(interestingHeaders.get(key)); } buf.append("\n"); } // don't include the query parameters... int queryIndex = resource.indexOf('?'); if (queryIndex == -1) { buf.append("/" + resource); } else { buf.append("/" + resource.substring(0, queryIndex)); } // ...unless there is an acl or torrent parameter if (resource.matches(".*[&?]acl($|=|&).*")) { buf.append("?acl"); } else if (resource.matches(".*[&?]torrent($|=|&).*")) { buf.append("?torrent"); } return buf.toString(); } /** * Concatenates a bunch of header values, seperating them with a comma. * @param values List of header values. * @return String of all headers, with commas. */ private String concatenateList(List values) { StringBuffer buf = new StringBuffer(); for (int i = 0, size = values.size(); i < size; ++ i) { buf.append(((String)values.get(i)).replaceAll("\n", "").trim()); if (i != (size - 1)) { buf.append(","); } } return buf.toString(); } /** * Generate an rfc822 date for use in the Date HTTP header. */ private static String httpDate() { final String DateFormat = "EEE, dd MMM yyyy HH:mm:ss "; SimpleDateFormat format = new SimpleDateFormat( DateFormat, Locale.US ); format.setTimeZone( TimeZone.getTimeZone( "GMT" ) ); return format.format( new Date() ) + "GMT"; } }