Package org.jets3t.service.utils

Source Code of org.jets3t.service.utils.ServiceUtils

/*
* JetS3t : Java S3 Toolkit
* Project hosted at http://bitbucket.org/jmurty/jets3t/
*
* Copyright 2006-2010 James Murty
*
* 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 org.jets3t.service.utils;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SimpleTimeZone;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jets3t.service.Constants;
import org.jets3t.service.ServiceException;
import org.jets3t.service.model.S3Object;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

/**
* General utility methods used throughout the jets3t project.
*
* @author James Murty
*/
public class ServiceUtils {
    public static String HASH_SHA256 = "SHA-256";

    private static final Log log = LogFactory.getLog(ServiceUtils.class);

    protected static final SimpleDateFormat iso8601DateParser = new SimpleDateFormat(
        "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");

    // The Eucalyptus Walrus storage service returns short, non-UTC date time values.
    protected static final SimpleDateFormat iso8601DateParser_Walrus = new SimpleDateFormat(
        "yyyy-MM-dd'T'HH:mm:ss");

    protected static final SimpleDateFormat rfc822DateParser = new SimpleDateFormat(
        "EEE, dd MMM yyyy HH:mm:ss z", Locale.US);

    static {
        iso8601DateParser.setTimeZone(new SimpleTimeZone(0, "GMT"));
        rfc822DateParser.setTimeZone(new SimpleTimeZone(0, "GMT"));
    }

    public static Date parseIso8601Date(String dateString) throws ParseException {
        ParseException exception = null;
        synchronized (iso8601DateParser) {
            try {
                return iso8601DateParser.parse(dateString);
            } catch (ParseException e) {
                exception = e;
            }
        }
        // Work-around to parse datetime value returned by Walrus
        synchronized (iso8601DateParser_Walrus) {
            try {
                return iso8601DateParser_Walrus.parse(dateString);
            } catch (ParseException e) {
                // Ignore work-around exceptions
            }
        }
        // Throw original exception if the Walrus work-around doesn't save us.
        throw exception;
    }

    public static String formatIso8601Date(Date date) {
        synchronized (iso8601DateParser) {
            return iso8601DateParser.format(date);
        }
    }

    public static Date parseRfc822Date(String dateString) throws ParseException {
        synchronized (rfc822DateParser) {
            return rfc822DateParser.parse(dateString);
        }
    }

    public static String formatRfc822Date(Date date) {
        synchronized (rfc822DateParser) {
            return rfc822DateParser.format(date);
        }
    }

