SproutCMS

This is the code documentation for the SproutCMS project

source of /sprout/Helpers/HttpReq.php

  1. <?php
  2. /*
  3.  * Copyright (C) 2017 Karmabunny Pty Ltd.
  4.  *
  5.  * This file is a part of SproutCMS.
  6.  *
  7.  * SproutCMS is free software: you can redistribute it and/or modify it under the terms
  8.  * of the GNU General Public License as published by the Free Software Foundation, either
  9.  * version 2 of the License, or (at your option) any later version.
  10.  *
  11.  * For more information, visit <http://getsproutcms.com>.
  12.  */
  13.  
  14. namespace Sprout\Helpers;
  15.  
  16. use Exception;
  17. use InvalidArgumentException;
  18.  
  19. use Kohana;
  20.  
  21.  
  22. /**
  23.  * Simple HTTP(s) request wrapper
  24.  */
  25. class HttpReq
  26. {
  27. const METHOD_GET = 'GET';
  28. const METHOD_POST = 'POST';
  29. const METHOD_PUT = 'PUT';
  30.  
  31. private static $http_status;
  32. private static $http_info;
  33. private static $http_headers;
  34.  
  35.  
  36. /**
  37.   * Make a simple GET request.
  38.   * If you need more control, use @see HttpReq::req
  39.   *
  40.   * @param string $url The URL to fetch
  41.   * @return string Response data
  42.   */
  43. public static function get($url)
  44. {
  45. return self::req(
  46. $url,
  47. array('method' => 'GET'),
  48. null
  49. );
  50. }
  51.  
  52.  
  53. /**
  54.   * Make a simple POST request, with optional data
  55.   * If you need more control, use @see HttpReq::req
  56.   *
  57.   * @param string $url The URL to send a POST request to
  58.   * @param array $data The POST data
  59.   * @return string Response data
  60.   */
  61. public static function post($url, $data = null)
  62. {
  63. return self::req(
  64. $url,
  65. array('method' => 'POST'),
  66. $data
  67. );
  68. }
  69.  
  70.  
  71. /**
  72.   * Make a HTTP request. Returns the response content.
  73.   *
  74.   * The request options array accepts the following keys:
  75.   * method The HTTP method to use ('GET', 'POST')
  76.   * headers An array of headers; see ::buildHeadersString
  77.   * for format information
  78.   *
  79.   * @param string $url The URL to request.
  80.   * @param array $opts Request options array.
  81.   * @param string/array $data Request data, for POST requests.
  82.   *
  83.   * @return Reponse or FALSE on error.
  84.   */
  85. public static function req($url, array $opts, $data = null)
  86. {
  87. if (is_array($data)) $data = http_build_query($data);
  88.  
  89. $url = trim($url);
  90. if (! $url) return false;
  91.  
  92. if (empty($opts['method'])) {
  93. $opts['method'] = 'GET';
  94. }
  95.  
  96. $opts['method'] = strtoupper($opts['method']);
  97.  
  98. if (function_exists('curl_init')) {
  99. return self::reqCurl($url, $opts, $data);
  100. } else {
  101. return self::reqFopen($url, $opts, $data);
  102. }
  103. }
  104.  
  105.  
  106. /**
  107.   * Sends a HTTP request using fopen (i.e. file_get_contents)
  108.   */
  109. private static function reqFopen($url, array $opts, $data = null)
  110. {
  111. $http_opts = array(
  112. 'method' => $opts['method'],
  113. 'ignore_errors' => true,
  114. );
  115.  
  116. $ssl_opts = array(
  117. 'cafile' => APPPATH . 'cacert.pem',
  118. );
  119.  
  120. if ($opts['method'] == 'POST') {
  121. $http_opts['content'] = $data;
  122. }
  123.  
  124. if (!empty($opts['headers'])) {
  125. $http_opts['header'] = self::buildHeadersString($opts['headers']);
  126. }
  127.  
  128. $context = stream_context_create(array('http' => $http_opts, 'ssl' => $ssl_opts));
  129. $response = @file_get_contents($url, 0, $context);
  130.  
  131. $matches = null;
  132. if (preg_match('/ ([0-9]+) /', $http_response_header[0], $matches)) {
  133. self::$http_status = $matches[1];
  134. } else {
  135. self::$http_status = null;
  136. }
  137.  
  138. return $response;
  139. }
  140.  
  141.  
  142. /**
  143.   * Sends a HTTP request using cURL.
  144.   */
  145. private static function reqCurl($url, array $opts, $data = '')
  146. {
  147. $ch = curl_init($url);
  148. $headers = [];
  149.  
  150. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  151.  
  152. if (!empty($opts['getheaders'])) {
  153. curl_setopt($ch, CURLOPT_HEADER, true);
  154. } else {
  155. curl_setopt($ch, CURLOPT_HEADER, false);
  156. }
  157.  
  158. // Headers
  159. curl_setopt($ch, CURLOPT_HEADERFUNCTION,
  160. function($curl, $header) use (&$headers)
  161. {
  162. $len = strlen($header);
  163. $header = explode(':', $header, 2);
  164. if (count($header) < 2) // ignore invalid headers
  165. return $len;
  166.  
  167. $headers[strtolower(trim($header[0]))][] = trim($header[1]);
  168.  
  169. return $len;
  170. }
  171. );
  172. $headers['method'] = $opts['method'];
  173.  
  174. if ($opts['method'] === self::METHOD_POST) {
  175. curl_setopt($ch, CURLOPT_POST, true);
  176. } else if ($opts['method'] !== self::METHOD_GET) {
  177. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $opts['method']);
  178. }
  179.  
  180. if (!empty($data)) {
  181. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  182. }
  183.  
  184. if (!empty($opts['headers'])) {
  185. $hdrs = self::buildHeadersString($opts['headers']);
  186. curl_setopt($ch, CURLOPT_HTTPHEADER, explode("\r\n", $hdrs));
  187. }
  188.  
  189. if (!empty($opts['httpauth'])) {
  190. curl_setopt($ch, CURLOPT_USERPWD, $opts['httpauth']['username'] . ':' . $opts['httpauth']['password']);
  191. curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
  192. }
  193.  
  194. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
  195. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
  196. curl_setopt($ch, CURLOPT_CAINFO, APPPATH . 'cacert.pem');
  197. curl_setopt($ch, CURLOPT_CAPATH, APPPATH);
  198.  
  199. if (Kohana::config('sprout.httpreq_proxy_host') and Kohana::config('sprout.httpreq_proxy_port')) {
  200. curl_setopt($ch, CURLOPT_PROXY, Kohana::config('sprout.httpreq_proxy_host'));
  201. curl_setopt($ch, CURLOPT_PROXYPORT, Kohana::config('sprout.httpreq_proxy_port'));
  202.  
  203. if (Kohana::config('sprout.httpreq_proxy_auth')) {
  204. curl_setopt($ch, CURLOPT_PROXYUSERPWD, Kohana::config('sprout.httpreq_proxy_auth'));
  205. }
  206.  
  207. if (Kohana::config('sprout.httpreq_proxy_type') == 'socks5') {
  208. curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
  209. } else {
  210. curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
  211. }
  212. }
  213.  
  214. if (!empty($opts['ssl_self_sign'])) {
  215. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
  216. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
  217. }
  218.  
  219. $resp = @curl_exec($ch);
  220. $info = curl_getinfo($ch);
  221.  
  222. if (curl_errno($ch)) {
  223. $error = curl_error($ch);
  224. curl_close($ch);
  225. throw new Exception('cURL error: ' . $error);
  226. }
  227.  
  228. self::$http_status = $info['http_code'];
  229. self::$http_info = $info;
  230. self::$http_headers = $headers;
  231.  
  232. return $resp;
  233. }
  234.  
  235.  
  236. /**
  237.   * Performs a request while permitting advanced cURL options to be specified.
  238.   * Essentially just a wrapper around cURL that has some sane options set by default;
  239.   * e.g. return transfer, no return headers, SSL configured, proxy settings.
  240.   *
  241.   * @param string $url The URL to fetch.
  242.   * @param string $method The HTTP request method, e.g. 'POST', 'GET', etc. Case sensitive.
  243.   * @param string|array $post_data Data for a POST/PUT request, other request types should leave this null.
  244.   * @param array $curl_options Any options for cURL; these will override any default.
  245.   * @return string The response data (if any)
  246.   * @throws \Exception If a cURL error is encountered.
  247.   */
  248. public static function reqAdvanced($url, $method, $post_data = null, array $curl_options = [])
  249. {
  250. $ch = curl_init($url);
  251. $headers = [];
  252.  
  253. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  254.  
  255. if (!empty($curl_options['getheaders'])) {
  256. curl_setopt($ch, CURLOPT_HEADER, true);
  257. } else {
  258. curl_setopt($ch, CURLOPT_HEADER, false);
  259. }
  260.  
  261. // Headers
  262. curl_setopt($ch, CURLOPT_HEADERFUNCTION,
  263. function($curl, $header) use (&$headers)
  264. {
  265. $len = strlen($header);
  266. $header = explode(':', $header, 2);
  267. if (count($header) < 2) // ignore invalid headers
  268. return $len;
  269.  
  270. $headers[strtolower(trim($header[0]))][] = trim($header[1]);
  271.  
  272. return $len;
  273. }
  274. );
  275.  
  276. if ($method === self::METHOD_POST) {
  277. curl_setopt($ch, CURLOPT_POST, true);
  278. } else if ($method !== self::METHOD_GET) {
  279. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
  280. }
  281.  
  282. if ($post_data !== null) {
  283. curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
  284. }
  285.  
  286. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
  287. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
  288. curl_setopt($ch, CURLOPT_CAINFO, APPPATH . 'cacert.pem');
  289. curl_setopt($ch, CURLOPT_CAPATH, APPPATH);
  290.  
  291. if (Kohana::config('sprout.httpreq_proxy_host') and Kohana::config('sprout.httpreq_proxy_port')) {
  292. curl_setopt($ch, CURLOPT_PROXY, Kohana::config('sprout.httpreq_proxy_host'));
  293. curl_setopt($ch, CURLOPT_PROXYPORT, Kohana::config('sprout.httpreq_proxy_port'));
  294.  
  295. if (Kohana::config('sprout.httpreq_proxy_auth')) {
  296. curl_setopt($ch, CURLOPT_PROXYUSERPWD, Kohana::config('sprout.httpreq_proxy_auth'));
  297. }
  298.  
  299. if (Kohana::config('sprout.httpreq_proxy_type') == 'socks5') {
  300. curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
  301. } else {
  302. curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
  303. }
  304. }
  305.  
  306. // Override any of the defaults with those specified
  307. if (count($curl_options)) {
  308. curl_setopt_array($ch, $curl_options);
  309. }
  310.  
  311. $resp = @curl_exec($ch);
  312. $info = curl_getinfo($ch);
  313.  
  314. if (curl_errno($ch)) {
  315. $error = curl_error($ch);
  316. curl_close($ch);
  317. throw new Exception('cURL error: ' . $error);
  318. }
  319.  
  320. curl_close($ch);
  321.  
  322. self::$http_status = $info['http_code'];
  323. self::$http_info = $info;
  324. self::$http_headers = $headers;
  325.  
  326. return $resp;
  327. }
  328.  
  329.  
  330. /**
  331.   * Return the http status code ofthe last request
  332.   *
  333.   * @return int The HTTP status code of the last request
  334.   */
  335. public static function getLastreqStatus()
  336. {
  337. return self::$http_status;
  338. }
  339.  
  340.  
  341. /**
  342.   * Return info of the last request
  343.   *
  344.   * @return array
  345.   */
  346. public static function getLastreqInfo()
  347. {
  348. return self::$http_info;
  349. }
  350.  
  351.  
  352. /**
  353.   * Return headers of the last request
  354.   *
  355.   * @return array
  356.   */
  357. public static function getLastreqHeaders()
  358. {
  359. return self::$http_headers;
  360. }
  361.  
  362.  
  363. /**
  364.   * Converts an array of headers into a \r\n-delimeted string
  365.   *
  366.   * Accepts three formats:
  367.   * - Strings will be passed through as is.
  368.   * - Arrays of strings (with numeric keys) will be used as-is
  369.   * - Arrays of strings (with string keys) will be joined
  370.   * using : to separate the key and value. Also, values
  371.   * containing quotes or spaces will be quoted.
  372.   *
  373.   * @param string/array $headers The headers to process
  374.   * @return string HTTP headers
  375.   */
  376. protected static function buildHeadersString($headers)
  377. {
  378. if (is_string($headers)) return $headers;
  379. if (! is_array($headers)) return null;
  380.  
  381. $out = '';
  382. foreach ($headers as $key => $val) {
  383. if (is_int($key)) {
  384. $out .= $val . "\r\n";
  385. } else {
  386. $key = str_replace(["\n", "\r", "\t"], '', Enc::cleanfunky($key));
  387. $val = str_replace(["\n", "\r", "\t"], '', Enc::cleanfunky($val));
  388. if (empty($key) or empty($val)) {
  389. throw new InvalidArgumentException('Invalid header key or value');
  390. }
  391.  
  392. $out .= $key . ': ' . $val . "\r\n";
  393. }
  394. }
  395.  
  396. return rtrim($out, "\r\n");
  397. }
  398.  
  399. }
  400.