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