1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Guzzle\Http\Message; |
4
|
|
|
|
5
|
|
|
use Guzzle\Http\EntityBody; |
6
|
|
|
use Guzzle\Http\EntityBodyInterface; |
7
|
|
|
use Guzzle\Http\QueryString; |
8
|
|
|
use Guzzle\Http\RedirectPlugin; |
9
|
|
|
use Guzzle\Http\Exception\RequestException; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* HTTP request that sends an entity-body in the request message (POST, PUT, PATCH, DELETE) |
13
|
|
|
*/ |
14
|
|
|
class EntityEnclosingRequest extends Request implements EntityEnclosingRequestInterface |
15
|
|
|
{ |
16
|
|
|
/** @var int When the size of the body is greater than 1MB, then send Expect: 100-Continue */ |
17
|
|
|
protected $expectCutoff = 1048576; |
18
|
|
|
|
19
|
|
|
/** @var EntityBodyInterface $body Body of the request */ |
20
|
|
|
protected $body; |
21
|
|
|
|
22
|
|
|
/** @var QueryString POST fields to use in the EntityBody */ |
23
|
|
|
protected $postFields; |
24
|
|
|
|
25
|
|
|
/** @var array POST files to send with the request */ |
26
|
|
|
protected $postFiles = array(); |
27
|
|
|
|
28
|
|
|
public function __construct($method, $url, $headers = array()) |
29
|
|
|
{ |
30
|
|
|
$this->postFields = new QueryString(); |
31
|
|
|
parent::__construct($method, $url, $headers); |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @return string |
36
|
|
|
*/ |
37
|
|
|
public function __toString() |
38
|
|
|
{ |
39
|
|
|
// Only attempt to include the POST data if it's only fields |
40
|
|
|
if (count($this->postFields) && empty($this->postFiles)) { |
41
|
|
|
return parent::__toString() . (string) $this->postFields; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
return parent::__toString() . $this->body; |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
public function setState($state, array $context = array()) |
48
|
|
|
{ |
49
|
|
|
parent::setState($state, $context); |
50
|
|
|
if ($state == self::STATE_TRANSFER && !$this->body && !count($this->postFields) && !count($this->postFiles)) { |
51
|
|
|
$this->setHeader('Content-Length', 0)->removeHeader('Transfer-Encoding'); |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
return $this->state; |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
public function setBody($body, $contentType = null) |
58
|
|
|
{ |
59
|
|
|
$this->body = EntityBody::factory($body); |
|
|
|
|
60
|
|
|
|
61
|
|
|
// Auto detect the Content-Type from the path of the request if possible |
62
|
|
|
if ($contentType === null && !$this->hasHeader('Content-Type')) { |
63
|
|
|
$contentType = $this->body->getContentType(); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
if ($contentType) { |
|
|
|
|
67
|
|
|
$this->setHeader('Content-Type', $contentType); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
// Always add the Expect 100-Continue header if the body cannot be rewound. This helps with redirects. |
71
|
|
|
if (!$this->body->isSeekable() && $this->expectCutoff !== false) { |
72
|
|
|
$this->setHeader('Expect', '100-Continue'); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
// Set the Content-Length header if it can be determined |
76
|
|
|
$size = $this->body->getContentLength(); |
77
|
|
|
if ($size !== null && $size !== false) { |
78
|
|
|
$this->setHeader('Content-Length', $size); |
79
|
|
|
if ($size > $this->expectCutoff) { |
80
|
|
|
$this->setHeader('Expect', '100-Continue'); |
81
|
|
|
} |
82
|
|
|
} elseif (!$this->hasHeader('Content-Length')) { |
83
|
|
|
if ('1.1' == $this->protocolVersion) { |
84
|
|
|
$this->setHeader('Transfer-Encoding', 'chunked'); |
85
|
|
|
} else { |
86
|
|
|
throw new RequestException( |
87
|
|
|
'Cannot determine Content-Length and cannot use chunked Transfer-Encoding when using HTTP/1.0' |
88
|
|
|
); |
89
|
|
|
} |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
return $this; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
public function getBody() |
96
|
|
|
{ |
97
|
|
|
return $this->body; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* Set the size that the entity body of the request must exceed before adding the Expect: 100-Continue header. |
102
|
|
|
* |
103
|
|
|
* @param int|bool $size Cutoff in bytes. Set to false to never send the expect header (even with non-seekable data) |
104
|
|
|
* |
105
|
|
|
* @return self |
106
|
|
|
*/ |
107
|
|
|
public function setExpectHeaderCutoff($size) |
108
|
|
|
{ |
109
|
|
|
$this->expectCutoff = $size; |
|
|
|
|
110
|
|
|
if ($size === false || !$this->body) { |
111
|
|
|
$this->removeHeader('Expect'); |
112
|
|
|
} elseif ($this->body && $this->body->getSize() && $this->body->getSize() > $size) { |
113
|
|
|
$this->setHeader('Expect', '100-Continue'); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
return $this; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
public function configureRedirects($strict = false, $maxRedirects = 5) |
120
|
|
|
{ |
121
|
|
|
$this->getParams()->set(RedirectPlugin::STRICT_REDIRECTS, $strict); |
122
|
|
|
if ($maxRedirects == 0) { |
123
|
|
|
$this->getParams()->set(RedirectPlugin::DISABLE, true); |
124
|
|
|
} else { |
125
|
|
|
$this->getParams()->set(RedirectPlugin::MAX_REDIRECTS, $maxRedirects); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
return $this; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
public function getPostField($field) |
132
|
|
|
{ |
133
|
|
|
return $this->postFields->get($field); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
public function getPostFields() |
137
|
|
|
{ |
138
|
|
|
return $this->postFields; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
public function setPostField($key, $value) |
142
|
|
|
{ |
143
|
|
|
$this->postFields->set($key, $value); |
144
|
|
|
$this->processPostFields(); |
145
|
|
|
|
146
|
|
|
return $this; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
public function addPostFields($fields) |
150
|
|
|
{ |
151
|
|
|
$this->postFields->merge($fields); |
152
|
|
|
$this->processPostFields(); |
153
|
|
|
|
154
|
|
|
return $this; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
public function removePostField($field) |
158
|
|
|
{ |
159
|
|
|
$this->postFields->remove($field); |
160
|
|
|
$this->processPostFields(); |
161
|
|
|
|
162
|
|
|
return $this; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
public function getPostFiles() |
166
|
|
|
{ |
167
|
|
|
return $this->postFiles; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
public function getPostFile($fieldName) |
171
|
|
|
{ |
172
|
|
|
return isset($this->postFiles[$fieldName]) ? $this->postFiles[$fieldName] : null; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
public function removePostFile($fieldName) |
176
|
|
|
{ |
177
|
|
|
unset($this->postFiles[$fieldName]); |
178
|
|
|
$this->processPostFields(); |
179
|
|
|
|
180
|
|
|
return $this; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
public function addPostFile($field, $filename = null, $contentType = null, $postname = null) |
184
|
|
|
{ |
185
|
|
|
$data = null; |
186
|
|
|
|
187
|
|
|
if ($field instanceof PostFileInterface) { |
188
|
|
|
$data = $field; |
189
|
|
|
} elseif (is_array($filename)) { |
190
|
|
|
// Allow multiple values to be set in a single key |
191
|
|
|
foreach ($filename as $file) { |
192
|
|
|
$this->addPostFile($field, $file, $contentType); |
193
|
|
|
} |
194
|
|
|
return $this; |
195
|
|
|
} elseif (!is_string($filename)) { |
196
|
|
|
throw new RequestException('The path to a file must be a string'); |
197
|
|
|
} elseif (!empty($filename)) { |
198
|
|
|
// Adding an empty file will cause cURL to error out |
199
|
|
|
$data = new PostFile($field, $filename, $contentType, $postname); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
if ($data) { |
203
|
|
|
if (!isset($this->postFiles[$data->getFieldName()])) { |
204
|
|
|
$this->postFiles[$data->getFieldName()] = array($data); |
205
|
|
|
} else { |
206
|
|
|
$this->postFiles[$data->getFieldName()][] = $data; |
207
|
|
|
} |
208
|
|
|
$this->processPostFields(); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
return $this; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
public function addPostFiles(array $files) |
215
|
|
|
{ |
216
|
|
|
foreach ($files as $key => $file) { |
217
|
|
|
if ($file instanceof PostFileInterface) { |
218
|
|
|
$this->addPostFile($file, null, null, false); |
|
|
|
|
219
|
|
|
} elseif (is_string($file)) { |
220
|
|
|
// Convert non-associative array keys into 'file' |
221
|
|
|
if (is_numeric($key)) { |
222
|
|
|
$key = 'file'; |
223
|
|
|
} |
224
|
|
|
$this->addPostFile($key, $file, null, false); |
|
|
|
|
225
|
|
|
} else { |
226
|
|
|
throw new RequestException('File must be a string or instance of PostFileInterface'); |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
return $this; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* Determine what type of request should be sent based on post fields |
235
|
|
|
*/ |
236
|
|
|
protected function processPostFields() |
237
|
|
|
{ |
238
|
|
|
if (!$this->postFiles) { |
|
|
|
|
239
|
|
|
$this->removeHeader('Expect')->setHeader('Content-Type', self::URL_ENCODED); |
240
|
|
|
} else { |
241
|
|
|
$this->setHeader('Content-Type', self::MULTIPART); |
242
|
|
|
if ($this->expectCutoff !== false) { |
243
|
|
|
$this->setHeader('Expect', '100-Continue'); |
244
|
|
|
} |
245
|
|
|
} |
246
|
|
|
} |
247
|
|
|
} |
248
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.