All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.davidmoten.aws.lw.client.RequestHelper Maven / Gradle / Ivy

Go to download

Lightweight client for all AWS services (but still with useful builders and XML parser)

The newest version!
package com.github.davidmoten.aws.lw.client;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import com.github.davidmoten.aws.lw.client.internal.Clock;
import com.github.davidmoten.aws.lw.client.internal.auth.AwsSignatureVersion4;
import com.github.davidmoten.aws.lw.client.internal.util.Preconditions;
import com.github.davidmoten.aws.lw.client.internal.util.Util;

final class RequestHelper {

    private RequestHelper() {
        // prevent instantiation
    }

    static void put(Map> map, String name, String value) {
        Preconditions.checkNotNull(map);
        Preconditions.checkNotNull(name);
        Preconditions.checkNotNull(value);
        List list = map.get(name);
        if (list == null) {
            list = new ArrayList<>();
            map.put(name, list);
        }
        list.add(value);
    }

    static Map combineHeaders(Map> headers) {
        Preconditions.checkNotNull(headers);
        return headers.entrySet().stream().collect(Collectors.toMap(x -> x.getKey(),
                x -> x.getValue().stream().collect(Collectors.joining(","))));
    }

    static String presignedUrl(Clock clock, String url, String method, Map headers,
            byte[] requestBody, String serviceName, Optional regionName, Credentials credentials,
            int connectTimeoutMs, int readTimeoutMs, long expirySeconds, boolean signPayload) {

        // the region-specific endpoint to the target object expressed in path style
        URL endpointUrl = Util.toUrl(url);

        Map h = new HashMap<>(headers);
        final String contentHashString;
        if (isEmpty(requestBody)) {
            contentHashString = AwsSignatureVersion4.UNSIGNED_PAYLOAD;
            h.put("x-amz-content-sha256", "");
        } else if (!signPayload) {
            contentHashString = AwsSignatureVersion4.UNSIGNED_PAYLOAD;
            h.put("x-amz-content-sha256", contentHashString);
        } else {
            // compute hash of the body content
            byte[] contentHash = Util.sha256(requestBody);
            contentHashString = Util.toHex(contentHash);
            h.put("content-length", "" + requestBody.length);
            h.put("x-amz-content-sha256", contentHashString);
        }

        List parameters = extractQueryParameters(endpointUrl);
        // don't use Collectors.toMap because it doesn't accept null values in map
        Map q = new HashMap<>();
        parameters.forEach(p -> q.put(p.name, p.value));
        
        // construct the query parameter string to accompany the url

        // for SignatureV4, the max expiry for a presigned url is 7 days,
        // expressed in seconds
        q.put("X-Amz-Expires", "" + expirySeconds);

        String authorizationQueryParameters = AwsSignatureVersion4.computeSignatureForQueryAuth(
                endpointUrl, method, serviceName, regionName, clock, h, q, contentHashString,
                credentials.accessKey(), credentials.secretKey(), credentials.sessionToken());

        // build the presigned url to incorporate the authorization elements as query
        // parameters
        String u = endpointUrl.toString();
        final String presignedUrl;
        if (u.contains("?")) {
            presignedUrl = u + "&" + authorizationQueryParameters;
        } else {
            presignedUrl = u + "?" + authorizationQueryParameters;
        }
        return presignedUrl;
    }

    private static void includeTokenIfPresent(Credentials credentials, Map h) {
        if (credentials.sessionToken().isPresent()) {
            h.put("x-amz-security-token", credentials.sessionToken().get());
        }
    }

    static ResponseInputStream request(Clock clock, HttpClient httpClient, String url,
            HttpMethod method, Map headers, byte[] requestBody, String serviceName,
            Optional regionName, Credentials credentials, int connectTimeoutMs, int readTimeoutMs, //
            boolean signPayload) throws IOException {

        // the region-specific endpoint to the target object expressed in path style
        URL endpointUrl = Util.toUrl(url);

        Map h = new HashMap<>(headers);
        final String contentHashString;
        if (isEmpty(requestBody)) {
            contentHashString = AwsSignatureVersion4.EMPTY_BODY_SHA256;
        } else {
            if (!signPayload) {
                contentHashString = AwsSignatureVersion4.UNSIGNED_PAYLOAD;
            } else {
                // compute hash of the body content
                byte[] contentHash = Util.sha256(requestBody);
                contentHashString = Util.toHex(contentHash);
            }
            h.put("content-length", "" + requestBody.length);
        }
        h.put("x-amz-content-sha256", contentHashString);

        includeTokenIfPresent(credentials, h);

        List parameters = extractQueryParameters(endpointUrl);
        // don't use Collectors.toMap because it doesn't accept null values in map
        Map q = new HashMap<>();
        parameters.forEach(p -> q.put(p.name, p.value));
        String authorization = AwsSignatureVersion4.computeSignatureForAuthorizationHeader(
                endpointUrl, method.toString(), serviceName, regionName.orElse("us-east-1"), clock, h, q,
                contentHashString, credentials.accessKey(), credentials.secretKey());

        // place the computed signature into a formatted 'Authorization' header
        // and call S3
        h.put("Authorization", authorization);
        return httpClient.request(endpointUrl, method.toString(), h, requestBody, connectTimeoutMs,
                readTimeoutMs);
    }

    private static List extractQueryParameters(URL endpointUrl) {
        String query = endpointUrl.getQuery();
        if (query == null) {
            return Collections.emptyList();
        } else {
            return extractQueryParameters(query);
        }
    }

    private static final char QUERY_PARAMETER_SEPARATOR = '&';
    private static final char QUERY_PARAMETER_VALUE_SEPARATOR = '=';

    /**
     * Extract parameters from a query string, preserving encoding.
     * 

* We can't use Apache HTTP Client's URLEncodedUtils.parse, mainly because we * don't want to decode names/values. * * @param rawQuery the query to parse * @return The list of parameters, in the order they were found. */ // VisibleForTesting static List extractQueryParameters(String rawQuery) { List results = new ArrayList<>(); int endIndex = rawQuery.length() - 1; int index = 0; while (index <= endIndex) { /* * Ideally we should first look for '&', then look for '=' before the '&', but * obviously that's not how AWS understand query parsing; see the test * "post-vanilla-query-nonunreserved" in the test suite. A string such as * "?foo&bar=qux" will be understood as one parameter with name "foo&bar" and * value "qux". Don't ask me why. */ String name; String value; int nameValueSeparatorIndex = rawQuery.indexOf(QUERY_PARAMETER_VALUE_SEPARATOR, index); if (nameValueSeparatorIndex < 0) { // No value name = rawQuery.substring(index); value = null; index = endIndex + 1; } else { int parameterSeparatorIndex = rawQuery.indexOf(QUERY_PARAMETER_SEPARATOR, nameValueSeparatorIndex); if (parameterSeparatorIndex < 0) { parameterSeparatorIndex = endIndex + 1; } name = rawQuery.substring(index, nameValueSeparatorIndex); value = rawQuery.substring(nameValueSeparatorIndex + 1, parameterSeparatorIndex); index = parameterSeparatorIndex + 1; } // note that value = null is valid as we can have a parameter without a value in // a query string (legal http) results.add(parameter(name, value, "UTF-8")); } return results; } // VisibleForTesting static Parameter parameter(String name, String value, String charset) { try { return new Parameter(URLDecoder.decode(name, charset), value == null ? value : URLDecoder.decode(value, charset)); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } // VisibleForTesting static final class Parameter { final String name; final String value; Parameter(String name, String value) { this.name = name; this.value = value; } } static boolean isEmpty(byte[] array) { return array == null || array.length == 0; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy