HttpRequest.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Utils;
  4. use const CURL_IPRESOLVE_V4;
  5. use const CURLINFO_HTTP_CODE;
  6. use const CURLINFO_SSL_VERIFYRESULT;
  7. use const CURLOPT_CAINFO;
  8. use const CURLOPT_CAPATH;
  9. use const CURLOPT_CONNECTTIMEOUT;
  10. use const CURLOPT_CUSTOMREQUEST;
  11. use const CURLOPT_FOLLOWLOCATION;
  12. use const CURLOPT_HTTPHEADER;
  13. use const CURLOPT_IPRESOLVE;
  14. use const CURLOPT_POSTFIELDS;
  15. use const CURLOPT_PROXY;
  16. use const CURLOPT_PROXYUSERPWD;
  17. use const CURLOPT_RETURNTRANSFER;
  18. use const CURLOPT_SSL_VERIFYHOST;
  19. use const CURLOPT_SSL_VERIFYPEER;
  20. use const CURLOPT_TIMEOUT;
  21. use const CURLOPT_USERAGENT;
  22. use function base64_encode;
  23. use function curl_exec;
  24. use function curl_getinfo;
  25. use function curl_init;
  26. use function curl_setopt;
  27. use function file_get_contents;
  28. use function function_exists;
  29. use function ini_get;
  30. use function intval;
  31. use function preg_match;
  32. use function stream_context_create;
  33. use function strlen;
  34. /**
  35. * Handles HTTP requests
  36. */
  37. class HttpRequest
  38. {
  39. /** @var string */
  40. private $proxyUrl;
  41. /** @var string */
  42. private $proxyUser;
  43. /** @var string */
  44. private $proxyPass;
  45. public function __construct()
  46. {
  47. global $cfg;
  48. $this->proxyUrl = $cfg['ProxyUrl'];
  49. $this->proxyUser = $cfg['ProxyUser'];
  50. $this->proxyPass = $cfg['ProxyPass'];
  51. }
  52. /**
  53. * Returns information with regards to handling the http request
  54. *
  55. * @param array $context Data about the context for which
  56. * to http request is sent
  57. *
  58. * @return array of updated context information
  59. */
  60. private function handleContext(array $context)
  61. {
  62. if (strlen($this->proxyUrl) > 0) {
  63. $context['http'] = [
  64. 'proxy' => $this->proxyUrl,
  65. 'request_fulluri' => true,
  66. ];
  67. if (strlen($this->proxyUser) > 0) {
  68. $auth = base64_encode(
  69. $this->proxyUser . ':' . $this->proxyPass
  70. );
  71. $context['http']['header'] .= 'Proxy-Authorization: Basic '
  72. . $auth . "\r\n";
  73. }
  74. }
  75. return $context;
  76. }
  77. /**
  78. * Creates HTTP request using curl
  79. *
  80. * @param mixed $response HTTP response
  81. * @param int $httpStatus HTTP response status code
  82. * @param bool $returnOnlyStatus If set to true, the method would only return response status
  83. *
  84. * @return string|bool|null
  85. */
  86. private function response(
  87. $response,
  88. $httpStatus,
  89. $returnOnlyStatus
  90. ) {
  91. if ($httpStatus == 404) {
  92. return false;
  93. }
  94. if ($httpStatus != 200) {
  95. return null;
  96. }
  97. if ($returnOnlyStatus) {
  98. return true;
  99. }
  100. return $response;
  101. }
  102. /**
  103. * Creates HTTP request using curl
  104. *
  105. * @param string $url Url to send the request
  106. * @param string $method HTTP request method (GET, POST, PUT, DELETE, etc)
  107. * @param bool $returnOnlyStatus If set to true, the method would only return response status
  108. * @param mixed $content Content to be sent with HTTP request
  109. * @param string $header Header to be set for the HTTP request
  110. * @param int $ssl SSL mode to use
  111. *
  112. * @return string|bool|null
  113. */
  114. private function curl(
  115. $url,
  116. $method,
  117. $returnOnlyStatus = false,
  118. $content = null,
  119. $header = '',
  120. $ssl = 0
  121. ) {
  122. $curlHandle = curl_init($url);
  123. if ($curlHandle === false) {
  124. return null;
  125. }
  126. $curlStatus = true;
  127. if (strlen($this->proxyUrl) > 0) {
  128. $curlStatus &= curl_setopt($curlHandle, CURLOPT_PROXY, $this->proxyUrl);
  129. if (strlen($this->proxyUser) > 0) {
  130. $curlStatus &= curl_setopt(
  131. $curlHandle,
  132. CURLOPT_PROXYUSERPWD,
  133. $this->proxyUser . ':' . $this->proxyPass
  134. );
  135. }
  136. }
  137. $curlStatus &= curl_setopt($curlHandle, CURLOPT_USERAGENT, 'phpMyAdmin');
  138. if ($method !== 'GET') {
  139. $curlStatus &= curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, $method);
  140. }
  141. if ($header) {
  142. $curlStatus &= curl_setopt($curlHandle, CURLOPT_HTTPHEADER, [$header]);
  143. }
  144. if ($method === 'POST') {
  145. $curlStatus &= curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $content);
  146. }
  147. $curlStatus &= curl_setopt($curlHandle, CURLOPT_SSL_VERIFYHOST, '2');
  148. $curlStatus &= curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, '1');
  149. /**
  150. * Configure ISRG Root X1 to be able to verify Let's Encrypt SSL
  151. * certificates even without properly configured curl in PHP.
  152. *
  153. * See https://letsencrypt.org/certificates/
  154. */
  155. $certsDir = ROOT_PATH . 'libraries/certs/';
  156. /* See code below for logic */
  157. if ($ssl == CURLOPT_CAPATH) {
  158. $curlStatus &= curl_setopt($curlHandle, CURLOPT_CAPATH, $certsDir);
  159. } elseif ($ssl == CURLOPT_CAINFO) {
  160. $curlStatus &= curl_setopt($curlHandle, CURLOPT_CAINFO, $certsDir . 'cacert.pem');
  161. }
  162. $curlStatus &= curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
  163. $curlStatus &= curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, 0);
  164. $curlStatus &= curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  165. $curlStatus &= curl_setopt($curlHandle, CURLOPT_TIMEOUT, 10);
  166. $curlStatus &= curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 10);
  167. if (! $curlStatus) {
  168. return null;
  169. }
  170. $response = @curl_exec($curlHandle);
  171. if ($response === false) {
  172. /*
  173. * In case of SSL verification failure let's try configuring curl
  174. * certificate verification. Unfortunately it is tricky as setting
  175. * options incompatible with PHP build settings can lead to failure.
  176. *
  177. * So let's rather try the options one by one.
  178. *
  179. * 1. Try using system SSL storage.
  180. * 2. Try setting CURLOPT_CAINFO.
  181. * 3. Try setting CURLOPT_CAPATH.
  182. * 4. Fail.
  183. */
  184. if (curl_getinfo($curlHandle, CURLINFO_SSL_VERIFYRESULT) != 0) {
  185. if ($ssl == 0) {
  186. return $this->curl($url, $method, $returnOnlyStatus, $content, $header, CURLOPT_CAINFO);
  187. }
  188. if ($ssl == CURLOPT_CAINFO) {
  189. return $this->curl($url, $method, $returnOnlyStatus, $content, $header, CURLOPT_CAPATH);
  190. }
  191. }
  192. return null;
  193. }
  194. $httpStatus = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
  195. return $this->response($response, $httpStatus, $returnOnlyStatus);
  196. }
  197. /**
  198. * Creates HTTP request using file_get_contents
  199. *
  200. * @param string $url Url to send the request
  201. * @param string $method HTTP request method (GET, POST, PUT, DELETE, etc)
  202. * @param bool $returnOnlyStatus If set to true, the method would only return response status
  203. * @param mixed $content Content to be sent with HTTP request
  204. * @param string $header Header to be set for the HTTP request
  205. *
  206. * @return string|bool|null
  207. */
  208. private function fopen(
  209. $url,
  210. $method,
  211. $returnOnlyStatus = false,
  212. $content = null,
  213. $header = ''
  214. ) {
  215. $context = [
  216. 'http' => [
  217. 'method' => $method,
  218. 'request_fulluri' => true,
  219. 'timeout' => 10,
  220. 'user_agent' => 'phpMyAdmin',
  221. 'header' => 'Accept: */*',
  222. ],
  223. ];
  224. if ($header) {
  225. $context['http']['header'] .= "\n" . $header;
  226. }
  227. if ($method === 'POST') {
  228. $context['http']['content'] = $content;
  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. }