    /**
     * Calculate the HMAC/SHA1 on a string.
     *
     * @param awsSecretKey
     * AWS secret key.
     * @param canonicalString
     * canonical string representing the request to sign.
     * @return Signature
     */
    public static String signWithHmacSha1(String awsSecretKey, String canonicalString)
    {
        if (awsSecretKey == null) {
            if (log.isDebugEnabled()) {
                log.debug("Canonical string will not be signed, as no AWS Secret Key was provided");
            }
            return null;
        }

        // The following HMAC/SHA1 code for the signature is taken from the
        // AWS Platform's implementation of RFC2104 (amazon.webservices.common.Signature)
        //
        // Acquire an HMAC/SHA1 from the raw key bytes.
        SecretKeySpec signingKey = null;
        signingKey = new SecretKeySpec(
            stringToBytes(awsSecretKey), Constants.HMAC_SHA1_ALGORITHM);

        // Acquire the MAC instance and initialize with the signing key.
        Mac mac = null;
        try {
            mac = Mac.getInstance(Constants.HMAC_SHA1_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            // should not happen
            throw new RuntimeException("Could not find sha1 algorithm", e);
        }
        try {
            mac.init(signingKey);
        } catch (InvalidKeyException e) {
            // also should not happen
            throw new RuntimeException("Could not initialize the MAC algorithm", e);
        }

        // Compute the HMAC on the digest, and set it.
        byte[] b64;
        try {
            b64 = Base64.encodeBase64(mac.doFinal(stringToBytes(canonicalString)));
            return new String(b64, Constants.DEFAULT_ENCODING);
        }
        catch(UnsupportedEncodingException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * @param str
     * @return String as bytes using default JetS3t encoding (UTF-8)
     */
    public static byte[] stringToBytes(String str) {
        try {
            return str.getBytes(Constants.DEFAULT_ENCODING);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(
                "Unsupported encoding \"" + Constants.DEFAULT_ENCODING
                + "\" for: " + str, e);
        }
    }

    /**
     *
     * @param data
     * @param cryptoHash
     * @return lowercase hex-encoded hash value.
     */
    public static byte[] hash(byte[] data, String cryptoHash) {
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance(cryptoHash);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(
                "Could not find hashing algorithm \"" + cryptoHash + "\"", e);
        }
        md.update(data);
        return md.digest();
    }

    /**
    *
    * @param dataIS
    * @param cryptoHash
    * @return lowercase hex-encoded hash value.
     * @throws IOException
    */
   public static byte[] hash(InputStream dataIS, String cryptoHash)
           throws IOException
   {
       MessageDigest md = null;
       try {
           md = MessageDigest.getInstance(cryptoHash);
       } catch (NoSuchAlgorithmException e) {
           throw new RuntimeException(
               "Could not find hashing algorithm \"" + cryptoHash + "\"", e);
       }

       BufferedInputStream bis = new BufferedInputStream(dataIS);
       try {
           byte[] buffer = new byte[16384];
           int bytesRead = -1;
           while ((bytesRead = bis.read(buffer, 0, buffer.length)) != -1) {
               md.update(buffer, 0, bytesRead);
           }
       } finally {
           try {
               bis.close();
           } catch (Exception e) {
           }
       }

       return md.digest();
   }

    /**
     *
     * @param data
     * @param cryptoHash
     * @return lowercase hex-encoded hash value.
     */
    public static byte[] hash(String data, String cryptoHash) {
        return hash(stringToBytes(data), cryptoHash);
    }

    public static byte[] hashSHA256(byte[] data) {
        return hash(data, "SHA-256");
    }

    public static byte[] hashSHA256(InputStream dataIS) throws IOException {
        return hash(dataIS, "SHA-256");
    }

    /**
     *
     * @param key
     * key for HMAC
     * @param data
     * data to be HMAC'd
     * @param cryptoAlgorithm
     * cryptographic algorithm to use for HMAC, e.g. "SHA-256"
     * @return HMAC hash value with given crypto hashing algorithm.
     */
    public static byte[] hmac(byte[] key, byte[] data, String cryptoAlgorithm) {
        String hmacDefinition = "Hmac" + cryptoAlgorithm;
        try {
            SecretKeySpec signingKey = new SecretKeySpec(key, hmacDefinition);
            Mac mac = Mac.getInstance(hmacDefinition);
            mac.init(signingKey);
            return mac.doFinal(data);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(
                "Could not find hashing algorithm \"" + hmacDefinition + "\"", e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException(
                "Could not init hashing algorithm \"" + hmacDefinition + "\"", e);
        }
    }

    /**
     * Return lowercase hex-encoded HMAC message digest of given data using the
     * given key, using a crypto hash like "SHA256".
     *
     * @param key
     * @param data
     * @return HMAC SHA256 hash value.
     */
    public static byte[] hmacSHA256(String key, String data) {
        return hmac(stringToBytes(key), stringToBytes(data), "SHA256");
    }

    /**
     *
     * @param key
     * @param data
     * @return HMAC SHA256 hash value.
     */
    public static byte[] hmacSHA256(byte[] key, byte[] data) {
        return hmac(key, data, "SHA256");
    }

    /**
     * Reads text data from an input stream and returns it as a String.
     *
     * @param is
     * input stream from which text data is read.
     * @param encoding
     * the character encoding of the textual data in the input stream. If this
     * parameter is null, the default system encoding will be used.
     *
     * @return
     * text data read from the input stream.
     *
     * @throws IOException
     */
    public static String readInputStreamToString(InputStream is, String encoding) throws IOException {
        StringBuilder sb = new StringBuilder();
        BufferedReader br = null;
        if (encoding != null) {
            br = new BufferedReader(new InputStreamReader(is, encoding));
        } else {
            br = new BufferedReader(new InputStreamReader(is));
        }
        String line = null;
        try {
            boolean firstLine = true;
            while ((line = br.readLine()) != null) {
                if (!firstLine) {
                    sb.append("\n");
                }
                sb.append(line);
                firstLine = false;
            }
        } catch (Exception e) {
            if (log.isWarnEnabled()) {
                log.warn("Unable to read String from Input Stream", e);
            }
        }
        return sb.toString();
    }

    /**
     * Reads from an input stream until a newline character or the end of the stream is reached.
     *
     * @param is
     * @return
     * text data read from the input stream, not including the newline character.
     * @throws IOException
     */
    public static String readInputStreamLineToString(InputStream is, String encoding) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int b = -1;
        while ((b = is.read()) != -1) {
            if ('\n' == (char) b) {
                break;
            } else {
                baos.write(b);
            }
        }
        return new String(baos.toByteArray(), encoding);
    }

    /**
     * Reads binary data from an input stream and returns it as a byte array.
     *
     * @param is
     * input stream from which data is read.
     *
     * @return
     * byte array containing data read from the input stream.
     *
     * @throws IOException
     */
    public static byte[] readInputStreamToBytes(InputStream is) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int b = -1;
        while ((b = is.read()) != -1) {
            baos.write(b);
        }
        return baos.toByteArray();
    }

    /**
     * Counts the total number of bytes in a set of S3Objects by summing the
     * content length of each.
     *
     * @param objects
     * @return
     * total number of bytes in all S3Objects.
     */
    public static long countBytesInObjects(S3Object[] objects) {
        long byteTotal = 0;
        for (int i = 0; objects != null && i < objects.length; i++) {
            byteTotal += objects[i].getContentLength();
        }
        return byteTotal;
    }

    /**
     * From a map of metadata returned from a REST GET or HEAD request, returns a map
     * of metadata with the HTTP-connection-specific metadata items removed.
     *
     * @param metadata
     * metadata map to be cleaned
     * @param serviceMetadataPrefix
     * prefix denoting service-specific "header" HTTP header values (case insensitive)
     * @param userMetadataPrefix
     * prefix denoting service-specific "user metadata" HTTP header values (case insensitive)
     * @return
     * metadata map with HTTP-connection-specific items removed.
     */
    public static Map<String, Object> cleanRestMetadataMap(
        Map<String, Object> metadata, String serviceMetadataPrefix, String userMetadataPrefix)
    {
        if (log.isDebugEnabled()) {
            log.debug("Processing REST metadata items");
        }

        Map<String, Object> combinedMap = new HashMap<String, Object>();
        Map<String, Object> serviceMetadataMap = new HashMap<String, Object>();
        Map<String, Object> userMetadataMap = new HashMap<String, Object>();
        Map<String, Object> completeMetadataMap = new HashMap<String, Object>();

        if (metadata != null) {
            for (Map.Entry<String, Object> entry: metadata.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();

                // Convert connection header string Collections into simple strings (where
                // appropriate)
                if (value instanceof Collection) {
                    Collection<?> coll = (Collection<?>) value;
                    if (coll.size() == 1) {
                        if (log.isDebugEnabled()) {
                            log.debug("Converted metadata single-item Collection "
                                + coll.getClass() + " " + coll + " for key: " + key);
                        }
                        value = coll.iterator().next();
                    } else {
                        if (log.isWarnEnabled()) {
                            log.warn("Collection " + coll
                                + " has too many items to convert to a single string");
                        }
                    }
                }

                // Parse date strings into Date objects, if necessary.
                if ("Date".equals(key) || "Last-Modified".equals(key)) {
                    if (!(value instanceof Date)) {
                        if (log.isDebugEnabled()) {
                            log.debug("Parsing date string '" + value
                            + "' into Date object for key: " + key);
                        }
                        try {
                            value = ServiceUtils.parseRfc822Date(value.toString());
                        } catch (ParseException pe) {
                            // Try ISO-8601 date format, just in case
                            try {
                                value = ServiceUtils.parseIso8601Date(value.toString());
                            } catch (ParseException pe2) {
                                // Log original exception if the work-around fails.
                                if (log.isWarnEnabled()) {
                                    log.warn("Date string is not RFC 822 compliant for metadata field " + key, pe);
                                }
                            }
                        }
                    }
                }

                // Recognize user/headers metadata items
                String keyStr = (key != null ? key.toString() : "");
                completeMetadataMap.put(keyStr, value);

                if (keyStr.toLowerCase().startsWith(userMetadataPrefix)) {
                    key = keyStr.substring(userMetadataPrefix.length(), keyStr.length());
                    userMetadataMap.put(key, value);
                    if (log.isDebugEnabled()) {
                        log.debug("Removed user metadata header prefix "
                            + userMetadataPrefix + " from key: " + keyStr + "=>" + key);
                    }
                } else if (keyStr.toLowerCase().startsWith(serviceMetadataPrefix)) {
                    key = keyStr.substring(serviceMetadataPrefix.length(), keyStr.length());
                    serviceMetadataMap.put(key, value);
                    if (log.isDebugEnabled()) {
                        log.debug("Removed header prefix "
                            + serviceMetadataPrefix + " from key: " + keyStr + "=>" + key);
                    }
                } else if (RestUtils.HTTP_HEADER_METADATA_NAMES.contains(keyStr.toLowerCase(Locale.ENGLISH))) {
                    key = keyStr;
                    if (log.isDebugEnabled()) {
                        log.debug("Leaving HTTP header item unchanged: " + key + "=" + value);
                    }
                } else if ("ETag".equalsIgnoreCase(keyStr)
                    || "Date".equalsIgnoreCase(keyStr)
                    || "Last-Modified".equalsIgnoreCase(keyStr)
                    || "Content-Range".equalsIgnoreCase(keyStr))
                {
                    key = keyStr;
                    if (log.isDebugEnabled()) {
                        log.debug("Leaving header item unchanged: " + key + "=" + value);
                    }
                } else if (keyStr.toLowerCase().startsWith("x-jets3t-")) {
                    // Permit pass-through of internal JetS3t "Header" data
                    key = keyStr;
                    if (log.isDebugEnabled()) {
                        log.debug("Leaving internal JetS3t header item unchanged: "
                            + key + "=" + value);
                    }
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("Ignoring metadata item: " + keyStr + "=" + value);
                    }
                    continue;
                }

                combinedMap.put(key, value);
            }
        }

        // Add user and header metadata sub-maps to combined map
        combinedMap.put(Constants.KEY_FOR_SERVICE_METADATA, serviceMetadataMap);
        combinedMap.put(Constants.KEY_FOR_USER_METADATA, userMetadataMap);
        combinedMap.put(Constants.KEY_FOR_COMPLETE_METADATA, completeMetadataMap);

        return combinedMap;
    }

