1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
14: namespace Cake\Http\Client\Adapter;
15:
16: use Cake\Core\Exception\Exception;
17: use Cake\Http\Client\AdapterInterface;
18: use Cake\Http\Client\Request;
19: use Cake\Http\Client\Response;
20: use Cake\Http\Exception\HttpException;
21:
22: 23: 24: 25: 26: 27:
28: class Stream implements AdapterInterface
29: {
30:
31: 32: 33: 34: 35:
36: protected $_context;
37:
38: 39: 40: 41: 42:
43: protected $_contextOptions;
44:
45: 46: 47: 48: 49:
50: protected $_sslContextOptions;
51:
52: 53: 54: 55: 56:
57: protected $_stream;
58:
59: 60: 61: 62: 63:
64: protected $_connectionErrors = [];
65:
66: 67: 68:
69: public function send(Request $request, array $options)
70: {
71: $this->_stream = null;
72: $this->_context = null;
73: $this->_contextOptions = [];
74: $this->_sslContextOptions = [];
75: $this->_connectionErrors = [];
76:
77: $this->_buildContext($request, $options);
78:
79: return $this->_send($request);
80: }
81:
82: 83: 84: 85: 86: 87: 88: 89: 90: 91:
92: public function createResponses($headers, $content)
93: {
94: $indexes = $responses = [];
95: foreach ($headers as $i => $header) {
96: if (strtoupper(substr($header, 0, 5)) === 'HTTP/') {
97: $indexes[] = $i;
98: }
99: }
100: $last = count($indexes) - 1;
101: foreach ($indexes as $i => $start) {
102: $end = isset($indexes[$i + 1]) ? $indexes[$i + 1] - $start : null;
103: $headerSlice = array_slice($headers, $start, $end);
104: $body = $i == $last ? $content : '';
105: $responses[] = $this->_buildResponse($headerSlice, $body);
106: }
107:
108: return $responses;
109: }
110:
111: 112: 113: 114: 115: 116: 117:
118: protected function _buildContext(Request $request, $options)
119: {
120: $this->_buildContent($request, $options);
121: $this->_buildHeaders($request, $options);
122: $this->_buildOptions($request, $options);
123:
124: $url = $request->getUri();
125: $scheme = parse_url($url, PHP_URL_SCHEME);
126: if ($scheme === 'https') {
127: $this->_buildSslContext($request, $options);
128: }
129: $this->_context = stream_context_create([
130: 'http' => $this->_contextOptions,
131: 'ssl' => $this->_sslContextOptions,
132: ]);
133: }
134:
135: 136: 137: 138: 139: 140: 141: 142: 143:
144: protected function _buildHeaders(Request $request, $options)
145: {
146: $headers = [];
147: foreach ($request->getHeaders() as $name => $values) {
148: $headers[] = sprintf('%s: %s', $name, implode(', ', $values));
149: }
150: $this->_contextOptions['header'] = implode("\r\n", $headers);
151: }
152:
153: 154: 155: 156: 157: 158: 159: 160: 161: 162:
163: protected function _buildContent(Request $request, $options)
164: {
165: $body = $request->getBody();
166: if (empty($body)) {
167: $this->_contextOptions['content'] = '';
168:
169: return;
170: }
171: $body->rewind();
172: $this->_contextOptions['content'] = $body->getContents();
173: }
174:
175: 176: 177: 178: 179: 180: 181:
182: protected function _buildOptions(Request $request, $options)
183: {
184: $this->_contextOptions['method'] = $request->getMethod();
185: $this->_contextOptions['protocol_version'] = $request->getProtocolVersion();
186: $this->_contextOptions['ignore_errors'] = true;
187:
188: if (isset($options['timeout'])) {
189: $this->_contextOptions['timeout'] = $options['timeout'];
190: }
191:
192: $this->_contextOptions['max_redirects'] = 0;
193:
194: if (isset($options['proxy']['proxy'])) {
195: $this->_contextOptions['request_fulluri'] = true;
196: $this->_contextOptions['proxy'] = $options['proxy']['proxy'];
197: }
198: }
199:
200: 201: 202: 203: 204: 205: 206:
207: protected function _buildSslContext(Request $request, $options)
208: {
209: $sslOptions = [
210: 'ssl_verify_peer',
211: 'ssl_verify_peer_name',
212: 'ssl_verify_depth',
213: 'ssl_allow_self_signed',
214: 'ssl_cafile',
215: 'ssl_local_cert',
216: 'ssl_passphrase',
217: ];
218: if (empty($options['ssl_cafile'])) {
219: $options['ssl_cafile'] = CORE_PATH . 'config' . DIRECTORY_SEPARATOR . 'cacert.pem';
220: }
221: if (!empty($options['ssl_verify_host'])) {
222: $url = $request->getUri();
223: $host = parse_url($url, PHP_URL_HOST);
224: $this->_sslContextOptions['peer_name'] = $host;
225: }
226: foreach ($sslOptions as $key) {
227: if (isset($options[$key])) {
228: $name = substr($key, 4);
229: $this->_sslContextOptions[$name] = $options[$key];
230: }
231: }
232: }
233:
234: 235: 236: 237: 238: 239: 240:
241: protected function _send(Request $request)
242: {
243: $deadline = false;
244: if (isset($this->_contextOptions['timeout']) && $this->_contextOptions['timeout'] > 0) {
245: $deadline = time() + $this->_contextOptions['timeout'];
246: }
247:
248: $url = $request->getUri();
249: $this->_open($url);
250: $content = '';
251: $timedOut = false;
252:
253: while (!feof($this->_stream)) {
254: if ($deadline !== false) {
255: stream_set_timeout($this->_stream, max($deadline - time(), 1));
256: }
257:
258: $content .= fread($this->_stream, 8192);
259:
260: $meta = stream_get_meta_data($this->_stream);
261: if ($meta['timed_out'] || ($deadline !== false && time() > $deadline)) {
262: $timedOut = true;
263: break;
264: }
265: }
266: $meta = stream_get_meta_data($this->_stream);
267: fclose($this->_stream);
268:
269: if ($timedOut) {
270: throw new HttpException('Connection timed out ' . $url, 504);
271: }
272:
273: $headers = $meta['wrapper_data'];
274: if (isset($headers['headers']) && is_array($headers['headers'])) {
275: $headers = $headers['headers'];
276: }
277:
278: return $this->createResponses($headers, $content);
279: }
280:
281: 282: 283: 284: 285: 286: 287: 288:
289: protected function _buildResponse($headers, $body)
290: {
291: return new Response($headers, $body);
292: }
293:
294: 295: 296: 297: 298: 299: 300:
301: protected function _open($url)
302: {
303: set_error_handler(function ($code, $message) {
304: $this->_connectionErrors[] = $message;
305: });
306: try {
307: $this->_stream = fopen($url, 'rb', false, $this->_context);
308: } finally {
309: restore_error_handler();
310: }
311:
312: if (!$this->_stream || !empty($this->_connectionErrors)) {
313: throw new Exception(implode("\n", $this->_connectionErrors));
314: }
315: }
316:
317: 318: 319: 320: 321: 322: 323:
324: public function contextOptions()
325: {
326: return array_merge($this->_contextOptions, $this->_sslContextOptions);
327: }
328: }
329:
330:
331: class_alias('Cake\Http\Client\Adapter\Stream', 'Cake\Network\Http\Adapter\Stream');
332: