/libraries/classes/Utils/HttpRequest.php

http://github.com/phpmyadmin/phpmyadmin · PHP · 312 lines · 213 code · 43 blank · 56 comment · 26 complexity · 23540bd20812e454247d8f94d22e8232 MD5 · raw file

  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Utils;
  4. use Composer\CaBundle\CaBundle;
  5. use function base64_encode;
  6. use function curl_exec;
  7. use function curl_getinfo;
  8. use function curl_init;
  9. use function curl_setopt;
  10. use function file_get_contents;
  11. use function function_exists;
  12. use function getenv;
  13. use function ini_get;
  14. use function intval;
  15. use function is_array;
  16. use function is_dir;
  17. use function parse_url;
  18. use function preg_match;
  19. use function stream_context_create;
  20. use function strlen;
  21. use const CURL_IPRESOLVE_V4;
  22. use const CURLINFO_HTTP_CODE;
  23. use const CURLOPT_CAINFO;
  24. use const CURLOPT_CAPATH;
  25. use const CURLOPT_CONNECTTIMEOUT;
  26. use const CURLOPT_CUSTOMREQUEST;
  27. use const CURLOPT_FOLLOWLOCATION;
  28. use const CURLOPT_HTTPHEADER;
  29. use const CURLOPT_IPRESOLVE;
  30. use const CURLOPT_POSTFIELDS;
  31. use const CURLOPT_PROXY;
  32. use const CURLOPT_PROXYUSERPWD;
  33. use const CURLOPT_RETURNTRANSFER;
  34. use const CURLOPT_SSL_VERIFYHOST;
  35. use const CURLOPT_SSL_VERIFYPEER;
  36. use const CURLOPT_TIMEOUT;
  37. use const CURLOPT_USERAGENT;
  38. use const PHP_SAPI;
  39. /**
  40. * Handles HTTP requests
  41. */
  42. class HttpRequest
  43. {
  44. /** @var string */
  45. private $proxyUrl;
  46. /** @var string */
  47. private $proxyUser;
  48. /** @var string */
  49. private $proxyPass;
  50. public function __construct()
  51. {
  52. global $cfg;
  53. $this->proxyUrl = $cfg['ProxyUrl'];
  54. $this->proxyUser = $cfg['ProxyUser'];
  55. $this->proxyPass = $cfg['ProxyPass'];
  56. }
  57. public static function setProxySettingsFromEnv(): void
  58. {
  59. global $cfg;
  60. $httpProxy = getenv('http_proxy');
  61. $urlInfo = parse_url((string) $httpProxy);
  62. if (PHP_SAPI !== 'cli' || ! is_array($urlInfo)) {
  63. return;
  64. }
  65. $proxyUrl = ($urlInfo['host'] ?? '')
  66. . (isset($urlInfo['port']) ? ':' . $urlInfo['port'] : '');
  67. $proxyUser = $urlInfo['user'] ?? '';
  68. $proxyPass = $urlInfo['pass'] ?? '';
  69. $cfg['ProxyUrl'] = $proxyUrl;
  70. $cfg['ProxyUser'] = $proxyUser;
  71. $cfg['ProxyPass'] = $proxyPass;
  72. }
  73. /**
  74. * Returns information with regards to handling the http request
  75. *
  76. * @param array $context Data about the context for which
  77. * to http request is sent
  78. *
  79. * @return array of updated context information
  80. */
  81. private function handleContext(array $context)
  82. {
  83. if (strlen($this->proxyUrl) > 0) {
  84. $context['http'] = [
  85. 'proxy' => $this->proxyUrl,
  86. 'request_fulluri' => true,
  87. ];
  88. if (strlen($this->proxyUser) > 0) {
  89. $auth = base64_encode($this->proxyUser . ':' . $this->proxyPass);
  90. $context['http']['header'] .= 'Proxy-Authorization: Basic '
  91. . $auth . "\r\n";
  92. }
  93. }
  94. return $context;
  95. }
  96. /**
  97. * Creates HTTP request using curl
  98. *
  99. * @param mixed $response HTTP response
  100. * @param int $httpStatus HTTP response status code
  101. * @param bool $returnOnlyStatus If set to true, the method would only return response status
  102. *
  103. * @return string|bool|null
  104. */
  105. private function response(
  106. $response,
  107. $httpStatus,
  108. $returnOnlyStatus
  109. ) {
  110. if ($httpStatus == 404) {
  111. return false;
  112. }
  113. if ($httpStatus != 200) {
  114. return null;
  115. }
  116. if ($returnOnlyStatus) {
  117. return true;
  118. }
  119. return $response;
  120. }
  121. /**
  122. * Creates HTTP request using curl
  123. *
  124. * @param string $url Url to send the request
  125. * @param string $method HTTP request method (GET, POST, PUT, DELETE, etc)
  126. * @param bool $returnOnlyStatus If set to true, the method would only return response status
  127. * @param mixed $content Content to be sent with HTTP request
  128. * @param string $header Header to be set for the HTTP request
  129. *
  130. * @return string|bool|null
  131. */
  132. private function curl(
  133. $url,
  134. $method,
  135. $returnOnlyStatus = false,
  136. $content = null,
  137. $header = ''
  138. ) {
  139. $curlHandle = curl_init($url);
  140. if ($curlHandle === false) {
  141. return null;
  142. }
  143. $curlStatus = 1;
  144. if (strlen($this->proxyUrl) > 0) {
  145. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_PROXY, $this->proxyUrl);
  146. if (strlen($this->proxyUser) > 0) {
  147. $curlStatus &= (int) curl_setopt(
  148. $curlHandle,
  149. CURLOPT_PROXYUSERPWD,
  150. $this->proxyUser . ':' . $this->proxyPass
  151. );
  152. }
  153. }
  154. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_USERAGENT, 'phpMyAdmin');
  155. if ($method !== 'GET') {
  156. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, $method);
  157. }
  158. if ($header) {
  159. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_HTTPHEADER, [$header]);
  160. }
  161. if ($method === 'POST') {
  162. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $content);
  163. }
  164. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_SSL_VERIFYHOST, 2);
  165. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, true);
  166. $caPathOrFile = CaBundle::getSystemCaRootBundlePath();
  167. if (is_dir($caPathOrFile)) {
  168. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_CAPATH, $caPathOrFile);
  169. } else {
  170. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_CAINFO, $caPathOrFile);
  171. }
  172. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
  173. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, 0);
  174. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  175. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_TIMEOUT, 10);
  176. $curlStatus &= (int) curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 10);
  177. if (! $curlStatus) {
  178. return null;
  179. }
  180. $response = @curl_exec($curlHandle);
  181. if ($response === false) {
  182. return null;
  183. }
  184. $httpStatus = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
  185. return $this->response($response, $httpStatus, $returnOnlyStatus);
  186. }
  187. /**
  188. * Creates HTTP request using file_get_contents
  189. *
  190. * @param string $url Url to send the request
  191. * @param string $method HTTP request method (GET, POST, PUT, DELETE, etc)
  192. * @param bool $returnOnlyStatus If set to true, the method would only return response status
  193. * @param mixed $content Content to be sent with HTTP request
  194. * @param string $header Header to be set for the HTTP request
  195. *
  196. * @return string|bool|null
  197. */
  198. private function fopen(
  199. $url,
  200. $method,
  201. $returnOnlyStatus = false,
  202. $content = null,
  203. $header = ''
  204. ) {
  205. $context = [
  206. 'http' => [
  207. 'method' => $method,
  208. 'request_fulluri' => true,
  209. 'timeout' => 10,
  210. 'user_agent' => 'phpMyAdmin',
  211. 'header' => 'Accept: */*',
  212. ],
  213. 'ssl' => [
  214. 'verify_peer' => true,
  215. 'verify_peer_name' => true,
  216. ],
  217. ];
  218. if ($header) {
  219. $context['http']['header'] .= "\n" . $header;
  220. }
  221. if ($method === 'POST') {
  222. $context['http']['content'] = $content;
  223. }
  224. $caPathOrFile = CaBundle::getSystemCaRootBundlePath();
  225. if (is_dir($caPathOrFile)) {
  226. $context['ssl']['capath'] = $caPathOrFile;
  227. } else {
  228. $context['ssl']['cafile'] = $caPathOrFile;
  229. }
  230. $context = $this->handleContext($context);
  231. $response = @file_get_contents(
  232. $url,
  233. false,
  234. stream_context_create($context)
  235. );
  236. if (! isset($http_response_header)) {
  237. return null;
  238. }
  239. preg_match('#HTTP/[0-9\.]+\s+([0-9]+)#', $http_response_header[0], $out);
  240. $httpStatus = intval($out[1]);
  241. return $this->response($response, $httpStatus, $returnOnlyStatus);
  242. }
  243. /**
  244. * Creates HTTP request
  245. *
  246. * @param string $url Url to send the request
  247. * @param string $method HTTP request method (GET, POST, PUT, DELETE, etc)
  248. * @param bool $returnOnlyStatus If set to true, the method would only return response status
  249. * @param mixed $content Content to be sent with HTTP request
  250. * @param string $header Header to be set for the HTTP request
  251. *
  252. * @return string|bool|null
  253. */
  254. public function create(
  255. $url,
  256. $method,
  257. $returnOnlyStatus = false,
  258. $content = null,
  259. $header = ''
  260. ) {
  261. if (function_exists('curl_init')) {
  262. return $this->curl($url, $method, $returnOnlyStatus, $content, $header);
  263. }
  264. if (ini_get('allow_url_fopen')) {
  265. return $this->fopen($url, $method, $returnOnlyStatus, $content, $header);
  266. }
  267. return null;
  268. }
  269. }