com.jpeterson.littles3.S3ObjectRequest.java Source code

Java tutorial

Introduction

Here is the source code for com.jpeterson.littles3.S3ObjectRequest.java

Source

/*
 * Copyright 2007 Jesse Peterson
 * 
 * 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.jpeterson.littles3;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.httpclient.util.DateUtil;

import com.jpeterson.littles3.bo.Authenticator;
import com.jpeterson.littles3.bo.AuthenticatorException;
import com.jpeterson.littles3.bo.CanonicalUser;
import com.jpeterson.littles3.bo.HackAuthenticator;
import com.jpeterson.littles3.bo.S3Authenticator;

/**
 * Data structure for parsing an S3 object request.
 * 
 * @author Jesse Peterson
 */
public class S3ObjectRequest {
    private String serviceEndpoint;

    private String bucket;

    private String key;

    private CanonicalUser requestor;

    private String stringToSign;

    private Date timestamp;

    private static final String PARAMETER_ACL = "acl";

    private static boolean debug = false;

    /**
     * Empty constructor.
     */
    protected S3ObjectRequest() {
    }

    /**
     * Create an <code>S3Object</code> based on the request supporting virtual
     * hosting of buckets.
     * 
     * @param req
     *            The original request.
     * @param baseHost
     *            The <code>baseHost</code> is the HTTP Host header that is
     *            "expected". This is used to help determine how the bucket name
     *            will be interpreted. This is used to implement the "Virtual
     *            Hosting of Buckets".
     * @param authenticator
     *            The authenticator to use to authenticate this request.
     * @return An object initialized from the request.
     * @throws IllegalArgumentException
     *             Invalid request.
     */
    @SuppressWarnings("unchecked")
    public static S3ObjectRequest create(HttpServletRequest req, String baseHost, Authenticator authenticator)
            throws IllegalArgumentException, AuthenticatorException {
        S3ObjectRequest o = new S3ObjectRequest();
        String pathInfo = req.getPathInfo();
        String contextPath = req.getContextPath();
        String requestURI = req.getRequestURI();
        String undecodedPathPart = null;
        int pathInfoLength;
        String requestURL;
        String serviceEndpoint;
        String bucket = null;
        String key = null;
        String host;
        String value;
        String timestamp;

        baseHost = baseHost.toLowerCase();

        host = req.getHeader("Host");
        if (host != null) {
            host = host.toLowerCase();
        }

        try {
            requestURL = URLDecoder.decode(req.getRequestURL().toString(), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            // should never happen
            e.printStackTrace();
            IllegalArgumentException t = new IllegalArgumentException("Unsupport encoding: UTF-8");
            t.initCause(e);
            throw t;
        }

        if (!requestURL.endsWith(pathInfo)) {
            String m = "requestURL [" + requestURL + "] does not end with pathInfo [" + pathInfo + "]";
            throw new IllegalArgumentException(m);
        }

        pathInfoLength = pathInfo.length();

        serviceEndpoint = requestURL.substring(0, requestURL.length() - pathInfoLength);

        if (debug) {
            System.out.println("---------------");
            System.out.println("requestURI: " + requestURI);
            System.out.println("serviceEndpoint: " + serviceEndpoint);
            System.out.println("---------------");
        }

        if ((host == null) || // http 1.0 form
                (host.equals(baseHost))) { // ordinary method
            // http 1.0 form
            // bucket first part of path info
            // key second part of path info
            if (pathInfoLength > 1) {
                int index = pathInfo.indexOf('/', 1);
                if (index > -1) {
                    bucket = pathInfo.substring(1, index);

                    if (pathInfoLength > (index + 1)) {
                        key = pathInfo.substring(index + 1);
                        undecodedPathPart = requestURI.substring(contextPath.length() + 1 + bucket.length(),
                                requestURI.length());
                    }
                } else {
                    bucket = pathInfo.substring(1);
                }
            }
        } else if (host.endsWith("." + baseHost)) {
            // bucket prefix of host
            // key is path info
            bucket = host.substring(0, host.length() - 1 - baseHost.length());
            if (pathInfoLength > 1) {
                key = pathInfo.substring(1);
                undecodedPathPart = requestURI.substring(contextPath.length(), requestURI.length());
            }
        } else {
            // bucket is host
            // key is path info
            bucket = host;
            if (pathInfoLength > 1) {
                key = pathInfo.substring(1);
                undecodedPathPart = requestURI.substring(contextPath.length(), requestURI.length());
            }
        }

        // timestamp
        timestamp = req.getHeader("Date");

        // CanonicalizedResource
        StringBuffer canonicalizedResource = new StringBuffer();

        canonicalizedResource.append('/');
        if (bucket != null) {
            canonicalizedResource.append(bucket);
        }
        if (undecodedPathPart != null) {
            canonicalizedResource.append(undecodedPathPart);
        }
        if (req.getParameter(PARAMETER_ACL) != null) {
            canonicalizedResource.append("?").append(PARAMETER_ACL);
        }

        // CanonicalizedAmzHeaders
        StringBuffer canonicalizedAmzHeaders = new StringBuffer();
        Map<String, String> headers = new TreeMap<String, String>();
        String headerName;
        String headerValue;

        for (Enumeration headerNames = req.getHeaderNames(); headerNames.hasMoreElements();) {
            headerName = ((String) headerNames.nextElement()).toLowerCase();

            if (headerName.startsWith("x-amz-")) {
                for (Enumeration headerValues = req.getHeaders(headerName); headerValues.hasMoreElements();) {
                    headerValue = (String) headerValues.nextElement();
                    String currentValue = headers.get(headerValue);

                    if (currentValue != null) {
                        // combine header fields with the same name
                        headers.put(headerName, currentValue + "," + headerValue);
                    } else {
                        headers.put(headerName, headerValue);
                    }

                    if (headerName.equals("x-amz-date")) {
                        timestamp = headerValue;
                    }
                }
            }
        }

        for (Iterator<String> iter = headers.keySet().iterator(); iter.hasNext();) {
            headerName = iter.next();
            headerValue = headers.get(headerName);
            canonicalizedAmzHeaders.append(headerName).append(":").append(headerValue).append("\n");
        }

        StringBuffer stringToSign = new StringBuffer();

        stringToSign.append(req.getMethod()).append("\n");
        value = req.getHeader("Content-MD5");
        if (value != null) {
            stringToSign.append(value);
        }
        stringToSign.append("\n");
        value = req.getHeader("Content-Type");
        if (value != null) {
            stringToSign.append(value);
        }
        stringToSign.append("\n");
        value = req.getHeader("Date");
        if (value != null) {
            stringToSign.append(value);
        }
        stringToSign.append("\n");
        stringToSign.append(canonicalizedAmzHeaders);
        stringToSign.append(canonicalizedResource);

        if (debug) {
            System.out.println(":v:v:v:v:");
            System.out.println("undecodedPathPart: " + undecodedPathPart);
            System.out.println("canonicalizedAmzHeaders: " + canonicalizedAmzHeaders);
            System.out.println("canonicalizedResource: " + canonicalizedResource);
            System.out.println("stringToSign: " + stringToSign);
            System.out.println(":^:^:^:^:");
        }

        o.setServiceEndpoint(serviceEndpoint);
        o.setBucket(bucket);
        o.setKey(key);
        try {
            if (timestamp == null) {
                o.setTimestamp(null);
            } else {
                o.setTimestamp(DateUtil.parseDate(timestamp));
            }
        } catch (DateParseException e) {
            o.setTimestamp(null);
        }
        o.setStringToSign(stringToSign.toString());
        o.setRequestor(authenticate(req, o));

        return o;
    }

    /**
     * The service endpoint. In a URL of "http://localhost/bucket/key", would
     * return "http://localhost".
     * 
     * @return The service endpoint.
     */
    public String getServiceEndpoint() {
        return serviceEndpoint;
    }

    /**
     * Set the service endpoint.
     * 
     * @param serviceEndpoint
     *            The service endpoint.
     */
    public void setServiceEndpoint(String serviceEndpoint) {
        this.serviceEndpoint = serviceEndpoint;
    }

    /**
     * The bucket. In a URL of "http://localhost/bucket/key", would return
     * "bucket".
     * 
     * @return The bucket. May be <code>null</code> if no bucket specified.
     */
    public String getBucket() {
        return bucket;
    }

    /**
     * Set the bucket.
     * 
     * @param bucket
     *            The bucket.
     */
    public void setBucket(String bucket) {
        this.bucket = bucket;
    }

    /**
     * The key. In a URL of "http://localhost/bucket/key", would return "key".
     * 
     * @return The key. May be <code>null</code> if no key specified.
     */
    public String getKey() {
        return key;
    }

    /**
     * Set the key.
     * 
     * @param key
     *            The key.
     */
    public void setKey(String key) {
        this.key = key;
    }

    /**
     * Get the principal who made the request.
     * 
     * @return The principal who made the request.
     */
    public CanonicalUser getRequestor() {
        return requestor;
    }

    /**
     * Set the principal who made the request.
     * 
     * @param requestor
     *            The principal who made the request.
     */
    public void setRequestor(CanonicalUser requestor) {
        this.requestor = requestor;
    }

    /**
     * The "String to Sign". Used in authentication.
     * 
     * @return The "String to Sign".
     */
    public String getStringToSign() {
        return stringToSign;
    }

    /**
     * Set the "String to Sign". Used in authentication.
     * 
     * @param stringToSign
     *            The "String to Sign".
     */
    public void setStringToSign(String stringToSign) {
        this.stringToSign = stringToSign;
    }

    /**
     * The request timestamp.
     * 
     * @return The request timestamp.
     */
    public Date getTimestamp() {
        return timestamp;
    }

    /**
     * Set the request timestamp.
     * 
     * @param timestamp
     *            The request timestamp
     */
    public void setTimestamp(Date timestamp) {
        this.timestamp = timestamp;
    }

    public String toString() {
        StringBuffer buffer = new StringBuffer();

        buffer.append("serviceEndpoint:[").append(serviceEndpoint);
        buffer.append("], bucket:[").append(bucket);
        buffer.append("], key:[").append(key);
        buffer.append("], requestor:[").append(requestor).append("]");

        return buffer.toString();
    }

    /**
     * Determine the principal making the request.
     * 
     * @param req
     *            an HttpServletRequest object that contains the request the
     *            client has made of the servlet
     * @return The principal making the request. Will be a
     *         <code>CanonicalUser</code> with an id of the user principal name
     *         if the request is authenticated or an "anonymous"
     *         <code>Canonicaluser</code> is the request is non authenticated.
     * @throws AuthenticatorException
     *             Unable to authenticate the request.
     */
    public static CanonicalUser authenticate(HttpServletRequest req, S3ObjectRequest o)
            throws AuthenticatorException {
        // TODO: remove hack
        try {
            Authenticator hackAuthenticator = new HackAuthenticator();
            return hackAuthenticator.authenticate(req, o);
        } catch (AuthenticatorException e) {
            // ignore
        }

        return new S3Authenticator().authenticate(req, o);
    }
}