    /**
     * Converts byte data to a Hex-encoded string.
     *
     * @param data
     * data to hex encode.
     * @return
     * hex-encoded string.
     */
    public static String toHex(byte[] data) {
        StringBuilder sb = new StringBuilder(data.length * 2);
        for (int i = 0; i < data.length; i++) {
            String hex = Integer.toHexString(data[i]);
            if (hex.length() == 1) {
                // Append leading zero.
                sb.append("0");
            } else if (hex.length() == 8) {
                // Remove ff prefix from negative numbers.
                hex = hex.substring(6);
            }
            sb.append(hex);
        }
        return sb.toString().toLowerCase(Locale.ENGLISH);
    }

    /**
     * Converts a Hex-encoded data string to the original byte data.
     *
     * @param hexData
     * hex-encoded data to decode.
     * @return
     * decoded data from the hex string.
     */
    public static byte[] fromHex(String hexData) {
        if ((hexData.length() & 1) != ||
            hexData.replaceAll("[a-fA-F0-9]", "").length() > 0) {
            throw new java.lang.IllegalArgumentException("'" + hexData + "' is not a hex string");
        }

        byte[] result = new byte[(hexData.length() + 1) / 2];
        String hexNumber = null;
        int stringOffset = 0;
        int byteOffset = 0;
        while (stringOffset < hexData.length()) {
            hexNumber = hexData.substring(stringOffset, stringOffset + 2);
            stringOffset += 2;
            result[byteOffset++] = (byte) Integer.parseInt(hexNumber, 16);
        }
        return result;
    }

