HttpRequest.php 8.5 KB

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