com.emc.atmos.api.RestUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.emc.atmos.api.RestUtil.java

Source

/*
 * Copyright 2014 EMC Corporation. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 * or in the "license" file accompanying this file. This file 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.emc.atmos.api;

import com.emc.atmos.AtmosException;
import com.emc.atmos.api.bean.Metadata;
import com.emc.atmos.api.bean.Permission;
import com.emc.util.HttpUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class RestUtil {
    public static final String HEADER_CONTENT_TYPE = "Content-Type";
    public static final String HEADER_DATE = "Date";
    public static final String HEADER_EXPECT = "Expect";
    public static final String HEADER_RANGE = "Range";

    public static final String XHEADER_CONTENT_CHECKSUM = "x-emc-content-checksum";
    public static final String XHEADER_DATE = "x-emc-date";
    public static final String XHEADER_EXPIRES = "x-emc-expires";
    public static final String XHEADER_FEATURES = "x-emc-features";
    public static final String XHEADER_FORCE = "x-emc-force";
    public static final String XHEADER_GENERATE_CHECKSUM = "x-emc-generate-checksum";
    public static final String XHEADER_GROUP_ACL = "x-emc-groupacl";
    public static final String XHEADER_INCLUDE_META = "x-emc-include-meta";
    public static final String XHEADER_LIMIT = "x-emc-limit";
    public static final String XHEADER_LISTABLE_META = "x-emc-listable-meta";
    public static final String XHEADER_LISTABLE_TAGS = "x-emc-listable-tags";
    public static final String XHEADER_META = "x-emc-meta";
    public static final String XHEADER_OBJECTID = "x-emc-objectid";
    public static final String XHEADER_PATH = "x-emc-path";
    public static final String XHEADER_POOL = "x-emc-pool";
    public static final String XHEADER_SIGNATURE = "x-emc-signature";
    public static final String XHEADER_SUPPORT_UTF8 = "x-emc-support-utf8";
    public static final String XHEADER_SYSTEM_TAGS = "x-emc-system-tags";
    public static final String XHEADER_TAGS = "x-emc-tags";
    public static final String XHEADER_TOKEN = "x-emc-token";
    public static final String XHEADER_UID = "x-emc-uid";
    public static final String XHEADER_USER_ACL = "x-emc-useracl";
    public static final String XHEADER_USER_TAGS = "x-emc-user-tags";
    public static final String XHEADER_UTF8 = "x-emc-utf8";
    public static final String XHEADER_VERSION_OID = "x-emc-version-oid";
    public static final String XHEADER_WSCHECKSUM = "x-emc-wschecksum";
    public static final String XHEADER_PROJECT = "x-emc-project-id";
    public static final String XHEADER_OBJECT_VPOOL = "x-emc-vpool";

    public static final String TYPE_MULTIPART = "multipart";
    public static final String TYPE_MULTIPART_BYTE_RANGES = "multipart/byteranges";
    public static final String TYPE_APPLICATION_OCTET_STREAM = "application/octet-stream";

    public static final String TYPE_DEFAULT = TYPE_APPLICATION_OCTET_STREAM;

    public static final String TYPE_PARAM_BOUNDARY = "boundary";

    public static final String PROP_ENABLE_EXPECT_100_CONTINUE = "com.emc.atmos.api.expect100Continue";

    private static final Logger l4j = Logger.getLogger(RestUtil.class);

    private static final Pattern OBJECTID_PATTERN = Pattern.compile("/\\w+/objects/([0-9a-f]{44,})");

    public static String sign(String string, byte[] hashKey) {
        try {
            // Compute the signature hash
            l4j.debug("Hashing: \n" + string);

            byte[] input = string.getBytes("UTF-8");

            Mac mac = Mac.getInstance("HmacSHA1");
            SecretKeySpec key = new SecretKeySpec(hashKey, "HmacSHA1");
            mac.init(key);

            byte[] hashBytes = mac.doFinal(input);

            // Encode the hash in Base64.
            String hash = new String(Base64.encodeBase64(hashBytes), "UTF-8");

            l4j.debug("Hash: " + hash);

            return hash;
        } catch (Exception e) {
            throw new RuntimeException("Error signing string:\n" + string + "\n", e);
        }
    }

    /**
     * Generates the HMAC-SHA1 signature used to authenticate the request using
     * the Java security APIs, then adds the uid and signature to the headers.
     *
     * @param method  the HTTP method used
     * @param path    the resource path including any querystring
     * @param headers the HTTP headers for the request
     * @param hashKey the secret key to use when signing
     */
    public static void signRequest(String method, String path, String query, Map<String, List<Object>> headers,
            String uid, byte[] hashKey, long serverClockSkew) {

        // Add date header
        Date serverTime = new Date(System.currentTimeMillis() - serverClockSkew);
        headers.put(HEADER_DATE, Arrays.asList((Object) HttpUtil.headerFormat(serverTime)));
        headers.put(XHEADER_DATE, Arrays.asList((Object) HttpUtil.headerFormat(serverTime)));

        // Add uid to headers
        if (!headers.containsKey(XHEADER_UID))
            headers.put(XHEADER_UID, Arrays.asList((Object) uid));

        // Build the string to hash.
        StringBuilder builder = new StringBuilder();

        builder.append(method).append("\n");

        // Add the following header values or blank lines if they aren't present
        builder.append(generateHashLine(headers, HEADER_CONTENT_TYPE));
        builder.append(generateHashLine(headers, HEADER_RANGE));
        builder.append(generateHashLine(headers, HEADER_DATE));

        // Add the resource
        builder.append(path.toLowerCase());
        if (query != null)
            builder.append("?").append(query);
        builder.append("\n");

        // Do the 'x-emc' headers. The headers must be hashed in alphabetic
        // order and the values must be stripped of whitespace and newlines.
        // TreeMap will automatically sort by key.
        Map<String, String> emcHeaders = new TreeMap<String, String>();
        for (String key : headers.keySet()) {
            String lowerKey = key.toLowerCase();
            if (lowerKey.indexOf("x-emc") == 0)
                emcHeaders.put(lowerKey, join(headers.get(key), ","));
        }
        for (Iterator<String> i = emcHeaders.keySet().iterator(); i.hasNext();) {
            String key = i.next();
            builder.append(key).append(':').append(normalizeSpace(emcHeaders.get(key)));
            if (i.hasNext())
                builder.append("\n");
        }

        String hash = sign(builder.toString(), hashKey);

        // Add signature to headers
        headers.put(XHEADER_SIGNATURE, Arrays.asList((Object) hash));
    }

    public static String normalizeSpace(String str) {
        int length;
        do {
            length = str.length();
            str = str.replace("  ", " ");
        } while (length != str.length());

        return str.replace("\n", "").trim();
    }

    public static String join(Iterable<?> list, String delimiter) {
        if (list == null)
            return null;
        StringBuilder builder = new StringBuilder();
        for (Iterator<?> i = list.iterator(); i.hasNext();) {
            Object value = i.next();
            builder.append(value);
            if (i.hasNext())
                builder.append(delimiter);
        }
        return builder.toString();
    }

    /**
     * Initializes new keys with an empty ArrayList. Convenience method for generating a header map.
     */
    public static void addValue(Map<String, List<Object>> multiValueMap, String key, Object value) {
        List<Object> values = multiValueMap.get(key);
        if (values == null) {
            values = new ArrayList<Object>();
            multiValueMap.put(key, values);
        }
        values.add(value);
    }

    public static String lastPathElement(String path) {
        if (path == null)
            return null;
        String[] elements = path.split("/");
        return elements[elements.length - 1];
    }

    public static ObjectId parseObjectId(String path) {
        Matcher matcher = OBJECTID_PATTERN.matcher(path);
        if (matcher.find())
            return new ObjectId(matcher.group(1));
        else
            throw new AtmosException("Cannot find object ID in path" + path);
    }

    public static Map<String, Metadata> parseMetadataHeader(String headerValue, boolean listable) {
        Map<String, Metadata> metadataMap = new TreeMap<String, Metadata>();
        if (headerValue == null)
            return metadataMap;
        String[] pairs = headerValue.split(",(?=[^,]+=)"); // comma with key as look-ahead (not part of match)
        for (String pair : pairs) {
            String[] components = pair.split("=", 2);
            String name = HttpUtil.decodeUtf8(components[0].trim());
            String value = components.length > 1 ? HttpUtil.decodeUtf8(components[1]) : null;
            Metadata metadata = new Metadata(name, value, listable);
            metadataMap.put(name, metadata);
        }
        return metadataMap;
    }

    public static Map<String, Permission> parseAclHeader(String headerValue) {
        Map<String, Permission> acl = new TreeMap<String, Permission>();
        if (headerValue == null)
            return acl;
        for (String pair : headerValue.split(",")) {
            String[] components = pair.split("=", 2);
            String name = components[0].trim();
            String permission = components[1];

            // Currently, the server returns "FULL" instead of "FULL_CONTROL".
            // For consistency, change this to the value used in the request
            if ("FULL".equals(permission)) {
                permission = "FULL_CONTROL";
            }

            acl.put(name, Permission.valueOf(permission));
        }
        return acl;
    }

    private static String generateHashLine(Map<String, List<Object>> headers, String headerName) {
        String value = join(headers.get(headerName), ",");
        l4j.debug(headerName + ": " + value);
        if (value != null)
            return value + "\n";
        return "\n";
    }

    private RestUtil() {
    }
}