1<?php
2
3namespace GuzzleHttp;
4
5use GuzzleHttp\Promise\PromiseInterface;
6use Psr\Http\Message\RequestInterface;
7
8/**
9 * Prepares requests that contain a body, adding the Content-Length,
10 * Content-Type, and Expect headers.
11 *
12 * @final
13 */
14class PrepareBodyMiddleware
15{
16    /**
17     * @var callable(RequestInterface, array): PromiseInterface
18     */
19    private $nextHandler;
20
21    /**
22     * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
23     */
24    public function __construct(callable $nextHandler)
25    {
26        $this->nextHandler = $nextHandler;
27    }
28
29    public function __invoke(RequestInterface $request, array $options): PromiseInterface
30    {
31        $fn = $this->nextHandler;
32
33        // Don't do anything if the request has no body.
34        if ($request->getBody()->getSize() === 0) {
35            return $fn($request, $options);
36        }
37
38        $modify = [];
39
40        // Add a default content-type if possible.
41        if (!$request->hasHeader('Content-Type')) {
42            if ($uri = $request->getBody()->getMetadata('uri')) {
43                if (is_string($uri) && $type = Psr7\MimeType::fromFilename($uri)) {
44                    $modify['set_headers']['Content-Type'] = $type;
45                }
46            }
47        }
48
49        // Add a default content-length or transfer-encoding header.
50        if (!$request->hasHeader('Content-Length')
51            && !$request->hasHeader('Transfer-Encoding')
52        ) {
53            $size = $request->getBody()->getSize();
54            if ($size !== null) {
55                $modify['set_headers']['Content-Length'] = $size;
56            } else {
57                $modify['set_headers']['Transfer-Encoding'] = 'chunked';
58            }
59        }
60
61        // Add the expect header if needed.
62        $this->addExpectHeader($request, $options, $modify);
63
64        return $fn(Psr7\Utils::modifyRequest($request, $modify), $options);
65    }
66
67    /**
68     * Add expect header
69     */
70    private function addExpectHeader(RequestInterface $request, array $options, array &$modify): void
71    {
72        // Determine if the Expect header should be used
73        if ($request->hasHeader('Expect')) {
74            return;
75        }
76
77        $expect = $options['expect'] ?? null;
78
79        // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0
80        if ($expect === false || $request->getProtocolVersion() < 1.1) {
81            return;
82        }
83
84        // The expect header is unconditionally enabled
85        if ($expect === true) {
86            $modify['set_headers']['Expect'] = '100-Continue';
87
88            return;
89        }
90
91        // By default, send the expect header when the payload is > 1mb
92        if ($expect === null) {
93            $expect = 1048576;
94        }
95
96        // Always add if the body cannot be rewound, the size cannot be
97        // determined, or the size is greater than the cutoff threshold
98        $body = $request->getBody();
99        $size = $body->getSize();
100
101        if ($size === null || $size >= (int) $expect || !$body->isSeekable()) {
102            $modify['set_headers']['Expect'] = '100-Continue';
103        }
104    }
105}
106