    /**
     * Converts byte data to a Base64-encoded string.
     *
     * @param data
     * data to Base64 encode.
     * @return
     * encoded Base64 string.
     */
    public static String toBase64(byte[] data) {
        byte[] b64 = Base64.encodeBase64(data);
        try {
            return new String(b64, Constants.DEFAULT_ENCODING);
        }
        catch(UnsupportedEncodingException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * Joins a list of items into a delimiter-separated string. Each item
     * is converted to a string value with the toString() method before being
     * added to the final delimited list.
     *
     * @param items
     * the items to include in a delimited string
     * @param delimiter
     * the delimiter character or string to insert between each item in the list
     * @return
     * a delimited string
     */
    public static String join(List<?> items, String delimiter) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < items.size(); i++) {
            sb.append(items.get(i).toString());
            if (i < items.size() - 1) {
                sb.append(delimiter);
            }
        }
        return sb.toString();
    }

    /**
     * Joins a list of items into a delimiter-separated string. Each item
     * is converted to a string value with the toString() method before being
     * added to the final delimited list.
     *
     * @param items
     * the items to include in a delimited string
     * @param delimiter
     * the delimiter character or string to insert between each item in the list
     * @return
     * a delimited string
     */
    public static String join(Object[] items, String delimiter) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < items.length; i++) {
            sb.append(items[i]);
            if (i < items.length - 1) {
                sb.append(delimiter);
            }
        }
        return sb.toString();
    }

    /**
     * Joins a list of <em>int</em>s into a delimiter-separated string.
     *
     * @param ints
     * the ints to include in a delimited string
     * @param delimiter
     * the delimiter character or string to insert between each item in the list
     * @return
     * a delimited string
     */
    public static String join(int[] ints, String delimiter) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < ints.length; i++) {
            sb.append(ints[i]);
            if (i < ints.length - 1) {
                sb.append(delimiter);
            }
        }
        return sb.toString();
    }

    /**
     * Converts a Base64-encoded string to the original byte data.
     *
     * @param b64Data
     * a Base64-encoded string to decode.
     *
     * @return
     * bytes decoded from a Base64 string.
     */
    public static byte[] fromBase64(String b64Data) {
        byte[] decoded = Base64.decodeBase64(stringToBytes(b64Data));
        return decoded;
    }

    /**
     * Computes the MD5 hash of the data in the given input stream and returns it as a hex string.
     * The provided input stream is consumed and closed by this method.
     *
     * @param is
     * @return
     * MD5 hash
     * @throws NoSuchAlgorithmException
     * @throws IOException
     */
    public static byte[] computeMD5Hash(InputStream is) throws NoSuchAlgorithmException, IOException {
        BufferedInputStream bis = new BufferedInputStream(is);
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            byte[] buffer = new byte[16384];
            int bytesRead = -1;
            while ((bytesRead = bis.read(buffer, 0, buffer.length)) != -1) {
                messageDigest.update(buffer, 0, bytesRead);
            }
            return messageDigest.digest();
        } finally {
            try {
                bis.close();
            } catch (Exception e) {
                System.err.println("Unable to close input stream of hash candidate: " + e);
            }
        }
    }

    /**
     * Computes the MD5 hash of the given data and returns it as a hex string.
     *
     * @param data
     * @return
     * MD5 hash.
     * @throws NoSuchAlgorithmException
     * @throws IOException
     */
    public static byte[] computeMD5Hash(byte[] data) throws NoSuchAlgorithmException, IOException {
        return computeMD5Hash(new ByteArrayInputStream(data));
    }

    /**
     * Guess whether the given ETag value is also an MD5 hash of an underlying object
     * in a storage service, as opposed to being some other kind of opaque hash.
     * <p>
     * This test was made necessary by Amazon S3's multipart upload feature, where
     * the ETag value returned after a re-assembled multipart upload is completed
     * is no longer the same as an MD5 hash of the assembled data.
     * <p>
     * An ETag is considered also an MD5 when:
     * <ul>
     * <li>The length is exactly 16 characters (excluding surrounding quote characters)</li>
     * <li>All characters in the string are hexadecimal values, i.e. [0-9a-f] when lowercased</li>
     * </ul>
     * <p>
     * These rules are drawn from the post by Carl@AWS on Nov 11, 2010 10:40 AM here:
     * <a href="https://forums.aws.amazon.com/thread.jspa?messageID=222158&tstart=0"
     *   >https://forums.aws.amazon.com/thread.jspa?messageID=222158&amp;tstart=0</a>
     *
     * @return
     * true if the ETag value can be assumed to also be an MD5 hash.
     */
    public static boolean isEtagAlsoAnMD5Hash(String etag) {
        if (etag == null || etag.length() != 32) {
            return false;
        }
        String nonHexChars = etag.toLowerCase().replaceAll("[a-f0-9]", "");
        if (nonHexChars.length() > 0) {
            return false;
        }
        return true;
    }

    /**
     * Identifies the name of a bucket from a given host name, if available.
     * Returns null if the bucket name cannot be identified, as might happen
     * when a bucket name is represented by the path component of a URL instead
     * of the host name component.
     *
     * @param host
     * the host name component of a URL that may include the bucket name,
     * if an alternative host name is in use.
     *
     * @return
     * The S3 bucket name represented by the DNS host name, or null if none.
     */
    public static String findBucketNameInHostname(String host, String s3Endpoint) {
        String bucketName = null;
        // Bucket name is available in URL's host name.
        if (host.endsWith(s3Endpoint)) {
            // Bucket name is available as S3 subdomain
            bucketName = host.substring(0,
                host.length() - s3Endpoint.length() - 1);
        } else {
            // URL refers to a virtual host name
            bucketName = host;
        }
        return bucketName;
    }

    /**
     * Builds an object based on the bucket name and object key information
     * available in the components of a URL.
     *
     * @param host
     * the host name component of a URL that may include the bucket name,
     * if an alternative host name is in use.
     * @param urlPath
     * the path of a URL that references an S3 object, and which may or may
     * not include the bucket name.
     *
     * @return
     * the object referred to by the URL components.
     */
    public static S3Object buildObjectFromUrl(String host, String urlPath, String s3Endpoint)
        throws UnsupportedEncodingException
    {
        if (urlPath.startsWith("/")) {
            urlPath = urlPath.substring(1); // Ignore first '/' character in url path.
        }

        String bucketName = null;
        String objectKey = null;

        if (!s3Endpoint.equals(host)) {
            bucketName = findBucketNameInHostname(host, s3Endpoint);
        } else {
            // Bucket name must be first component of URL path
            int slashIndex = urlPath.indexOf("/");
            bucketName = URLDecoder.decode(
                urlPath.substring(0, slashIndex), Constants.DEFAULT_ENCODING);

            // Remove the bucket name component of the host name
            urlPath = urlPath.substring(bucketName.length() + 1);
        }

        objectKey = URLDecoder.decode(
            urlPath, Constants.DEFAULT_ENCODING);

        S3Object object = new S3Object(objectKey);
        object.setBucketName(bucketName);
        return object;
    }

    /**
     * Returns true if the given bucket name can be used as a component of a valid
     * DNS name. If so, the bucket can be accessed using requests with the bucket name
     * as part of an S3 sub-domain. If not, the old-style bucket reference URLs must be
     * used, in which case the bucket name must be the first component of the resource
     * path.
     *
     * @param bucketName
     * the name of the bucket to test for DNS compatibility.
     */
    public static boolean isBucketNameValidDNSName(String bucketName) {
        if (bucketName == null || bucketName.length() > 63 || bucketName.length() < 3) {
            return false;
        }

        // Only lower-case letters, numbers, '.' or '-' characters allowed
        if (!Pattern.matches("^[a-z0-9][a-z0-9.-]+$", bucketName)) {
            return false;
        }

        // Cannot be an IP address, i.e. must not contain four '.'-delimited
        // sections with 1 to 3 digits each.
        if (Pattern.matches("([0-9]{1,3}\\.){3}[0-9]{1,3}", bucketName)) {
            return false;
        }

        // Components of name between '.' characters cannot start or end with '-',
        // and cannot be empty
        String[] fragments = bucketName.split("\\.");
        for (int i = 0; i < fragments.length; i++) {
            if (Pattern.matches("^-.*", fragments[i])
                || Pattern.matches(".*-$", fragments[i])
                || Pattern.matches("^$", fragments[i]))
            {
                return false;
            }
        }

        return true;
    }

    public static String generateS3HostnameForBucket(String bucketName,
        boolean isDnsBucketNamingDisabled, String s3Endpoint)
    {
        if (isBucketNameValidDNSName(bucketName) && !isDnsBucketNamingDisabled) {
            return bucketName + "." + s3Endpoint;
        } else {
            return s3Endpoint;
        }
    }

    /**
     * Returns a user agent string describing the jets3t library, and optionally the application
     * using it, to server-side services.
     *
     * @param applicationDescription
     * a description of the application using the jets3t toolkit, included at the end of the
     * user agent string. This value may be null.
     * @return
     * a string built with the following components (some elements may not be available):
     * <tt>JetS3t/</tt><i>{@link Constants#JETS3T_VERSION}</i>
     * (<i>os.name</i>/<i>os.version</i>; <i>os.arch</i>; <i>user.region</i>;
     * <i>user.region</i>; <i>user.language</i>) <i>applicationDescription</i>
     *
     */
    public static String getUserAgentDescription(String applicationDescription) {
        return
            "JetS3t/" + Constants.JETS3T_VERSION + " ("
            + System.getProperty("os.name") + "/"
            + System.getProperty("os.version") + ";"
            + " " + System.getProperty("os.arch")
            + (System.getProperty("user.region") != null
                ? "; " + System.getProperty("user.region")
                : "")
            + (System.getProperty("user.language") != null
                ? "; " + System.getProperty("user.language")
                : "")
            + (System.getProperty("java.version") != null
                ? "; JVM " + System.getProperty("java.version")
                : "")
            + ")"
            + (applicationDescription != null
                ? " " + applicationDescription
                : "");
    }

    /**
     * Find a SAX XMLReader by hook or by crook, with work-arounds for
     * non-standard platforms.
     *
     * @return an initialized XML SAX reader
     */
    public static XMLReader loadXMLReader() throws ServiceException {
        // Try loading the default SAX reader
        try {
            return XMLReaderFactory.createXMLReader();
        } catch (SAXException e) {
            // Ignore failure
        }

        // No dice using the standard approach, try loading alternatives...
        String[] altXmlReaderClasspaths = new String[] {
            "org.apache.crimson.parser.XMLReaderImpl"// JDK 1.4
            "org.xmlpull.v1.sax2.Driver"// Android
        };
        for (int i = 0; i < altXmlReaderClasspaths.length; i++) {
            String xmlReaderClasspath = altXmlReaderClasspaths[i];
            try {
                return XMLReaderFactory.createXMLReader(xmlReaderClasspath);
            } catch (SAXException e) {
                // Ignore failure
            }
        }
        // If we haven't found and returned an XMLReader yet, give up.
        throw new ServiceException("Failed to initialize a SAX XMLReader");
    }

    /**
     * Take the input we're given and wrap at the user-defined intervals
     *
     * @param p_Input The string to be modified by the line wrap.
     * @param p_Prefix a prefix to prebend to the output string
     * @param p_Len The maximum number of characters per line
     * @return The new string that contains the extra new-line escapes.
     */
    public static String wrapString(String p_Input, String p_Prefix, int p_Len) {
      if (p_Input==null){
        return "";
      }
      String in = p_Input.replace('\\', '/');
      boolean replaced = !in.equals(p_Input);
      String output = wrapString( p_Input, p_Prefix, p_Len, " /_");
      return replaced ? output.replace('/', '\\') : output;
    }

    /**
     * Take the input we're given and wrap at the user-defined intervals
     *
     * @param p_Input The string to be modified by the line wrap.
     * @param p_Prefix a prefix to prebend to the output string
     * @param p_Len The maximum number of characters per line
     * @param p_Delims are the characters on which wrapping is allowed
     * @return The new string that contains the extra new-line escapes.
     */
    public static String wrapString(
            String p_Input,
            String p_Prefix,
            int p_Len,
            String p_Delims) {
      if (p_Input==null){
        return "";
      }
      String temp;
        StringBuilder output = new StringBuilder();
      StringBuffer workBuf = new StringBuffer();

      StringTokenizer strTok = new StringTokenizer(p_Input, p_Delims, true);

      while (strTok.hasMoreTokens()) {
        temp = strTok.nextToken();

        if ((workBuf.length() + temp.length()) >= p_Len) {
          if (p_Prefix != null) {
            output.append(p_Prefix);
          }
          output.append(workBuf.toString());
          output.append("\n");
          workBuf = new StringBuffer();

          // Just to make things look a little nicer, we'll see if this
          // element starts with a space and lop it off if so.
          if (temp.startsWith(" ")) {

            int tempLen = temp.length();

            if (tempLen > 1) {
              temp = temp.substring(1, temp.length() - 1);
            } else {
              temp = "";
            }
          }
        }

        workBuf.append(temp);
      }

      // Now catch the last little bit of our work buffer
      if (p_Prefix != null) {
        output.append(p_Prefix);
      }
      output.append(workBuf.toString());
      return output.toString();
    }

}
TOP

Related Classes of org.jets3t.service.utils.ServiceUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.