Passed
Push — master ( da3f2a...c03036 )
by Shahrad
01:59
created

Middleware::stream_read()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 32
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 15
c 0
b 0
f 0
nc 5
nop 2
dl 0
loc 32
rs 9.4555
1
<?php
2
3
namespace EasyHttp;
4
5
use EasyHttp\Contracts\CommonsContract;
6
use EasyHttp\Exceptions\ConnectionException;
7
use EasyHttp\Model\HttpOptions;
8
use EasyHttp\Utils\Toolkit;
9
10
/**
11
 * Middleware class
12
 *
13
 * @link    https://github.com/shahradelahi/easy-http
14
 * @author  Shahrad Elahi (https://github.com/shahradelahi)
15
 * @license https://github.com/shahradelahi/easy-http/blob/master/LICENSE (MIT License)
16
 */
17
class Middleware
18
{
19
20
	/**
21
	 * Create curl handler.
22
	 *
23
	 * @param ?string $method
24
	 * @param string $uri
25
	 * @param array|HttpOptions $options
26
	 *
27
	 * @return \CurlHandle|false
28
	 */
29
	public static function create_curl_handler(?string $method, string $uri, array|HttpOptions $options = []): \CurlHandle|false
30
	{
31
		$handler = curl_init();
32
		if (is_resource($handler) || !$handler) {
33
			return false;
34
		}
35
36
		if (gettype($options) === 'array') {
37
			$options = new HttpOptions($options);
38
		}
39
40
		if (count($options->getQuery()) > 0) {
41
			if (!str_contains($uri, '?')) {
42
				$uri .= '?';
43
			}
44
			$uri .= $options->getQueryString();
45
		}
46
47
		curl_setopt($handler, CURLOPT_URL, $uri);
48
49
		self::set_curl_options($method, $handler, $options);
50
51
		return $handler;
52
	}
53
54
	/**
55
	 * Setup curl options based on the given method and our options.
56
	 *
57
	 * @param \CurlHandle $cHandler
58
	 * @param ?string $method
59
	 * @param HttpOptions $options
60
	 *
61
	 * @return void
62
	 */
63
	public static function set_curl_options(?string $method, \CurlHandle $cHandler, HttpOptions $options): void
64
	{
65
		curl_setopt($cHandler, CURLOPT_HEADER, true);
66
		curl_setopt($cHandler, CURLOPT_CUSTOMREQUEST, $method ?? 'GET');
67
68
		# Fetch the header
69
		$fetchedHeaders = [];
70
		foreach ($options->getHeaders() as $header => $value) {
71
			$fetchedHeaders[] = $header . ': ' . $value;
72
		}
73
74
		# Set headers
75
		curl_setopt($cHandler, CURLOPT_HTTPHEADER, $fetchedHeaders ?? []);
76
77
		# Add body if we have one.
78
		if ($options->getBody()) {
79
			curl_setopt($cHandler, CURLOPT_CUSTOMREQUEST, $method ?? 'POST');
80
			curl_setopt($cHandler, CURLOPT_POSTFIELDS, $options->getBody());
81
			curl_setopt($cHandler, CURLOPT_POST, true);
82
		}
83
84
		# Check for a proxy
85
		if ($options->getProxy() != null) {
86
			curl_setopt($cHandler, CURLOPT_PROXY, $options->getProxy()->getHost());
87
			curl_setopt($cHandler, CURLOPT_PROXYUSERPWD, $options->getProxy()->getAuth());
88
			if ($options->getProxy()->type !== null) {
89
				curl_setopt($cHandler, CURLOPT_PROXYTYPE, $options->getProxy()->type);
90
			}
91
		}
92
93
		curl_setopt($cHandler, CURLOPT_RETURNTRANSFER, true);
94
		curl_setopt($cHandler, CURLOPT_FOLLOWLOCATION, true);
95
96
		# Add and override the custom curl options.
97
		foreach ($options->getCurlOptions() as $option => $value) {
98
			curl_setopt($cHandler, $option, $value);
99
		}
100
101
		# if we have a timeout, set it.
102
		curl_setopt($cHandler, CURLOPT_TIMEOUT, $options->getTimeout());
103
104
		# If self-signed certs are allowed, set it.
105
		if ((bool)getenv('HAS_SELF_SIGNED_CERT') === true) {
106
			curl_setopt($cHandler, CURLOPT_SSL_VERIFYPEER, false);
107
			curl_setopt($cHandler, CURLOPT_SSL_VERIFYHOST, false);
108
		}
109
110
		(new Middleware())->handle_media($cHandler, $options);
111
	}
112
113
	/**
114
	 * Handle the media
115
	 *
116
	 * @param \CurlHandle $handler
117
	 * @param HttpOptions $options
118
	 * @return void
119
	 */
120
	private function handle_media(\CurlHandle $handler, HttpOptions $options): void
121
	{
122
		if (count($options->getMultipart()) > 0) {
123
			curl_setopt($handler, CURLOPT_POST, true);
124
			curl_setopt($handler, CURLOPT_CUSTOMREQUEST, 'POST');
125
126
			$form_data = new FormData();
127
			foreach ($options->getMultipart() as $key => $value) {
128
				$form_data->addFile($key, $value);
129
			}
130
131
			$headers = [];
132
			foreach ($options->getHeaders() as $header => $value) {
133
				if (Toolkit::insensitiveString($header, 'content-type')) continue;
134
				$headers[] = $header . ': ' . $value;
135
			}
136
			$headers[] = 'Content-Type: multipart/form-data';
137
138
			curl_setopt($handler, CURLOPT_HTTPHEADER, $headers);
139
			curl_setopt($handler, CURLOPT_POSTFIELDS, $form_data->getFiles());
140
		}
141
	}
142
143
	/**
144
	 * @param mixed $socket
145
	 * @param int $len
146
	 * @return string|null
147
	 * @throws ConnectionException
148
	 */
149
	public static function stream_read(mixed $socket, int $len): string|null
150
	{
151
		if (!is_resource($socket)) {
152
			throw new ConnectionException(sprintf(
153
				'%s is not a valid resource.', $socket
154
			));
155
		}
156
157
		$data = '';
158
		while (($dataLen = strlen($data)) < $len) {
159
			$buff = fread($socket, $len - $dataLen);
160
161
			if ($buff === false) {
162
//				$metadata = stream_get_meta_data($socket);
163
//				throw new ConnectionException(
164
//					sprintf('Broken frame, read %s of stated %s bytes.  Stream state: %s', strlen($data), $len, json_encode($metadata)),
165
//					CommonsContract::CLIENT_BROKEN_FRAME
166
//				);
167
				return null;
168
			}
169
170
			if ($buff === '') {
171
				$metadata = stream_get_meta_data($socket);
172
				throw new ConnectionException(
173
					sprintf('Empty read; connection dead?  Stream state: %s', json_encode($metadata)),
174
					CommonsContract::CLIENT_EMPTY_READ
175
				);
176
			}
177
			$data .= $buff;
178
		}
179
180
		return $data;
181
	}
182
183
	/**
184
	 * @param mixed $socket
185
	 * @param string $data
186
	 * @return bool
187
	 * @throws ConnectionException
188
	 */
189
	public static function stream_write(mixed $socket, string $data): bool
190
	{
191
		if (!is_resource($socket)) {
192
			throw new ConnectionException(sprintf('%s is not a valid resource.', $socket));
193
		}
194
195
		$written = fwrite($socket, $data);
196
197
		if ($written < strlen($data)) {
198
			throw new ConnectionException(
199
				sprintf('Could only write %s out of %s bytes.', $written, strlen($data)),
200
				CommonsContract::CLIENT_COULD_ONLY_WRITE_LESS
201
			);
202
		}
203
204
		return true;
205
	}
206
207
}