curl.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. <?php
  2. namespace Fuel\Core;
  3. class Request_Curl extends \Request_Driver
  4. {
  5. /**
  6. * @var string to preserve the original resource url when using get
  7. */
  8. protected $preserve_resource;
  9. /**
  10. * Extends parent constructor to detect availability of cURL
  11. *
  12. * @param string $resource url to use
  13. * @param array $options options array
  14. * @param string $method request method
  15. * @throws \RuntimeException
  16. */
  17. public function __construct($resource, array $options, $method = null)
  18. {
  19. // check if we have libcurl available
  20. if ( ! function_exists('curl_init'))
  21. {
  22. throw new \RuntimeException('Your PHP installation doesn\'t have cURL enabled. Rebuild PHP with --with-curl');
  23. }
  24. logger(\Fuel::L_INFO, 'Creating a new CURL Request with URI = "'.$resource.'"', __METHOD__);
  25. // If authentication is enabled use it
  26. if ( ! empty($options['auth']) and ! empty($options['user']) and ! empty($options['pass']))
  27. {
  28. $this->http_login($options['user'], $options['pass'], $options['auth']);
  29. }
  30. parent::__construct($resource, $options, $method);
  31. }
  32. /**
  33. * Fetch the connection, create if necessary
  34. *
  35. * @return \resource
  36. */
  37. protected function connection()
  38. {
  39. // If no a protocol in URL, assume its a local link
  40. ! preg_match('!^\w+://! i', $this->resource) and $this->resource = Uri::create($this->resource);
  41. return curl_init($this->resource);
  42. }
  43. /**
  44. * Authenticate to an http server
  45. *
  46. * @param string $username
  47. * @param string $password
  48. * @param string $type
  49. * @return Request_Curl
  50. */
  51. public function http_login($username = '', $password = '', $type = 'any')
  52. {
  53. $this->set_option(CURLOPT_HTTPAUTH, constant('CURLAUTH_' . strtoupper($type)));
  54. $this->set_option(CURLOPT_USERPWD, $username . ':' . $password);
  55. return $this;
  56. }
  57. /**
  58. * Overwrites driver method to set options driver specifically
  59. *
  60. * @param int|string $code
  61. * @param mixed $value
  62. * @return Request_Curl
  63. */
  64. public function set_options(array $options)
  65. {
  66. foreach ($options as $key => $val)
  67. {
  68. if (is_string($key) && ! is_numeric($key))
  69. {
  70. $key = constant('CURLOPT_' . strtoupper($key));
  71. }
  72. $this->options[$key] = $val;
  73. }
  74. return $this;
  75. }
  76. public function execute(array $additional_params = array())
  77. {
  78. // Reset response
  79. $this->response = null;
  80. $this->response_info = array();
  81. // Set two default options, and merge any extra ones in
  82. if ( ! isset($this->options[CURLOPT_TIMEOUT]))
  83. {
  84. $this->options[CURLOPT_TIMEOUT] = 30;
  85. }
  86. if ( ! isset($this->options[CURLOPT_RETURNTRANSFER]))
  87. {
  88. $this->options[CURLOPT_RETURNTRANSFER] = true;
  89. }
  90. if ( ! isset($this->options[CURLOPT_FAILONERROR]))
  91. {
  92. $this->options[CURLOPT_FAILONERROR] = false;
  93. }
  94. // Only set follow location if not running securely
  95. if ( ! ini_get('safe_mode') && ! ini_get('open_basedir'))
  96. {
  97. // Ok, follow location is not set already so lets set it to true
  98. if ( ! isset($this->options[CURLOPT_FOLLOWLOCATION]))
  99. {
  100. $this->options[CURLOPT_FOLLOWLOCATION] = true;
  101. }
  102. }
  103. if ( ! empty($this->headers))
  104. {
  105. $this->set_option(CURLOPT_HTTPHEADER, $this->get_headers());
  106. }
  107. $additional_params and $this->params = \Arr::merge($this->params, $additional_params);
  108. $this->method and $this->options[CURLOPT_CUSTOMREQUEST] = $this->method;
  109. if ( ! empty($this->method))
  110. {
  111. $this->options[CURLOPT_CUSTOMREQUEST] = $this->method;
  112. $this->{'method_'.strtolower($this->method)}();
  113. }
  114. else
  115. {
  116. $this->method_get();
  117. }
  118. $connection = $this->connection();
  119. curl_setopt_array($connection, $this->options);
  120. // Execute the request & and hide all output
  121. $body = curl_exec($connection);
  122. $this->response_info = curl_getinfo($connection);
  123. $mime = isset($this->headers['Accept']) ? $this->headers['Accept'] : $this->response_info('content_type', 'text/plain');
  124. // Was header data requested?
  125. $headers = array();
  126. if (isset($this->options[CURLOPT_HEADER]) and $this->options[CURLOPT_HEADER])
  127. {
  128. // Split the headers from the body
  129. $raw_headers = explode("\n", str_replace("\r", "", substr($body, 0, $this->response_info['header_size'])));
  130. $body = $this->response_info['header_size'] >= strlen($body) ? '' : substr($body, $this->response_info['header_size']);
  131. // Convert the header data
  132. foreach ($raw_headers as $header)
  133. {
  134. $header = explode(':', $header, 2);
  135. if (isset($header[1]))
  136. {
  137. $headers[trim($header[0])] = trim($header[1]);
  138. }
  139. }
  140. }
  141. $this->set_response($body, $this->response_info('http_code', 200), $mime, $headers);
  142. // Request failed
  143. if ($body === false)
  144. {
  145. $this->set_defaults();
  146. throw new \RequestException(curl_error($connection), curl_errno($connection));
  147. }
  148. elseif ($this->response->status >= 400)
  149. {
  150. $this->set_defaults();
  151. throw new \RequestStatusException($body, $this->response->status);
  152. }
  153. else
  154. {
  155. // Request successful
  156. curl_close($connection);
  157. $this->set_defaults();
  158. return $this;
  159. }
  160. }
  161. /**
  162. * Extends parent to reset headers as well
  163. *
  164. * @return Request_Curl
  165. */
  166. protected function set_defaults()
  167. {
  168. parent::set_defaults();
  169. $this->headers = array();
  170. if ( ! empty($this->preserve_resource))
  171. {
  172. $this->resource = $this->preserve_resource;
  173. $this->preserve_resource = null;
  174. }
  175. return $this;
  176. }
  177. /**
  178. * GET request
  179. *
  180. * @param array $params
  181. * @param array $options
  182. * @return void
  183. */
  184. protected function method_get()
  185. {
  186. $this->preserve_resource = $this->resource;
  187. $this->resource = \Uri::create($this->resource, array(), $this->params);
  188. }
  189. /**
  190. * POST request
  191. *
  192. * @param array $params
  193. * @return void
  194. */
  195. protected function method_post()
  196. {
  197. $params = is_array($this->params) ? $this->encode($this->params) : $this->params;
  198. $this->set_option(CURLOPT_POST, true);
  199. $this->set_option(CURLOPT_POSTFIELDS, $params);
  200. }
  201. /**
  202. * PUT request
  203. *
  204. * @param array $params
  205. * @return void
  206. */
  207. protected function method_put()
  208. {
  209. $params = is_array($this->params) ? $this->encode($this->params) : $this->params;
  210. $this->set_option(CURLOPT_POSTFIELDS, $params);
  211. // Override method, I think this makes $_POST DELETE data but... we'll see eh?
  212. $this->set_header('X-HTTP-Method-Override', 'PUT');
  213. }
  214. /**
  215. * DELETE request
  216. *
  217. * @param array $params
  218. * @return void
  219. */
  220. protected function method_delete()
  221. {
  222. $params = is_array($this->params) ? $this->encode($this->params) : $this->params;
  223. $this->set_option(CURLOPT_POSTFIELDS, $params);
  224. // Override method, I think this makes $_POST DELETE data but... we'll see eh?
  225. $this->set_header('X-HTTP-Method-Override', 'DELETE');
  226. }
  227. /**
  228. * Function to encode input array depending on the content type
  229. *
  230. * @param array $input
  231. * @return mixed encoded output
  232. */
  233. protected function encode(array $input)
  234. {
  235. // Detect the request content type, default to 'text/plain'
  236. $content_type = isset($this->headers['Content-Type']) ? $this->headers['Content-Type'] : $this->response_info('content_type', 'text/plain');
  237. // Get the correct format for the current content type
  238. $format = \Arr::key_exists(static::$auto_detect_formats, $content_type) ? static::$auto_detect_formats[$content_type] : null;
  239. switch($format)
  240. {
  241. // Format as XML
  242. case 'xml':
  243. /**
  244. * If the input array has one item in the top level
  245. * then use that item as the root XML element.
  246. */
  247. if(count($input) === 1)
  248. {
  249. $base_node = key($input);
  250. return \Format::forge($input[$base_node])->to_xml(null, null, $base_node);
  251. }
  252. else
  253. {
  254. return \Format::forge($input)->to_xml();
  255. }
  256. break;
  257. // Format as JSON
  258. case 'json':
  259. return \Format::forge($input)->to_json();
  260. break;
  261. // Format as PHP Serialized Array
  262. case 'serialize':
  263. return \Format::forge($input)->to_serialize();
  264. break;
  265. // Format as PHP Array
  266. case 'php':
  267. return \Format::forge($input)->to_php();
  268. break;
  269. // Format as CSV
  270. case 'csv':
  271. return \Format::forge($input)->to_csv();
  272. break;
  273. // Format as Query String
  274. default:
  275. return http_build_query($input, null, '&');
  276. break;
  277. }
  278. }
  279. }