| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 | <?phpnamespace Aws\Api\Serializer;use Aws\Api\MapShape;use Aws\Api\Service;use Aws\Api\Operation;use Aws\Api\Shape;use Aws\Api\StructureShape;use Aws\Api\TimestampShape;use Aws\CommandInterface;use Aws\EndpointV2\EndpointProviderV2;use Aws\EndpointV2\EndpointV2SerializerTrait;use Aws\EndpointV2\Ruleset\RulesetEndpoint;use GuzzleHttp\Psr7;use GuzzleHttp\Psr7\Request;use GuzzleHttp\Psr7\Uri;use GuzzleHttp\Psr7\UriResolver;use Psr\Http\Message\RequestInterface;/** * Serializes HTTP locations like header, uri, payload, etc... * @internal */abstract class RestSerializer{    use EndpointV2SerializerTrait;    /** @var Service */    private $api;    /** @var Uri */    private $endpoint;    /**     * @param Service $api      Service API description     * @param string  $endpoint Endpoint to connect to     */    public function __construct(Service $api, $endpoint)    {        $this->api = $api;        $this->endpoint = Psr7\Utils::uriFor($endpoint);    }    /**     * @param CommandInterface $command Command to serialize into a request.     * @param $endpointProvider Provider used for dynamic endpoint resolution.     * @param $clientArgs Client arguments used for dynamic endpoint resolution.     *     * @return RequestInterface     */    public function __invoke(        CommandInterface $command,        $endpoint = null    )    {        $operation = $this->api->getOperation($command->getName());        $commandArgs = $command->toArray();        $opts = $this->serialize($operation, $commandArgs);        $headers = isset($opts['headers']) ? $opts['headers'] : [];        if ($endpoint instanceof RulesetEndpoint) {            $this->setEndpointV2RequestOptions($endpoint, $headers);        }        $uri = $this->buildEndpoint($operation, $commandArgs, $opts);        return new Request(            $operation['http']['method'],            $uri,            $headers,            isset($opts['body']) ? $opts['body'] : null        );    }    /**     * Modifies a hash of request options for a payload body.     *     * @param StructureShape   $member  Member to serialize     * @param array            $value   Value to serialize     * @param array            $opts    Request options to modify.     */    abstract protected function payload(        StructureShape $member,        array $value,        array &$opts    );    private function serialize(Operation $operation, array $args)    {        $opts = [];        $input = $operation->getInput();        // Apply the payload trait if present        if ($payload = $input['payload']) {            $this->applyPayload($input, $payload, $args, $opts);        }        foreach ($args as $name => $value) {            if ($input->hasMember($name)) {                $member = $input->getMember($name);                $location = $member['location'];                if (!$payload && !$location) {                    $bodyMembers[$name] = $value;                } elseif ($location == 'header') {                    $this->applyHeader($name, $member, $value, $opts);                } elseif ($location == 'querystring') {                    $this->applyQuery($name, $member, $value, $opts);                } elseif ($location == 'headers') {                    $this->applyHeaderMap($name, $member, $value, $opts);                }            }        }        if (isset($bodyMembers)) {            $this->payload($operation->getInput(), $bodyMembers, $opts);        } else if (!isset($opts['body']) && $this->hasPayloadParam($input, $payload)) {            $this->payload($operation->getInput(), [], $opts);        }        return $opts;    }    private function applyPayload(StructureShape $input, $name, array $args, array &$opts)    {        if (!isset($args[$name])) {            return;        }        $m = $input->getMember($name);        if ($m['streaming'] ||           ($m['type'] == 'string' || $m['type'] == 'blob')        ) {            // Streaming bodies or payloads that are strings are            // always just a stream of data.            $opts['body'] = Psr7\Utils::streamFor($args[$name]);            return;        }        $this->payload($m, $args[$name], $opts);    }    private function applyHeader($name, Shape $member, $value, array &$opts)    {        if ($member->getType() === 'timestamp') {            $timestampFormat = !empty($member['timestampFormat'])                ? $member['timestampFormat']                : 'rfc822';            $value = TimestampShape::format($value, $timestampFormat);        } elseif ($member->getType() === 'boolean') {            $value = $value ? 'true' : 'false';        }        if ($member['jsonvalue']) {            $value = json_encode($value);            if (empty($value) && JSON_ERROR_NONE !== json_last_error()) {                throw new \InvalidArgumentException('Unable to encode the provided value'                    . ' with \'json_encode\'. ' . json_last_error_msg());            }            $value = base64_encode($value);        }        $opts['headers'][$member['locationName'] ?: $name] = $value;    }    /**     * Note: This is currently only present in the Amazon S3 model.     */    private function applyHeaderMap($name, Shape $member, array $value, array &$opts)    {        $prefix = $member['locationName'];        foreach ($value as $k => $v) {            $opts['headers'][$prefix . $k] = $v;        }    }    private function applyQuery($name, Shape $member, $value, array &$opts)    {        if ($member instanceof MapShape) {            $opts['query'] = isset($opts['query']) && is_array($opts['query'])                ? $opts['query'] + $value                : $value;        } elseif ($value !== null) {            $type = $member->getType();            if ($type === 'boolean') {                $value = $value ? 'true' : 'false';            } elseif ($type === 'timestamp') {                $timestampFormat = !empty($member['timestampFormat'])                    ? $member['timestampFormat']                    : 'iso8601';                $value = TimestampShape::format($value, $timestampFormat);            }            $opts['query'][$member['locationName'] ?: $name] = $value;        }    }    private function buildEndpoint(Operation $operation, array $args, array $opts)    {        // Create an associative array of variable definitions used in expansions        $varDefinitions = $this->getVarDefinitions($operation, $args);        $relative = preg_replace_callback(            '/\{([^\}]+)\}/',            function (array $matches) use ($varDefinitions) {                $isGreedy = substr($matches[1], -1, 1) == '+';                $k = $isGreedy ? substr($matches[1], 0, -1) : $matches[1];                if (!isset($varDefinitions[$k])) {                    return '';                }                if ($isGreedy) {                    return str_replace('%2F', '/', rawurlencode($varDefinitions[$k]));                }                return rawurlencode($varDefinitions[$k]);            },            $operation['http']['requestUri']        );        // Add the query string variables or appending to one if needed.        if (!empty($opts['query'])) {           $relative = $this->appendQuery($opts['query'], $relative);        }        $path = $this->endpoint->getPath();        //Accounts for trailing '/' in path when custom endpoint        //is provided to endpointProviderV2        if ($this->api->isModifiedModel()            && $this->api->getServiceName() === 's3'        ) {            if (substr($path, -1) === '/' && $relative[0] === '/') {                $path = rtrim($path, '/');            }            $relative = $path . $relative;            if (strpos($relative, '../') !== false                || substr($relative, -2) === '..'            ) {                if ($relative[0] !== '/') {                    $relative = '/' . $relative;                }                return new Uri($this->endpoint->withPath('') . $relative);            }        }        // If endpoint has path, remove leading '/' to preserve URI resolution.        if ($path && $relative[0] === '/') {            $relative = substr($relative, 1);        }        //Append path to endpoint when leading '//...'        // present as uri cannot be properly resolved        if ($this->api->isModifiedModel()            && strpos($relative, '//') === 0        ) {            return new Uri($this->endpoint . $relative);        }        // Expand path place holders using Amazon's slightly different URI        // template syntax.        return UriResolver::resolve($this->endpoint, new Uri($relative));    }    /**     * @param StructureShape $input     */    private function hasPayloadParam(StructureShape $input, $payload)    {        if ($payload) {            $potentiallyEmptyTypes = ['blob','string'];            if ($this->api->getMetadata('protocol') == 'rest-xml') {                $potentiallyEmptyTypes[] = 'structure';            }            $payloadMember = $input->getMember($payload);            if (in_array($payloadMember['type'], $potentiallyEmptyTypes)) {                return false;            }        }        foreach ($input->getMembers() as $member) {            if (!isset($member['location'])) {                return true;            }        }        return false;    }    private function appendQuery($query, $endpoint)    {        $append = Psr7\Query::build($query);        return $endpoint .= strpos($endpoint, '?') !== false ? "&{$append}" : "?{$append}";    }    private function getVarDefinitions($command, $args)    {        $varDefinitions = [];        foreach ($command->getInput()->getMembers() as $name => $member) {            if ($member['location'] == 'uri') {                $varDefinitions[$member['locationName'] ?: $name] =                    isset($args[$name])                        ? $args[$name]                        : null;            }        }        return $varDefinitions;    }}
 |