vendor/aws/aws-sdk-php/src/Api/Serializer/RestSerializer.php line 40

Open in your IDE?
  1. <?php
  2. namespace Aws\Api\Serializer;
  3. use Aws\Api\MapShape;
  4. use Aws\Api\Service;
  5. use Aws\Api\Operation;
  6. use Aws\Api\Shape;
  7. use Aws\Api\StructureShape;
  8. use Aws\Api\TimestampShape;
  9. use Aws\CommandInterface;
  10. use Aws\EndpointV2\EndpointV2SerializerTrait;
  11. use Aws\EndpointV2\Ruleset\RulesetEndpoint;
  12. use GuzzleHttp\Psr7;
  13. use GuzzleHttp\Psr7\Request;
  14. use GuzzleHttp\Psr7\Uri;
  15. use GuzzleHttp\Psr7\UriResolver;
  16. use Psr\Http\Message\RequestInterface;
  17. /**
  18.  * Serializes HTTP locations like header, uri, payload, etc...
  19.  * @internal
  20.  */
  21. abstract class RestSerializer
  22. {
  23.     use EndpointV2SerializerTrait;
  24.     /** @var Service */
  25.     private $api;
  26.     /** @var Uri */
  27.     private $endpoint;
  28.     /**
  29.      * @param Service $api      Service API description
  30.      * @param string  $endpoint Endpoint to connect to
  31.      */
  32.     public function __construct(Service $api$endpoint)
  33.     {
  34.         $this->api $api;
  35.         $this->endpoint Psr7\Utils::uriFor($endpoint);
  36.     }
  37.     /**
  38.      * @param CommandInterface $command Command to serialize into a request.
  39.      * @param $clientArgs Client arguments used for dynamic endpoint resolution.
  40.      *
  41.      * @return RequestInterface
  42.      */
  43.     public function __invoke(
  44.         CommandInterface $command,
  45.         $endpoint null
  46.     )
  47.     {
  48.         $operation $this->api->getOperation($command->getName());
  49.         $commandArgs $command->toArray();
  50.         $opts $this->serialize($operation$commandArgs);
  51.         $headers = isset($opts['headers']) ? $opts['headers'] : [];
  52.         if ($endpoint instanceof RulesetEndpoint) {
  53.             $this->setEndpointV2RequestOptions($endpoint$headers);
  54.         }
  55.         $uri $this->buildEndpoint($operation$commandArgs$opts);
  56.         return new Request(
  57.             $operation['http']['method'],
  58.             $uri,
  59.             $headers,
  60.             isset($opts['body']) ? $opts['body'] : null
  61.         );
  62.     }
  63.     /**
  64.      * Modifies a hash of request options for a payload body.
  65.      *
  66.      * @param StructureShape   $member  Member to serialize
  67.      * @param array            $value   Value to serialize
  68.      * @param array            $opts    Request options to modify.
  69.      */
  70.     abstract protected function payload(
  71.         StructureShape $member,
  72.         array $value,
  73.         array &$opts
  74.     );
  75.     private function serialize(Operation $operation, array $args)
  76.     {
  77.         $opts = [];
  78.         $input $operation->getInput();
  79.         // Apply the payload trait if present
  80.         if ($payload $input['payload']) {
  81.             $this->applyPayload($input$payload$args$opts);
  82.         }
  83.         foreach ($args as $name => $value) {
  84.             if ($input->hasMember($name)) {
  85.                 $member $input->getMember($name);
  86.                 $location $member['location'];
  87.                 if (!$payload && !$location) {
  88.                     $bodyMembers[$name] = $value;
  89.                 } elseif ($location == 'header') {
  90.                     $this->applyHeader($name$member$value$opts);
  91.                 } elseif ($location == 'querystring') {
  92.                     $this->applyQuery($name$member$value$opts);
  93.                 } elseif ($location == 'headers') {
  94.                     $this->applyHeaderMap($name$member$value$opts);
  95.                 }
  96.             }
  97.         }
  98.         if (isset($bodyMembers)) {
  99.             $this->payload($operation->getInput(), $bodyMembers$opts);
  100.         } else if (!isset($opts['body']) && $this->hasPayloadParam($input$payload)) {
  101.             $this->payload($operation->getInput(), [], $opts);
  102.         }
  103.         return $opts;
  104.     }
  105.     private function applyPayload(StructureShape $input$name, array $args, array &$opts)
  106.     {
  107.         if (!isset($args[$name])) {
  108.             return;
  109.         }
  110.         $m $input->getMember($name);
  111.         if ($m['streaming'] ||
  112.            ($m['type'] == 'string' || $m['type'] == 'blob')
  113.         ) {
  114.             // Streaming bodies or payloads that are strings are
  115.             // always just a stream of data.
  116.             $opts['body'] = Psr7\Utils::streamFor($args[$name]);
  117.             return;
  118.         }
  119.         $this->payload($m$args[$name], $opts);
  120.     }
  121.     private function applyHeader($nameShape $member$value, array &$opts)
  122.     {
  123.         if ($member->getType() === 'timestamp') {
  124.             $timestampFormat = !empty($member['timestampFormat'])
  125.                 ? $member['timestampFormat']
  126.                 : 'rfc822';
  127.             $value TimestampShape::format($value$timestampFormat);
  128.         } elseif ($member->getType() === 'boolean') {
  129.             $value $value 'true' 'false';
  130.         }
  131.         if ($member['jsonvalue']) {
  132.             $value json_encode($value);
  133.             if (empty($value) && JSON_ERROR_NONE !== json_last_error()) {
  134.                 throw new \InvalidArgumentException('Unable to encode the provided value'
  135.                     ' with \'json_encode\'. ' json_last_error_msg());
  136.             }
  137.             $value base64_encode($value);
  138.         }
  139.         $opts['headers'][$member['locationName'] ?: $name] = $value;
  140.     }
  141.     /**
  142.      * Note: This is currently only present in the Amazon S3 model.
  143.      */
  144.     private function applyHeaderMap($nameShape $member, array $value, array &$opts)
  145.     {
  146.         $prefix $member['locationName'];
  147.         foreach ($value as $k => $v) {
  148.             $opts['headers'][$prefix $k] = $v;
  149.         }
  150.     }
  151.     private function applyQuery($nameShape $member$value, array &$opts)
  152.     {
  153.         if ($member instanceof MapShape) {
  154.             $opts['query'] = isset($opts['query']) && is_array($opts['query'])
  155.                 ? $opts['query'] + $value
  156.                 $value;
  157.         } elseif ($value !== null) {
  158.             $type $member->getType();
  159.             if ($type === 'boolean') {
  160.                 $value $value 'true' 'false';
  161.             } elseif ($type === 'timestamp') {
  162.                 $timestampFormat = !empty($member['timestampFormat'])
  163.                     ? $member['timestampFormat']
  164.                     : 'iso8601';
  165.                 $value TimestampShape::format($value$timestampFormat);
  166.             }
  167.             $opts['query'][$member['locationName'] ?: $name] = $value;
  168.         }
  169.     }
  170.     private function buildEndpoint(Operation $operation, array $args, array $opts)
  171.     {
  172.         $isModifiedModel $this->api->isModifiedModel();
  173.         $serviceName $this->api->getServiceName();
  174.         // Create an associative array of variable definitions used in expansions
  175.         $varDefinitions $this->getVarDefinitions($operation$args);
  176.         $relative preg_replace_callback(
  177.             '/\{([^\}]+)\}/',
  178.             function (array $matches) use ($varDefinitions) {
  179.                 $isGreedy substr($matches[1], -11) == '+';
  180.                 $k $isGreedy substr($matches[1], 0, -1) : $matches[1];
  181.                 if (!isset($varDefinitions[$k])) {
  182.                     return '';
  183.                 }
  184.                 if ($isGreedy) {
  185.                     return str_replace('%2F''/'rawurlencode($varDefinitions[$k]));
  186.                 }
  187.                 return rawurlencode($varDefinitions[$k]);
  188.             },
  189.             $operation['http']['requestUri']
  190.         );
  191.         // Add the query string variables or appending to one if needed.
  192.         if (!empty($opts['query'])) {
  193.            $relative $this->appendQuery($opts['query'], $relative);
  194.         }
  195.         $path $this->endpoint->getPath();
  196.         if ($isModifiedModel && $serviceName === 's3') {
  197.             if (substr($path, -1) === '/' && $relative[0] === '/') {
  198.                 $path rtrim($path'/');
  199.             }
  200.             $relative $path $relative;
  201.             if (strpos($relative'../') !== false
  202.                 || substr($relative, -2) === '..'
  203.             ) {
  204.                 if ($relative[0] !== '/') {
  205.                     $relative '/' $relative;
  206.                 }
  207.                 return new Uri($this->endpoint->withPath('') . $relative);
  208.             }
  209.         }
  210.         if ((!empty($relative) && $relative !== '/')
  211.             && !$isModifiedModel
  212.             && $serviceName !== 's3'
  213.         ) {
  214.             $this->normalizePath($path);
  215.         }
  216.         // If endpoint has path, remove leading '/' to preserve URI resolution.
  217.         if ($path && $relative[0] === '/') {
  218.             $relative substr($relative1);
  219.         }
  220.         //Append path to endpoint when leading '//...'
  221.         // present as uri cannot be properly resolved
  222.         if ($isModifiedModel && strpos($relative'//') === 0) {
  223.             return new Uri($this->endpoint $relative);
  224.         }
  225.         // Expand path place holders using Amazon's slightly different URI
  226.         // template syntax.
  227.         return UriResolver::resolve($this->endpoint, new Uri($relative));
  228.     }
  229.     /**
  230.      * @param StructureShape $input
  231.      */
  232.     private function hasPayloadParam(StructureShape $input$payload)
  233.     {
  234.         if ($payload) {
  235.             $potentiallyEmptyTypes = ['blob','string'];
  236.             if ($this->api->getMetadata('protocol') == 'rest-xml') {
  237.                 $potentiallyEmptyTypes[] = 'structure';
  238.             }
  239.             $payloadMember $input->getMember($payload);
  240.             if (in_array($payloadMember['type'], $potentiallyEmptyTypes)) {
  241.                 return false;
  242.             }
  243.         }
  244.         foreach ($input->getMembers() as $member) {
  245.             if (!isset($member['location'])) {
  246.                 return true;
  247.             }
  248.         }
  249.         return false;
  250.     }
  251.     private function appendQuery($query$endpoint)
  252.     {
  253.         $append Psr7\Query::build($query);
  254.         return $endpoint .= strpos($endpoint'?') !== false "&{$append}"?{$append}";
  255.     }
  256.     private function getVarDefinitions($command$args)
  257.     {
  258.         $varDefinitions = [];
  259.         foreach ($command->getInput()->getMembers() as $name => $member) {
  260.             if ($member['location'] == 'uri') {
  261.                 $varDefinitions[$member['locationName'] ?: $name] =
  262.                     isset($args[$name])
  263.                         ? $args[$name]
  264.                         : null;
  265.             }
  266.         }
  267.         return $varDefinitions;
  268.     }
  269.     /**
  270.      * Appends trailing slash to non-empty paths with at least one segment
  271.      * to ensure proper URI resolution
  272.      *
  273.      * @param string $path
  274.      *
  275.      * @return void
  276.      */
  277.     private function normalizePath(string $path): void
  278.     {
  279.         if (!empty($path) && $path !== '/' && substr($path, -1) !== '/') {
  280.             $this->endpoint $this->endpoint->withPath($path '/');
  281.         }
  282.     }
  283. }